Merge branch 'main' into add-spanish-translation
4
.github/workflows/nightly.yml
vendored
|
|
@ -56,7 +56,7 @@ jobs:
|
|||
run: corepack enable && corepack prepare pnpm@latest --activate
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
|
@ -79,7 +79,7 @@ jobs:
|
|||
run: git tag "${{ steps.version.outputs.version }}"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
uses: goreleaser/goreleaser-action@v7
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: ~> v2
|
||||
|
|
|
|||
5
.github/workflows/pr.yml
vendored
|
|
@ -23,10 +23,13 @@ jobs:
|
|||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v2.10.1
|
||||
args: --build-tags=goolm,stdjson
|
||||
|
||||
vuln_check:
|
||||
name: Security Check
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GOFLAGS: -tags=goolm,stdjson
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
|
@ -59,4 +62,4 @@ jobs:
|
|||
run: go generate ./...
|
||||
|
||||
- name: Run go test
|
||||
run: go test ./...
|
||||
run: go test -tags goolm,stdjson ./...
|
||||
|
|
|
|||
4
.github/workflows/release.yml
vendored
|
|
@ -74,7 +74,7 @@ jobs:
|
|||
run: corepack enable && corepack prepare pnpm@latest --activate
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
|
@ -94,7 +94,7 @@ jobs:
|
|||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
uses: goreleaser/goreleaser-action@v7
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: ~> v2
|
||||
|
|
|
|||
8
.gitignore
vendored
|
|
@ -25,6 +25,9 @@ build/
|
|||
# Secrets & Config (keep templates, ignore actual secrets)
|
||||
.env
|
||||
config/config.json
|
||||
.security.yml
|
||||
onboard
|
||||
|
||||
|
||||
# Test
|
||||
coverage.txt
|
||||
|
|
@ -40,6 +43,7 @@ tasks/
|
|||
|
||||
# Plans
|
||||
docs/plans/
|
||||
docs/superpowers/
|
||||
|
||||
# Editors
|
||||
.vscode/
|
||||
|
|
@ -59,3 +63,7 @@ cmd/telegram/
|
|||
!web/backend/dist/
|
||||
web/backend/dist/*
|
||||
!web/backend/dist/.gitkeep
|
||||
|
||||
.claude/
|
||||
|
||||
docker/data
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
version: 2
|
||||
|
||||
git:
|
||||
ignore_tags:
|
||||
- nightly
|
||||
- ".*-nightly.*"
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
|
@ -15,6 +20,7 @@ builds:
|
|||
env:
|
||||
- CGO_ENABLED=0
|
||||
tags:
|
||||
- goolm
|
||||
- stdjson
|
||||
ldflags:
|
||||
- -s -w
|
||||
|
|
@ -57,6 +63,7 @@ builds:
|
|||
env:
|
||||
- CGO_ENABLED=0
|
||||
tags:
|
||||
- goolm
|
||||
- stdjson
|
||||
ldflags:
|
||||
- -s -w
|
||||
|
|
@ -95,6 +102,7 @@ builds:
|
|||
env:
|
||||
- CGO_ENABLED=0
|
||||
tags:
|
||||
- goolm
|
||||
- stdjson
|
||||
ldflags:
|
||||
- -s -w
|
||||
|
|
|
|||
91
Makefile
|
|
@ -17,7 +17,13 @@ LDFLAGS=-X $(CONFIG_PKG).Version=$(VERSION) -X $(CONFIG_PKG).GitCommit=$(GIT_COM
|
|||
# Go variables
|
||||
GO?=CGO_ENABLED=0 go
|
||||
WEB_GO?=$(GO)
|
||||
GOFLAGS?=-v -tags stdjson
|
||||
GO_BUILD_TAGS?=goolm,stdjson
|
||||
GOFLAGS?=-v -tags $(GO_BUILD_TAGS)
|
||||
comma:=,
|
||||
empty:=
|
||||
space:=$(empty) $(empty)
|
||||
GO_BUILD_TAGS_NO_GOOLM:=$(subst $(space),$(comma),$(strip $(filter-out goolm,$(subst $(comma),$(space),$(GO_BUILD_TAGS)))))
|
||||
GOFLAGS_NO_GOOLM?=-v -tags $(GO_BUILD_TAGS_NO_GOOLM)
|
||||
|
||||
# Patch MIPS LE ELF e_flags (offset 36) for NaN2008-only kernels (e.g. Ingenic X2600).
|
||||
#
|
||||
|
|
@ -41,6 +47,13 @@ define PATCH_MIPS_FLAGS
|
|||
fi
|
||||
endef
|
||||
|
||||
# Patch creack/pty for loong64 support (upstream doesn't have ztypes_loong64.go)
|
||||
PTY_PATCH_LOONG64=pty_dir=$$(go env GOMODCACHE)/github.com/creack/pty@v1.1.9; \
|
||||
if [ -d "$$pty_dir" ] && [ ! -f "$$pty_dir/ztypes_loong64.go" ]; then \
|
||||
chmod +w "$$pty_dir" 2>/dev/null || true; \
|
||||
printf '//go:build linux && loong64\npackage pty\ntype (_C_int int32; _C_uint uint32)\n' > "$$pty_dir/ztypes_loong64.go"; \
|
||||
fi
|
||||
|
||||
# Golangci-lint
|
||||
GOLANGCI_LINT?=golangci-lint
|
||||
|
||||
|
|
@ -117,28 +130,39 @@ build: generate
|
|||
build-launcher:
|
||||
@echo "Building picoclaw-launcher for $(PLATFORM)/$(ARCH)..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@if [ ! -f web/backend/dist/index.html ]; then \
|
||||
echo "Building frontend..."; \
|
||||
cd web/frontend && pnpm install && pnpm build:backend; \
|
||||
fi
|
||||
@$(WEB_GO) build $(GOFLAGS) -o $(BUILD_DIR)/picoclaw-launcher-$(PLATFORM)-$(ARCH) ./web/backend
|
||||
@$(MAKE) -C web build \
|
||||
OUTPUT="$(CURDIR)/$(BUILD_DIR)/picoclaw-launcher-$(PLATFORM)-$(ARCH)" \
|
||||
WEB_GO='$(WEB_GO)' \
|
||||
GO_BUILD_TAGS='$(GO_BUILD_TAGS)' \
|
||||
LDFLAGS='$(LDFLAGS)'
|
||||
@ln -sf picoclaw-launcher-$(PLATFORM)-$(ARCH) $(BUILD_DIR)/picoclaw-launcher
|
||||
@echo "Build complete: $(BUILD_DIR)/picoclaw-launcher"
|
||||
|
||||
build-launcher-frontend:
|
||||
@$(MAKE) -C web build-frontend
|
||||
|
||||
## build-launcher-tui: Build the picoclaw-launcher TUI binary
|
||||
build-launcher-tui:
|
||||
@echo "Building picoclaw-launcher-tui for $(PLATFORM)/$(ARCH)..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@$(GO) build $(GOFLAGS) -o $(BUILD_DIR)/picoclaw-launcher-tui-$(PLATFORM)-$(ARCH) ./cmd/picoclaw-launcher-tui
|
||||
@ln -sf picoclaw-launcher-tui-$(PLATFORM)-$(ARCH) $(BUILD_DIR)/picoclaw-launcher-tui
|
||||
@echo "Build complete: $(BUILD_DIR)/picoclaw-launcher-tui"
|
||||
|
||||
## build-whatsapp-native: Build with WhatsApp native (whatsmeow) support; larger binary
|
||||
build-whatsapp-native: generate
|
||||
## @echo "Building $(BINARY_NAME) with WhatsApp native for $(PLATFORM)/$(ARCH)..."
|
||||
@echo "Building for multiple platforms..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
GOOS=linux GOARCH=amd64 $(GO) build -tags whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=arm GOARM=7 $(GO) build -tags whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=arm64 $(GO) build -tags whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=loong64 $(GO) build -tags whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-loong64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=riscv64 $(GO) build -tags whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-riscv64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=mipsle GOMIPS=softfloat $(GO) build -tags whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=amd64 $(GO) build -tags $(GO_BUILD_TAGS),whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=arm GOARM=7 $(GO) build -tags $(GO_BUILD_TAGS),whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=arm64 $(GO) build -tags $(GO_BUILD_TAGS),whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=loong64 $(GO) build -tags $(GO_BUILD_TAGS),whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-loong64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=riscv64 $(GO) build -tags $(GO_BUILD_TAGS),whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-riscv64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=mipsle GOMIPS=softfloat $(GO) build -tags $(GO_BUILD_TAGS_NO_GOOLM),whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle ./$(CMD_DIR)
|
||||
$(call PATCH_MIPS_FLAGS,$(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle)
|
||||
GOOS=darwin GOARCH=arm64 $(GO) build -tags whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64 ./$(CMD_DIR)
|
||||
GOOS=windows GOARCH=amd64 $(GO) build -tags whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe ./$(CMD_DIR)
|
||||
GOOS=darwin GOARCH=arm64 $(GO) build -tags $(GO_BUILD_TAGS),whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64 ./$(CMD_DIR)
|
||||
GOOS=windows GOARCH=amd64 $(GO) build -tags $(GO_BUILD_TAGS),whatsapp_native -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe ./$(CMD_DIR)
|
||||
## @$(GO) build $(GOFLAGS) -tags whatsapp_native -ldflags "$(LDFLAGS)" -o $(BINARY_PATH) ./$(CMD_DIR)
|
||||
@echo "Build complete"
|
||||
## @ln -sf $(BINARY_NAME)-$(PLATFORM)-$(ARCH) $(BUILD_DIR)/$(BINARY_NAME)
|
||||
|
|
@ -147,21 +171,21 @@ build-whatsapp-native: generate
|
|||
build-linux-arm: generate
|
||||
@echo "Building for linux/arm (GOARM=7)..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
GOOS=linux GOARCH=arm GOARM=7 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=arm GOARM=7 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm ./$(CMD_DIR)
|
||||
@echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)-linux-arm"
|
||||
|
||||
## build-linux-arm64: Build for Linux ARM64 (e.g. Raspberry Pi Zero 2 W 64-bit)
|
||||
build-linux-arm64: generate
|
||||
@echo "Building for linux/arm64..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
GOOS=linux GOARCH=arm64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=arm64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 ./$(CMD_DIR)
|
||||
@echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64"
|
||||
|
||||
## build-linux-mipsle: Build for Linux MIPS32 LE
|
||||
build-linux-mipsle: generate
|
||||
@echo "Building for linux/mipsle (softfloat)..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
GOOS=linux GOARCH=mipsle GOMIPS=softfloat $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=mipsle GOMIPS=softfloat $(GO) build $(GOFLAGS_NO_GOOLM) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle ./$(CMD_DIR)
|
||||
$(call PATCH_MIPS_FLAGS,$(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle)
|
||||
@echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle"
|
||||
|
||||
|
|
@ -173,18 +197,19 @@ build-pi-zero: build-linux-arm build-linux-arm64
|
|||
build-all: generate
|
||||
@echo "Building for multiple platforms..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
GOOS=linux GOARCH=amd64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=arm GOARM=7 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=arm64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=loong64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-loong64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=riscv64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-riscv64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=mipsle GOMIPS=softfloat $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=amd64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=arm GOARM=7 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=arm64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 ./$(CMD_DIR)
|
||||
@$(PTY_PATCH_LOONG64)
|
||||
GOOS=linux GOARCH=loong64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-loong64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=riscv64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-riscv64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=mipsle GOMIPS=softfloat $(GO) build $(GOFLAGS_NO_GOOLM) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle ./$(CMD_DIR)
|
||||
$(call PATCH_MIPS_FLAGS,$(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle)
|
||||
GOOS=linux GOARCH=arm GOARM=7 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-armv7 ./$(CMD_DIR)
|
||||
GOOS=darwin GOARCH=arm64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64 ./$(CMD_DIR)
|
||||
GOOS=windows GOARCH=amd64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe ./$(CMD_DIR)
|
||||
GOOS=netbsd GOARCH=amd64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-netbsd-amd64 ./$(CMD_DIR)
|
||||
GOOS=netbsd GOARCH=arm64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-netbsd-arm64 ./$(CMD_DIR)
|
||||
GOOS=linux GOARCH=arm GOARM=7 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-armv7 ./$(CMD_DIR)
|
||||
GOOS=darwin GOARCH=arm64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64 ./$(CMD_DIR)
|
||||
GOOS=windows GOARCH=amd64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe ./$(CMD_DIR)
|
||||
GOOS=netbsd GOARCH=amd64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-netbsd-amd64 ./$(CMD_DIR)
|
||||
GOOS=netbsd GOARCH=arm64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-netbsd-arm64 ./$(CMD_DIR)
|
||||
@echo "All builds complete"
|
||||
|
||||
## install: Install picoclaw to system and copy builtin skills
|
||||
|
|
@ -221,13 +246,13 @@ clean:
|
|||
|
||||
## vet: Run go vet for static analysis
|
||||
vet: generate
|
||||
@packages="$$(go list ./...)" && \
|
||||
$(GO) vet $$(printf '%s\n' "$$packages" | grep -v '^github.com/sipeed/picoclaw/web/')
|
||||
@packages="$$($(GO) list $(GOFLAGS) ./...)" && \
|
||||
$(GO) vet $(GOFLAGS) $$(printf '%s\n' "$$packages" | grep -v '^github.com/sipeed/picoclaw/web/')
|
||||
@cd web/backend && $(WEB_GO) vet ./...
|
||||
|
||||
## test: Test Go code
|
||||
test: generate
|
||||
@$(GO) test $$(go list ./... | grep -v github.com/sipeed/picoclaw/web/)
|
||||
@$(GO) test $(GOFLAGS) $$($(GO) list $(GOFLAGS) ./... | grep -v github.com/sipeed/picoclaw/web/)
|
||||
@cd web && make test
|
||||
|
||||
## fmt: Format Go code
|
||||
|
|
@ -236,11 +261,11 @@ fmt:
|
|||
|
||||
## lint: Run linters
|
||||
lint:
|
||||
@$(GOLANGCI_LINT) run
|
||||
@$(GOLANGCI_LINT) run --build-tags $(GO_BUILD_TAGS)
|
||||
|
||||
## fix: Fix linting issues
|
||||
fix:
|
||||
@$(GOLANGCI_LINT) run --fix
|
||||
@$(GOLANGCI_LINT) run --fix --build-tags $(GO_BUILD_TAGS)
|
||||
|
||||
## deps: Download dependencies
|
||||
deps:
|
||||
|
|
|
|||
627
README.fr.md
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<h1>PicoClaw : Assistant IA Ultra-Efficace en Go</h1>
|
||||
|
||||
<h3>Matériel à $10 · <10 Mo de RAM · Démarrage en <1s · 皮皮虾,我们走!</h3>
|
||||
<h3>Matériel à $10 · 10 Mo de RAM · Démarrage en ms · Let's Go, PicoClaw!</h3>
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go&logoColor=white" alt="Go">
|
||||
<img src="https://img.shields.io/badge/Arch-x86__64%2C%20ARM64%2C%20MIPS%2C%20RISC--V%2C%20LoongArch-blue" alt="Hardware">
|
||||
|
|
@ -25,147 +25,146 @@
|
|||
|
||||
---
|
||||
|
||||
> **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 !
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> [!CAUTION]
|
||||
> **🚨 SÉCURITÉ & CANAUX OFFICIELS**
|
||||
> **Avis de sécurité**
|
||||
>
|
||||
> * **PAS DE CRYPTO :** PicoClaw n'a **AUCUN** token/jeton officiel. Toute annonce sur `pump.fun` ou d'autres plateformes de trading est une **ARNAQUE**.
|
||||
>
|
||||
> * **DOMAINE OFFICIEL :** Le **SEUL** site officiel est **[picoclaw.io](https://picoclaw.io)**, et le site de l'entreprise est **[sipeed.com](https://sipeed.com)**.
|
||||
> * **Attention :** De nombreux domaines `.ai/.org/.com/.net/...` sont enregistrés par des tiers.
|
||||
> * **Attention :** PicoClaw est en phase de développement précoce et peut présenter des problèmes de sécurité réseau non résolus. Ne déployez pas en environnement de production avant la version v1.0.
|
||||
> * **Note :** PicoClaw a récemment fusionné de nombreuses PR, ce qui peut entraîner une empreinte mémoire plus importante (10–20 Mo) dans les dernières versions. Nous prévoyons de prioriser l'optimisation des ressources dès que l'ensemble des fonctionnalités sera stabilisé.
|
||||
> * **PAS DE CRYPTO :** PicoClaw n'a **pas** émis de tokens officiels ni de cryptomonnaie. Toute affirmation sur `pump.fun` ou d'autres plateformes de trading est une **arnaque**.
|
||||
> * **DOMAINE OFFICIEL :** Le **SEUL** site officiel est **[picoclaw.io](https://picoclaw.io)**, et le site de l'entreprise est **[sipeed.com](https://sipeed.com)**
|
||||
> * **ATTENTION :** De nombreux domaines `.ai/.org/.com/.net/...` ont été enregistrés par des tiers. Ne leur faites pas confiance.
|
||||
> * **NOTE :** PicoClaw est en développement rapide précoce. Des problèmes de sécurité non résolus peuvent exister. Ne pas déployer en production avant la v1.0.
|
||||
> * **NOTE :** PicoClaw a récemment fusionné de nombreuses PRs. Les builds récents peuvent utiliser 10-20 Mo de RAM. L'optimisation des ressources est prévue après la stabilisation des fonctionnalités.
|
||||
|
||||
## 📢 Actualités
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 publié !** Interface système tray (Windows & Linux), suivi de statut des sous-agents (`spawn_status`), rechargement à chaud expérimental du gateway, portes de sécurité cron, et 2 correctifs de sécurité. PicoClaw atteint **25K ⭐** !
|
||||
2026-03-25 🚀 **v0.2.4 publiée !** Refonte de l'architecture Agent (SubTurn, Hooks, Steering, EventBus), intégration WeChat/WeCom, renforcement de la sécurité (.security.yml, filtrage des données sensibles), nouveaux providers (AWS Bedrock, Azure, Xiaomi MiMo), et 35 corrections de bugs. PicoClaw a atteint **26K Stars** !
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Plus grande mise à jour !** Support du protocole MCP, 4 nouveaux canaux (Matrix/IRC/WeCom/Discord Proxy), 3 nouveaux fournisseurs (Kimi/Minimax/Avian), pipeline de vision, stockage mémoire JSONL, et routage de modèles.
|
||||
2026-03-17 🚀 **v0.2.3 publiée !** Interface system tray (Windows & Linux), requête de statut des sous-agents (`spawn_status`), rechargement à chaud expérimental du Gateway, sécurisation Cron, et 2 correctifs de sécurité. PicoClaw a atteint **25K Stars** !
|
||||
|
||||
2026-02-28 📦 **v0.2.0** publié avec support Docker Compose et lanceur Web UI.
|
||||
2026-03-09 🎉 **v0.2.1 — Plus grande mise à jour à ce jour !** Support du protocole MCP, 4 nouveaux channels (Matrix/IRC/WeCom/Discord Proxy), 3 nouveaux providers (Kimi/Minimax/Avian), pipeline vision, stockage mémoire JSONL, routage de modèles.
|
||||
|
||||
2026-02-26 🎉 PicoClaw a atteint **20K étoiles** en seulement 17 jours ! L'orchestration automatique des canaux et les interfaces de capacités sont arrivées.
|
||||
2026-02-28 📦 **v0.2.0** publiée avec support Docker Compose et Web UI Launcher.
|
||||
|
||||
<details>
|
||||
<summary>Actualités précédentes...</summary>
|
||||
|
||||
2026-02-16 🎉 PicoClaw a atteint 12K étoiles en une semaine ! Les rôles de mainteneurs communautaires et la [feuille de route](ROADMAP.md) sont officiellement publiés.
|
||||
2026-02-26 🎉 PicoClaw atteint **20K Stars** en seulement 17 jours ! L'orchestration automatique des channels et les interfaces de capacités sont disponibles.
|
||||
|
||||
2026-02-13 🎉 PicoClaw a atteint 5000 étoiles en 4 jours ! La Feuille de Route du Projet et le Groupe de Développeurs sont en cours de mise en place.
|
||||
2026-02-16 🎉 PicoClaw dépasse 12K Stars en une semaine ! Rôles de mainteneurs communautaires et [Roadmap](ROADMAP.md) officiellement lancés.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw est lancé !** Construit en 1 jour pour apporter les Agents IA au matériel à $10 avec <10 Mo de RAM. 🦐 PicoClaw, c'est parti !
|
||||
2026-02-13 🎉 PicoClaw dépasse 5000 Stars en 4 jours ! Roadmap du projet et groupes de développeurs en cours.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw publié !** Construit en 1 jour pour apporter les Agents IA sur du matériel à $10 avec <10 Mo de RAM. Let's Go, PicoClaw !
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## ✨ Fonctionnalités
|
||||
|
||||
🪶 **Ultra-Léger** : Empreinte mémoire <10 Mo — 99% plus petit que les fonctionnalités essentielles d'OpenClaw.*
|
||||
🪶 **Ultra-léger** : Empreinte mémoire du cœur <10 Mo — 99% plus petit qu'OpenClaw.*
|
||||
|
||||
💰 **Coût Minimal** : Suffisamment efficace pour fonctionner sur du matériel à $10 — 98% moins cher qu'un Mac mini.
|
||||
💰 **Coût minimal** : Suffisamment efficace pour fonctionner sur du matériel à $10 — 98% moins cher qu'un Mac mini.
|
||||
|
||||
⚡️ **Démarrage Éclair** : Temps de démarrage 400X plus rapide, boot en <1 seconde même sur un cœur unique à 0,6 GHz.
|
||||
⚡️ **Démarrage ultra-rapide** : 400x plus rapide au démarrage. Démarre en <1s même sur un processeur monocœur à 0,6 GHz.
|
||||
|
||||
🌍 **Véritable Portabilité** : Un seul binaire autonome pour RISC-V, ARM, MIPS et x86. Un clic et c'est parti !
|
||||
🌍 **Vraiment portable** : Binaire unique pour les architectures RISC-V, ARM, MIPS et x86. Un seul binaire, fonctionne partout !
|
||||
|
||||
🤖 **Auto-Construit par l'IA** : Implémentation native en Go de manière autonome — 95% du cœur généré par l'Agent avec affinement humain dans la boucle.
|
||||
🤖 **Auto-amorcé par IA** : Implémentation native pure Go — 95% du code principal a été généré par un Agent et affiné via une révision humaine en boucle.
|
||||
|
||||
🔌 **Support MCP** : Intégration native du [Model Context Protocol](https://modelcontextprotocol.io/) — connectez n'importe quel serveur MCP pour étendre les capacités de l'agent.
|
||||
🔌 **Support MCP** : Intégration native du [Model Context Protocol](https://modelcontextprotocol.io/) — connectez n'importe quel serveur MCP pour étendre les capacités de l'Agent.
|
||||
|
||||
👁️ **Pipeline de Vision** : Envoyez des images et fichiers directement à l'agent — encodage base64 automatique pour les LLM multimodaux.
|
||||
👁️ **Pipeline vision** : Envoyez des images et des fichiers directement à l'Agent — encodage base64 automatique pour les LLMs multimodaux.
|
||||
|
||||
🧠 **Routage Intelligent** : Routage de modèles basé sur des règles — les requêtes simples vont vers des modèles légers, économisant les coûts API.
|
||||
🧠 **Routage intelligent** : Routage de modèles basé sur des règles — les requêtes simples vont vers des modèles légers, économisant les coûts API.
|
||||
|
||||
_*Les versions récentes peuvent utiliser 10–20 Mo en raison des fusions rapides de fonctionnalités. L'optimisation des ressources est prévue. La comparaison de démarrage est basée sur des benchmarks à cœur unique 0,8 GHz (voir tableau ci-dessous)._
|
||||
_*Les builds récents peuvent utiliser 10-20 Mo en raison des fusions rapides de PRs. L'optimisation des ressources est prévue. Comparaison de vitesse de démarrage basée sur des benchmarks monocœur à 0,8 GHz (voir tableau ci-dessous)._
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ----------------------------- | ------------- | ------------------------ | ----------------------------------------- |
|
||||
| **Langage** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1 Go | >100 Mo | **< 10 Mo*** |
|
||||
| **Démarrage**</br>(cœur 0,8 GHz) | >500s | >30s | **<1s** |
|
||||
| **Coût** | Mac Mini $599 | La plupart des SBC Linux </br>~$50 | **N'importe quelle carte Linux**</br>**À partir de $10** |
|
||||
<div align="center">
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ------------------------------ | ------------- | ------------------------ | -------------------------------------- |
|
||||
| **Langage** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1 Go | >100 Mo | **< 10 Mo*** |
|
||||
| **Temps de démarrage**</br>(cœur 0,8 GHz) | >500s | >30s | **<1s** |
|
||||
| **Coût** | Mac Mini $599 | La plupart des cartes Linux ~$50 | **N'importe quelle carte Linux**</br>**à partir de $10** |
|
||||
|
||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
</div>
|
||||
|
||||
> **[Liste de compatibilité matérielle](docs/fr/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 !
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/hardware-banner.jpg" alt="PicoClaw Hardware Compatibility" width="100%">
|
||||
</p>
|
||||
|
||||
## 🦾 Démonstration
|
||||
|
||||
### 🛠️ Flux de Travail Standard de l'Assistant
|
||||
### 🛠️ Flux de travail standard de l'assistant
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<th><p align="center">🧩 Ingénieur Full-Stack</p></th>
|
||||
<th><p align="center">🗂️ Gestion des Logs & Planification</p></th>
|
||||
<th><p align="center">🔎 Recherche Web & Apprentissage</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Développer • Déployer • Mettre à l'échelle</td>
|
||||
<td align="center">Planifier • Automatiser • Mémoriser</td>
|
||||
<td align="center">Découvrir • Analyser • Tendances</td>
|
||||
</tr>
|
||||
<tr align="center">
|
||||
<th><p align="center">Mode Ingénieur Full-Stack</p></th>
|
||||
<th><p align="center">Journalisation & Planification</p></th>
|
||||
<th><p align="center">Recherche Web & Apprentissage</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Développer · Déployer · Mettre à l'échelle</td>
|
||||
<td align="center">Planifier · Automatiser · Mémoriser</td>
|
||||
<td align="center">Découvrir · Analyser · Tendances</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 📱 Utiliser sur d'anciens téléphones Android
|
||||
|
||||
Donnez une seconde vie à votre téléphone d'il y a dix ans ! Transformez-le en assistant IA intelligent avec PicoClaw. Démarrage rapide :
|
||||
|
||||
1. **Installez [Termux](https://github.com/termux/termux-app)** (Téléchargez depuis [GitHub Releases](https://github.com/termux/termux-app/releases), ou recherchez sur F-Droid / Google Play).
|
||||
2. **Exécutez les commandes**
|
||||
|
||||
```bash
|
||||
# Téléchargez la dernière version depuis https://github.com/sipeed/picoclaw/releases
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard
|
||||
```
|
||||
|
||||
Puis suivez les instructions de la section « Démarrage Rapide » pour terminer la configuration !
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
### 🐜 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
|
||||
|
||||
<https://private-user-images.githubusercontent.com/83055338/547056448-e7b031ff-d6f5-4468-bcca-5726b6fecb5c.mp4>
|
||||
|
||||
🌟 Encore plus de scénarios de déploiement vous attendent !
|
||||
🌟 D'autres cas de déploiement vous attendent !
|
||||
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
### Installer avec un binaire précompilé
|
||||
### Télécharger depuis picoclaw.io (Recommandé)
|
||||
|
||||
Téléchargez le binaire pour votre plateforme depuis la page des [Releases](https://github.com/sipeed/picoclaw/releases).
|
||||
Visitez **[picoclaw.io](https://picoclaw.io)** — le site officiel détecte automatiquement votre plateforme et fournit un téléchargement en un clic. Pas besoin de choisir manuellement une architecture.
|
||||
|
||||
### Installer depuis les sources (dernières fonctionnalités, recommandé pour le développement)
|
||||
### Télécharger le binaire précompilé
|
||||
|
||||
Vous pouvez aussi télécharger le binaire pour votre plateforme depuis la page [GitHub Releases](https://github.com/sipeed/picoclaw/releases).
|
||||
|
||||
### Compiler depuis les sources (pour le développement)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
|
|
@ -173,78 +172,442 @@ git clone https://github.com/sipeed/picoclaw.git
|
|||
cd picoclaw
|
||||
make deps
|
||||
|
||||
# Compiler, pas besoin d'installer
|
||||
# Compiler le binaire principal
|
||||
make build
|
||||
|
||||
# Compiler le Web UI Launcher (requis pour le mode WebUI)
|
||||
make build-launcher
|
||||
|
||||
# Compiler pour plusieurs plateformes
|
||||
make build-all
|
||||
|
||||
# Compiler pour Raspberry Pi Zero 2 W (32-bit : make build-linux-arm ; 64-bit : make build-linux-arm64)
|
||||
# Compiler pour Raspberry Pi Zero 2 W (32 bits : make build-linux-arm ; 64 bits : make build-linux-arm64)
|
||||
make build-pi-zero
|
||||
|
||||
# Compiler et Installer
|
||||
# Compiler et installer
|
||||
make install
|
||||
```
|
||||
|
||||
**Raspberry Pi Zero 2 W :** Utilisez le binaire correspondant à votre OS : Raspberry Pi OS 32-bit → `make build-linux-arm` ; 64-bit → `make build-linux-arm64`. Ou exécutez `make build-pi-zero` pour compiler les deux.
|
||||
**Raspberry Pi Zero 2 W :** Utilisez le binaire correspondant à votre OS : Raspberry Pi OS 32 bits -> `make build-linux-arm` ; 64 bits -> `make build-linux-arm64`. Ou exécutez `make build-pi-zero` pour compiler les deux.
|
||||
|
||||
## 📚 Documentation
|
||||
## 🚀 Guide de démarrage rapide
|
||||
|
||||
Pour des guides détaillés, consultez la documentation ci-dessous. Ce README ne couvre que le démarrage rapide.
|
||||
### 🌐 WebUI Launcher (Recommandé pour le bureau)
|
||||
|
||||
| Sujet | Description |
|
||||
|-------|-------------|
|
||||
| 🐳 [Docker & Démarrage Rapide](docs/fr/docker.md) | Configuration Docker Compose, modes Launcher/Agent, configuration rapide |
|
||||
| 💬 [Applications de Chat](docs/fr/chat-apps.md) | Telegram, Discord, WhatsApp, Matrix, QQ, Slack, IRC, DingTalk, LINE, Feishu, WeCom, et plus |
|
||||
| ⚙️ [Configuration](docs/fr/configuration.md) | Variables d'environnement, structure du workspace, sources de compétences, bac à sable de sécurité, heartbeat |
|
||||
| 🔌 [Fournisseurs & Modèles](docs/fr/providers.md) | 20+ fournisseurs LLM, routage de modèles, configuration model_list, architecture des fournisseurs |
|
||||
| 🔄 [Spawn & Tâches Asynchrones](docs/fr/spawn-tasks.md) | Tâches rapides, tâches longues avec spawn, orchestration asynchrone de sous-agents |
|
||||
| 🐛 [Dépannage](docs/fr/troubleshooting.md) | Problèmes courants et solutions |
|
||||
| 🔧 [Configuration des Outils](docs/fr/tools_configuration.md) | Activation/désactivation par outil, politiques exec |
|
||||
Le WebUI Launcher fournit une interface basée sur navigateur pour la configuration et le chat. C'est la façon la plus simple de démarrer — aucune connaissance de la ligne de commande requise.
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> Rejoignez le Réseau Social d'Agents
|
||||
**Option 1 : Double-clic (Bureau)**
|
||||
|
||||
Connectez PicoClaw au Réseau Social d'Agents simplement en envoyant un seul message via le CLI ou n'importe quelle application de chat intégrée.
|
||||
Après téléchargement depuis [picoclaw.io](https://picoclaw.io), double-cliquez sur `picoclaw-launcher` (ou `picoclaw-launcher.exe` sous Windows). Votre navigateur s'ouvrira automatiquement sur `http://localhost:18800`.
|
||||
|
||||
**Option 2 : Ligne de commande**
|
||||
|
||||
```bash
|
||||
picoclaw-launcher
|
||||
# Ouvrez http://localhost:18800 dans votre navigateur
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **Accès distant / Docker / VM :** Ajoutez le flag `-public` pour écouter sur toutes les interfaces :
|
||||
> ```bash
|
||||
> picoclaw-launcher -public
|
||||
> ```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-webui.jpg" alt="WebUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Pour commencer :**
|
||||
|
||||
Ouvrez le WebUI, puis : **1)** Configurez un Provider (ajoutez votre clé API LLM) -> **2)** Configurez un Channel (ex. Telegram) -> **3)** Démarrez le Gateway -> **4)** Chattez !
|
||||
|
||||
Pour la documentation détaillée du WebUI, voir [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
<details>
|
||||
<summary><b>Docker (alternative)</b></summary>
|
||||
|
||||
```bash
|
||||
# 1. Cloner ce dépôt
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
|
||||
# 2. Premier lancement — génère automatiquement docker/data/config.json puis s'arrête
|
||||
# (se déclenche uniquement quand config.json et workspace/ sont tous deux absents)
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up
|
||||
# Le conteneur affiche "First-run setup complete." et s'arrête.
|
||||
|
||||
# 3. Définir vos clés API
|
||||
vim docker/data/config.json
|
||||
|
||||
# 4. Démarrer
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
# Ouvrez http://localhost:18800
|
||||
```
|
||||
|
||||
> **Utilisateurs Docker / VM :** Le Gateway écoute sur `127.0.0.1` par défaut. Définissez `PICOCLAW_GATEWAY_HOST=0.0.0.0` ou utilisez le flag `-public` pour le rendre accessible depuis l'hôte.
|
||||
|
||||
```bash
|
||||
# Vérifier les logs
|
||||
docker compose -f docker/docker-compose.yml logs -f
|
||||
|
||||
# Arrêter
|
||||
docker compose -f docker/docker-compose.yml --profile launcher down
|
||||
|
||||
# Mettre à jour
|
||||
docker compose -f docker/docker-compose.yml pull
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Avertissement de sécurité au premier lancement</b></summary>
|
||||
|
||||
macOS peut bloquer `picoclaw-launcher` au premier lancement car il est téléchargé depuis Internet et n'est pas notarisé via le Mac App Store.
|
||||
|
||||
**Étape 1 :** Double-cliquez sur `picoclaw-launcher`. Un avertissement de sécurité s'affiche :
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Avertissement macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" n'a pas pu être ouvert — Apple n'a pas pu vérifier que "picoclaw-launcher" ne contient pas de logiciel malveillant susceptible de nuire à votre Mac ou de compromettre votre confidentialité.*
|
||||
|
||||
**Étape 2 :** Ouvrez **Réglages Système** → **Confidentialité et sécurité** → faites défiler jusqu'à la section **Sécurité** → cliquez sur **Ouvrir quand même** → confirmez en cliquant sur **Ouvrir quand même** dans la boîte de dialogue.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Confidentialité et sécurité — Ouvrir quand même" width="600">
|
||||
</p>
|
||||
|
||||
Après cette étape unique, `picoclaw-launcher` s'ouvrira normalement lors des lancements suivants.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Recommandé pour les environnements sans interface / SSH)
|
||||
|
||||
Le TUI (Terminal UI) Launcher fournit une interface terminal complète pour la configuration et la gestion. Idéal pour les serveurs, Raspberry Pi et autres environnements sans interface graphique.
|
||||
|
||||
```bash
|
||||
picoclaw-launcher-tui
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-tui.jpg" alt="TUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Pour commencer :**
|
||||
|
||||
Utilisez les menus TUI pour : **1)** Configurer un Provider -> **2)** Configurer un Channel -> **3)** Démarrer le Gateway -> **4)** Chattez !
|
||||
|
||||
Pour la documentation détaillée du TUI, voir [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
### 📱 Android
|
||||
|
||||
Donnez une seconde vie à votre téléphone vieux de dix ans ! Transformez-le en assistant IA intelligent avec PicoClaw.
|
||||
|
||||
**Option 1 : Termux (disponible maintenant)**
|
||||
|
||||
1. Installez [Termux](https://github.com/termux/termux-app) (téléchargez depuis [GitHub Releases](https://github.com/termux/termux-app/releases), ou cherchez dans F-Droid / Google Play)
|
||||
2. Exécutez les commandes suivantes :
|
||||
|
||||
```bash
|
||||
# Télécharger la dernière version
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard # chroot fournit une arborescence Linux standard
|
||||
```
|
||||
|
||||
Suivez ensuite la section Terminal Launcher ci-dessous pour terminer la configuration.
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**Option 2 : Installation APK (bientôt disponible)**
|
||||
|
||||
Un APK Android autonome avec WebUI intégré est en développement. Restez à l'écoute !
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (pour les environnements à ressources limitées)</b></summary>
|
||||
|
||||
Pour les environnements minimaux où seul le binaire principal `picoclaw` est disponible (sans Launcher UI), vous pouvez tout configurer via la ligne de commande et un fichier de configuration JSON.
|
||||
|
||||
**1. Initialiser**
|
||||
|
||||
```bash
|
||||
picoclaw onboard
|
||||
```
|
||||
|
||||
Cela crée `~/.picoclaw/config.json` et le répertoire workspace.
|
||||
|
||||
**2. Configurer** (`~/.picoclaw/config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-api-key"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Voir `config/config.example.json` dans le dépôt pour un modèle de configuration complet avec toutes les options disponibles.
|
||||
|
||||
**3. Chatter**
|
||||
|
||||
```bash
|
||||
# Question ponctuelle
|
||||
picoclaw agent -m "What is 2+2?"
|
||||
|
||||
# Mode interactif
|
||||
picoclaw agent
|
||||
|
||||
# Démarrer le gateway pour l'intégration d'applications de chat
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## 🔌 Providers (LLM)
|
||||
|
||||
PicoClaw supporte plus de 30 providers LLM via la configuration `model_list`. Utilisez le format `protocole/modèle` :
|
||||
|
||||
| Provider | Protocole | Clé API | Notes |
|
||||
|----------|-----------|---------|-------|
|
||||
| [OpenAI](https://platform.openai.com/api-keys) | `openai/` | Requise | GPT-5.4, GPT-4o, o3, etc. |
|
||||
| [Anthropic](https://console.anthropic.com/settings/keys) | `anthropic/` | Requise | Claude Opus 4.6, Sonnet 4.6, etc. |
|
||||
| [Google Gemini](https://aistudio.google.com/apikey) | `gemini/` | Requise | Gemini 3 Flash, 2.5 Pro, etc. |
|
||||
| [OpenRouter](https://openrouter.ai/keys) | `openrouter/` | Requise | 200+ modèles, API unifiée |
|
||||
| [Zhipu (GLM)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | `zhipu/` | Requise | GLM-4.7, GLM-5, etc. |
|
||||
| [DeepSeek](https://platform.deepseek.com/api_keys) | `deepseek/` | Requise | DeepSeek-V3, DeepSeek-R1 |
|
||||
| [Volcengine](https://console.volcengine.com) | `volcengine/` | Requise | Modèles Doubao, Ark |
|
||||
| [Qwen](https://dashscope.console.aliyun.com/apiKey) | `qwen/` | Requise | Qwen3, Qwen-Max, etc. |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/` | Requise | Inférence rapide (Llama, Mixtral) |
|
||||
| [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | Requise | Modèles Kimi |
|
||||
| [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | Requise | Modèles MiniMax |
|
||||
| [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | Requise | Mistral Large, Codestral |
|
||||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Requise | Modèles hébergés NVIDIA |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Requise | Inférence rapide |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Requise | Divers modèles open |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Requise | Modèles MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Non requise | Modèles locaux, auto-hébergé |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Non requise | Déploiement local, compatible OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Variable | Proxy pour 100+ providers |
|
||||
| [Azure OpenAI](https://portal.azure.com/) | `azure/` | Requise | Déploiement Azure entreprise |
|
||||
| [GitHub Copilot](https://github.com/features/copilot) | `github-copilot/` | OAuth | Connexion par code appareil |
|
||||
| [Antigravity](https://console.cloud.google.com/) | `antigravity/` | OAuth | Google Cloud AI |
|
||||
|
||||
<details>
|
||||
<summary><b>Déploiement local (Ollama, vLLM, etc.)</b></summary>
|
||||
|
||||
**Ollama :**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-llama",
|
||||
"model": "ollama/llama3.1:8b",
|
||||
"api_base": "http://localhost:11434/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**vLLM :**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-vllm",
|
||||
"model": "vllm/your-model",
|
||||
"api_base": "http://localhost:8000/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Pour les détails complets de configuration des providers, voir [Providers & Models](docs/fr/providers.md).
|
||||
|
||||
</details>
|
||||
|
||||
## 💬 Channels (Applications de chat)
|
||||
|
||||
Parlez à votre PicoClaw via plus de 17 plateformes de messagerie :
|
||||
|
||||
| Channel | Configuration | Protocole | Docs |
|
||||
|---------|---------------|-----------|------|
|
||||
| **Telegram** | Facile (token bot) | Long polling | [Guide](docs/channels/telegram/README.fr.md) |
|
||||
| **Discord** | Facile (token bot + intents) | WebSocket | [Guide](docs/channels/discord/README.fr.md) |
|
||||
| **WhatsApp** | Facile (scan QR ou URL bridge) | Natif / Bridge | [Guide](docs/fr/chat-apps.md#whatsapp) |
|
||||
| **Weixin** | Facile (scan QR natif) | iLink API | [Guide](docs/fr/chat-apps.md#weixin) |
|
||||
| **QQ** | Facile (AppID + AppSecret) | WebSocket | [Guide](docs/channels/qq/README.fr.md) |
|
||||
| **Slack** | Facile (token bot + app) | Socket Mode | [Guide](docs/channels/slack/README.fr.md) |
|
||||
| **Matrix** | Moyen (homeserver + token) | Sync API | [Guide](docs/channels/matrix/README.fr.md) |
|
||||
| **DingTalk** | Moyen (identifiants client) | Stream | [Guide](docs/channels/dingtalk/README.fr.md) |
|
||||
| **Feishu / Lark** | Moyen (App ID + Secret) | WebSocket/SDK | [Guide](docs/channels/feishu/README.fr.md) |
|
||||
| **LINE** | Moyen (identifiants + webhook) | Webhook | [Guide](docs/channels/line/README.fr.md) |
|
||||
| **WeCom** | Facile (QR login ou manuel) | WebSocket | [Guide](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Moyen (serveur + pseudo) | Protocole IRC | [Guide](docs/fr/chat-apps.md#irc) |
|
||||
| **OneBot** | Moyen (URL WebSocket) | OneBot v11 | [Guide](docs/channels/onebot/README.fr.md) |
|
||||
| **MaixCam** | Facile (activer) | Socket TCP | [Guide](docs/channels/maixcam/README.fr.md) |
|
||||
| **Pico** | Facile (activer) | Protocole natif | Intégré |
|
||||
| **Pico Client** | Facile (URL WebSocket) | WebSocket | Intégré |
|
||||
|
||||
> Tous les channels basés sur webhook partagent un seul serveur HTTP Gateway (`gateway.host`:`gateway.port`, par défaut `127.0.0.1:18790`). Feishu utilise le mode WebSocket/SDK et n'utilise pas le serveur HTTP partagé.
|
||||
|
||||
> La verbosité des logs est contrôlée par `gateway.log_level` (par défaut : `warn`). Valeurs supportées : `debug`, `info`, `warn`, `error`, `fatal`. Peut aussi être défini via `PICOCLAW_LOG_LEVEL`. Voir [Configuration](docs/fr/configuration.md#niveau-de-log-du-gateway) pour plus de détails.
|
||||
|
||||
Pour les instructions détaillées de configuration des channels, voir [Configuration des applications de chat](docs/fr/chat-apps.md).
|
||||
|
||||
## 🔧 Outils
|
||||
|
||||
### 🔍 Recherche Web
|
||||
|
||||
PicoClaw peut effectuer des recherches sur le web pour fournir des informations à jour. Configurez dans `tools.web` :
|
||||
|
||||
| Moteur de recherche | Clé API | Niveau gratuit | Lien |
|
||||
|--------------------|---------|----------------|------|
|
||||
| DuckDuckGo | Non requise | Illimité | Fallback intégré |
|
||||
| [Baidu Search](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5) | Requise | 1000 requêtes/jour | IA, optimisé pour le chinois |
|
||||
| [Tavily](https://tavily.com) | Requise | 1000 requêtes/mois | Optimisé pour les Agents IA |
|
||||
| [Brave Search](https://brave.com/search/api) | Requise | 2000 requêtes/mois | Rapide et privé |
|
||||
| [Perplexity](https://www.perplexity.ai) | Requise | Payant | Recherche propulsée par IA |
|
||||
| [SearXNG](https://github.com/searxng/searxng) | Non requise | Auto-hébergé | Métamoteur de recherche gratuit |
|
||||
| [GLM Search](https://open.bigmodel.cn/) | Requise | Variable | Recherche web Zhipu |
|
||||
|
||||
### ⚙️ Autres outils
|
||||
|
||||
PicoClaw inclut des outils intégrés pour les opérations sur fichiers, l'exécution de code, la planification et plus encore. Voir [Configuration des outils](docs/fr/tools_configuration.md) pour les détails.
|
||||
|
||||
## 🎯 Skills
|
||||
|
||||
Les Skills sont des capacités modulaires qui étendent votre Agent. Elles sont chargées depuis les fichiers `SKILL.md` dans votre workspace.
|
||||
|
||||
**Installer des Skills depuis ClawHub :**
|
||||
|
||||
```bash
|
||||
picoclaw skills search "web scraping"
|
||||
picoclaw skills install <skill-name>
|
||||
```
|
||||
|
||||
**Configurer le token ClawHub** (optionnel, pour des limites de débit plus élevées) :
|
||||
|
||||
Ajoutez à votre `config.json` :
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"skills": {
|
||||
"registries": {
|
||||
"clawhub": {
|
||||
"auth_token": "your-clawhub-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pour plus de détails, voir [Configuration des outils - Skills](docs/fr/tools_configuration.md#skills-tool).
|
||||
|
||||
## 🔗 MCP (Model Context Protocol)
|
||||
|
||||
PicoClaw supporte nativement [MCP](https://modelcontextprotocol.io/) — connectez n'importe quel serveur MCP pour étendre les capacités de votre Agent avec des outils et sources de données externes.
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"servers": {
|
||||
"filesystem": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pour la configuration MCP complète (transports stdio, SSE, HTTP, Tool Discovery), voir [Configuration des outils - MCP](docs/fr/tools_configuration.md#mcp-tool).
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> Rejoignez le réseau social des Agents
|
||||
|
||||
Connectez PicoClaw au réseau social des Agents simplement en envoyant un seul message via le CLI ou n'importe quelle application de chat intégrée.
|
||||
|
||||
**Lisez `https://clawdchat.ai/skill.md` et suivez les instructions pour rejoindre [ClawdChat.ai](https://clawdchat.ai)**
|
||||
|
||||
## 🖥️ Référence CLI
|
||||
|
||||
| Commande | Description |
|
||||
| ------------------------- | ---------------------------------- |
|
||||
| `picoclaw onboard` | Initialiser la config & le workspace |
|
||||
| `picoclaw agent -m "..."` | Discuter avec l'agent |
|
||||
| `picoclaw agent` | Mode chat interactif |
|
||||
| `picoclaw gateway` | Démarrer le gateway |
|
||||
| `picoclaw status` | Afficher le statut |
|
||||
| `picoclaw version` | Afficher les infos de version |
|
||||
| `picoclaw cron list` | Lister les tâches planifiées |
|
||||
| `picoclaw cron add ...` | Ajouter une tâche planifiée |
|
||||
| `picoclaw cron disable` | Désactiver une tâche planifiée |
|
||||
| `picoclaw cron remove` | Supprimer une tâche planifiée |
|
||||
| `picoclaw skills list` | Lister les compétences installées |
|
||||
| `picoclaw skills install` | Installer une compétence |
|
||||
| `picoclaw migrate` | Migrer les données des anciennes versions |
|
||||
| `picoclaw auth login` | S'authentifier auprès des fournisseurs |
|
||||
| Commande | Description |
|
||||
| ------------------------- | ---------------------------------------- |
|
||||
| `picoclaw onboard` | Initialiser la config & le workspace |
|
||||
| `picoclaw auth weixin` | Connecter un compte WeChat via QR |
|
||||
| `picoclaw agent -m "..."` | Chatter avec l'agent |
|
||||
| `picoclaw agent` | Mode chat interactif |
|
||||
| `picoclaw gateway` | Démarrer le gateway |
|
||||
| `picoclaw status` | Afficher le statut |
|
||||
| `picoclaw version` | Afficher les informations de version |
|
||||
| `picoclaw model` | Voir ou changer le modèle par défaut |
|
||||
| `picoclaw cron list` | Lister toutes les tâches planifiées |
|
||||
| `picoclaw cron add ...` | Ajouter une tâche planifiée |
|
||||
| `picoclaw cron disable` | Désactiver une tâche planifiée |
|
||||
| `picoclaw cron remove` | Supprimer une tâche planifiée |
|
||||
| `picoclaw skills list` | Lister les Skills installées |
|
||||
| `picoclaw skills install` | Installer une Skill |
|
||||
| `picoclaw migrate` | Migrer les données depuis d'anciennes versions |
|
||||
| `picoclaw auth login` | S'authentifier auprès des providers |
|
||||
|
||||
### Tâches Planifiées / Rappels
|
||||
### ⏰ Tâches planifiées / Rappels
|
||||
|
||||
PicoClaw prend en charge les rappels planifiés et les tâches récurrentes via l'outil `cron` :
|
||||
PicoClaw supporte les rappels planifiés et les tâches récurrentes via l'outil `cron` :
|
||||
|
||||
* **Rappels ponctuels** : « Rappelle-moi dans 10 minutes » → se déclenche une fois après 10 min
|
||||
* **Tâches récurrentes** : « Rappelle-moi toutes les 2 heures » → se déclenche toutes les 2 heures
|
||||
* **Expressions cron** : « Rappelle-moi à 9h chaque jour » → utilise une expression cron
|
||||
* **Rappels ponctuels** : "Rappelle-moi dans 10 minutes" -> se déclenche une fois après 10 min
|
||||
* **Tâches récurrentes** : "Rappelle-moi toutes les 2 heures" -> se déclenche toutes les 2 heures
|
||||
* **Expressions cron** : "Rappelle-moi à 9h chaque jour" -> utilise une expression cron
|
||||
|
||||
## 🤝 Contribuer & Feuille de Route
|
||||
## 📚 Documentation
|
||||
|
||||
Les PR sont les bienvenues ! Le code est intentionnellement petit et lisible. 🤗
|
||||
Pour des guides détaillés au-delà de ce README :
|
||||
|
||||
Consultez notre [Feuille de Route Communautaire](https://github.com/sipeed/picoclaw/blob/main/ROADMAP.md) complète.
|
||||
| Sujet | Description |
|
||||
|-------|-------------|
|
||||
| [Docker & Démarrage rapide](docs/fr/docker.md) | Configuration Docker Compose, modes Launcher/Agent |
|
||||
| [Applications de chat](docs/fr/chat-apps.md) | Guides de configuration pour les 17+ channels |
|
||||
| [Configuration](docs/fr/configuration.md) | Variables d'environnement, structure du workspace, sandbox de sécurité |
|
||||
| [Providers & Modèles](docs/fr/providers.md) | 30+ providers LLM, routage de modèles, configuration model_list |
|
||||
| [Spawn & Tâches asynchrones](docs/fr/spawn-tasks.md) | Tâches rapides, tâches longues avec spawn, orchestration de sous-agents asynchrones |
|
||||
| [Hooks](docs/hooks/README.md) | Système de hooks événementiels : observateurs, intercepteurs, hooks d'approbation |
|
||||
| [Steering](docs/steering.md) | Injecter des messages dans une boucle agent en cours d'exécution |
|
||||
| [SubTurn](docs/subturn.md) | Coordination de subagents, contrôle de concurrence, cycle de vie |
|
||||
| [Dépannage](docs/fr/troubleshooting.md) | Problèmes courants et solutions |
|
||||
| [Configuration des outils](docs/fr/tools_configuration.md) | Activation/désactivation par outil, politiques d'exécution, MCP, Skills |
|
||||
| [Compatibilité matérielle](docs/fr/hardware-compatibility.md) | Cartes testées, exigences minimales |
|
||||
|
||||
Groupe de développeurs en construction, rejoignez-nous après votre première PR fusionnée !
|
||||
## 🤝 Contribuer & Roadmap
|
||||
|
||||
Les PRs sont les bienvenues ! Le code source est intentionnellement petit et lisible.
|
||||
|
||||
Consultez notre [Roadmap communautaire](https://github.com/sipeed/picoclaw/issues/988) et [CONTRIBUTING.md](CONTRIBUTING.md) pour les directives.
|
||||
|
||||
Groupe de développeurs en construction, rejoignez-le après votre première PR fusionnée !
|
||||
|
||||
Groupes d'utilisateurs :
|
||||
|
||||
discord : <https://discord.gg/V4sAZ9XWpN>
|
||||
Discord : <https://discord.gg/V4sAZ9XWpN>
|
||||
|
||||
WeChat :
|
||||
<img src="assets/wechat.png" alt="WeChat group QR code" width="512">
|
||||
|
||||
|
||||
|
||||
|
||||
<img src="assets/wechat.png" alt="PicoClaw" width="512">
|
||||
|
|
|
|||
605
README.id.md
Normal file
|
|
@ -0,0 +1,605 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: Asisten AI Super Ringan berbasis Go</h1>
|
||||
|
||||
<h3>Perangkat Keras $10 · RAM 10MB · Boot ms · Let's Go, PicoClaw!</h3>
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go&logoColor=white" alt="Go">
|
||||
<img src="https://img.shields.io/badge/Arch-x86__64%2C%20ARM64%2C%20MIPS%2C%20RISC--V%2C%20LoongArch-blue" alt="Hardware">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green" alt="License">
|
||||
<br>
|
||||
<a href="https://picoclaw.io"><img src="https://img.shields.io/badge/Website-picoclaw.io-blue?style=flat&logo=google-chrome&logoColor=white" alt="Website"></a>
|
||||
<a href="https://docs.picoclaw.io/"><img src="https://img.shields.io/badge/Docs-Official-007acc?style=flat&logo=read-the-docs&logoColor=white" alt="Docs"></a>
|
||||
<a href="https://deepwiki.com/sipeed/picoclaw"><img src="https://img.shields.io/badge/Wiki-DeepWiki-FFA500?style=flat&logo=wikipedia&logoColor=white" alt="Wiki"></a>
|
||||
<br>
|
||||
<a href="https://x.com/SipeedIO"><img src="https://img.shields.io/badge/X_(Twitter)-SipeedIO-black?style=flat&logo=x&logoColor=white" alt="Twitter"></a>
|
||||
<a href="./assets/wechat.png"><img src="https://img.shields.io/badge/WeChat-Group-41d56b?style=flat&logo=wechat&logoColor=white"></a>
|
||||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Malay](README.my.md) | [English](README.md) | **Bahasa Indonesia**
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
> **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). 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 dari Mac mini!
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> [!CAUTION]
|
||||
> **Peringatan Keamanan**
|
||||
>
|
||||
> * **TANPA KRIPTO:** PicoClaw **tidak** menerbitkan token atau cryptocurrency resmi apa pun. Semua klaim di `pump.fun` atau platform trading lainnya adalah **penipuan**.
|
||||
> * **DOMAIN RESMI:** Satu-satunya website resmi adalah **[picoclaw.io](https://picoclaw.io)**, dan website perusahaan adalah **[sipeed.com](https://sipeed.com)**
|
||||
> * **WASPADA:** Banyak domain `.ai/.org/.com/.net/...` telah didaftarkan oleh pihak ketiga. Jangan percaya mereka.
|
||||
> * **CATATAN:** PicoClaw masih dalam tahap pengembangan awal yang cepat. Mungkin ada masalah keamanan yang belum terselesaikan. Jangan deploy ke produksi sebelum v1.0.
|
||||
> * **CATATAN:** PicoClaw baru-baru ini menggabungkan banyak PR. Build terbaru mungkin menggunakan RAM 10-20MB. Optimasi sumber daya direncanakan setelah fitur stabil.
|
||||
|
||||
## 📢 Berita
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 Dirilis!** Perombakan arsitektur Agent (SubTurn, Hooks, Steering, EventBus), integrasi WeChat/WeCom, penguatan keamanan (.security.yml, penyaringan data sensitif), provider baru (AWS Bedrock, Azure, Xiaomi MiMo), dan 35 perbaikan bug. PicoClaw telah mencapai **26K Stars**!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 Dirilis!** UI system tray (Windows & Linux), pelacakan status sub-agent (`spawn_status`), eksperimental Gateway hot-reload, gerbang keamanan Cron, dan 2 perbaikan keamanan. PicoClaw telah mencapai **25K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Pembaruan terbesar sejauh ini!** Dukungan protokol MCP, 4 channel baru (Matrix/IRC/WeCom/Discord Proxy), 3 provider baru (Kimi/Minimax/Avian), pipeline visi, penyimpanan memori JSONL, perutean model.
|
||||
|
||||
2026-02-28 📦 **v0.2.0** dirilis dengan dukungan Docker Compose dan Web UI Launcher.
|
||||
|
||||
<details>
|
||||
<summary>Berita sebelumnya...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw mencapai **20K Stars** hanya dalam 17 hari! Orkestrasi channel otomatis dan antarmuka kapabilitas kini aktif.
|
||||
|
||||
2026-02-16 🎉 PicoClaw menembus 12K Stars dalam satu minggu! Peran maintainer komunitas dan [Roadmap](ROADMAP.md) resmi diluncurkan.
|
||||
|
||||
2026-02-13 🎉 PicoClaw menembus 5000 Stars dalam 4 hari! Roadmap proyek dan grup pengembang sedang dalam proses.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw Diluncurkan!** Dibangun dalam 1 hari untuk menghadirkan AI Agent ke perangkat keras $10 dengan RAM <10MB. Let's Go, PicoClaw!
|
||||
|
||||
</details>
|
||||
|
||||
## ✨ Fitur
|
||||
|
||||
🪶 **Super Ringan**: Penggunaan memori inti <10MB — 99% lebih kecil dari OpenClaw.*
|
||||
|
||||
💰 **Biaya Minimal**: Cukup efisien untuk berjalan di perangkat keras $10 — 98% lebih murah dari Mac mini.
|
||||
|
||||
⚡️ **Boot Secepat Kilat**: Startup 400x lebih cepat. Boot dalam <1 detik bahkan di prosesor single-core 0,6GHz.
|
||||
|
||||
🌍 **Portabilitas Sejati**: Satu binary untuk RISC-V, ARM, MIPS, dan x86. Satu binary, jalan di mana saja!
|
||||
|
||||
🤖 **AI-Bootstrapped**: Implementasi Go native murni — 95% kode inti dihasilkan oleh Agent dengan penyempurnaan human-in-the-loop.
|
||||
|
||||
🔌 **Dukungan MCP**: Integrasi [Model Context Protocol](https://modelcontextprotocol.io/) native — hubungkan server MCP mana pun untuk memperluas kapabilitas Agent.
|
||||
|
||||
👁️ **Pipeline Vision**: Kirim gambar dan file langsung ke Agent — encoding base64 otomatis untuk LLM multimodal.
|
||||
|
||||
🧠 **Routing Cerdas**: Routing model berbasis aturan — kueri sederhana diarahkan ke model ringan, menghemat biaya API.
|
||||
|
||||
_*Build terbaru mungkin menggunakan 10-20MB karena penggabungan PR yang cepat. Optimasi sumber daya direncanakan. Perbandingan kecepatan boot berdasarkan benchmark single-core 0,8GHz (lihat tabel di bawah)._
|
||||
|
||||
<div align="center">
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ------------------------------ | ------------- | ------------------------ | -------------------------------------- |
|
||||
| **Bahasa** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **Waktu Boot**</br>(core 0,8GHz) | >500d | >30d | **<1d** |
|
||||
| **Biaya** | Mac Mini $599 | Kebanyakan board Linux ~$50 | **Board Linux mana pun**</br>**mulai $10** |
|
||||
|
||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
</div>
|
||||
|
||||
> **[Daftar Kompatibilitas Hardware](docs/hardware-compatibility.md)** — Lihat semua board yang telah diuji, dari RISC-V $5 hingga Raspberry Pi hingga ponsel Android. Board Anda belum terdaftar? Kirim PR!
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/hardware-banner.jpg" alt="PicoClaw Hardware Compatibility" width="100%">
|
||||
</p>
|
||||
|
||||
## 🦾 Demonstrasi
|
||||
|
||||
### 🛠️ Alur Kerja Asisten Standar
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<th><p align="center">Mode Full-Stack Engineer</p></th>
|
||||
<th><p align="center">Pencatatan & Perencanaan</p></th>
|
||||
<th><p align="center">Pencarian Web & Pembelajaran</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Develop · Deploy · Scale</td>
|
||||
<td align="center">Jadwal · Otomasi · Ingat</td>
|
||||
<td align="center">Temukan · Wawasan · Tren</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 🐜 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 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
|
||||
|
||||
<https://private-user-images.githubusercontent.com/83055338/547056448-e7b031ff-d6f5-4468-bcca-5726b6fecb5c.mp4>
|
||||
|
||||
🌟 Lebih Banyak Kasus Deploy Menanti!
|
||||
|
||||
## 📦 Instalasi
|
||||
|
||||
### Unduh dari picoclaw.io (Direkomendasikan)
|
||||
|
||||
Kunjungi **[picoclaw.io](https://picoclaw.io)** — website resmi mendeteksi platform Anda secara otomatis dan menyediakan unduhan satu klik. Tidak perlu memilih arsitektur secara manual.
|
||||
|
||||
### Unduh binary yang sudah dikompilasi
|
||||
|
||||
Atau, unduh binary untuk platform Anda dari halaman [GitHub Releases](https://github.com/sipeed/picoclaw/releases).
|
||||
|
||||
### Build dari source (untuk pengembangan)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
|
||||
cd picoclaw
|
||||
make deps
|
||||
|
||||
# Build binary inti
|
||||
make build
|
||||
|
||||
# Build Web UI Launcher (diperlukan untuk mode WebUI)
|
||||
make build-launcher
|
||||
|
||||
# Build untuk berbagai platform
|
||||
make build-all
|
||||
|
||||
# Build untuk Raspberry Pi Zero 2 W (32-bit: make build-linux-arm; 64-bit: make build-linux-arm64)
|
||||
make build-pi-zero
|
||||
|
||||
# Build dan instal
|
||||
make install
|
||||
```
|
||||
|
||||
**Raspberry Pi Zero 2 W:** Gunakan binary yang sesuai dengan OS Anda: Raspberry Pi OS 32-bit -> `make build-linux-arm`; 64-bit -> `make build-linux-arm64`. Atau jalankan `make build-pi-zero` untuk build keduanya.
|
||||
|
||||
## 🚀 Panduan Memulai Cepat
|
||||
|
||||
### 🌐 WebUI Launcher (Direkomendasikan untuk Desktop)
|
||||
|
||||
WebUI Launcher menyediakan antarmuka berbasis browser untuk konfigurasi dan chat. Ini adalah cara termudah untuk memulai — tidak perlu pengetahuan command-line.
|
||||
|
||||
**Opsi 1: Klik dua kali (Desktop)**
|
||||
|
||||
Setelah mengunduh dari [picoclaw.io](https://picoclaw.io), klik dua kali `picoclaw-launcher` (atau `picoclaw-launcher.exe` di Windows). Browser Anda akan terbuka otomatis di `http://localhost:18800`.
|
||||
|
||||
**Opsi 2: Command line**
|
||||
|
||||
```bash
|
||||
picoclaw-launcher
|
||||
# Buka http://localhost:18800 di browser Anda
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **Akses jarak jauh / Docker / VM:** Tambahkan flag `-public` untuk mendengarkan di semua antarmuka:
|
||||
> ```bash
|
||||
> picoclaw-launcher -public
|
||||
> ```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-webui.jpg" alt="WebUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Memulai:**
|
||||
|
||||
Buka WebUI, lalu: **1)** Konfigurasi Provider (tambahkan API key LLM Anda) -> **2)** Konfigurasi Channel (mis. Telegram) -> **3)** Mulai Gateway -> **4)** Chat!
|
||||
|
||||
Untuk dokumentasi WebUI lengkap, lihat [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
<details>
|
||||
<summary><b>Docker (alternatif)</b></summary>
|
||||
|
||||
```bash
|
||||
# 1. Clone repo ini
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
|
||||
# 2. Jalankan pertama kali — otomatis membuat docker/data/config.json lalu keluar
|
||||
# (hanya terpicu ketika config.json dan workspace/ keduanya tidak ada)
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up
|
||||
# Container mencetak "First-run setup complete." dan berhenti.
|
||||
|
||||
# 3. Atur API key Anda
|
||||
vim docker/data/config.json
|
||||
|
||||
# 4. Mulai
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
# Buka http://localhost:18800
|
||||
```
|
||||
|
||||
> **Pengguna Docker / VM:** Gateway mendengarkan di `127.0.0.1` secara default. Atur `PICOCLAW_GATEWAY_HOST=0.0.0.0` atau gunakan flag `-public` agar dapat diakses dari host.
|
||||
|
||||
```bash
|
||||
# Cek log
|
||||
docker compose -f docker/docker-compose.yml logs -f
|
||||
|
||||
# Hentikan
|
||||
docker compose -f docker/docker-compose.yml --profile launcher down
|
||||
|
||||
# Update
|
||||
docker compose -f docker/docker-compose.yml pull
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Peringatan Keamanan saat Pertama Kali Diluncurkan</b></summary>
|
||||
|
||||
macOS mungkin memblokir `picoclaw-launcher` saat pertama kali diluncurkan karena diunduh dari internet dan tidak dinotarisasi melalui Mac App Store.
|
||||
|
||||
**Langkah 1:** Klik dua kali `picoclaw-launcher`. Anda akan melihat peringatan keamanan:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Peringatan macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" Tidak Dapat Dibuka — Apple tidak dapat memverifikasi bahwa "picoclaw-launcher" bebas dari malware yang dapat membahayakan Mac Anda atau mengancam privasi Anda.*
|
||||
|
||||
**Langkah 2:** Buka **Pengaturan Sistem** → **Privasi & Keamanan** → gulir ke bawah ke bagian **Keamanan** → klik **Tetap Buka** → konfirmasi dengan mengklik **Tetap Buka** pada dialog.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Privasi & Keamanan — Tetap Buka" width="600">
|
||||
</p>
|
||||
|
||||
Setelah langkah satu kali ini, `picoclaw-launcher` akan terbuka secara normal pada peluncuran berikutnya.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Direkomendasikan untuk Headless / SSH)
|
||||
|
||||
TUI (Terminal UI) Launcher menyediakan antarmuka terminal lengkap untuk konfigurasi dan manajemen. Ideal untuk server, Raspberry Pi, dan lingkungan headless lainnya.
|
||||
|
||||
```bash
|
||||
picoclaw-launcher-tui
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-tui.jpg" alt="TUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Memulai:**
|
||||
|
||||
Gunakan menu TUI untuk: **1)** Konfigurasi Provider -> **2)** Konfigurasi Channel -> **3)** Mulai Gateway -> **4)** Chat!
|
||||
|
||||
Untuk dokumentasi TUI lengkap, lihat [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
### 📱 Android
|
||||
|
||||
Berikan kehidupan kedua untuk ponsel lama Anda! Ubah menjadi Asisten AI pintar dengan PicoClaw.
|
||||
|
||||
**Opsi 1: Termux (tersedia sekarang)**
|
||||
|
||||
1. Instal [Termux](https://github.com/termux/termux-app) (unduh dari [GitHub Releases](https://github.com/termux/termux-app/releases), atau cari di F-Droid / Google Play)
|
||||
2. Jalankan perintah berikut:
|
||||
|
||||
```bash
|
||||
# Unduh rilis terbaru
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard # chroot menyediakan tata letak filesystem Linux standar
|
||||
```
|
||||
|
||||
Kemudian ikuti bagian Terminal Launcher di bawah untuk menyelesaikan konfigurasi.
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**Opsi 2: Instal APK (segera hadir)**
|
||||
|
||||
APK Android mandiri dengan WebUI bawaan sedang dalam pengembangan. Pantau terus!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (untuk lingkungan dengan sumber daya terbatas)</b></summary>
|
||||
|
||||
Untuk lingkungan minimal di mana hanya binary inti `picoclaw` yang tersedia (tanpa Launcher UI), Anda dapat mengonfigurasi semuanya melalui command line dan file konfigurasi JSON.
|
||||
|
||||
**1. Inisialisasi**
|
||||
|
||||
```bash
|
||||
picoclaw onboard
|
||||
```
|
||||
|
||||
Ini membuat `~/.picoclaw/config.json` dan direktori workspace.
|
||||
|
||||
**2. Konfigurasi** (`~/.picoclaw/config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-api-key"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Lihat `config/config.example.json` di repo untuk template konfigurasi lengkap dengan semua opsi yang tersedia.
|
||||
|
||||
**3. Chat**
|
||||
|
||||
```bash
|
||||
# Pertanyaan satu kali
|
||||
picoclaw agent -m "What is 2+2?"
|
||||
|
||||
# Mode interaktif
|
||||
picoclaw agent
|
||||
|
||||
# Mulai gateway untuk integrasi aplikasi chat
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 🔌 Providers (LLM)
|
||||
|
||||
PicoClaw mendukung 30+ provider LLM melalui konfigurasi `model_list`. Gunakan format `protocol/model`:
|
||||
|
||||
| Provider | Protocol | API Key | Catatan |
|
||||
|----------|----------|---------|---------|
|
||||
| [OpenAI](https://platform.openai.com/api-keys) | `openai/` | Diperlukan | GPT-5.4, GPT-4o, o3, dll. |
|
||||
| [Anthropic](https://console.anthropic.com/settings/keys) | `anthropic/` | Diperlukan | Claude Opus 4.6, Sonnet 4.6, dll. |
|
||||
| [Google Gemini](https://aistudio.google.com/apikey) | `gemini/` | Diperlukan | Gemini 3 Flash, 2.5 Pro, dll. |
|
||||
| [OpenRouter](https://openrouter.ai/keys) | `openrouter/` | Diperlukan | 200+ model, API terpadu |
|
||||
| [Zhipu (GLM)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | `zhipu/` | Diperlukan | GLM-4.7, GLM-5, dll. |
|
||||
| [DeepSeek](https://platform.deepseek.com/api_keys) | `deepseek/` | Diperlukan | DeepSeek-V3, DeepSeek-R1 |
|
||||
| [Volcengine](https://console.volcengine.com) | `volcengine/` | Diperlukan | Doubao, model Ark |
|
||||
| [Qwen](https://dashscope.console.aliyun.com/apiKey) | `qwen/` | Diperlukan | Qwen3, Qwen-Max, dll. |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/` | Diperlukan | Inferensi cepat (Llama, Mixtral) |
|
||||
| [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | Diperlukan | Model Kimi |
|
||||
| [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | Diperlukan | Model MiniMax |
|
||||
| [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | Diperlukan | Mistral Large, Codestral |
|
||||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Diperlukan | Model yang di-host NVIDIA |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Diperlukan | Inferensi cepat |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Diperlukan | Berbagai model open |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Diperlukan | Model MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Tidak perlu | Model lokal, self-hosted |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Tidak perlu | Deploy lokal, kompatibel OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Bervariasi | Proxy untuk 100+ provider |
|
||||
| [Azure OpenAI](https://portal.azure.com/) | `azure/` | Diperlukan | Deploy Azure enterprise |
|
||||
| [GitHub Copilot](https://github.com/features/copilot) | `github-copilot/` | OAuth | Login dengan device code |
|
||||
| [Antigravity](https://console.cloud.google.com/) | `antigravity/` | OAuth | Google Cloud AI |
|
||||
|
||||
<details>
|
||||
<summary><b>Deploy lokal (Ollama, vLLM, dll.)</b></summary>
|
||||
|
||||
**Ollama:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-llama",
|
||||
"model": "ollama/llama3.1:8b",
|
||||
"api_base": "http://localhost:11434/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**vLLM:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-vllm",
|
||||
"model": "vllm/your-model",
|
||||
"api_base": "http://localhost:8000/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Untuk detail konfigurasi provider lengkap, lihat [Providers & Models](docs/providers.md).
|
||||
|
||||
</details>
|
||||
|
||||
## 💬 Channels (Aplikasi Chat)
|
||||
|
||||
Bicara dengan PicoClaw Anda melalui 17+ platform pesan:
|
||||
|
||||
| Channel | Pengaturan | Protocol | Dokumentasi |
|
||||
|---------|------------|----------|-------------|
|
||||
| **Telegram** | Mudah (bot token) | Long polling | [Panduan](docs/channels/telegram/README.md) |
|
||||
| **Discord** | Mudah (bot token + intents) | WebSocket | [Panduan](docs/channels/discord/README.md) |
|
||||
| **WhatsApp** | Mudah (scan QR atau bridge URL) | Native / Bridge | [Panduan](docs/chat-apps.md#whatsapp) |
|
||||
| **Weixin** | Mudah (scan QR native) | iLink API | [Panduan](docs/chat-apps.md#weixin) |
|
||||
| **QQ** | Mudah (AppID + AppSecret) | WebSocket | [Panduan](docs/channels/qq/README.md) |
|
||||
| **Slack** | Mudah (bot + app token) | Socket Mode | [Panduan](docs/channels/slack/README.md) |
|
||||
| **Matrix** | Sedang (homeserver + token) | Sync API | [Panduan](docs/channels/matrix/README.md) |
|
||||
| **DingTalk** | Sedang (client credentials) | Stream | [Panduan](docs/channels/dingtalk/README.md) |
|
||||
| **Feishu / Lark** | Sedang (App ID + Secret) | WebSocket/SDK | [Panduan](docs/channels/feishu/README.md) |
|
||||
| **LINE** | Sedang (credentials + webhook) | Webhook | [Panduan](docs/channels/line/README.md) |
|
||||
| **WeCom** | Mudah (login QR atau manual) | WebSocket | [Panduan](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Sedang (server + nick) | IRC protocol | [Panduan](docs/chat-apps.md#irc) |
|
||||
| **OneBot** | Sedang (WebSocket URL) | OneBot v11 | [Panduan](docs/channels/onebot/README.md) |
|
||||
| **MaixCam** | Mudah (aktifkan) | TCP socket | [Panduan](docs/channels/maixcam/README.md) |
|
||||
| **Pico** | Mudah (aktifkan) | Native protocol | Bawaan |
|
||||
| **Pico Client** | Mudah (WebSocket URL) | WebSocket | Bawaan |
|
||||
|
||||
> Semua channel berbasis webhook berbagi satu server HTTP Gateway (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu menggunakan mode WebSocket/SDK dan tidak menggunakan server HTTP bersama.
|
||||
|
||||
> Verbositas log dikontrol oleh `gateway.log_level` (default: `warn`). Nilai yang didukung: `debug`, `info`, `warn`, `error`, `fatal`. Juga dapat diatur melalui `PICOCLAW_LOG_LEVEL`. Lihat [Konfigurasi](docs/configuration.md#gateway-log-level) untuk detail.
|
||||
|
||||
Untuk instruksi pengaturan channel lengkap, lihat [Konfigurasi Aplikasi Chat](docs/chat-apps.md).
|
||||
|
||||
## 🔧 Tools
|
||||
|
||||
### 🔍 Pencarian Web
|
||||
|
||||
PicoClaw dapat mencari web untuk memberikan informasi terkini. Konfigurasi di `tools.web`:
|
||||
|
||||
| Mesin Pencari | API Key | Tier Gratis | Tautan |
|
||||
|--------------|---------|-------------|--------|
|
||||
| DuckDuckGo | Tidak perlu | Tidak terbatas | Fallback bawaan |
|
||||
| [Baidu Search](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5) | Diperlukan | 1000 kueri/hari | Bertenaga AI, dioptimalkan untuk bahasa Mandarin |
|
||||
| [Tavily](https://tavily.com) | Diperlukan | 1000 kueri/bulan | Dioptimalkan untuk AI Agent |
|
||||
| [Brave Search](https://brave.com/search/api) | Diperlukan | 2000 kueri/bulan | Cepat dan privat |
|
||||
| [Perplexity](https://www.perplexity.ai) | Diperlukan | Berbayar | Pencarian bertenaga AI |
|
||||
| [SearXNG](https://github.com/searxng/searxng) | Tidak perlu | Self-hosted | Mesin metasearch gratis |
|
||||
| [GLM Search](https://open.bigmodel.cn/) | Diperlukan | Bervariasi | Pencarian web Zhipu |
|
||||
|
||||
### ⚙️ Tools Lainnya
|
||||
|
||||
PicoClaw menyertakan tools bawaan untuk operasi file, eksekusi kode, penjadwalan, dan lainnya. Lihat [Konfigurasi Tools](docs/tools_configuration.md) untuk detail.
|
||||
|
||||
## 🎯 Skills
|
||||
|
||||
Skills adalah kapabilitas modular yang memperluas Agent Anda. Dimuat dari file `SKILL.md` di workspace Anda.
|
||||
|
||||
**Instal skills dari ClawHub:**
|
||||
|
||||
```bash
|
||||
picoclaw skills search "web scraping"
|
||||
picoclaw skills install <skill-name>
|
||||
```
|
||||
|
||||
**Konfigurasi token ClawHub** (opsional, untuk rate limit lebih tinggi):
|
||||
|
||||
Tambahkan ke `config.json` Anda:
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"skills": {
|
||||
"registries": {
|
||||
"clawhub": {
|
||||
"auth_token": "your-clawhub-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Untuk detail lebih lanjut, lihat [Konfigurasi Tools - Skills](docs/tools_configuration.md#skills-tool).
|
||||
|
||||
## 🔗 MCP (Model Context Protocol)
|
||||
|
||||
PicoClaw mendukung [MCP](https://modelcontextprotocol.io/) secara native — hubungkan server MCP mana pun untuk memperluas kapabilitas Agent Anda dengan tools dan sumber data eksternal.
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"servers": {
|
||||
"filesystem": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Untuk konfigurasi MCP lengkap (transport stdio, SSE, HTTP, Tool Discovery), lihat [Konfigurasi Tools - MCP](docs/tools_configuration.md#mcp-tool).
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> Bergabung dengan Jaringan Sosial Agent
|
||||
|
||||
Hubungkan PicoClaw ke Jaringan Sosial Agent hanya dengan mengirim satu pesan melalui CLI atau Aplikasi Chat terintegrasi mana pun.
|
||||
|
||||
**Baca `https://clawdchat.ai/skill.md` dan ikuti instruksi untuk bergabung dengan [ClawdChat.ai](https://clawdchat.ai)**
|
||||
|
||||
## 🖥️ Referensi CLI
|
||||
|
||||
| Perintah | Deskripsi |
|
||||
| -------------------------- | -------------------------------- |
|
||||
| `picoclaw onboard` | Inisialisasi konfigurasi & workspace |
|
||||
| `picoclaw auth weixin` | Hubungkan akun WeChat via QR |
|
||||
| `picoclaw agent -m "..."` | Chat dengan agent |
|
||||
| `picoclaw agent` | Mode chat interaktif |
|
||||
| `picoclaw gateway` | Mulai gateway |
|
||||
| `picoclaw status` | Tampilkan status |
|
||||
| `picoclaw version` | Tampilkan info versi |
|
||||
| `picoclaw model` | Lihat atau ganti model default |
|
||||
| `picoclaw cron list` | Daftar semua tugas terjadwal |
|
||||
| `picoclaw cron add ...` | Tambah tugas terjadwal |
|
||||
| `picoclaw cron disable` | Nonaktifkan tugas terjadwal |
|
||||
| `picoclaw cron remove` | Hapus tugas terjadwal |
|
||||
| `picoclaw skills list` | Daftar skill yang terinstal |
|
||||
| `picoclaw skills install` | Instal skill |
|
||||
| `picoclaw migrate` | Migrasi data dari versi lama |
|
||||
| `picoclaw auth login` | Autentikasi dengan provider |
|
||||
|
||||
### ⏰ Tugas Terjadwal / Pengingat
|
||||
|
||||
PicoClaw mendukung pengingat terjadwal dan tugas berulang melalui tool `cron`:
|
||||
|
||||
* **Pengingat satu kali**: "Ingatkan saya dalam 10 menit" -> terpicu sekali setelah 10 menit
|
||||
* **Tugas berulang**: "Ingatkan saya setiap 2 jam" -> terpicu setiap 2 jam
|
||||
* **Ekspresi cron**: "Ingatkan saya jam 9 pagi setiap hari" -> menggunakan ekspresi cron
|
||||
|
||||
## 📚 Dokumentasi
|
||||
|
||||
Untuk panduan lengkap di luar README ini:
|
||||
|
||||
| Topik | Deskripsi |
|
||||
|-------|-----------|
|
||||
| [Docker & Panduan Cepat](docs/docker.md) | Pengaturan Docker Compose, mode Launcher/Agent |
|
||||
| [Aplikasi Chat](docs/chat-apps.md) | Semua 17+ panduan pengaturan channel |
|
||||
| [Konfigurasi](docs/configuration.md) | Variabel environment, tata letak workspace, sandbox keamanan |
|
||||
| [Providers & Models](docs/providers.md) | 30+ provider LLM, routing model, konfigurasi model_list |
|
||||
| [Spawn & Tugas Async](docs/spawn-tasks.md) | Tugas cepat, tugas panjang dengan spawn, orkestrasi sub-agent async |
|
||||
| [Hooks](docs/hooks/README.md) | Sistem hook berbasis event: observer, interceptor, approval hook |
|
||||
| [Steering](docs/steering.md) | Menyuntikkan pesan ke dalam loop agent yang sedang berjalan |
|
||||
| [SubTurn](docs/subturn.md) | Koordinasi subagent, kontrol konkurensi, siklus hidup |
|
||||
| [Pemecahan Masalah](docs/troubleshooting.md) | Masalah umum dan solusinya |
|
||||
| [Konfigurasi Tools](docs/tools_configuration.md) | Aktifkan/nonaktifkan per-tool, kebijakan exec, MCP, Skills |
|
||||
| [Kompatibilitas Hardware](docs/hardware-compatibility.md) | Board yang telah diuji, persyaratan minimum |
|
||||
|
||||
## 🤝 Kontribusi & Roadmap
|
||||
|
||||
PR sangat diterima! Codebase sengaja dibuat kecil dan mudah dibaca.
|
||||
|
||||
Lihat [Roadmap Komunitas](https://github.com/sipeed/picoclaw/issues/988) dan [CONTRIBUTING.md](CONTRIBUTING.md) untuk panduan.
|
||||
|
||||
Grup pengembang sedang dibangun, bergabunglah setelah PR pertama Anda di-merge!
|
||||
|
||||
Grup Pengguna:
|
||||
|
||||
Discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
|
||||
WeChat:
|
||||
<img src="assets/wechat.png" alt="Kode QR grup WeChat" width="512">
|
||||
|
||||
604
README.it.md
Normal file
|
|
@ -0,0 +1,604 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: Assistente IA Ultra-Efficiente in Go</h1>
|
||||
|
||||
<h3>Hardware da $10 · 10MB di RAM · Avvio in ms · Let's Go, PicoClaw!</h3>
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go&logoColor=white" alt="Go">
|
||||
<img src="https://img.shields.io/badge/Arch-x86__64%2C%20ARM64%2C%20MIPS%2C%20RISC--V%2C%20LoongArch-blue" alt="Hardware">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green" alt="License">
|
||||
<br>
|
||||
<a href="https://picoclaw.io"><img src="https://img.shields.io/badge/Website-picoclaw.io-blue?style=flat&logo=google-chrome&logoColor=white" alt="Website"></a>
|
||||
<a href="https://docs.picoclaw.io/"><img src="https://img.shields.io/badge/Docs-Official-007acc?style=flat&logo=read-the-docs&logoColor=white" alt="Docs"></a>
|
||||
<a href="https://deepwiki.com/sipeed/picoclaw"><img src="https://img.shields.io/badge/Wiki-DeepWiki-FFA500?style=flat&logo=wikipedia&logoColor=white" alt="Wiki"></a>
|
||||
<br>
|
||||
<a href="https://x.com/SipeedIO"><img src="https://img.shields.io/badge/X_(Twitter)-SipeedIO-black?style=flat&logo=x&logoColor=white" alt="Twitter"></a>
|
||||
<a href="./assets/wechat.png"><img src="https://img.shields.io/badge/WeChat-Group-41d56b?style=flat&logo=wechat&logoColor=white"></a>
|
||||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | **Italiano** | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
> **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). È 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 <10MB di RAM** — il 99% di memoria in meno rispetto a OpenClaw e il 98% più economico di un Mac mini!
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> [!CAUTION]
|
||||
> **Avviso di Sicurezza**
|
||||
>
|
||||
> * **NESSUNA CRYPTO:** PicoClaw **non** ha emesso token o criptovalute ufficiali. Qualsiasi annuncio su `pump.fun` o altre piattaforme di trading è una **truffa**.
|
||||
> * **DOMINIO UFFICIALE:** L'**UNICO** sito ufficiale è **[picoclaw.io](https://picoclaw.io)**, e il sito aziendale è **[sipeed.com](https://sipeed.com)**
|
||||
> * **ATTENZIONE:** Molti domini `.ai/.org/.com/.net/...` sono stati registrati da terze parti. Non fidarti di essi.
|
||||
> * **NOTA:** PicoClaw è in fase di sviluppo iniziale rapido. Potrebbero esserci problemi di sicurezza non risolti. Non distribuire in produzione prima della v1.0.
|
||||
> * **NOTA:** PicoClaw ha recentemente unito molte PR. Le build recenti potrebbero usare 10-20MB di RAM. L'ottimizzazione delle risorse è pianificata dopo la stabilizzazione delle funzionalità.
|
||||
|
||||
## 📢 Novità
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 rilasciata!** Revisione dell'architettura Agent (SubTurn, Hooks, Steering, EventBus), integrazione WeChat/WeCom, rafforzamento della sicurezza (.security.yml, filtraggio dati sensibili), nuovi provider (AWS Bedrock, Azure, Xiaomi MiMo) e 35 correzioni di bug. PicoClaw raggiunge **26K Stars**!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 rilasciata!** Interfaccia system tray (Windows & Linux), query sullo stato dei sub-agent (`spawn_status`), hot-reload sperimentale del Gateway, gate di sicurezza per Cron e 2 correzioni di sicurezza. PicoClaw raggiunge **25K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Il più grande aggiornamento di sempre!** Supporto al protocollo MCP, 4 nuovi canali (Matrix/IRC/WeCom/Discord Proxy), 3 nuovi provider (Kimi/Minimax/Avian), pipeline visiva, archivio memoria JSONL, routing dei modelli.
|
||||
|
||||
2026-02-28 📦 **v0.2.0** rilasciata con supporto Docker Compose e Web UI Launcher.
|
||||
|
||||
<details>
|
||||
<summary>Notizie precedenti...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw raggiunge **20K stelle** in soli 17 giorni! Orchestrazione automatica dei canali e interfacce di capacità sono attive.
|
||||
|
||||
2026-02-16 🎉 PicoClaw supera 12K stelle in una settimana! Ruoli di maintainer della community e [Roadmap](ROADMAP.md) pubblicati ufficialmente.
|
||||
|
||||
2026-02-13 🎉 PicoClaw supera 5000 stelle in 4 giorni! Roadmap del progetto e gruppi sviluppatori in fase di avvio.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw lanciato!** Costruito in 1 giorno per portare gli AI Agent su hardware da $10 con <10MB di RAM. Let's Go, PicoClaw!
|
||||
|
||||
</details>
|
||||
|
||||
## ✨ Caratteristiche
|
||||
|
||||
🪶 **Ultra-Leggero**: Impronta di memoria <10MB — il 99% più piccolo rispetto a OpenClaw.*
|
||||
|
||||
💰 **Costo Minimo**: Abbastanza efficiente da girare su hardware da $10 — il 98% più economico di un Mac mini.
|
||||
|
||||
⚡️ **Avvio Fulmineo**: Avvio 400 volte più veloce. Boot in meno di 1 secondo anche su un singolo core a 0,6 GHz.
|
||||
|
||||
🌍 **Vera Portabilità**: Singolo binario per RISC-V, ARM, MIPS e x86. Un binario, funziona ovunque!
|
||||
|
||||
🤖 **Auto-Costruito dall'IA**: Implementazione nativa in Go — il 95% del codice core è stato generato da un Agent e perfezionato tramite revisione umana nel ciclo.
|
||||
|
||||
🔌 **Supporto MCP**: Integrazione nativa del [Model Context Protocol](https://modelcontextprotocol.io/) — connetti qualsiasi server MCP per estendere le capacità dell'Agent.
|
||||
|
||||
👁️ **Pipeline di Visione**: Invia immagini e file direttamente all'Agent — codifica base64 automatica per LLM multimodali.
|
||||
|
||||
🧠 **Routing Intelligente**: Routing dei modelli basato su regole — le query semplici vanno verso modelli leggeri, risparmiando sui costi API.
|
||||
|
||||
_*Le build recenti potrebbero usare 10-20MB a causa delle fusioni rapide di PR. L'ottimizzazione delle risorse è pianificata. Il confronto dell'avvio è basato su benchmark con singolo core a 0,8 GHz (vedi tabella sotto)._
|
||||
|
||||
<div align="center">
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ------------------------------ | ------------- | ------------------------ | -------------------------------------- |
|
||||
| **Linguaggio** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **Avvio**</br>(core 0,8 GHz) | >500s | >30s | **<1s** |
|
||||
| **Costo** | Mac Mini $599 | La maggior parte degli SBC Linux ~$50 | **Qualsiasi scheda Linux**</br>**a partire da $10** |
|
||||
|
||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
</div>
|
||||
|
||||
> **[Lista di Compatibilità Hardware](docs/hardware-compatibility.md)** — Vedi tutte le schede testate, dai $5 RISC-V al Raspberry Pi ai telefoni Android. La tua scheda non è elencata? Invia una PR!
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/hardware-banner.jpg" alt="PicoClaw Hardware Compatibility" width="100%">
|
||||
</p>
|
||||
|
||||
## 🦾 Dimostrazione
|
||||
|
||||
### 🛠️ Flussi di Lavoro Standard dell'Assistente
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<th><p align="center">Modalità Ingegnere Full-Stack</p></th>
|
||||
<th><p align="center">Log & Pianificazione</p></th>
|
||||
<th><p align="center">Ricerca Web & Apprendimento</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Sviluppa · Distribuisci · Scala</td>
|
||||
<td align="center">Pianifica · Automatizza · Memorizza</td>
|
||||
<td align="center">Scopri · Analizza · Tendenze</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 🐜 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 la sorveglianza intelligente
|
||||
|
||||
<https://private-user-images.githubusercontent.com/83055338/547056448-e7b031ff-d6f5-4468-bcca-5726b6fecb5c.mp4>
|
||||
|
||||
🌟 Molti altri scenari di deploy ti aspettano!
|
||||
|
||||
## 📦 Installazione
|
||||
|
||||
### Scarica da picoclaw.io (Consigliato)
|
||||
|
||||
Visita **[picoclaw.io](https://picoclaw.io)** — il sito ufficiale rileva automaticamente la tua piattaforma e fornisce il download con un clic. Non è necessario scegliere manualmente l'architettura.
|
||||
|
||||
### Scarica il binario precompilato
|
||||
|
||||
In alternativa, scarica il binario per la tua piattaforma dalla pagina delle [GitHub Releases](https://github.com/sipeed/picoclaw/releases).
|
||||
|
||||
### Compila dai sorgenti (per lo sviluppo)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
|
||||
cd picoclaw
|
||||
make deps
|
||||
|
||||
# Compila il binario core
|
||||
make build
|
||||
|
||||
# Compila il Web UI Launcher (necessario per la modalità WebUI)
|
||||
make build-launcher
|
||||
|
||||
# Compila per più piattaforme
|
||||
make build-all
|
||||
|
||||
# Compila per Raspberry Pi Zero 2 W (32-bit: make build-linux-arm; 64-bit: make build-linux-arm64)
|
||||
make build-pi-zero
|
||||
|
||||
# Compila e installa
|
||||
make install
|
||||
```
|
||||
|
||||
**Raspberry Pi Zero 2 W:** Usa il binario che corrisponde al tuo OS: Raspberry Pi OS 32-bit -> `make build-linux-arm`; 64-bit -> `make build-linux-arm64`. Oppure esegui `make build-pi-zero` per compilare entrambi.
|
||||
|
||||
## 🚀 Guida Rapida
|
||||
|
||||
### 🌐 WebUI Launcher (Consigliato per Desktop)
|
||||
|
||||
Il WebUI Launcher fornisce un'interfaccia basata su browser per la configurazione e la chat. È il modo più semplice per iniziare — non è richiesta alcuna conoscenza della riga di comando.
|
||||
|
||||
**Opzione 1: Doppio clic (Desktop)**
|
||||
|
||||
Dopo aver scaricato da [picoclaw.io](https://picoclaw.io), fai doppio clic su `picoclaw-launcher` (o `picoclaw-launcher.exe` su Windows). Il browser si aprirà automaticamente su `http://localhost:18800`.
|
||||
|
||||
**Opzione 2: Riga di comando**
|
||||
|
||||
```bash
|
||||
picoclaw-launcher
|
||||
# Apri http://localhost:18800 nel browser
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **Accesso remoto / Docker / VM:** Aggiungi il flag `-public` per ascoltare su tutte le interfacce:
|
||||
> ```bash
|
||||
> picoclaw-launcher -public
|
||||
> ```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-webui.jpg" alt="WebUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Per iniziare:**
|
||||
|
||||
Apri il WebUI, poi: **1)** Configura un Provider (aggiungi la tua API key LLM) -> **2)** Configura un Channel (es. Telegram) -> **3)** Avvia il Gateway -> **4)** Chatta!
|
||||
|
||||
Per la documentazione dettagliata del WebUI, vedi [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
<details>
|
||||
<summary><b>Docker (alternativa)</b></summary>
|
||||
|
||||
```bash
|
||||
# 1. Clona questo repo
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
|
||||
# 2. Prima esecuzione — genera automaticamente docker/data/config.json poi si ferma
|
||||
# (si attiva solo quando sia config.json che workspace/ sono assenti)
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up
|
||||
# Il container stampa "First-run setup complete." e si ferma.
|
||||
|
||||
# 3. Imposta le tue API key
|
||||
vim docker/data/config.json
|
||||
|
||||
# 4. Avvia
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
# Apri http://localhost:18800
|
||||
```
|
||||
|
||||
> **Utenti Docker / VM:** Il Gateway ascolta su `127.0.0.1` per impostazione predefinita. Imposta `PICOCLAW_GATEWAY_HOST=0.0.0.0` o usa il flag `-public` per renderlo accessibile dall'host.
|
||||
|
||||
```bash
|
||||
# Controlla i log
|
||||
docker compose -f docker/docker-compose.yml logs -f
|
||||
|
||||
# Ferma
|
||||
docker compose -f docker/docker-compose.yml --profile launcher down
|
||||
|
||||
# Aggiorna
|
||||
docker compose -f docker/docker-compose.yml pull
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Avviso di sicurezza al primo avvio</b></summary>
|
||||
|
||||
macOS potrebbe bloccare `picoclaw-launcher` al primo avvio perché è stato scaricato da internet e non è notarizzato tramite il Mac App Store.
|
||||
|
||||
**Passo 1:** Fai doppio clic su `picoclaw-launcher`. Verrà visualizzato un avviso di sicurezza:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Avviso macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" Non Aperto — Apple non è riuscita a verificare che "picoclaw-launcher" sia privo di malware che potrebbe danneggiare il Mac o compromettere la privacy.*
|
||||
|
||||
**Passo 2:** Apri **Impostazioni di Sistema** → **Privacy e sicurezza** → scorri fino alla sezione **Sicurezza** → clicca su **Apri comunque** → conferma cliccando su **Apri comunque** nella finestra di dialogo.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Privacy e sicurezza — Apri comunque" width="600">
|
||||
</p>
|
||||
|
||||
Dopo questo passaggio una tantum, `picoclaw-launcher` si aprirà normalmente ai lanci successivi.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Consigliato per Headless / SSH)
|
||||
|
||||
Il TUI (Terminal UI) Launcher fornisce un'interfaccia terminale completa per la configurazione e la gestione. Ideale per server, Raspberry Pi e altri ambienti headless.
|
||||
|
||||
```bash
|
||||
picoclaw-launcher-tui
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-tui.jpg" alt="TUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Per iniziare:**
|
||||
|
||||
Usa i menu TUI per: **1)** Configurare un Provider -> **2)** Configurare un Channel -> **3)** Avviare il Gateway -> **4)** Chattare!
|
||||
|
||||
Per la documentazione dettagliata del TUI, vedi [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
### 📱 Android
|
||||
|
||||
Dai una seconda vita al tuo telefono di dieci anni fa! Trasformalo in un assistente IA intelligente con PicoClaw.
|
||||
|
||||
**Opzione 1: Termux (disponibile ora)**
|
||||
|
||||
1. Installa [Termux](https://github.com/termux/termux-app) (scarica da [GitHub Releases](https://github.com/termux/termux-app/releases), o cerca su F-Droid / Google Play)
|
||||
2. Esegui i seguenti comandi:
|
||||
|
||||
```bash
|
||||
# Scarica l'ultima release
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard # chroot fornisce un layout standard del filesystem Linux
|
||||
```
|
||||
|
||||
Poi segui la sezione Terminal Launcher qui sotto per completare la configurazione.
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**Opzione 2: APK Install (prossimamente)**
|
||||
|
||||
Un APK Android standalone con WebUI integrato è in sviluppo. Resta sintonizzato!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (per ambienti con risorse limitate)</b></summary>
|
||||
|
||||
Per ambienti minimali dove è disponibile solo il binario core `picoclaw` (senza Launcher UI), puoi configurare tutto tramite riga di comando e un file di configurazione JSON.
|
||||
|
||||
**1. Inizializza**
|
||||
|
||||
```bash
|
||||
picoclaw onboard
|
||||
```
|
||||
|
||||
Questo crea `~/.picoclaw/config.json` e la directory workspace.
|
||||
|
||||
**2. Configura** (`~/.picoclaw/config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-api-key"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Vedi `config/config.example.json` nel repo per un template di configurazione completo con tutte le opzioni disponibili.
|
||||
|
||||
**3. Chatta**
|
||||
|
||||
```bash
|
||||
# Domanda singola
|
||||
picoclaw agent -m "Quanto fa 2+2?"
|
||||
|
||||
# Modalità interattiva
|
||||
picoclaw agent
|
||||
|
||||
# Avvia il gateway per l'integrazione con app di chat
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 🔌 Provider (LLM)
|
||||
|
||||
PicoClaw supporta 30+ provider LLM tramite la configurazione `model_list`. Usa il formato `protocollo/modello`:
|
||||
|
||||
| Provider | Protocollo | API Key | Note |
|
||||
|----------|------------|---------|------|
|
||||
| [OpenAI](https://platform.openai.com/api-keys) | `openai/` | Richiesta | GPT-5.4, GPT-4o, o3, ecc. |
|
||||
| [Anthropic](https://console.anthropic.com/settings/keys) | `anthropic/` | Richiesta | Claude Opus 4.6, Sonnet 4.6, ecc. |
|
||||
| [Google Gemini](https://aistudio.google.com/apikey) | `gemini/` | Richiesta | Gemini 3 Flash, 2.5 Pro, ecc. |
|
||||
| [OpenRouter](https://openrouter.ai/keys) | `openrouter/` | Richiesta | 200+ modelli, API unificata |
|
||||
| [Zhipu (GLM)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | `zhipu/` | Richiesta | GLM-4.7, GLM-5, ecc. |
|
||||
| [DeepSeek](https://platform.deepseek.com/api_keys) | `deepseek/` | Richiesta | DeepSeek-V3, DeepSeek-R1 |
|
||||
| [Volcengine](https://console.volcengine.com) | `volcengine/` | Richiesta | Doubao, modelli Ark |
|
||||
| [Qwen](https://dashscope.console.aliyun.com/apiKey) | `qwen/` | Richiesta | Qwen3, Qwen-Max, ecc. |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/` | Richiesta | Inferenza veloce (Llama, Mixtral) |
|
||||
| [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | Richiesta | Modelli Kimi |
|
||||
| [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | Richiesta | Modelli MiniMax |
|
||||
| [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | Richiesta | Mistral Large, Codestral |
|
||||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Richiesta | Modelli ospitati NVIDIA |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Richiesta | Inferenza veloce |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Richiesta | Vari modelli open |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Richiesta | Modelli MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Non necessaria | Modelli locali, self-hosted |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Non necessaria | Deploy locale, compatibile OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Variabile | Proxy per 100+ provider |
|
||||
| [Azure OpenAI](https://portal.azure.com/) | `azure/` | Richiesta | Deploy Azure enterprise |
|
||||
| [GitHub Copilot](https://github.com/features/copilot) | `github-copilot/` | OAuth | Login con device code |
|
||||
| [Antigravity](https://console.cloud.google.com/) | `antigravity/` | OAuth | Google Cloud AI |
|
||||
|
||||
<details>
|
||||
<summary><b>Deploy locale (Ollama, vLLM, ecc.)</b></summary>
|
||||
|
||||
**Ollama:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-llama",
|
||||
"model": "ollama/llama3.1:8b",
|
||||
"api_base": "http://localhost:11434/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**vLLM:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-vllm",
|
||||
"model": "vllm/your-model",
|
||||
"api_base": "http://localhost:8000/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Per i dettagli completi sulla configurazione dei provider, vedi [Provider & Modelli](docs/providers.md).
|
||||
|
||||
</details>
|
||||
|
||||
## 💬 Channel (App di Chat)
|
||||
|
||||
Parla con il tuo PicoClaw attraverso 17+ piattaforme di messaggistica:
|
||||
|
||||
| Channel | Configurazione | Protocollo | Docs |
|
||||
|---------|----------------|------------|------|
|
||||
| **Telegram** | Facile (bot token) | Long polling | [Guida](docs/channels/telegram/README.md) |
|
||||
| **Discord** | Facile (bot token + intents) | WebSocket | [Guida](docs/channels/discord/README.md) |
|
||||
| **WhatsApp** | Facile (QR scan o bridge URL) | Nativo / Bridge | [Guida](docs/chat-apps.md#whatsapp) |
|
||||
| **Weixin** | Facile (scan QR nativo) | iLink API | [Guida](docs/chat-apps.md#weixin) |
|
||||
| **QQ** | Facile (AppID + AppSecret) | WebSocket | [Guida](docs/channels/qq/README.md) |
|
||||
| **Slack** | Facile (bot + app token) | Socket Mode | [Guida](docs/channels/slack/README.md) |
|
||||
| **Matrix** | Medio (homeserver + token) | Sync API | [Guida](docs/channels/matrix/README.md) |
|
||||
| **DingTalk** | Medio (credenziali client) | Stream | [Guida](docs/channels/dingtalk/README.md) |
|
||||
| **Feishu / Lark** | Medio (App ID + Secret) | WebSocket/SDK | [Guida](docs/channels/feishu/README.md) |
|
||||
| **LINE** | Medio (credenziali + webhook) | Webhook | [Guida](docs/channels/line/README.md) |
|
||||
| **WeCom** | Facile (login QR o manuale) | WebSocket | [Guida](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Medio (server + nick) | Protocollo IRC | [Guida](docs/chat-apps.md#irc) |
|
||||
| **OneBot** | Medio (WebSocket URL) | OneBot v11 | [Guida](docs/channels/onebot/README.md) |
|
||||
| **MaixCam** | Facile (abilita) | TCP socket | [Guida](docs/channels/maixcam/README.md) |
|
||||
| **Pico** | Facile (abilita) | Protocollo nativo | Integrato |
|
||||
| **Pico Client** | Facile (WebSocket URL) | WebSocket | Integrato |
|
||||
|
||||
> Tutti i channel basati su webhook condividono un singolo server HTTP Gateway (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu usa la modalità WebSocket/SDK e non usa il server HTTP condiviso.
|
||||
|
||||
> La verbosità dei log è controllata da `gateway.log_level` (default: `warn`). Valori supportati: `debug`, `info`, `warn`, `error`, `fatal`. Può essere impostato anche tramite `PICOCLAW_LOG_LEVEL`. Vedi [Configurazione](docs/configuration.md#gateway-log-level) per i dettagli.
|
||||
|
||||
Per istruzioni dettagliate sulla configurazione dei channel, vedi [Configurazione App di Chat](docs/chat-apps.md).
|
||||
|
||||
## 🔧 Strumenti
|
||||
|
||||
### 🔍 Ricerca Web
|
||||
|
||||
PicoClaw può cercare sul web per fornire informazioni aggiornate. Configura in `tools.web`:
|
||||
|
||||
| Motore di Ricerca | API Key | Piano Gratuito | Link |
|
||||
|-------------------|---------|----------------|------|
|
||||
| DuckDuckGo | Non necessaria | Illimitato | Fallback integrato |
|
||||
| [Baidu Search](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5) | Richiesta | 1000 query/giorno | IA, ottimizzato per il cinese |
|
||||
| [Tavily](https://tavily.com) | Richiesta | 1000 query/mese | Ottimizzato per AI Agent |
|
||||
| [Brave Search](https://brave.com/search/api) | Richiesta | 2000 query/mese | Veloce e privato |
|
||||
| [Perplexity](https://www.perplexity.ai) | Richiesta | A pagamento | Ricerca potenziata dall'IA |
|
||||
| [SearXNG](https://github.com/searxng/searxng) | Non necessaria | Self-hosted | Metasearch engine gratuito |
|
||||
| [GLM Search](https://open.bigmodel.cn/) | Richiesta | Variabile | Ricerca web Zhipu |
|
||||
|
||||
### ⚙️ Altri Strumenti
|
||||
|
||||
PicoClaw include strumenti integrati per operazioni su file, esecuzione di codice, pianificazione e altro. Vedi [Configurazione degli Strumenti](docs/tools_configuration.md) per i dettagli.
|
||||
|
||||
## 🎯 Skill
|
||||
|
||||
Le Skill sono capacità modulari che estendono il tuo Agent. Vengono caricate dai file `SKILL.md` nel tuo workspace.
|
||||
|
||||
**Installa skill da ClawHub:**
|
||||
|
||||
```bash
|
||||
picoclaw skills search "web scraping"
|
||||
picoclaw skills install <skill-name>
|
||||
```
|
||||
|
||||
**Configura il token ClawHub** (opzionale, per limiti di frequenza più alti):
|
||||
|
||||
Aggiungi al tuo `config.json`:
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"skills": {
|
||||
"registries": {
|
||||
"clawhub": {
|
||||
"auth_token": "your-clawhub-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Per maggiori dettagli, vedi [Configurazione degli Strumenti - Skill](docs/tools_configuration.md#skills-tool).
|
||||
|
||||
## 🔗 MCP (Model Context Protocol)
|
||||
|
||||
PicoClaw supporta nativamente [MCP](https://modelcontextprotocol.io/) — connetti qualsiasi server MCP per estendere le capacità del tuo Agent con strumenti e sorgenti di dati esterni.
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"servers": {
|
||||
"filesystem": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Per la configurazione MCP completa (trasporti stdio, SSE, HTTP, Tool Discovery), vedi [Configurazione degli Strumenti - MCP](docs/tools_configuration.md#mcp-tool).
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> Unisciti al Social Network degli Agent
|
||||
|
||||
Connetti PicoClaw al Social Network degli Agent semplicemente inviando un singolo messaggio tramite CLI o qualsiasi app di chat integrata.
|
||||
|
||||
**Leggi `https://clawdchat.ai/skill.md` e segui le istruzioni per unirti a [ClawdChat.ai](https://clawdchat.ai)**
|
||||
|
||||
## 🖥️ Riferimento CLI
|
||||
|
||||
| Comando | Descrizione |
|
||||
| ------------------------- | ---------------------------------- |
|
||||
| `picoclaw onboard` | Inizializza config & workspace |
|
||||
| `picoclaw auth weixin` | Connetti account WeChat tramite QR |
|
||||
| `picoclaw agent -m "..."` | Chatta con l'agent |
|
||||
| `picoclaw agent` | Modalità chat interattiva |
|
||||
| `picoclaw gateway` | Avvia il gateway |
|
||||
| `picoclaw status` | Mostra lo stato |
|
||||
| `picoclaw version` | Mostra le info sulla versione |
|
||||
| `picoclaw model` | Visualizza o cambia il modello predefinito |
|
||||
| `picoclaw cron list` | Elenca tutti i job pianificati |
|
||||
| `picoclaw cron add ...` | Aggiunge un job pianificato |
|
||||
| `picoclaw cron disable` | Disabilita un job pianificato |
|
||||
| `picoclaw cron remove` | Rimuove un job pianificato |
|
||||
| `picoclaw skills list` | Elenca le skill installate |
|
||||
| `picoclaw skills install` | Installa una skill |
|
||||
| `picoclaw migrate` | Migra i dati dalle versioni precedenti |
|
||||
| `picoclaw auth login` | Autenticazione con i provider |
|
||||
|
||||
### ⏰ Task Pianificati / Promemoria
|
||||
|
||||
PicoClaw supporta promemoria pianificati e task ricorrenti tramite lo strumento `cron`:
|
||||
|
||||
* **Promemoria una tantum**: "Ricordami tra 10 minuti" -> si attiva una volta dopo 10 min
|
||||
* **Task ricorrenti**: "Ricordami ogni 2 ore" -> si attiva ogni 2 ore
|
||||
* **Espressioni cron**: "Ricordami alle 9 ogni giorno" -> usa un'espressione cron
|
||||
|
||||
## 📚 Documentazione
|
||||
|
||||
Per guide dettagliate oltre questo README:
|
||||
|
||||
| Argomento | Descrizione |
|
||||
|-----------|-------------|
|
||||
| [Docker & Avvio Rapido](docs/docker.md) | Configurazione Docker Compose, modalità Launcher/Agent |
|
||||
| [App di Chat](docs/chat-apps.md) | Tutte le guide di configurazione per 17+ channel |
|
||||
| [Configurazione](docs/configuration.md) | Variabili d'ambiente, struttura del workspace, sandbox di sicurezza |
|
||||
| [Provider & Modelli](docs/providers.md) | 30+ provider LLM, routing dei modelli, configurazione model_list |
|
||||
| [Spawn & Task Asincroni](docs/spawn-tasks.md) | Task veloci, task lunghi con spawn, orchestrazione asincrona di sub-agent |
|
||||
| [Hooks](docs/hooks/README.md) | Sistema di hook event-driven: observer, interceptor, approval hook |
|
||||
| [Steering](docs/steering.md) | Iniettare messaggi in un loop agent in esecuzione |
|
||||
| [SubTurn](docs/subturn.md) | Coordinamento subagent, controllo concorrenza, ciclo di vita |
|
||||
| [Risoluzione Problemi](docs/troubleshooting.md) | Problemi comuni e soluzioni |
|
||||
| [Configurazione degli Strumenti](docs/tools_configuration.md) | Abilitazione/disabilitazione per strumento, politiche exec, MCP, Skill |
|
||||
| [Compatibilità Hardware](docs/hardware-compatibility.md) | Schede testate, requisiti minimi |
|
||||
|
||||
## 🤝 Contribuisci & Roadmap
|
||||
|
||||
Le PR sono benvenute! Il codice è volutamente piccolo e leggibile.
|
||||
|
||||
Consulta la nostra [Roadmap della Community](https://github.com/sipeed/picoclaw/issues/988) e [CONTRIBUTING.md](CONTRIBUTING.md) per le linee guida.
|
||||
|
||||
Gruppo sviluppatori in costruzione, unisciti dopo la tua prima PR accettata!
|
||||
|
||||
Gruppi utenti:
|
||||
|
||||
Discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
|
||||
WeChat:
|
||||
<img src="assets/wechat.png" alt="WeChat group QR code" width="512">
|
||||
525
README.ja.md
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<h1>PicoClaw: Go で書かれた超効率 AI アシスタント</h1>
|
||||
|
||||
<h3>$10 ハードウェア · <10MB RAM · <1秒起動 · 行くぜ、シャコ!</h3>
|
||||
<h3>$10 ハードウェア · 10MB RAM · ms 起動 · Let's Go, PicoClaw!</h3>
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go&logoColor=white" alt="Go">
|
||||
<img src="https://img.shields.io/badge/Arch-x86__64%2C%20ARM64%2C%20MIPS%2C%20RISC--V%2C%20LoongArch-blue" alt="Hardware">
|
||||
|
|
@ -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% 安い!
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
|
|
@ -46,106 +46,98 @@
|
|||
</table>
|
||||
|
||||
> [!CAUTION]
|
||||
> **🚨 セキュリティ&公式チャンネル**
|
||||
> **セキュリティに関する注意**
|
||||
>
|
||||
> * **暗号通貨なし:** PicoClaw には公式トークン/コインは**一切ありません**。`pump.fun` やその他の取引プラットフォームでの主張はすべて**詐欺**です。
|
||||
>
|
||||
> * **公式ドメイン:** **唯一**の公式サイトは **[picoclaw.io](https://picoclaw.io)**、企業サイトは **[sipeed.com](https://sipeed.com)** です。
|
||||
> * **注意:** 多くの `.ai/.org/.com/.net/...` ドメインは第三者によって登録されています。
|
||||
> * **注意:** PicoClaw は初期開発段階にあり、未解決のネットワークセキュリティ問題がある可能性があります。v1.0 リリース前に本番環境へのデプロイは避けてください。
|
||||
> * **注意:** 多くの `.ai/.org/.com/.net/...` ドメインは第三者によって登録されています。信頼しないでください。
|
||||
> * **注記:** PicoClaw は初期開発段階にあり、未解決のネットワークセキュリティ問題がある可能性があります。v1.0 リリース前に本番環境へのデプロイは避けてください。
|
||||
> * **注記:** PicoClaw は最近多くの PR をマージしており、最新バージョンではメモリフットプリントが大きくなる場合があります(10〜20MB)。機能セットが安定次第、リソース最適化を優先する予定です。
|
||||
|
||||
## 📢 ニュース
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 リリース!** システムトレイ UI(Windows & Linux)、サブエージェントステータス追跡(`spawn_status`)、実験的ゲートウェイホットリロード、cron セキュリティゲート、セキュリティ修正 2 件。PicoClaw **25K ⭐** 達成!
|
||||
2026-03-25 🚀 **v0.2.4 リリース!** Agent アーキテクチャ全面刷新(SubTurn、Hooks、Steering、EventBus)、WeChat/WeCom 統合、セキュリティ強化(.security.yml、機密データフィルタリング)、新プロバイダー(AWS Bedrock、Azure、Xiaomi MiMo)、35 件のバグ修正。PicoClaw **26K ⭐** 達成!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — 史上最大のアップデート!** MCP プロトコル対応、4 つの新チャネル(Matrix/IRC/WeCom/Discord Proxy)、3 つの新プロバイダー(Kimi/Minimax/Avian)、ビジョンパイプライン、JSONL メモリストア、モデルルーティング。
|
||||
2026-03-17 🚀 **v0.2.3 リリース!** システムトレイ UI(Windows & Linux)、サブエージェントステータス追跡(`spawn_status`)、実験的 Gateway ホットリロード、cron セキュリティゲート、セキュリティ修正 2 件。PicoClaw **25K ⭐** 達成!
|
||||
|
||||
2026-02-28 📦 **v0.2.0** リリース — Docker Compose 対応と Web UI ランチャー。
|
||||
2026-03-09 🎉 **v0.2.1 — 最大のアップデート!** MCP プロトコルサポート、4 つの新チャンネル (Matrix/IRC/WeCom/Discord Proxy)、3 つの新プロバイダー (Kimi/Minimax/Avian)、ビジョンパイプライン、JSONL メモリストア、モデルルーティング。
|
||||
|
||||
2026-02-26 🎉 PicoClaw がわずか 17 日で **20K スター** 達成!チャネル自動オーケストレーションとケイパビリティインターフェースが実装されました。
|
||||
2026-02-28 📦 **v0.2.0** リリース — Docker Compose と Web UI Launcher サポート。
|
||||
|
||||
<details>
|
||||
<summary>過去のニュース...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw がわずか 17 日で **20K スター** 達成!Channel 自動オーケストレーションとケイパビリティインターフェースが実装されました。
|
||||
|
||||
2026-02-16 🎉 PicoClaw が 1 週間で 12K スター達成!コミュニティメンテナーの役割と[ロードマップ](ROADMAP.md)が正式に公開されました。
|
||||
|
||||
2026-02-13 🎉 PicoClaw が 4 日間で 5000 スター達成!プロジェクトロードマップと開発者グループの準備が進行中。
|
||||
|
||||
2026-02-09 🎉 **PicoClaw リリース!** $10 ハードウェアで 10MB 未満の RAM で動く AI エージェントを 1 日で構築。🦐 行くぜ、シャコ!
|
||||
2026-02-09 🎉 **PicoClaw リリース!** $10 ハードウェアで 10MB 未満の RAM で動く AI Agent を 1 日で構築。Let's Go, PicoClaw!
|
||||
|
||||
</details>
|
||||
|
||||
## ✨ 特徴
|
||||
|
||||
🪶 **超軽量**: メモリフットプリント 10MB 未満 — OpenClaw のコア機能より 99% 小さい。*
|
||||
🪶 **超軽量**: コアメモリフットプリント 10MB 未満 — OpenClaw より 99% 小さい。*
|
||||
|
||||
💰 **最小コスト**: $10 ハードウェアで動作 — Mac mini より 98% 安い。
|
||||
|
||||
⚡️ **超高速**: 起動時間 400 倍高速、0.6GHz シングルコアでも 1 秒未満で起動。
|
||||
⚡️ **超高速起動**: 起動時間 400 倍高速。0.6GHz シングルコアでも 1 秒未満で起動。
|
||||
|
||||
🌍 **真のポータビリティ**: RISC-V、ARM、MIPS、x86 対応の単一バイナリ。ワンクリックで Go!
|
||||
🌍 **真のポータビリティ**: RISC-V、ARM、MIPS、x86 対応の単一バイナリ。どこでも動く!
|
||||
|
||||
🤖 **AI ブートストラップ**: 自律的な Go ネイティブ実装 — コアの 95% が AI 生成、人間によるレビュー付き。
|
||||
🤖 **AI ブートストラップ**: 純粋な Go ネイティブ実装 — コアコードの 95% が Agent によって生成され、人間によるレビューで調整。
|
||||
|
||||
🔌 **MCP 対応**: ネイティブ [Model Context Protocol](https://modelcontextprotocol.io/) 統合 — 任意の MCP サーバーに接続してエージェント機能を拡張。
|
||||
🔌 **MCP 対応**: ネイティブ [Model Context Protocol](https://modelcontextprotocol.io/) 統合 — 任意の MCP サーバーに接続して Agent 機能を拡張。
|
||||
|
||||
👁️ **ビジョンパイプライン**: 画像やファイルをエージェントに直接送信 — マルチモーダル LLM 向けの自動 base64 エンコーディング。
|
||||
👁️ **ビジョンパイプライン**: 画像やファイルを Agent に直接送信 — マルチモーダル LLM 向けの自動 base64 エンコーディング。
|
||||
|
||||
🧠 **スマートルーティング**: ルールベースのモデルルーティング — 簡単なクエリは軽量モデルへ、API コストを節約。
|
||||
|
||||
_*最近のバージョンでは急速な機能マージにより 10〜20MB になる場合があります。リソース最適化は計画中です。起動時間の比較は 0.8GHz シングルコアベンチマークに基づいています(下表参照)。_
|
||||
_*最近のバージョンでは急速な PR マージにより 10〜20MB になる場合があります。リソース最適化は計画中です。起動時間の比較は 0.8GHz シングルコアベンチマークに基づいています(下表参照)。_
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ----------------------------- | ------------- | ------------------------ | ----------------------------------------- |
|
||||
| **言語** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **起動時間**</br>(0.8GHz コア) | >500秒 | >30秒 | **<1秒** |
|
||||
| **コスト** | Mac Mini $599 | 大半の Linux SBC </br>~$50 | **あらゆる Linux ボード**</br>**最安 $10** |
|
||||
<div align="center">
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ------------------------------ | ------------- | ------------------------ | -------------------------------------- |
|
||||
| **言語** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **起動時間**</br>(0.8GHz コア) | >500秒 | >30秒 | **<1秒** |
|
||||
| **コスト** | Mac Mini $599 | 大半の Linux ボード ~$50 | **あらゆる Linux ボード**</br>**最安 $10** |
|
||||
|
||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
</div>
|
||||
|
||||
> **[ハードウェア互換性リスト](docs/ja/hardware-compatibility.md)** — テスト済みの全ボード一覧($5 RISC-V から Raspberry Pi、Android スマートフォンまで)。お使いのボードが未掲載?PR を送ってください!
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/hardware-banner.jpg" alt="PicoClaw Hardware Compatibility" width="100%">
|
||||
</p>
|
||||
|
||||
## 🦾 デモンストレーション
|
||||
|
||||
### 🛠️ スタンダードアシスタントワークフロー
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<th><p align="center">🧩 フルスタックエンジニア</p></th>
|
||||
<th><p align="center">🗂️ ログ&計画管理</p></th>
|
||||
<th><p align="center">🔎 Web 検索&学習</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">開発 · デプロイ · スケール</td>
|
||||
<td align="center">スケジュール · 自動化 · メモリ</td>
|
||||
<td align="center">発見 · インサイト · トレンド</td>
|
||||
</tr>
|
||||
<tr align="center">
|
||||
<th><p align="center">フルスタックエンジニアモード</p></th>
|
||||
<th><p align="center">ログ&計画管理</p></th>
|
||||
<th><p align="center">Web 検索&学習</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">開発 · デプロイ · スケール</td>
|
||||
<td align="center">スケジュール · 自動化 · メモリ</td>
|
||||
<td align="center">発見 · インサイト · トレンド</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 📱 古い Android スマホで動かす
|
||||
|
||||
10 年前のスマホに第二の人生を!PicoClaw でスマート AI アシスタントに変身させましょう。クイックスタート:
|
||||
|
||||
1. **[Termux](https://github.com/termux/termux-app) をインストール**([GitHub Releases](https://github.com/termux/termux-app/releases) からダウンロード、または F-Droid / Google Play で検索)。
|
||||
2. **コマンドを実行**
|
||||
|
||||
```bash
|
||||
# https://github.com/sipeed/picoclaw/releases から最新リリースをダウンロード
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard
|
||||
```
|
||||
|
||||
その後「クイックスタート」セクションの手順に従って設定を完了してください!
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
### 🐜 革新的な省フットプリントデプロイ
|
||||
|
||||
PicoClaw はほぼすべての Linux デバイスにデプロイできます!
|
||||
|
|
@ -160,11 +152,15 @@ PicoClaw はほぼすべての Linux デバイスにデプロイできます!
|
|||
|
||||
## 📦 インストール
|
||||
|
||||
### コンパイル済みバイナリでインストール
|
||||
### picoclaw.io からダウンロード(推奨)
|
||||
|
||||
[リリースページ](https://github.com/sipeed/picoclaw/releases) からお使いのプラットフォーム用のバイナリをダウンロードしてください。
|
||||
**[picoclaw.io](https://picoclaw.io)** にアクセス — 公式サイトがプラットフォームを自動検出し、ワンクリックでダウンロードできます。アーキテクチャを手動で選ぶ必要はありません。
|
||||
|
||||
### ソースからインストール(最新機能、開発向け推奨)
|
||||
### プリコンパイル済みバイナリをダウンロード
|
||||
|
||||
または、[GitHub Releases](https://github.com/sipeed/picoclaw/releases) ページからプラットフォームに合ったバイナリをダウンロードしてください。
|
||||
|
||||
### ソースからビルド(開発用)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
|
|
@ -172,9 +168,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
|
||||
|
||||
|
|
@ -187,19 +186,354 @@ 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 ポリシー |
|
||||
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
|
||||
> ```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-webui.jpg" alt="WebUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**始め方:**
|
||||
|
||||
WebUI を開いたら:**1)** Provider を設定(LLM API キーを追加)→ **2)** Channel を設定(例:Telegram)→ **3)** Gateway を起動 → **4)** チャット!
|
||||
|
||||
WebUI の詳細なドキュメントは [docs.picoclaw.io](https://docs.picoclaw.io) を参照してください。
|
||||
|
||||
<details>
|
||||
<summary><b>Docker(代替手段)</b></summary>
|
||||
|
||||
```bash
|
||||
# 1. このリポジトリをクローン
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
|
||||
# 2. 初回実行 — docker/data/config.json を自動生成して終了
|
||||
# (config.json と workspace/ の両方が存在しない場合のみ実行)
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up
|
||||
# コンテナが "First-run setup complete." を出力して停止します。
|
||||
|
||||
# 3. API キーを設定
|
||||
vim docker/data/config.json
|
||||
|
||||
# 4. 起動
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
# http://localhost:18800 を開く
|
||||
```
|
||||
|
||||
> **Docker / VM ユーザー:** Gateway はデフォルトで `127.0.0.1` でリッスンします。ホストからアクセスできるようにするには `PICOCLAW_GATEWAY_HOST=0.0.0.0` を設定するか、`-public` フラグを使用してください。
|
||||
|
||||
```bash
|
||||
# ログを確認
|
||||
docker compose -f docker/docker-compose.yml logs -f
|
||||
|
||||
# 停止
|
||||
docker compose -f docker/docker-compose.yml --profile launcher down
|
||||
|
||||
# 更新
|
||||
docker compose -f docker/docker-compose.yml pull
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — 初回起動時のセキュリティ警告</b></summary>
|
||||
|
||||
`picoclaw-launcher` はインターネットからダウンロードされ、Mac App Store を通じて公証されていないため、macOS が初回起動時にブロックする場合があります。
|
||||
|
||||
**ステップ 1:** `picoclaw-launcher` をダブルクリックすると、セキュリティ警告が表示されます:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="macOS Gatekeeper 警告" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" は開けません — "picoclaw-launcher" がMacに害を与えたりプライバシーを侵害するマルウェアを含まないことをAppleは確認できません。*
|
||||
|
||||
**ステップ 2:** **システム設定** → **プライバシーとセキュリティ** を開き、**セキュリティ** セクションまでスクロールして **このまま開く** をクリック → ダイアログで再度 **開く** をクリックします。
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS プライバシーとセキュリティ — このまま開く" width="600">
|
||||
</p>
|
||||
|
||||
この操作を一度行うと、以降の起動では警告が表示されなくなります。
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher(ヘッドレス / SSH 向け推奨)
|
||||
|
||||
TUI(Terminal UI)Launcher は設定と管理のためのフル機能ターミナルインターフェースを提供します。サーバー、Raspberry Pi、その他のヘッドレス環境に最適です。
|
||||
|
||||
```bash
|
||||
picoclaw-launcher-tui
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-tui.jpg" alt="TUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**始め方:**
|
||||
|
||||
TUI メニューを使って:**1)** Provider を設定 → **2)** Channel を設定 → **3)** Gateway を起動 → **4)** チャット!
|
||||
|
||||
TUI の詳細なドキュメントは [docs.picoclaw.io](https://docs.picoclaw.io) を参照してください。
|
||||
|
||||
### 📱 Android
|
||||
|
||||
10 年前のスマホに第二の人生を!PicoClaw でスマート AI アシスタントに変身させましょう。
|
||||
|
||||
**オプション 1: Termux(現在利用可能)**
|
||||
|
||||
1. [Termux](https://github.com/termux/termux-app) をインストール([GitHub Releases](https://github.com/termux/termux-app/releases) からダウンロード、または F-Droid / Google Play で検索)
|
||||
2. 以下のコマンドを実行:
|
||||
|
||||
```bash
|
||||
# 最新リリースをダウンロード
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard # chroot で標準的な Linux ファイルシステムレイアウトを提供
|
||||
```
|
||||
|
||||
その後、下記の Terminal Launcher セクションの手順に従って設定を完了してください。
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**オプション 2: APK インストール(近日公開)**
|
||||
|
||||
内蔵 WebUI を備えたスタンドアロン Android APK を開発中です。お楽しみに!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher(リソース制約環境向け)</b></summary>
|
||||
|
||||
`picoclaw` コアバイナリのみが利用可能な最小環境(Launcher UI なし)では、コマンドラインと JSON 設定ファイルですべてを設定できます。
|
||||
|
||||
**1. 初期化**
|
||||
|
||||
```bash
|
||||
picoclaw onboard
|
||||
```
|
||||
|
||||
`~/.picoclaw/config.json` とワークスペースディレクトリが作成されます。
|
||||
|
||||
**2. 設定** (`~/.picoclaw/config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-api-key"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> 利用可能なすべてのオプションを含む完全な設定テンプレートは、リポジトリの `config/config.example.json` を参照してください。
|
||||
|
||||
**3. チャット**
|
||||
|
||||
```bash
|
||||
# ワンショット質問
|
||||
picoclaw agent -m "What is 2+2?"
|
||||
|
||||
# インタラクティブモード
|
||||
picoclaw agent
|
||||
|
||||
# チャットアプリ統合用 Gateway を起動
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 🔌 Provider(LLM)
|
||||
|
||||
PicoClaw は `model_list` 設定を通じて 30 以上の LLM Provider をサポートしています。`protocol/model` 形式を使用してください:
|
||||
|
||||
| Provider | Protocol | API キー | 備考 |
|
||||
|----------|----------|---------|------|
|
||||
| [OpenAI](https://platform.openai.com/api-keys) | `openai/` | 必須 | GPT-5.4、GPT-4o、o3 など |
|
||||
| [Anthropic](https://console.anthropic.com/settings/keys) | `anthropic/` | 必須 | Claude Opus 4.6、Sonnet 4.6 など |
|
||||
| [Google Gemini](https://aistudio.google.com/apikey) | `gemini/` | 必須 | Gemini 3 Flash、2.5 Pro など |
|
||||
| [OpenRouter](https://openrouter.ai/keys) | `openrouter/` | 必須 | 200 以上のモデル、統合 API |
|
||||
| [Zhipu (GLM)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | `zhipu/` | 必須 | GLM-4.7、GLM-5 など |
|
||||
| [DeepSeek](https://platform.deepseek.com/api_keys) | `deepseek/` | 必須 | DeepSeek-V3、DeepSeek-R1 |
|
||||
| [Volcengine](https://console.volcengine.com) | `volcengine/` | 必須 | Doubao、Ark モデル |
|
||||
| [Qwen](https://dashscope.console.aliyun.com/apiKey) | `qwen/` | 必須 | Qwen3、Qwen-Max など |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/` | 必須 | 高速推論(Llama、Mixtral) |
|
||||
| [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | 必須 | Kimi モデル |
|
||||
| [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | 必須 | MiniMax モデル |
|
||||
| [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | 必須 | Mistral Large、Codestral |
|
||||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | 必須 | NVIDIA ホスティングモデル |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | 必須 | 高速推論 |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | 必須 | 各種オープンモデル |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | 必須 | MiMo モデル |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | 不要 | ローカルモデル、セルフホスト |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | 不要 | ローカルデプロイ、OpenAI 互換 |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | 場合による | 100 以上の Provider のプロキシ |
|
||||
| [Azure OpenAI](https://portal.azure.com/) | `azure/` | 必須 | エンタープライズ Azure デプロイ |
|
||||
| [GitHub Copilot](https://github.com/features/copilot) | `github-copilot/` | OAuth | デバイスコードログイン |
|
||||
| [Antigravity](https://console.cloud.google.com/) | `antigravity/` | OAuth | Google Cloud AI |
|
||||
|
||||
<details>
|
||||
<summary><b>ローカルデプロイ(Ollama、vLLM など)</b></summary>
|
||||
|
||||
**Ollama:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-llama",
|
||||
"model": "ollama/llama3.1:8b",
|
||||
"api_base": "http://localhost:11434/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**vLLM:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-vllm",
|
||||
"model": "vllm/your-model",
|
||||
"api_base": "http://localhost:8000/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Provider の完全な設定詳細は [Provider とモデル](docs/ja/providers.md) を参照してください。
|
||||
|
||||
</details>
|
||||
|
||||
## 💬 Channel(チャットアプリ)
|
||||
|
||||
17 以上のメッセージングプラットフォームで PicoClaw と会話できます:
|
||||
|
||||
| Channel | セットアップ | Protocol | ドキュメント |
|
||||
|---------|------------|----------|------------|
|
||||
| **Telegram** | 簡単(bot トークン) | Long polling | [ガイド](docs/channels/telegram/README.ja.md) |
|
||||
| **Discord** | 簡単(bot トークン + intents) | WebSocket | [ガイド](docs/channels/discord/README.ja.md) |
|
||||
| **WhatsApp** | 簡単(QR スキャンまたは bridge URL) | Native / Bridge | [ガイド](docs/ja/chat-apps.md#whatsapp) |
|
||||
| **微信 (Weixin)** | 簡単(QR スキャン) | iLink API | [ガイド](docs/ja/chat-apps.md#weixin) |
|
||||
| **QQ** | 簡単(AppID + AppSecret) | WebSocket | [ガイド](docs/channels/qq/README.ja.md) |
|
||||
| **Slack** | 簡単(bot + app トークン) | Socket Mode | [ガイド](docs/channels/slack/README.ja.md) |
|
||||
| **Matrix** | 中級(homeserver + トークン) | Sync API | [ガイド](docs/channels/matrix/README.ja.md) |
|
||||
| **DingTalk** | 中級(クライアント認証情報) | Stream | [ガイド](docs/channels/dingtalk/README.ja.md) |
|
||||
| **Feishu / Lark** | 中級(App ID + Secret) | WebSocket/SDK | [ガイド](docs/channels/feishu/README.ja.md) |
|
||||
| **LINE** | 中級(認証情報 + webhook) | Webhook | [ガイド](docs/channels/line/README.ja.md) |
|
||||
| **WeCom** | 簡単(QR ログインまたは手動) | WebSocket | [ガイド](docs/channels/wecom/README.md) |
|
||||
| **IRC** | 中級(サーバー + nick) | IRC protocol | [ガイド](docs/ja/chat-apps.md#irc) |
|
||||
| **OneBot** | 中級(WebSocket URL) | OneBot v11 | [ガイド](docs/channels/onebot/README.ja.md) |
|
||||
| **MaixCam** | 簡単(有効化) | TCP socket | [ガイド](docs/channels/maixcam/README.ja.md) |
|
||||
| **Pico** | 簡単(有効化) | Native protocol | 内蔵 |
|
||||
| **Pico Client** | 簡単(WebSocket URL) | WebSocket | 内蔵 |
|
||||
|
||||
> webhook ベースのすべての Channel は単一の Gateway HTTP サーバー(`gateway.host`:`gateway.port`、デフォルト `127.0.0.1:18790`)を共有します。Feishu は WebSocket/SDK モードを使用し、共有 HTTP サーバーを使用しません。
|
||||
|
||||
> ログの詳細度は `gateway.log_level` で制御します(デフォルト:`warn`)。サポートされる値:`debug`、`info`、`warn`、`error`、`fatal`。`PICOCLAW_LOG_LEVEL` 環境変数でも設定可能です。詳細は[設定ガイド](docs/ja/configuration.md#gateway-ログレベル)を参照してください。
|
||||
|
||||
Channel の詳細なセットアップ手順は [チャットアプリ設定](docs/ja/chat-apps.md) を参照してください。
|
||||
|
||||
## 🔧 ツール
|
||||
|
||||
### 🔍 Web 検索
|
||||
|
||||
PicoClaw は最新情報を提供するために Web を検索できます。`tools.web` で設定してください:
|
||||
|
||||
| 検索エンジン | API キー | 無料枠 | リンク |
|
||||
|------------|---------|--------|-------|
|
||||
| DuckDuckGo | 不要 | 無制限 | 内蔵フォールバック |
|
||||
| [Baidu Search](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5) | 必須 | 1000 クエリ/日 | AI 搭載、中国語に最適化 |
|
||||
| [Tavily](https://tavily.com) | 必須 | 1000 クエリ/月 | AI Agent 向けに最適化 |
|
||||
| [Brave Search](https://brave.com/search/api) | 必須 | 2000 クエリ/月 | 高速でプライベート |
|
||||
| [Perplexity](https://www.perplexity.ai) | 必須 | 有料 | AI 搭載検索 |
|
||||
| [SearXNG](https://github.com/searxng/searxng) | 不要 | セルフホスト | 無料メタ検索エンジン |
|
||||
| [GLM Search](https://open.bigmodel.cn/) | 必須 | 場合による | Zhipu Web 検索 |
|
||||
|
||||
### ⚙️ その他のツール
|
||||
|
||||
PicoClaw にはファイル操作、コード実行、スケジューリングなどの組み込みツールが含まれています。詳細は [ツール設定](docs/ja/tools_configuration.md) を参照してください。
|
||||
|
||||
## 🎯 Skill
|
||||
|
||||
Skill は Agent を拡張するモジュール型の機能です。ワークスペース内の `SKILL.md` ファイルから読み込まれます。
|
||||
|
||||
**ClawHub から Skill をインストール:**
|
||||
|
||||
```bash
|
||||
picoclaw skills search "web scraping"
|
||||
picoclaw skills install <skill-name>
|
||||
```
|
||||
|
||||
**ClawHub トークンを設定**(オプション、レート制限を上げるため):
|
||||
|
||||
`config.json` に追加:
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"skills": {
|
||||
"registries": {
|
||||
"clawhub": {
|
||||
"auth_token": "your-clawhub-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
詳細は [ツール設定 - Skill](docs/ja/tools_configuration.md#skills-tool) を参照してください。
|
||||
|
||||
## 🔗 MCP(Model Context Protocol)
|
||||
|
||||
PicoClaw は [MCP](https://modelcontextprotocol.io/) をネイティブサポートしています — 任意の MCP サーバーに接続して、外部ツールやデータソースで Agent の機能を拡張できます。
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"servers": {
|
||||
"filesystem": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
MCP の完全な設定(stdio、SSE、HTTP トランスポート、Tool Discovery)は [ツール設定 - MCP](docs/ja/tools_configuration.md#mcp-tool) を参照してください。
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> エージェントソーシャルネットワークに参加
|
||||
|
||||
|
|
@ -209,24 +543,26 @@ CLI または統合チャットアプリからメッセージを 1 つ送るだ
|
|||
|
||||
## 🖥️ CLI リファレンス
|
||||
|
||||
| コマンド | 説明 |
|
||||
| コマンド | 説明 |
|
||||
| ------------------------- | ------------------------------ |
|
||||
| `picoclaw onboard` | 設定&ワークスペースの初期化 |
|
||||
| `picoclaw agent -m "..."` | エージェントとチャット |
|
||||
| `picoclaw auth weixin` | WeChat アカウントを QR で接続 |
|
||||
| `picoclaw agent -m "..."` | Agent とチャット |
|
||||
| `picoclaw agent` | インタラクティブチャットモード |
|
||||
| `picoclaw gateway` | ゲートウェイを起動 |
|
||||
| `picoclaw gateway` | Gateway を起動 |
|
||||
| `picoclaw status` | ステータスを表示 |
|
||||
| `picoclaw version` | バージョン情報を表示 |
|
||||
| `picoclaw model` | デフォルトモデルの表示・切替 |
|
||||
| `picoclaw cron list` | スケジュールジョブ一覧 |
|
||||
| `picoclaw cron add ...` | スケジュールジョブを追加 |
|
||||
| `picoclaw cron disable` | スケジュールジョブを無効化 |
|
||||
| `picoclaw cron remove` | スケジュールジョブを削除 |
|
||||
| `picoclaw skills list` | インストール済みスキル一覧 |
|
||||
| `picoclaw skills install` | スキルをインストール |
|
||||
| `picoclaw skills list` | インストール済み Skill 一覧 |
|
||||
| `picoclaw skills install` | Skill をインストール |
|
||||
| `picoclaw migrate` | 旧バージョンからデータを移行 |
|
||||
| `picoclaw auth login` | プロバイダーへの認証 |
|
||||
| `picoclaw auth login` | Provider への認証 |
|
||||
|
||||
### スケジュールタスク / リマインダー
|
||||
### ⏰ スケジュールタスク / リマインダー
|
||||
|
||||
PicoClaw は `cron` ツールによるスケジュールリマインダーと定期タスクをサポートしています:
|
||||
|
||||
|
|
@ -234,16 +570,35 @@ PicoClaw は `cron` ツールによるスケジュールリマインダーと定
|
|||
* **定期タスク**: 「2時間ごとにリマインド」→ 2時間ごとにトリガー
|
||||
* **Cron 式**: 「毎日9時にリマインド」→ cron 式を使用
|
||||
|
||||
## 📚 ドキュメント
|
||||
|
||||
この README を超えた詳細なガイドについては:
|
||||
|
||||
| トピック | 説明 |
|
||||
|---------|------|
|
||||
| [Docker & クイックスタート](docs/ja/docker.md) | Docker Compose セットアップ、Launcher/Agent モード |
|
||||
| [チャットアプリ](docs/ja/chat-apps.md) | 17 以上の Channel セットアップガイド |
|
||||
| [設定](docs/ja/configuration.md) | 環境変数、ワークスペース構成、セキュリティサンドボックス |
|
||||
| [Provider とモデル](docs/ja/providers.md) | 30 以上の LLM Provider、モデルルーティング、model_list 設定 |
|
||||
| [Spawn & 非同期タスク](docs/ja/spawn-tasks.md) | クイックタスク、spawn による長時間タスク、非同期サブエージェントオーケストレーション |
|
||||
| [Hook システム](docs/hooks/README.md) | イベント駆動 Hook:オブザーバー、インターセプター、承認 Hook |
|
||||
| [Steering](docs/steering.md) | 実行中の Agent ループにメッセージを注入 |
|
||||
| [SubTurn](docs/subturn.md) | サブ Agent の調整、並行制御、ライフサイクル |
|
||||
| [トラブルシューティング](docs/ja/troubleshooting.md) | よくある問題と解決策 |
|
||||
| [ツール設定](docs/ja/tools_configuration.md) | ツールごとの有効/無効、exec ポリシー、MCP、Skill |
|
||||
| [ハードウェア互換性](docs/ja/hardware-compatibility.md) | テスト済みボード、最小要件 |
|
||||
|
||||
## 🤝 コントリビュート&ロードマップ
|
||||
|
||||
PR 歓迎!コードベースは意図的に小さく読みやすくしています。🤗
|
||||
PR 歓迎!コードベースは意図的に小さく読みやすくしています。
|
||||
|
||||
完全な[コミュニティロードマップ](https://github.com/sipeed/picoclaw/blob/main/ROADMAP.md)をご覧ください。
|
||||
[コミュニティロードマップ](https://github.com/sipeed/picoclaw/issues/988)と[CONTRIBUTING.md](CONTRIBUTING.md)をご覧ください。
|
||||
|
||||
開発者グループ構築中、最初の PR がマージされたら参加できます!
|
||||
|
||||
ユーザーグループ:
|
||||
|
||||
discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
Discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
|
||||
<img src="assets/wechat.png" alt="PicoClaw" width="512">
|
||||
WeChat:
|
||||
<img src="assets/wechat.png" alt="WeChat group QR code" width="512">
|
||||
|
|
|
|||
620
README.md
|
|
@ -1,9 +1,9 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: Ultra-Efficient AI Assistant in Go</h1>
|
||||
<h1>PicoClaw: Ultra-Efficient AI Assistant in Go</h1>
|
||||
|
||||
<h3>$10 Hardware · <10MB RAM · <1s Boot · 皮皮虾,我们走!</h3>
|
||||
<h3>$10 Hardware · 10MB RAM · ms Boot · Let's Go, PicoClaw!</h3>
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go&logoColor=white" alt="Go">
|
||||
<img src="https://img.shields.io/badge/Arch-x86__64%2C%20ARM64%2C%20MIPS%2C%20RISC--V%2C%20LoongArch-blue" alt="Hardware">
|
||||
|
|
@ -24,147 +24,143 @@
|
|||
|
||||
---
|
||||
|
||||
> **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!
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> [!CAUTION]
|
||||
> **🚨 SECURITY & OFFICIAL CHANNELS / 安全声明**
|
||||
>
|
||||
> * **NO CRYPTO:** PicoClaw has **NO** official token/coin. All claims on `pump.fun` or other trading platforms are **SCAMS**.
|
||||
> **Security Notice**
|
||||
>
|
||||
> * **NO CRYPTO:** PicoClaw has **not** issued any official tokens or cryptocurrency. All claims on `pump.fun` or other trading platforms are **scams**.
|
||||
> * **OFFICIAL DOMAIN:** The **ONLY** official website is **[picoclaw.io](https://picoclaw.io)**, and company website is **[sipeed.com](https://sipeed.com)**
|
||||
> * **Warning:** Many `.ai/.org/.com/.net/...` domains are registered by third parties.
|
||||
> * **Warning:** picoclaw is in early development now and may have unresolved network security issues. Do not deploy to production environments before the v1.0 release.
|
||||
> * **Note:** picoclaw has recently merged a lot of PRs, which may result in a larger memory footprint (10–20MB) in the latest versions. We plan to prioritize resource optimization as soon as the current feature set reaches a stable state.
|
||||
> * **BEWARE:** Many `.ai/.org/.com/.net/...` domains have been registered by third parties. Do not trust them.
|
||||
> * **NOTE:** PicoClaw is in early rapid development. There may be unresolved security issues. Do not deploy to production before v1.0.
|
||||
> * **NOTE:** PicoClaw has recently merged many PRs. Recent builds may use 10-20MB RAM. Resource optimization is planned after feature stabilization.
|
||||
|
||||
## 📢 News
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 Released!** System tray UI (Windows & Linux), sub-agent status tracking (`spawn_status`), experimental gateway hot-reload, cron security gates, and 2 security fixes. PicoClaw now at **25K ⭐**!
|
||||
2026-03-25 🚀 **v0.2.4 Released!** Agent architecture overhaul (SubTurn, Hooks, Steering, EventBus), WeChat/WeCom integration, security hardening (.security.yml, sensitive data filtering), new providers (AWS Bedrock, Azure, Xiaomi MiMo), and 35 bug fixes. PicoClaw has reached **26K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Biggest update yet!** MCP protocol support, 4 new channels (Matrix/IRC/WeCom/Discord Proxy), 3 new providers (Kimi/Minimax/Avian), vision pipeline, JSONL memory store, and model routing.
|
||||
2026-03-17 🚀 **v0.2.3 Released!** System tray UI (Windows & Linux), sub-agent status query (`spawn_status`), experimental Gateway hot-reload, Cron security gating, and 2 security fixes. PicoClaw has reached **25K Stars**!
|
||||
|
||||
2026-02-28 📦 **v0.2.0** released with Docker Compose support and Web UI launcher.
|
||||
2026-03-09 🎉 **v0.2.1 — Biggest update yet!** MCP protocol support, 4 new channels (Matrix/IRC/WeCom/Discord Proxy), 3 new providers (Kimi/Minimax/Avian), vision pipeline, JSONL memory store, model routing.
|
||||
|
||||
2026-02-26 🎉 PicoClaw hit **20K stars** in just 17 days! Channel auto-orchestration and capability interfaces landed.
|
||||
2026-02-28 📦 **v0.2.0** released with Docker Compose and Web UI Launcher support.
|
||||
|
||||
<details>
|
||||
<summary>Older news...</summary>
|
||||
<summary>Earlier news...</summary>
|
||||
|
||||
2026-02-16 🎉 PicoClaw hit 12K stars in one week! Community maintainer roles and [roadmap](ROADMAP.md) officially posted.
|
||||
2026-02-26 🎉 PicoClaw hits **20K Stars** in just 17 days! Channel auto-orchestration and capability interfaces are live.
|
||||
|
||||
2026-02-13 🎉 PicoClaw hit 5000 stars in 4 days! Project Roadmap and Developer Group setup underway.
|
||||
2026-02-16 🎉 PicoClaw breaks 12K Stars in one week! Community maintainer roles and [Roadmap](ROADMAP.md) officially launched.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw Launched!** Built in 1 day to bring AI Agents to $10 hardware with <10MB RAM. 🦐 PicoClaw,Let's Go!
|
||||
2026-02-13 🎉 PicoClaw breaks 5000 Stars in 4 days! Project roadmap and developer groups in progress.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw Released!** Built in 1 day to bring AI Agents to $10 hardware with <10MB RAM. Let's Go, PicoClaw!
|
||||
|
||||
</details>
|
||||
|
||||
## ✨ Features
|
||||
|
||||
🪶 **Ultra-Lightweight**: <10MB Memory footprint — 99% smaller than OpenClaw core functionality.*
|
||||
🪶 **Ultra-lightweight**: Core memory footprint <10MB — 99% smaller than OpenClaw.*
|
||||
|
||||
💰 **Minimal Cost**: Efficient enough to run on $10 Hardware — 98% cheaper than a Mac mini.
|
||||
💰 **Minimal cost**: Efficient enough to run on $10 hardware — 98% cheaper than a Mac mini.
|
||||
|
||||
⚡️ **Lightning Fast**: 400X Faster startup time, boot in <1 second even on 0.6GHz single core.
|
||||
⚡️ **Lightning-fast boot**: 400x faster startup. Boots in <1s even on a 0.6GHz single-core processor.
|
||||
|
||||
🌍 **True Portability**: Single self-contained binary across RISC-V, ARM, MIPS, and x86, One-click to Go!
|
||||
🌍 **Truly portable**: Single binary across RISC-V, ARM, MIPS, and x86 architectures. One binary, runs everywhere!
|
||||
|
||||
🤖 **AI-Bootstrapped**: Autonomous Go-native implementation — 95% Agent-generated core with human-in-the-loop refinement.
|
||||
🤖 **AI-bootstrapped**: Pure Go native implementation — 95% of core code was generated by an Agent and fine-tuned through human-in-the-loop review.
|
||||
|
||||
🔌 **MCP Support**: Native [Model Context Protocol](https://modelcontextprotocol.io/) integration — connect any MCP server to extend agent capabilities.
|
||||
🔌 **MCP support**: Native [Model Context Protocol](https://modelcontextprotocol.io/) integration — connect any MCP server to extend Agent capabilities.
|
||||
|
||||
👁️ **Vision Pipeline**: Send images and files directly to the agent — automatic base64 encoding for multimodal LLMs.
|
||||
👁️ **Vision pipeline**: Send images and files directly to the Agent — automatic base64 encoding for multimodal LLMs.
|
||||
|
||||
🧠 **Smart Routing**: Rule-based model routing — simple queries go to lightweight models, saving API costs.
|
||||
🧠 **Smart routing**: Rule-based model routing — simple queries go to lightweight models, saving API costs.
|
||||
|
||||
_*Recent versions may use 10–20MB due to rapid feature merges. Resource optimization is planned. Startup comparison based on 0.8GHz single-core benchmarks (see table below)._
|
||||
_*Recent builds may use 10-20MB due to rapid PR merges. Resource optimization is planned. Boot speed comparison based on 0.8GHz single-core benchmarks (see table below)._
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ----------------------------- | ------------- | ------------------------ | ----------------------------------------- |
|
||||
| **Language** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **Startup**</br>(0.8GHz core) | >500s | >30s | **<1s** |
|
||||
| **Cost** | Mac Mini $599 | Most Linux SBC </br>~$50 | **Any Linux Board**</br>**As low as $10** |
|
||||
<div align="center">
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ------------------------------ | ------------- | ------------------------ | -------------------------------------- |
|
||||
| **Language** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **Boot time**</br>(0.8GHz core) | >500s | >30s | **<1s** |
|
||||
| **Cost** | Mac Mini $599 | Most Linux boards ~$50 | **Any Linux board**</br>**from $10** |
|
||||
|
||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
</div>
|
||||
|
||||
> **[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!
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/hardware-banner.jpg" alt="PicoClaw Hardware Compatibility" width="100%">
|
||||
</p>
|
||||
|
||||
## 🦾 Demonstration
|
||||
|
||||
### 🛠️ Standard Assistant Workflows
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<th><p align="center">🧩 Full-Stack Engineer</p></th>
|
||||
<th><p align="center">🗂️ Logging & Planning Management</p></th>
|
||||
<th><p align="center">🔎 Web Search & Learning</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Develop • Deploy • Scale</td>
|
||||
<td align="center">Schedule • Automate • Memory</td>
|
||||
<td align="center">Discovery • Insights • Trends</td>
|
||||
</tr>
|
||||
<tr align="center">
|
||||
<th><p align="center">Full-Stack Engineer Mode</p></th>
|
||||
<th><p align="center">Logging & Planning</p></th>
|
||||
<th><p align="center">Web Search & Learning</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Develop · Deploy · Scale</td>
|
||||
<td align="center">Schedule · Automate · Remember</td>
|
||||
<td align="center">Discover · Insights · Trends</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 📱 Run on old Android Phones
|
||||
### 🐜 Innovative Low-Footprint Deployment
|
||||
|
||||
Give your decade-old phone a second life! Turn it into a smart AI Assistant with PicoClaw. Quick Start:
|
||||
PicoClaw can be deployed on virtually any Linux device!
|
||||
|
||||
1. **Install [Termux](https://github.com/termux/termux-app)** (Download from [GitHub Releases](https://github.com/termux/termux-app/releases), or search in F-Droid / Google Play).
|
||||
2. **Execute cmds**
|
||||
|
||||
```bash
|
||||
# Download the latest release from https://github.com/sipeed/picoclaw/releases
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard
|
||||
```
|
||||
|
||||
And then follow the instructions in the "Quick Start" section to complete the configuration!
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
### 🐜 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
|
||||
|
||||
<https://private-user-images.githubusercontent.com/83055338/547056448-e7b031ff-d6f5-4468-bcca-5726b6fecb5c.mp4>
|
||||
|
||||
🌟 More Deployment Cases Await!
|
||||
🌟 More Deployment Cases Await!
|
||||
|
||||
## 📦 Install
|
||||
|
||||
### Install with precompiled binary
|
||||
### Download from picoclaw.io (Recommended)
|
||||
|
||||
Download the binary for your platform from the [Releases](https://github.com/sipeed/picoclaw/releases) page.
|
||||
Visit **[picoclaw.io](https://picoclaw.io)** — the official website auto-detects your platform and provides one-click download. No need to manually pick an architecture.
|
||||
|
||||
### Install from source (latest features, recommended for development)
|
||||
### Download precompiled binary
|
||||
|
||||
Alternatively, download the binary for your platform from the [GitHub Releases](https://github.com/sipeed/picoclaw/releases) page.
|
||||
|
||||
### Build from source (for development)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
|
|
@ -172,78 +168,446 @@ git clone https://github.com/sipeed/picoclaw.git
|
|||
cd picoclaw
|
||||
make deps
|
||||
|
||||
# Build, no need to install
|
||||
# Build core binary
|
||||
make build
|
||||
|
||||
# Build Web UI Launcher (required for WebUI mode)
|
||||
make build-launcher
|
||||
|
||||
# Build for multiple platforms
|
||||
make build-all
|
||||
|
||||
# Build for Raspberry Pi Zero 2 W (32-bit: make build-linux-arm; 64-bit: make build-linux-arm64)
|
||||
make build-pi-zero
|
||||
|
||||
# Build And Install
|
||||
# Build and install
|
||||
make install
|
||||
```
|
||||
|
||||
**Raspberry Pi Zero 2 W:** Use the binary that matches your OS: 32-bit Raspberry Pi OS → `make build-linux-arm`; 64-bit → `make build-linux-arm64`. Or run `make build-pi-zero` to build both.
|
||||
**Raspberry Pi Zero 2 W:** Use the binary that matches your OS: 32-bit Raspberry Pi OS -> `make build-linux-arm`; 64-bit -> `make build-linux-arm64`. Or run `make build-pi-zero` to build both.
|
||||
|
||||
## 📚 Documentation
|
||||
## 🚀 Quick Start Guide
|
||||
|
||||
For detailed guides, see the docs below. The README covers quick start only.
|
||||
### 🌐 WebUI Launcher (Recommended for Desktop)
|
||||
|
||||
| Topic | Description |
|
||||
|-------|-------------|
|
||||
| 🐳 [Docker & Quick Start](docs/docker.md) | Docker Compose setup, Launcher/Agent modes, Quick Start configuration |
|
||||
| 💬 [Chat Apps](docs/chat-apps.md) | Telegram, Discord, WhatsApp, Matrix, QQ, Slack, IRC, DingTalk, LINE, Feishu, WeCom, and more |
|
||||
| ⚙️ [Configuration](docs/configuration.md) | Environment variables, workspace layout, skill sources, security sandbox, heartbeat |
|
||||
| 🔌 [Providers & Models](docs/providers.md) | 20+ LLM providers, model routing, model_list configuration, provider architecture |
|
||||
| 🔄 [Spawn & Async Tasks](docs/spawn-tasks.md) | Quick tasks, long tasks with spawn, async sub-agent orchestration |
|
||||
| 🐛 [Troubleshooting](docs/troubleshooting.md) | Common issues and solutions |
|
||||
| 🔧 [Tools Configuration](docs/tools_configuration.md) | Per-tool enable/disable, exec policies |
|
||||
The WebUI Launcher provides a browser-based interface for configuration and chat. This is the easiest way to get started — no command-line knowledge required.
|
||||
|
||||
**Option 1: Double-click (Desktop)**
|
||||
|
||||
After downloading from [picoclaw.io](https://picoclaw.io), double-click `picoclaw-launcher` (or `picoclaw-launcher.exe` on Windows). Your browser will open automatically at `http://localhost:18800`.
|
||||
|
||||
**Option 2: Command line**
|
||||
|
||||
```bash
|
||||
picoclaw-launcher
|
||||
# Open http://localhost:18800 in your browser
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **Remote access / Docker / VM:** Add the `-public` flag to listen on all interfaces:
|
||||
> ```bash
|
||||
> picoclaw-launcher -public
|
||||
> ```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-webui.jpg" alt="WebUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Getting started:**
|
||||
|
||||
Open the WebUI, then: **1)** Configure a Provider (add your LLM API key) -> **2)** Configure a Channel (e.g., Telegram) -> **3)** Start the Gateway -> **4)** Chat!
|
||||
|
||||
For detailed WebUI documentation, see [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
<details>
|
||||
<summary><b>Docker (alternative)</b></summary>
|
||||
|
||||
```bash
|
||||
# 1. Clone this repo
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
|
||||
# 2. First run — auto-generates docker/data/config.json then exits
|
||||
# (only triggers when both config.json and workspace/ are missing)
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up
|
||||
# The container prints "First-run setup complete." and stops.
|
||||
|
||||
# 3. Set your API keys
|
||||
vim docker/data/config.json
|
||||
|
||||
# 4. Start
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
# Open http://localhost:18800
|
||||
```
|
||||
|
||||
> **Docker / VM users:** The Gateway listens on `127.0.0.1` by default. Set `PICOCLAW_GATEWAY_HOST=0.0.0.0` or use the `-public` flag to make it accessible from the host.
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
docker compose -f docker/docker-compose.yml logs -f
|
||||
|
||||
# Stop
|
||||
docker compose -f docker/docker-compose.yml --profile launcher down
|
||||
|
||||
# Update
|
||||
docker compose -f docker/docker-compose.yml pull
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — First Launch Security Warning</b></summary>
|
||||
|
||||
macOS may block `picoclaw-launcher` on first launch because it is downloaded from the internet and not notarized through the Mac App Store.
|
||||
|
||||
**Step 1:** Double-click `picoclaw-launcher`. You will see a security warning:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="macOS Gatekeeper warning" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" Not Opened — Apple could not verify "picoclaw-launcher" is free of malware that may harm your Mac or compromise your privacy.*
|
||||
|
||||
**Step 2:** Open **System Settings** → **Privacy & Security** → scroll down to the **Security** section → click **Open Anyway** → confirm by clicking **Open Anyway** in the dialog.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Privacy & Security — Open Anyway" width="600">
|
||||
</p>
|
||||
|
||||
After this one-time step, `picoclaw-launcher` will open normally on subsequent launches.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Recommended for Headless / SSH)
|
||||
|
||||
The TUI (Terminal UI) Launcher provides a full-featured terminal interface for configuration and management. Ideal for servers, Raspberry Pi, and other headless environments.
|
||||
|
||||
```bash
|
||||
picoclaw-launcher-tui
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-tui.jpg" alt="TUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Getting started:**
|
||||
|
||||
Use the TUI menus to: **1)** Configure a Provider -> **2)** Configure a Channel -> **3)** Start the Gateway -> **4)** Chat!
|
||||
|
||||
For detailed TUI documentation, see [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
### 📱 Android
|
||||
|
||||
Give your decade-old phone a second life! Turn it into a smart AI Assistant with PicoClaw.
|
||||
|
||||
**Option 1: Termux (available now)**
|
||||
|
||||
1. Install [Termux](https://github.com/termux/termux-app) (download from [GitHub Releases](https://github.com/termux/termux-app/releases), or search in F-Droid / Google Play)
|
||||
2. Run the following commands:
|
||||
|
||||
```bash
|
||||
# Download the latest release
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard # chroot provides a standard Linux filesystem layout
|
||||
```
|
||||
|
||||
Then follow the Terminal Launcher section below to complete configuration.
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**Option 2: APK Install (coming soon)**
|
||||
|
||||
A standalone Android APK with built-in WebUI is in development. Stay tuned!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (for resource-constrained environments)</b></summary>
|
||||
|
||||
For minimal environments where only the `picoclaw` core binary is available (no Launcher UI), you can configure everything via the command line and a JSON config file.
|
||||
|
||||
**1. Initialize**
|
||||
|
||||
```bash
|
||||
picoclaw onboard
|
||||
```
|
||||
|
||||
This creates `~/.picoclaw/config.json` and the workspace directory.
|
||||
|
||||
**2. Configure** (`~/.picoclaw/config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4"
|
||||
// api_key is now loaded from .security.yml
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> See `config/config.example.json` in the repo for a complete configuration template with all available options.
|
||||
>
|
||||
> Please note: config.example.json format is version 0, with sensitive codes in it, and will be auto migrated to version 1+, then, the config.json will only store insensitive data, the sensitive codes will be stored in .security.yml, if you need manually modify the codes, please see `docs/security_configuration.md` for more details.
|
||||
|
||||
|
||||
**3. Chat**
|
||||
|
||||
```bash
|
||||
# One-shot question
|
||||
picoclaw agent -m "What is 2+2?"
|
||||
|
||||
# Interactive mode
|
||||
picoclaw agent
|
||||
|
||||
# Start gateway for chat app integration
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 🔌 Providers (LLM)
|
||||
|
||||
PicoClaw supports 30+ LLM providers through the `model_list` configuration. Use the `protocol/model` format:
|
||||
|
||||
| Provider | Protocol | API Key | Notes |
|
||||
|----------|----------|---------|-------|
|
||||
| [OpenAI](https://platform.openai.com/api-keys) | `openai/` | Required | GPT-5.4, GPT-4o, o3, etc. |
|
||||
| [Anthropic](https://console.anthropic.com/settings/keys) | `anthropic/` | Required | Claude Opus 4.6, Sonnet 4.6, etc. |
|
||||
| [Google Gemini](https://aistudio.google.com/apikey) | `gemini/` | Required | Gemini 3 Flash, 2.5 Pro, etc. |
|
||||
| [OpenRouter](https://openrouter.ai/keys) | `openrouter/` | Required | 200+ models, unified API |
|
||||
| [Zhipu (GLM)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | `zhipu/` | Required | GLM-4.7, GLM-5, etc. |
|
||||
| [DeepSeek](https://platform.deepseek.com/api_keys) | `deepseek/` | Required | DeepSeek-V3, DeepSeek-R1 |
|
||||
| [Volcengine](https://console.volcengine.com) | `volcengine/` | Required | Doubao, Ark models |
|
||||
| [Qwen](https://dashscope.console.aliyun.com/apiKey) | `qwen/` | Required | Qwen3, Qwen-Max, etc. |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/` | Required | Fast inference (Llama, Mixtral) |
|
||||
| [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | Required | Kimi models |
|
||||
| [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | Required | MiniMax models |
|
||||
| [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | Required | Mistral Large, Codestral |
|
||||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Required | NVIDIA hosted models |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Required | Fast inference |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Required | Various open models |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Required | MiMo models |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Not needed | Local models, self-hosted |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Not needed | Local deployment, OpenAI-compatible |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Varies | Proxy for 100+ providers |
|
||||
| [Azure OpenAI](https://portal.azure.com/) | `azure/` | Required | Enterprise Azure deployment |
|
||||
| [GitHub Copilot](https://github.com/features/copilot) | `github-copilot/` | OAuth | Device code login |
|
||||
| [Antigravity](https://console.cloud.google.com/) | `antigravity/` | OAuth | Google Cloud AI |
|
||||
| [AWS Bedrock](https://console.aws.amazon.com/bedrock)* | `bedrock/` | AWS credentials | Claude, Llama, Mistral on AWS |
|
||||
|
||||
> \* AWS Bedrock requires build tag: `go build -tags bedrock`. Set `api_base` to a region name (e.g., `us-east-1`) for automatic endpoint resolution across all AWS partitions (aws, aws-cn, aws-us-gov). When using a full endpoint URL instead, you must also configure `AWS_REGION` via environment variable or AWS config/profile.
|
||||
|
||||
<details>
|
||||
<summary><b>Local deployment (Ollama, vLLM, etc.)</b></summary>
|
||||
|
||||
**Ollama:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-llama",
|
||||
"model": "ollama/llama3.1:8b",
|
||||
"api_base": "http://localhost:11434/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**vLLM:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-vllm",
|
||||
"model": "vllm/your-model",
|
||||
"api_base": "http://localhost:8000/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
For full provider configuration details, see [Providers & Models](docs/providers.md).
|
||||
|
||||
</details>
|
||||
|
||||
## 💬 Channels (Chat Apps)
|
||||
|
||||
Talk to your PicoClaw through 17+ messaging platforms:
|
||||
|
||||
| Channel | Setup | Protocol | Docs |
|
||||
|---------|-------|----------|------|
|
||||
| **Telegram** | Easy (bot token) | Long polling | [Guide](docs/channels/telegram/README.md) |
|
||||
| **Discord** | Easy (bot token + intents) | WebSocket | [Guide](docs/channels/discord/README.md) |
|
||||
| **WhatsApp** | Easy (QR scan or bridge URL) | Native / Bridge | [Guide](docs/chat-apps.md#whatsapp) |
|
||||
| **Weixin** | Easy (Native QR scan) | iLink API | [Guide](docs/chat-apps.md#weixin) |
|
||||
| **QQ** | Easy (AppID + AppSecret) | WebSocket | [Guide](docs/channels/qq/README.md) |
|
||||
| **Slack** | Easy (bot + app token) | Socket Mode | [Guide](docs/channels/slack/README.md) |
|
||||
| **Matrix** | Medium (homeserver + token) | Sync API | [Guide](docs/channels/matrix/README.md) |
|
||||
| **DingTalk** | Medium (client credentials) | Stream | [Guide](docs/channels/dingtalk/README.md) |
|
||||
| **Feishu / Lark** | Medium (App ID + Secret) | WebSocket/SDK | [Guide](docs/channels/feishu/README.md) |
|
||||
| **LINE** | Medium (credentials + webhook) | Webhook | [Guide](docs/channels/line/README.md) |
|
||||
| **WeCom** | Easy (QR login or manual) | WebSocket | [Guide](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Medium (server + nick) | IRC protocol | [Guide](docs/chat-apps.md#irc) |
|
||||
| **OneBot** | Medium (WebSocket URL) | OneBot v11 | [Guide](docs/channels/onebot/README.md) |
|
||||
| **MaixCam** | Easy (enable) | TCP socket | [Guide](docs/channels/maixcam/README.md) |
|
||||
| **Pico** | Easy (enable) | Native protocol | Built-in |
|
||||
| **Pico Client** | Easy (WebSocket URL) | WebSocket | Built-in |
|
||||
|
||||
> All webhook-based channels share a single Gateway HTTP server (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu uses WebSocket/SDK mode and does not use the shared HTTP server.
|
||||
|
||||
> Log verbosity is controlled by `gateway.log_level` (default: `warn`). Supported values: `debug`, `info`, `warn`, `error`, `fatal`. Can also be set via `PICOCLAW_LOG_LEVEL`. See [Configuration](docs/configuration.md#gateway-log-level) for details.
|
||||
|
||||
For detailed channel setup instructions, see [Chat Apps Configuration](docs/chat-apps.md).
|
||||
|
||||
## 🔧 Tools
|
||||
|
||||
### 🔍 Web Search
|
||||
|
||||
PicoClaw can search the web to provide up-to-date information. Configure in `tools.web`:
|
||||
|
||||
| Search Engine | API Key | Free Tier | Link |
|
||||
|--------------|---------|-----------|------|
|
||||
| DuckDuckGo | Not needed | Unlimited | Built-in fallback |
|
||||
| [Baidu Search](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5) | Required | 1000 queries/day | AI-powered, China-optimized |
|
||||
| [Tavily](https://tavily.com) | Required | 1000 queries/month | Optimized for AI Agents |
|
||||
| [Brave Search](https://brave.com/search/api) | Required | 2000 queries/month | Fast and private |
|
||||
| [Perplexity](https://www.perplexity.ai) | Required | Paid | AI-powered search |
|
||||
| [SearXNG](https://github.com/searxng/searxng) | Not needed | Self-hosted | Free metasearch engine |
|
||||
| [GLM Search](https://open.bigmodel.cn/) | Required | Varies | Zhipu web search |
|
||||
|
||||
### ⚙️ Other Tools
|
||||
|
||||
PicoClaw includes built-in tools for file operations, code execution, scheduling, and more. See [Tools Configuration](docs/tools_configuration.md) for details.
|
||||
|
||||
## 🎯 Skills
|
||||
|
||||
Skills are modular capabilities that extend your Agent. They are loaded from `SKILL.md` files in your workspace.
|
||||
|
||||
**Install skills from ClawHub:**
|
||||
|
||||
```bash
|
||||
picoclaw skills search "web scraping"
|
||||
picoclaw skills install <skill-name>
|
||||
```
|
||||
|
||||
**Configure ClawHub token** (optional, for higher rate limits):
|
||||
|
||||
Add to your `config.json`:
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"skills": {
|
||||
"registries": {
|
||||
"clawhub": {
|
||||
"auth_token": "your-clawhub-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more details, see [Tools Configuration - Skills](docs/tools_configuration.md#skills-tool).
|
||||
|
||||
## 🔗 MCP (Model Context Protocol)
|
||||
|
||||
PicoClaw natively supports [MCP](https://modelcontextprotocol.io/) — connect any MCP server to extend your Agent's capabilities with external tools and data sources.
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"servers": {
|
||||
"filesystem": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For full MCP configuration (stdio, SSE, HTTP transports, Tool Discovery), see [Tools Configuration - MCP](docs/tools_configuration.md#mcp-tool).
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> Join the Agent Social Network
|
||||
|
||||
Connect Picoclaw to the Agent Social Network simply by sending a single message via the CLI or any integrated Chat App.
|
||||
Connect PicoClaw to the Agent Social Network simply by sending a single message via the CLI or any integrated Chat App.
|
||||
|
||||
**Read `https://clawdchat.ai/skill.md` and follow the instructions to join [ClawdChat.ai](https://clawdchat.ai)**
|
||||
|
||||
## 🖥️ CLI Reference
|
||||
|
||||
| Command | Description |
|
||||
| ------------------------- | ----------------------------- |
|
||||
| `picoclaw onboard` | Initialize config & workspace |
|
||||
| `picoclaw agent -m "..."` | Chat with the agent |
|
||||
| `picoclaw agent` | Interactive chat mode |
|
||||
| `picoclaw gateway` | Start the gateway |
|
||||
| `picoclaw status` | Show status |
|
||||
| `picoclaw version` | Show version info |
|
||||
| `picoclaw cron list` | List all scheduled jobs |
|
||||
| `picoclaw cron add ...` | Add a scheduled job |
|
||||
| `picoclaw cron disable` | Disable a scheduled job |
|
||||
| `picoclaw cron remove` | Remove a scheduled job |
|
||||
| `picoclaw skills list` | List installed skills |
|
||||
| `picoclaw skills install` | Install a skill |
|
||||
| Command | Description |
|
||||
| ------------------------- | -------------------------------- |
|
||||
| `picoclaw onboard` | Initialize config & workspace |
|
||||
| `picoclaw auth weixin` | Connect WeChat account via QR |
|
||||
| `picoclaw agent -m "..."` | Chat with the agent |
|
||||
| `picoclaw agent` | Interactive chat mode |
|
||||
| `picoclaw gateway` | Start the gateway |
|
||||
| `picoclaw status` | Show status |
|
||||
| `picoclaw version` | Show version info |
|
||||
| `picoclaw model` | View or switch the default model |
|
||||
| `picoclaw cron list` | List all scheduled jobs |
|
||||
| `picoclaw cron add ...` | Add a scheduled job |
|
||||
| `picoclaw cron disable` | Disable a scheduled job |
|
||||
| `picoclaw cron remove` | Remove a scheduled job |
|
||||
| `picoclaw skills list` | List installed skills |
|
||||
| `picoclaw skills install` | Install a skill |
|
||||
| `picoclaw migrate` | Migrate data from older versions |
|
||||
| `picoclaw auth login` | Authenticate with providers |
|
||||
| `picoclaw auth login` | Authenticate with providers |
|
||||
|
||||
### Scheduled Tasks / Reminders
|
||||
### ⏰ Scheduled Tasks / Reminders
|
||||
|
||||
PicoClaw supports scheduled reminders and recurring tasks through the `cron` tool:
|
||||
|
||||
* **One-time reminders**: "Remind me in 10 minutes" → triggers once after 10min
|
||||
* **Recurring tasks**: "Remind me every 2 hours" → triggers every 2 hours
|
||||
* **Cron expressions**: "Remind me at 9am daily" → uses cron expression
|
||||
* **One-time reminders**: "Remind me in 10 minutes" -> triggers once after 10min
|
||||
* **Recurring tasks**: "Remind me every 2 hours" -> triggers every 2 hours
|
||||
* **Cron expressions**: "Remind me at 9am daily" -> uses cron expression
|
||||
|
||||
See [docs/cron.md](docs/cron.md) for current schedule types, execution modes, command-job gates, and persistence details.
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
For detailed guides beyond this README:
|
||||
|
||||
| Topic | Description |
|
||||
|-------|-------------|
|
||||
| [Docker & Quick Start](docs/docker.md) | Docker Compose setup, Launcher/Agent modes |
|
||||
| [Chat Apps](docs/chat-apps.md) | All 17+ channel setup guides |
|
||||
| [Configuration](docs/configuration.md) | Environment variables, workspace layout, security sandbox |
|
||||
| [Scheduled Tasks and Cron Jobs](docs/cron.md) | Cron schedule types, deliver modes, command gates, job storage |
|
||||
| [Providers & Models](docs/providers.md) | 30+ LLM providers, model routing, model_list configuration |
|
||||
| [Spawn & Async Tasks](docs/spawn-tasks.md) | Quick tasks, long tasks with spawn, async sub-agent orchestration |
|
||||
| [Hooks](docs/hooks/README.md) | Event-driven hook system: observers, interceptors, approval hooks |
|
||||
| [Steering](docs/steering.md) | Inject messages into a running agent loop between tool calls |
|
||||
| [SubTurn](docs/subturn.md) | Subagent coordination, concurrency control, lifecycle |
|
||||
| [Troubleshooting](docs/troubleshooting.md) | Common issues and solutions |
|
||||
| [Tools Configuration](docs/tools_configuration.md) | Per-tool enable/disable, exec policies, MCP, Skills |
|
||||
| [Hardware Compatibility](docs/hardware-compatibility.md) | Tested boards, minimum requirements |
|
||||
|
||||
## 🤝 Contribute & Roadmap
|
||||
|
||||
PRs welcome! The codebase is intentionally small and readable. 🤗
|
||||
PRs welcome! The codebase is intentionally small and readable.
|
||||
|
||||
See our full [Community Roadmap](https://github.com/sipeed/picoclaw/blob/main/ROADMAP.md).
|
||||
See our [Community Roadmap](https://github.com/sipeed/picoclaw/issues/988) and [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
||||
|
||||
Developer group building, join after your first merged PR!
|
||||
|
||||
User Groups:
|
||||
|
||||
discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
Discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
|
||||
<img src="assets/wechat.png" alt="PicoClaw" width="512">
|
||||
WeChat:
|
||||
<img src="assets/wechat.png" alt="WeChat group QR code" width="512">
|
||||
|
|
|
|||
601
README.my.md
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: Pembantu AI Ultra-Cekap dalam Go</h1>
|
||||
|
||||
<h3>Perkakasan $10 · RAM 10MB · Boot ms · Jom, PicoClaw!</h3>
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go&logoColor=white" alt="Go">
|
||||
<img src="https://img.shields.io/badge/Arch-x86__64%2C%20ARM64%2C%20MIPS%2C%20RISC--V%2C%20LoongArch-blue" alt="Hardware">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green" alt="License">
|
||||
<br>
|
||||
<a href="https://picoclaw.io"><img src="https://img.shields.io/badge/Website-picoclaw.io-blue?style=flat&logo=google-chrome&logoColor=white" alt="Website"></a>
|
||||
<a href="https://docs.picoclaw.io/"><img src="https://img.shields.io/badge/Docs-Official-007acc?style=flat&logo=read-the-docs&logoColor=white" alt="Docs"></a>
|
||||
<a href="https://deepwiki.com/sipeed/picoclaw"><img src="https://img.shields.io/badge/Wiki-DeepWiki-FFA500?style=flat&logo=wikipedia&logoColor=white" alt="Wiki"></a>
|
||||
<br>
|
||||
<a href="https://x.com/SipeedIO"><img src="https://img.shields.io/badge/X_(Twitter)-SipeedIO-black?style=flat&logo=x&logoColor=white" alt="Twitter"></a>
|
||||
<a href="./assets/wechat.png"><img src="https://img.shields.io/badge/WeChat-Group-41d56b?style=flat&logo=wechat&logoColor=white"></a>
|
||||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | **Malay** | [English](README.md)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
> **PicoClaw** adalah projek sumber terbuka bebas yang dilancarkan oleh [Sipeed](https://sipeed.com), ditulis sepenuhnya dalam **Go** dari awal — bukan cabang OpenClaw, NanoBot, atau projek lain.
|
||||
|
||||
**PicoClaw** adalah pembantu AI peribadi ultra-ringan yang terinspirasi oleh [NanoBot](https://github.com/HKUDS/nanobot). Ia dibina semula dari awal dalam **Go** melalui proses "self-bootstrapping" — AI Agent itu sendiri yang memacu migrasi seni bina dan pengoptimuman kod.
|
||||
|
||||
**Berjalan pada perkakasan $10 dengan RAM <10MB** — 99% lebih sedikit memori daripada OpenClaw dan 98% lebih murah daripada Mac mini!
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> [!CAUTION]
|
||||
> **Notis Keselamatan**
|
||||
>
|
||||
> * **TIADA KRIPTO:** PicoClaw **tidak** mengeluarkan sebarang token atau mata wang kripto rasmi. Semua tuntutan di `pump.fun` atau platform dagangan lain adalah **penipuan**.
|
||||
> * **DOMAIN RASMI:** Satu-satunya laman web rasmi ialah **[picoclaw.io](https://picoclaw.io)**, dan laman web syarikat ialah **[sipeed.com](https://sipeed.com)**
|
||||
> * **BERHATI-HATI:** Banyak domain `.ai/.org/.com/.net/...` telah didaftarkan oleh pihak ketiga. Jangan percayai mereka.
|
||||
> * **NOTA:** PicoClaw dalam pembangunan pesat awal. Mungkin terdapat isu keselamatan yang belum diselesaikan. Jangan deploy ke pengeluaran sebelum v1.0.
|
||||
|
||||
|
||||
## 📢 Berita
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 Dikeluarkan!** Penstrukturan semula seni bina Agent (SubTurn, Hooks, Steering, EventBus), integrasi WeChat/WeCom, penguatan keselamatan (.security.yml, penapisan data sensitif), penyedia baharu (AWS Bedrock, Azure, Xiaomi MiMo), dan 35 pembetulan pepijat. PicoClaw mencapai **26K Stars**!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 Dikeluarkan!** UI dulang sistem (Windows & Linux), pertanyaan status sub-agent (`spawn_status`), muat semula panas Gateway eksperimental, kawalan keselamatan Cron, dan 2 pembetulan keselamatan. PicoClaw mencapai **25K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Kemas kini terbesar setakat ini!** Sokongan protokol MCP, 4 saluran baharu (Matrix/IRC/WeCom/Discord Proxy), 3 penyedia baharu (Kimi/Minimax/Avian), saluran paip visi, storan memori JSONL, penghalaan model.
|
||||
|
||||
2026-02-28 📦 **v0.2.0** dikeluarkan dengan sokongan Docker Compose dan Pelancar Web UI.
|
||||
|
||||
<details>
|
||||
<summary>Berita terdahulu...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw mencapai **20K Stars** hanya dalam 17 hari! Orkestrasi saluran automatik dan antara muka keupayaan kini aktif.
|
||||
|
||||
2026-02-16 🎉 PicoClaw melepasi 12K Stars dalam seminggu! Peranan penyelenggara komuniti dan [Peta Jalan](ROADMAP.md) dilancarkan secara rasmi.
|
||||
|
||||
2026-02-13 🎉 PicoClaw melepasi 5000 Stars dalam 4 hari! Peta jalan projek dan kumpulan pembangun sedang dalam proses.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw Dikeluarkan!** Dibina dalam 1 hari untuk membawa AI Agent ke perkakasan $10 dengan RAM <10MB. Jom, PicoClaw!
|
||||
|
||||
</details>
|
||||
|
||||
## ✨ Ciri-ciri
|
||||
|
||||
🪶 **Ultra-ringan**: Jejak memori teras <10MB — 99% lebih kecil daripada OpenClaw.*
|
||||
|
||||
💰 **Kos minimum**: Cukup cekap untuk berjalan pada perkakasan $10 — 98% lebih murah daripada Mac mini.
|
||||
|
||||
⚡️ **Boot kilat**: 400x lebih pantas. Boot dalam <1s walaupun pada pemproses teras tunggal 0.6GHz.
|
||||
|
||||
🌍 **Benar-benar mudah alih**: Binari tunggal merentasi seni bina RISC-V, ARM, MIPS, dan x86.
|
||||
|
||||
🤖 **Dibantu AI**: Pelaksanaan Go tulen — 95% kod teras dijana oleh Agent dan diperhalusi melalui semakan manusia.
|
||||
|
||||
🔌 **Sokongan MCP**: Integrasi [Model Context Protocol](https://modelcontextprotocol.io/) natif.
|
||||
|
||||
👁️ **Saluran paip visi**: Hantar imej dan fail terus ke Agent — pengekodan base64 automatik untuk LLM multimodal.
|
||||
|
||||
🧠 **Penghalaan pintar**: Penghalaan model berasaskan peraturan — pertanyaan mudah ke model ringan, menjimatkan kos API.
|
||||
|
||||
_*Binaan terkini mungkin menggunakan 10-20MB disebabkan penggabungan PR yang pesat. Pengoptimuman sumber dirancang. Perbandingan kelajuan boot berdasarkan penanda aras teras tunggal 0.8GHz (lihat jadual di bawah)._
|
||||
|
||||
<div align="center">
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ------------------------------ | ------------- | ------------------------ | -------------------------------------- |
|
||||
| **Bahasa** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **Masa Boot** (teras 0.8GHz) | >500s | >30s | **<1s** |
|
||||
| **Kos** | Mac Mini $599 | Kebanyakan papan Linux ~$50 | **Mana-mana papan Linux dari $10** |
|
||||
|
||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
</div>
|
||||
|
||||
> **[Senarai Keserasian Perkakasan](docs/hardware-compatibility.md)** — Lihat semua papan yang diuji, dari RISC-V $5 hingga Raspberry Pi hingga telefon Android.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/hardware-banner.jpg" alt="Keserasian Perkakasan PicoClaw" width="100%">
|
||||
</p>
|
||||
|
||||
## 🦾 Demonstrasi
|
||||
|
||||
### 🛠️ Aliran Kerja Pembantu Standard
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<th><p align="center">Mod Jurutera Full-Stack</p></th>
|
||||
<th><p align="center">Pengelogan & Perancangan</p></th>
|
||||
<th><p align="center">Carian Web & Pembelajaran</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Bangun · Deploy · Skala</td>
|
||||
<td align="center">Jadual · Automatik · Ingat</td>
|
||||
<td align="center">Temui · Wawasan · Trend</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 🐜 Deployment Jejak Rendah yang Inovatif
|
||||
|
||||
PicoClaw boleh digunakan pada hampir mana-mana peranti Linux!
|
||||
|
||||
- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) untuk pembantu rumah minimal
|
||||
- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html) untuk operasi pelayan automatik
|
||||
- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) untuk pengawasan pintar
|
||||
|
||||
<https://private-user-images.githubusercontent.com/83055338/547056448-e7b031ff-d6f5-4468-bcca-5726b6fecb5c.mp4>
|
||||
|
||||
🌟 Lebih Banyak Kes Deployment Menanti!
|
||||
|
||||
|
||||
## 📦 Pemasangan
|
||||
|
||||
### Muat turun dari picoclaw.io (Disyorkan)
|
||||
|
||||
Lawati **[picoclaw.io](https://picoclaw.io)** — laman web rasmi mengesan platform anda secara automatik dan menyediakan muat turun satu klik.
|
||||
|
||||
### Muat turun binari pra-kompil
|
||||
|
||||
Muat turun binari untuk platform anda dari halaman [GitHub Releases](https://github.com/sipeed/picoclaw/releases).
|
||||
|
||||
### Bina dari sumber (untuk pembangunan)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
make deps
|
||||
|
||||
# Bina binari teras
|
||||
make build
|
||||
|
||||
# Bina Pelancar Web UI (diperlukan untuk mod WebUI)
|
||||
make build-launcher
|
||||
|
||||
# Bina untuk pelbagai platform
|
||||
make build-all
|
||||
|
||||
# Bina untuk Raspberry Pi Zero 2 W (32-bit: make build-linux-arm; 64-bit: make build-linux-arm64)
|
||||
make build-pi-zero
|
||||
|
||||
# Bina dan pasang
|
||||
make install
|
||||
```
|
||||
|
||||
**Raspberry Pi Zero 2 W:** Gunakan binari yang sepadan dengan OS anda: Raspberry Pi OS 32-bit -> `make build-linux-arm`; 64-bit -> `make build-linux-arm64`. Atau jalankan `make build-pi-zero` untuk membina kedua-duanya.
|
||||
|
||||
## 🚀 Panduan Permulaan Pantas
|
||||
|
||||
### 🌐 Pelancar WebUI (Disyorkan untuk Desktop)
|
||||
|
||||
Pelancar WebUI menyediakan antara muka berasaskan pelayar untuk konfigurasi dan sembang. Ini adalah cara termudah untuk bermula — tiada pengetahuan baris arahan diperlukan.
|
||||
|
||||
**Pilihan 1: Klik dua kali (Desktop)**
|
||||
|
||||
Selepas memuat turun dari [picoclaw.io](https://picoclaw.io), klik dua kali `picoclaw-launcher` (atau `picoclaw-launcher.exe` pada Windows). Pelayar anda akan dibuka secara automatik di `http://localhost:18800`.
|
||||
|
||||
**Pilihan 2: Baris arahan**
|
||||
|
||||
```bash
|
||||
picoclaw-launcher
|
||||
# Buka http://localhost:18800 dalam pelayar anda
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **Akses jauh / Docker / VM:** Tambah bendera `-public` untuk mendengar pada semua antara muka:
|
||||
> ```bash
|
||||
> picoclaw-launcher -public
|
||||
> ```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-webui.jpg" alt="Pelancar WebUI" width="600">
|
||||
</p>
|
||||
|
||||
**Memulakan:** Buka WebUI, kemudian: **1)** Konfigurasikan Penyedia (tambah kunci API LLM) -> **2)** Konfigurasikan Saluran (cth. Telegram) -> **3)** Mulakan Gateway -> **4)** Sembang!
|
||||
|
||||
Untuk dokumentasi WebUI terperinci, lihat [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
<details>
|
||||
<summary><b>Docker (alternatif)</b></summary>
|
||||
|
||||
```bash
|
||||
# 1. Klon repo ini
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
|
||||
# 2. Jalankan pertama kali — jana docker/data/config.json secara automatik kemudian keluar
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up
|
||||
|
||||
# 3. Tetapkan kunci API anda
|
||||
vim docker/data/config.json
|
||||
|
||||
# 4. Mulakan
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
# Buka http://localhost:18800
|
||||
```
|
||||
|
||||
> **Pengguna Docker / VM:** Gateway mendengar pada `127.0.0.1` secara lalai. Tetapkan `PICOCLAW_GATEWAY_HOST=0.0.0.0` atau gunakan bendera `-public` untuk membolehkan akses dari hos.
|
||||
|
||||
```bash
|
||||
# Semak log
|
||||
docker compose -f docker/docker-compose.yml logs -f
|
||||
|
||||
# Henti
|
||||
docker compose -f docker/docker-compose.yml --profile launcher down
|
||||
|
||||
# Kemas kini
|
||||
docker compose -f docker/docker-compose.yml pull
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Amaran Keselamatan Pelancaran Pertama</b></summary>
|
||||
|
||||
macOS mungkin menyekat `picoclaw-launcher` pada pelancaran pertama kerana ia dimuat turun dari internet dan tidak disahkan melalui Mac App Store.
|
||||
|
||||
**Langkah 1:** Klik dua kali `picoclaw-launcher`. Anda akan melihat amaran keselamatan:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Amaran macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" Tidak Dibuka — Apple tidak dapat mengesahkan "picoclaw-launcher" bebas daripada perisian hasad yang mungkin membahayakan Mac anda atau menjejaskan privasi anda.*
|
||||
|
||||
**Langkah 2:** Buka **Tetapan Sistem** → **Privasi & Keselamatan** → tatal ke bawah ke bahagian **Keselamatan** → klik **Buka Juga** → sahkan dengan mengklik **Buka Juga** dalam dialog.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Privasi & Keselamatan — Buka Juga" width="600">
|
||||
</p>
|
||||
|
||||
Selepas langkah sekali ini, `picoclaw-launcher` akan dibuka secara normal pada pelancaran seterusnya.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 Pelancar TUI (Disyorkan untuk Headless / SSH)
|
||||
|
||||
Pelancar TUI menyediakan antara muka terminal lengkap untuk konfigurasi dan pengurusan. Sesuai untuk pelayan, Raspberry Pi, dan persekitaran tanpa kepala lain.
|
||||
|
||||
```bash
|
||||
picoclaw-launcher-tui
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-tui.jpg" alt="Pelancar TUI" width="600">
|
||||
</p>
|
||||
|
||||
**Memulakan:**
|
||||
|
||||
Gunakan menu TUI untuk: **1)** Konfigurasikan Penyedia -> **2)** Konfigurasikan Saluran -> **3)** Mulakan Gateway -> **4)** Sembang!
|
||||
|
||||
Untuk dokumentasi TUI terperinci, lihat [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
### 📱 Android
|
||||
|
||||
Berikan telefon lama anda kehidupan baru! Jadikannya Pembantu AI pintar dengan PicoClaw.
|
||||
|
||||
**Pilihan 1: Termux (tersedia sekarang)**
|
||||
|
||||
1. Pasang [Termux](https://github.com/termux/termux-app) (muat turun dari [GitHub Releases](https://github.com/termux/termux-app/releases), atau cari di F-Droid / Google Play)
|
||||
2. Jalankan arahan berikut:
|
||||
|
||||
```bash
|
||||
# Muat turun keluaran terkini
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard # chroot menyediakan susun atur sistem fail Linux standard
|
||||
```
|
||||
|
||||
Kemudian ikuti bahagian Pelancar Terminal di bawah untuk melengkapkan konfigurasi.
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw pada Termux" width="512">
|
||||
|
||||
**Pilihan 2: APK (akan datang)**
|
||||
|
||||
APK Android bebas dengan WebUI terbina dalam sedang dalam pembangunan. Nantikan!
|
||||
|
||||
<details>
|
||||
<summary><b>Pelancar Terminal (untuk persekitaran terhad sumber)</b></summary>
|
||||
|
||||
Untuk persekitaran minimal di mana hanya binari teras `picoclaw` tersedia (tiada UI Pelancar), anda boleh mengkonfigurasi semua melalui baris arahan dan fail konfigurasi JSON.
|
||||
|
||||
**1. Mulakan**
|
||||
|
||||
```bash
|
||||
picoclaw onboard
|
||||
```
|
||||
|
||||
Ini mencipta `~/.picoclaw/config.json` dan direktori ruang kerja.
|
||||
|
||||
**2. Konfigurasikan** (`~/.picoclaw/config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Lihat `config/config.example.json` dalam repo untuk templat konfigurasi lengkap. Nota: kunci API kini disimpan dalam `.security.yml`, bukan `config.json`.
|
||||
|
||||
**3. Sembang**
|
||||
|
||||
```bash
|
||||
picoclaw agent -m "Apa itu 2+2?"
|
||||
|
||||
# Mod interaktif
|
||||
picoclaw agent
|
||||
|
||||
# Mulakan gateway untuk integrasi aplikasi sembang
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## 🔌 Penyedia (LLM)
|
||||
|
||||
PicoClaw menyokong 30+ penyedia LLM melalui konfigurasi `model_list`. Gunakan format `protokol/model`:
|
||||
|
||||
| Penyedia | Protokol | Kunci API | Nota |
|
||||
|----------|----------|-----------|------|
|
||||
| [OpenAI](https://platform.openai.com/api-keys) | `openai/` | Diperlukan | GPT-5.4, GPT-4o, o3, dll. |
|
||||
| [Anthropic](https://console.anthropic.com/settings/keys) | `anthropic/` | Diperlukan | Claude Opus 4.6, Sonnet 4.6, dll. |
|
||||
| [Google Gemini](https://aistudio.google.com/apikey) | `gemini/` | Diperlukan | Gemini 3 Flash, 2.5 Pro, dll. |
|
||||
| [OpenRouter](https://openrouter.ai/keys) | `openrouter/` | Diperlukan | 200+ model, API bersatu |
|
||||
| [Zhipu (GLM)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | `zhipu/` | Diperlukan | GLM-4.7, GLM-5, dll. |
|
||||
| [DeepSeek](https://platform.deepseek.com/api_keys) | `deepseek/` | Diperlukan | DeepSeek-V3, DeepSeek-R1 |
|
||||
| [Volcengine](https://console.volcengine.com) | `volcengine/` | Diperlukan | Doubao, model Ark |
|
||||
| [Qwen](https://dashscope.console.aliyun.com/apiKey) | `qwen/` | Diperlukan | Qwen3, Qwen-Max, dll. |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/` | Diperlukan | Inferens pantas (Llama, Mixtral) |
|
||||
| [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | Diperlukan | Model Kimi |
|
||||
| [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | Diperlukan | Model MiniMax |
|
||||
| [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | Diperlukan | Mistral Large, Codestral |
|
||||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Diperlukan | Model hos NVIDIA |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Diperlukan | Inferens pantas |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Diperlukan | Pelbagai model terbuka |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Diperlukan | Model MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Tidak perlu | Model tempatan, self-hosted |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Tidak perlu | Deployment tempatan, serasi OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Berbeza | Proksi untuk 100+ penyedia |
|
||||
| [Azure OpenAI](https://portal.azure.com/) | `azure/` | Diperlukan | Deployment Azure perusahaan |
|
||||
| [GitHub Copilot](https://github.com/features/copilot) | `github-copilot/` | OAuth | Log masuk kod peranti |
|
||||
| [Antigravity](https://console.cloud.google.com/) | `antigravity/` | OAuth | Google Cloud AI |
|
||||
| [AWS Bedrock](https://console.aws.amazon.com/bedrock)* | `bedrock/` | Kelayakan AWS | Claude, Llama, Mistral pada AWS |
|
||||
|
||||
> \* AWS Bedrock memerlukan tag binaan: `go build -tags bedrock`. Tetapkan `api_base` kepada nama rantau (cth. `us-east-1`) untuk resolusi endpoint automatik merentasi semua partition AWS. Apabila menggunakan URL endpoint penuh, anda juga perlu mengkonfigurasi `AWS_REGION` melalui pemboleh ubah persekitaran.
|
||||
|
||||
<details>
|
||||
<summary><b>Deployment tempatan (Ollama, vLLM, dll.)</b></summary>
|
||||
|
||||
**Ollama:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-llama",
|
||||
"model": "ollama/llama3.1:8b",
|
||||
"api_base": "http://localhost:11434/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**vLLM:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-vllm",
|
||||
"model": "vllm/your-model",
|
||||
"api_base": "http://localhost:8000/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Untuk butiran konfigurasi penyedia penuh, lihat [Penyedia & Model](docs/providers.md).
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## 💬 Saluran (Aplikasi Sembang)
|
||||
|
||||
Bercakap dengan PicoClaw anda melalui 17+ platform pemesejan:
|
||||
|
||||
| Saluran | Persediaan | Protokol | Dok |
|
||||
|---------|-----------|----------|-----|
|
||||
| **Telegram** | Mudah (token bot) | Long polling | [Panduan](docs/channels/telegram/README.md) |
|
||||
| **Discord** | Mudah (token bot + intents) | WebSocket | [Panduan](docs/channels/discord/README.md) |
|
||||
| **WhatsApp** | Mudah (imbas QR atau URL jambatan) | Natif / Jambatan | [Panduan](docs/chat-apps.md#whatsapp) |
|
||||
| **Weixin** | Mudah (imbas QR natif) | iLink API | [Panduan](docs/chat-apps.md#weixin) |
|
||||
| **QQ** | Mudah (AppID + AppSecret) | WebSocket | [Panduan](docs/channels/qq/README.md) |
|
||||
| **Slack** | Mudah (token bot + app) | Socket Mode | [Panduan](docs/channels/slack/README.md) |
|
||||
| **Matrix** | Sederhana (homeserver + token) | Sync API | [Panduan](docs/channels/matrix/README.md) |
|
||||
| **DingTalk** | Sederhana (kelayakan klien) | Stream | [Panduan](docs/channels/dingtalk/README.md) |
|
||||
| **Feishu / Lark** | Sederhana (App ID + Secret) | WebSocket/SDK | [Panduan](docs/channels/feishu/README.md) |
|
||||
| **LINE** | Sederhana (kelayakan + webhook) | Webhook | [Panduan](docs/channels/line/README.md) |
|
||||
| **WeCom** | Mudah (log masuk QR atau manual) | WebSocket | [Panduan](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Sederhana (pelayan + nick) | Protokol IRC | [Panduan](docs/chat-apps.md#irc) |
|
||||
| **OneBot** | Sederhana (URL WebSocket) | OneBot v11 | [Panduan](docs/channels/onebot/README.md) |
|
||||
| **MaixCam** | Mudah (aktifkan) | TCP socket | [Panduan](docs/channels/maixcam/README.md) |
|
||||
| **Pico** | Mudah (aktifkan) | Protokol natif | Terbina dalam |
|
||||
| **Pico Client** | Mudah (URL WebSocket) | WebSocket | Terbina dalam |
|
||||
|
||||
> Semua saluran berasaskan webhook berkongsi satu pelayan HTTP Gateway (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`). Feishu menggunakan mod WebSocket/SDK dan tidak menggunakan pelayan HTTP yang dikongsi.
|
||||
|
||||
> Tahap perincian log dikawal oleh `gateway.log_level` (lalai: `warn`). Nilai yang disokong: `debug`, `info`, `warn`, `error`, `fatal`. Boleh juga ditetapkan melalui `PICOCLAW_LOG_LEVEL`. Lihat [Konfigurasi](docs/configuration.md#gateway-log-level) untuk butiran.
|
||||
|
||||
Untuk arahan persediaan saluran terperinci, lihat [Konfigurasi Aplikasi Sembang](docs/my/chat-apps.md).
|
||||
|
||||
## 🔧 Alat
|
||||
|
||||
### 🔍 Carian Web
|
||||
|
||||
PicoClaw boleh mencari web untuk menyediakan maklumat terkini. Konfigurasikan dalam `tools.web`:
|
||||
|
||||
| Enjin Carian | Kunci API | Peringkat Percuma | Pautan |
|
||||
|-------------|-----------|-------------------|--------|
|
||||
| DuckDuckGo | Tidak perlu | Tanpa had | Sandaran terbina dalam |
|
||||
| [Baidu Search](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5) | Diperlukan | 1000 pertanyaan/hari | Dikuasai AI, dioptimumkan untuk China |
|
||||
| [Tavily](https://tavily.com) | Diperlukan | 1000 pertanyaan/bulan | Dioptimumkan untuk AI Agent |
|
||||
| [Brave Search](https://brave.com/search/api) | Diperlukan | 2000 pertanyaan/bulan | Pantas dan peribadi |
|
||||
| [Perplexity](https://www.perplexity.ai) | Diperlukan | Berbayar | Carian dikuasai AI |
|
||||
| [SearXNG](https://github.com/searxng/searxng) | Tidak perlu | Self-hosted | Enjin metasearch percuma |
|
||||
| [GLM Search](https://open.bigmodel.cn/) | Diperlukan | Berbeza | Carian web Zhipu |
|
||||
|
||||
### ⚙️ Alat Lain
|
||||
|
||||
PicoClaw menyertakan alat terbina dalam untuk operasi fail, pelaksanaan kod, penjadualan, dan banyak lagi. Lihat [Konfigurasi Alat](docs/tools_configuration.md) untuk butiran.
|
||||
|
||||
## 🎯 Kemahiran
|
||||
|
||||
Kemahiran adalah keupayaan modular yang melanjutkan Agent anda. Ia dimuatkan dari fail `SKILL.md` dalam ruang kerja anda.
|
||||
|
||||
**Pasang kemahiran dari ClawHub:**
|
||||
|
||||
```bash
|
||||
picoclaw skills search "web scraping"
|
||||
picoclaw skills install <nama-kemahiran>
|
||||
```
|
||||
|
||||
**Konfigurasikan token ClawHub** (pilihan, untuk had kadar lebih tinggi):
|
||||
|
||||
Tambah ke `config.json` anda:
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"skills": {
|
||||
"registries": {
|
||||
"clawhub": {
|
||||
"auth_token": "your-clawhub-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Untuk butiran lanjut, lihat [Konfigurasi Alat - Kemahiran](docs/tools_configuration.md#skills-tool).
|
||||
|
||||
## 🔗 MCP (Protokol Konteks Model)
|
||||
|
||||
PicoClaw menyokong [MCP](https://modelcontextprotocol.io/) secara natif — sambungkan mana-mana pelayan MCP untuk melanjutkan keupayaan Agent anda dengan alat dan sumber data luaran.
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"servers": {
|
||||
"filesystem": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Untuk konfigurasi MCP penuh (pengangkutan stdio, SSE, HTTP, Penemuan Alat), lihat [Konfigurasi Alat - MCP](docs/tools_configuration.md#mcp-tool).
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> Sertai Rangkaian Sosial Agent
|
||||
|
||||
Sambungkan PicoClaw ke Rangkaian Sosial Agent dengan menghantar satu mesej melalui CLI atau mana-mana Aplikasi Sembang yang disepadukan.
|
||||
|
||||
**Baca `https://clawdchat.ai/skill.md` dan ikuti arahan untuk menyertai [ClawdChat.ai](https://clawdchat.ai)**
|
||||
|
||||
## 🖥️ Rujukan CLI
|
||||
|
||||
| Arahan | Penerangan |
|
||||
| ------ | ---------- |
|
||||
| `picoclaw onboard` | Mulakan konfigurasi & ruang kerja |
|
||||
| `picoclaw auth weixin` | Sambungkan akaun WeChat melalui QR |
|
||||
| `picoclaw agent -m "..."` | Sembang dengan agent |
|
||||
| `picoclaw agent` | Mod sembang interaktif |
|
||||
| `picoclaw gateway` | Mulakan gateway |
|
||||
| `picoclaw status` | Tunjukkan status |
|
||||
| `picoclaw version` | Tunjukkan maklumat versi |
|
||||
| `picoclaw model` | Lihat atau tukar model lalai |
|
||||
| `picoclaw cron list` | Senaraikan semua kerja berjadual |
|
||||
| `picoclaw cron add ...` | Tambah kerja berjadual |
|
||||
| `picoclaw cron disable` | Lumpuhkan kerja berjadual |
|
||||
| `picoclaw cron remove` | Buang kerja berjadual |
|
||||
| `picoclaw skills list` | Senaraikan kemahiran yang dipasang |
|
||||
| `picoclaw skills install` | Pasang kemahiran |
|
||||
| `picoclaw migrate` | Migrasi data dari versi lama |
|
||||
| `picoclaw auth login` | Sahkan dengan penyedia |
|
||||
|
||||
### ⏰ Tugasan Berjadual / Peringatan
|
||||
|
||||
PicoClaw menyokong peringatan berjadual dan tugasan berulang melalui alat `cron`:
|
||||
|
||||
* **Peringatan sekali**: "Ingatkan saya dalam 10 minit" -> pencetus sekali selepas 10 minit
|
||||
* **Tugasan berulang**: "Ingatkan saya setiap 2 jam" -> pencetus setiap 2 jam
|
||||
* **Ungkapan Cron**: "Ingatkan saya pada pukul 9 pagi setiap hari" -> menggunakan ungkapan cron
|
||||
|
||||
## 📚 Dokumentasi
|
||||
|
||||
Untuk panduan terperinci melebihi README ini:
|
||||
|
||||
| Topik | Penerangan |
|
||||
|-------|------------|
|
||||
| [Docker & Permulaan Pantas](docs/my/docker.md) | Persediaan Docker Compose, mod Launcher/Agent |
|
||||
| [Aplikasi Sembang](docs/my/chat-apps.md) | Panduan persediaan 17+ saluran |
|
||||
| [Konfigurasi](docs/my/configuration.md) | Pemboleh ubah persekitaran, susun atur ruang kerja |
|
||||
| [Penyedia & Model](docs/providers.md) | 30+ penyedia LLM, penghalaan model |
|
||||
| [Spawn & Tugasan Async](docs/my/spawn-tasks.md) | Tugasan pantas, tugasan panjang dengan spawn |
|
||||
| [Penyelesaian Masalah](docs/my/troubleshooting.md) | Isu biasa dan penyelesaian |
|
||||
| [Konfigurasi Alat](docs/tools_configuration.md) | Aktif/nyahaktif alat, dasar exec, MCP, Kemahiran |
|
||||
| [Keserasian Perkakasan](docs/hardware-compatibility.md) | Papan yang diuji, keperluan minimum |
|
||||
|
||||
## 🤝 Sumbangan & Peta Jalan
|
||||
|
||||
PR dialu-alukan! Kod sumber sengaja dibuat kecil dan mudah dibaca.
|
||||
|
||||
Lihat [Peta Jalan Komuniti](https://github.com/sipeed/picoclaw/issues/988) dan [CONTRIBUTING.md](CONTRIBUTING.md) untuk panduan.
|
||||
|
||||
Kumpulan pembangun sedang dibina, sertai selepas PR pertama anda digabungkan!
|
||||
|
||||
Kumpulan Pengguna:
|
||||
|
||||
Discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
|
||||
WeChat:
|
||||
<img src="assets/wechat.png" alt="Kod QR kumpulan WeChat" width="512">
|
||||
625
README.pt-br.md
|
|
@ -1,9 +1,9 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: Assistente de IA Ultra-Eficiente em Go</h1>
|
||||
<h1>PicoClaw: Assistente de IA Ultra-Eficiente em Go</h1>
|
||||
|
||||
<h3>Hardware de $10 · <10MB de RAM · Boot em <1s · 皮皮虾,我们走!</h3>
|
||||
<h3>Hardware de $10 · 10MB de RAM · Boot em ms · Let's Go, PicoClaw!</h3>
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go&logoColor=white" alt="Go">
|
||||
<img src="https://img.shields.io/badge/Arch-x86__64%2C%20ARM64%2C%20MIPS%2C%20RISC--V%2C%20LoongArch-blue" alt="Hardware">
|
||||
|
|
@ -23,147 +23,143 @@
|
|||
|
||||
---
|
||||
|
||||
> **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!
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> [!CAUTION]
|
||||
> **🚨 DECLARAÇÃO DE SEGURANÇA & CANAIS OFICIAIS**
|
||||
> **Aviso de Segurança**
|
||||
>
|
||||
> * **SEM CRIPTOMOEDAS:** O PicoClaw **NÃO** possui nenhum token/moeda oficial. Todas as alegações no `pump.fun` ou outras plataformas de negociação são **GOLPES**.
|
||||
>
|
||||
> * **DOMÍNIO OFICIAL:** O **ÚNICO** site oficial é o **[picoclaw.io](https://picoclaw.io)**, e o site da empresa é o **[sipeed.com](https://sipeed.com)**
|
||||
> * **Aviso:** Muitos domínios `.ai/.org/.com/.net/...` foram registrados por terceiros.
|
||||
> * **Aviso:** O PicoClaw está em fase inicial de desenvolvimento e pode ter problemas de segurança de rede não resolvidos. Não implante em ambientes de produção antes da versão v1.0.
|
||||
> * **Nota:** O PicoClaw recentemente fez merge de muitos PRs, o que pode resultar em maior consumo de memória (10–20MB) nas versões mais recentes. Planejamos priorizar a otimização de recursos assim que o conjunto de funcionalidades estiver estável.
|
||||
> * **SEM CRIPTO:** O PicoClaw **não** emitiu nenhum token oficial ou criptomoeda. Todas as alegações no `pump.fun` ou outras plataformas de negociação são **golpes**.
|
||||
> * **DOMÍNIO OFICIAL:** O **ÚNICO** site oficial é **[picoclaw.io](https://picoclaw.io)**, e o site da empresa é **[sipeed.com](https://sipeed.com)**
|
||||
> * **ATENÇÃO:** Muitos domínios `.ai/.org/.com/.net/...` foram registrados por terceiros. Não confie neles.
|
||||
> * **NOTA:** O PicoClaw está em desenvolvimento rápido inicial. Podem existir problemas de segurança não resolvidos. Não implante em produção antes da v1.0.
|
||||
> * **NOTA:** O PicoClaw mesclou muitos PRs recentemente. Builds recentes podem usar 10-20MB de RAM. A otimização de recursos está planejada após a estabilização de funcionalidades.
|
||||
|
||||
## 📢 Novidades
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 Lançado!** Interface de bandeja do sistema (Windows & Linux), rastreamento de status de sub-agentes (`spawn_status`), hot-reload experimental do gateway, portões de segurança para cron e 2 correções de segurança. PicoClaw agora com **25K ⭐**!
|
||||
2026-03-25 🚀 **v0.2.4 Lançada!** Reformulação da arquitetura Agent (SubTurn, Hooks, Steering, EventBus), integração WeChat/WeCom, fortalecimento de segurança (.security.yml, filtragem de dados sensíveis), novos providers (AWS Bedrock, Azure, Xiaomi MiMo) e 35 correções de bugs. O PicoClaw atingiu **26K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Maior atualização até agora!** Suporte ao protocolo MCP, 4 novos canais (Matrix/IRC/WeCom/Discord Proxy), 3 novos provedores (Kimi/Minimax/Avian), pipeline de visão, armazenamento de memória JSONL e roteamento de modelos.
|
||||
2026-03-17 🚀 **v0.2.3 Lançada!** UI na bandeja do sistema (Windows e Linux), consulta de status de sub-agent (`spawn_status`), hot-reload experimental do Gateway, controle de segurança do Cron e 2 correções de segurança. O PicoClaw atingiu **25K Stars**!
|
||||
|
||||
2026-02-28 📦 **v0.2.0** lançado com suporte a Docker Compose e launcher Web UI.
|
||||
2026-03-09 🎉 **v0.2.1 — Maior atualização até agora!** Suporte ao protocolo MCP, 4 novos channels (Matrix/IRC/WeCom/Discord Proxy), 3 novos providers (Kimi/Minimax/Avian), pipeline de visão, armazenamento de memória JSONL, roteamento de modelos.
|
||||
|
||||
2026-02-26 🎉 PicoClaw atingiu **20K stars** em apenas 17 dias! Orquestração automática de canais e interfaces de capacidade implementadas.
|
||||
2026-02-28 📦 **v0.2.0** lançada com suporte a Docker Compose e Web UI Launcher.
|
||||
|
||||
<details>
|
||||
<summary>Novidades anteriores...</summary>
|
||||
<summary>Notícias anteriores...</summary>
|
||||
|
||||
2026-02-16 🎉 PicoClaw atingiu 12K stars em uma semana! Papéis de maintainers da comunidade e [roadmap](ROADMAP.md) publicados oficialmente.
|
||||
2026-02-26 🎉 O PicoClaw atinge **20K Stars** em apenas 17 dias! Orquestração automática de channels e interfaces de capacidade estão disponíveis.
|
||||
|
||||
2026-02-13 🎉 PicoClaw atingiu 5000 stars em 4 dias! Roadmap do Projeto e Grupo de Desenvolvedores em preparação.
|
||||
2026-02-16 🎉 O PicoClaw ultrapassa 12K Stars em uma semana! Funções de mantenedor da comunidade e [Roadmap](ROADMAP.md) lançados oficialmente.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw Lançado!** Construído em 1 dia para trazer Agentes de IA para hardware de $10 com <10MB de RAM. 🦐 PicoClaw, Partiu!
|
||||
2026-02-13 🎉 O PicoClaw ultrapassa 5000 Stars em 4 dias! Roadmap do projeto e grupos de desenvolvedores em andamento.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw Lançado!** Construído em 1 dia para levar AI Agents a hardware de $10 com menos de 10MB de RAM. Let's Go, PicoClaw!
|
||||
|
||||
</details>
|
||||
|
||||
## ✨ Funcionalidades
|
||||
|
||||
🪶 **Ultra-Leve**: Consumo de memória <10MB — 99% menor que o OpenClaw para funcionalidades essenciais.*
|
||||
🪶 **Ultra-leve**: Footprint de memória do núcleo <10MB — 99% menor que o OpenClaw.*
|
||||
|
||||
💰 **Custo Mínimo**: Eficiente o suficiente para rodar em hardware de $10 — 98% mais barato que um Mac mini.
|
||||
💰 **Custo mínimo**: Eficiente o suficiente para rodar em hardware de $10 — 98% mais barato que um Mac mini.
|
||||
|
||||
⚡️ **Inicialização Relâmpago**: Tempo de inicialização 400X mais rápido, boot em <1 segundo mesmo em CPU single-core de 0.6GHz.
|
||||
⚡️ **Boot ultrarrápido**: Inicialização 400x mais rápida. Boot em menos de 1s mesmo em um processador single-core de 0,6GHz.
|
||||
|
||||
🌍 **Portabilidade Real**: Um único binário auto-contido para RISC-V, ARM, MIPS e x86. Um clique e já era!
|
||||
🌍 **Verdadeiramente portátil**: Binário único para arquiteturas RISC-V, ARM, MIPS e x86. Um binário, roda em qualquer lugar!
|
||||
|
||||
🤖 **Auto-Construído por IA**: Implementação nativa em Go de forma autônoma — 95% do núcleo gerado pelo Agente com refinamento humano no loop.
|
||||
🤖 **Bootstrapped por IA**: Implementação nativa pura em Go — 95% do código principal foi gerado por um Agent e refinado por revisão humana.
|
||||
|
||||
🔌 **Suporte MCP**: Integração nativa com o [Model Context Protocol](https://modelcontextprotocol.io/) — conecte qualquer servidor MCP para estender as capacidades do agente.
|
||||
🔌 **Suporte a MCP**: Integração nativa com o [Model Context Protocol](https://modelcontextprotocol.io/) — conecte qualquer servidor MCP para estender as capacidades do Agent.
|
||||
|
||||
👁️ **Pipeline de Visão**: Envie imagens e arquivos diretamente ao agente — codificação base64 automática para LLMs multimodais.
|
||||
👁️ **Pipeline de visão**: Envie imagens e arquivos diretamente ao Agent — codificação base64 automática para LLMs multimodais.
|
||||
|
||||
🧠 **Roteamento Inteligente**: Roteamento de modelos baseado em regras — consultas simples vão para modelos leves, economizando custos de API.
|
||||
🧠 **Roteamento inteligente**: Roteamento de modelos baseado em regras — consultas simples vão para modelos leves, economizando custos de API.
|
||||
|
||||
_*Versões recentes podem usar 10–20MB devido a merges rápidos de funcionalidades. Otimização de recursos está planejada. Comparação de inicialização baseada em benchmarks de single-core a 0.8GHz (veja tabela abaixo)._
|
||||
_*Builds recentes podem usar 10-20MB devido a merges rápidos de PRs. Otimização de recursos está planejada. Comparação de velocidade de boot baseada em benchmarks de single-core a 0,8GHz (veja tabela abaixo)._
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ----------------------------- | ------------- | ------------------------ | ----------------------------------------- |
|
||||
| **Linguagem** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **Inicialização**</br>(CPU 0.8GHz) | >500s | >30s | **<1s** |
|
||||
| **Custo** | Mac Mini $599 | Maioria dos SBC Linux </br>~$50 | **Qualquer placa Linux**</br>**A partir de $10** |
|
||||
<div align="center">
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ------------------------------ | ------------- | ------------------------ | -------------------------------------- |
|
||||
| **Linguagem** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **Tempo de boot**</br>(core 0,8GHz) | >500s | >30s | **<1s** |
|
||||
| **Custo** | Mac Mini $599 | Maioria das placas Linux ~$50 | **Qualquer placa Linux**</br>**a partir de $10** |
|
||||
|
||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
</div>
|
||||
|
||||
> **[Lista de Compatibilidade de Hardware](docs/pt-br/hardware-compatibility.md)** — Veja todas as placas testadas, de RISC-V de $5 ao Raspberry Pi e celulares Android. Sua placa não está listada? Envie um PR!
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/hardware-banner.jpg" alt="PicoClaw Hardware Compatibility" width="100%">
|
||||
</p>
|
||||
|
||||
## 🦾 Demonstração
|
||||
|
||||
### 🛠️ Fluxos de Trabalho Padrão do Assistente
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<th><p align="center">🧩 Engenharia Full-Stack</p></th>
|
||||
<th><p align="center">🗂️ Gerenciamento de Logs & Planejamento</p></th>
|
||||
<th><p align="center">🔎 Busca Web & Aprendizado</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Desenvolver • Implantar • Escalar</td>
|
||||
<td align="center">Agendar • Automatizar • Memorizar</td>
|
||||
<td align="center">Descobrir • Analisar • Tendências</td>
|
||||
</tr>
|
||||
<tr align="center">
|
||||
<th><p align="center">Modo Engenheiro Full-Stack</p></th>
|
||||
<th><p align="center">Registro e Planejamento</p></th>
|
||||
<th><p align="center">Busca na Web e Aprendizado</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Desenvolver · Implantar · Escalar</td>
|
||||
<td align="center">Agendar · Automatizar · Lembrar</td>
|
||||
<td align="center">Descobrir · Insights · Tendências</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 📱 Rode em celulares Android antigos
|
||||
|
||||
Dê uma segunda vida ao seu celular de dez anos atrás! Transforme-o em um assistente de IA inteligente com o PicoClaw. Início rápido:
|
||||
|
||||
1. **Instale o [Termux](https://github.com/termux/termux-app)** (Baixe em [GitHub Releases](https://github.com/termux/termux-app/releases), ou busque no F-Droid / Google Play).
|
||||
2. **Execute os comandos**
|
||||
|
||||
```bash
|
||||
# Baixe a versão mais recente em https://github.com/sipeed/picoclaw/releases
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard
|
||||
```
|
||||
|
||||
Depois siga as instruções na seção "Início Rápido" para completar a configuração!
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
### 🐜 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
|
||||
|
||||
<https://private-user-images.githubusercontent.com/83055338/547056448-e7b031ff-d6f5-4468-bcca-5726b6fecb5c.mp4>
|
||||
|
||||
🌟 Mais cenários de implantação aguardam você!
|
||||
🌟 Mais Casos de Implantação Aguardam!
|
||||
|
||||
## 📦 Instalação
|
||||
|
||||
### Instalar com binário pré-compilado
|
||||
### Download pelo picoclaw.io (Recomendado)
|
||||
|
||||
Baixe o binário para sua plataforma na página de [Releases](https://github.com/sipeed/picoclaw/releases).
|
||||
Acesse **[picoclaw.io](https://picoclaw.io)** — o site oficial detecta automaticamente sua plataforma e fornece download com um clique. Não é necessário selecionar a arquitetura manualmente.
|
||||
|
||||
### Instalar a partir do código-fonte (funcionalidades mais recentes, recomendado para desenvolvimento)
|
||||
### Download do binário pré-compilado
|
||||
|
||||
Alternativamente, baixe o binário para sua plataforma na página de [GitHub Releases](https://github.com/sipeed/picoclaw/releases).
|
||||
|
||||
### Compilar a partir do código-fonte (para desenvolvimento)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
|
|
@ -171,78 +167,437 @@ git clone https://github.com/sipeed/picoclaw.git
|
|||
cd picoclaw
|
||||
make deps
|
||||
|
||||
# Build, sem necessidade de instalar
|
||||
# Compilar o binário principal
|
||||
make build
|
||||
|
||||
# Build para múltiplas plataformas
|
||||
# Compilar o Web UI Launcher (necessário para o modo WebUI)
|
||||
make build-launcher
|
||||
|
||||
# Compilar para múltiplas plataformas
|
||||
make build-all
|
||||
|
||||
# Build para Raspberry Pi Zero 2 W (32-bit: make build-linux-arm; 64-bit: make build-linux-arm64)
|
||||
# Compilar para Raspberry Pi Zero 2 W (32-bit: make build-linux-arm; 64-bit: make build-linux-arm64)
|
||||
make build-pi-zero
|
||||
|
||||
# Build e Instalar
|
||||
# Compilar e instalar
|
||||
make install
|
||||
```
|
||||
|
||||
**Raspberry Pi Zero 2 W:** Use o binário correspondente ao seu SO: Raspberry Pi OS 32-bit → `make build-linux-arm`; 64-bit → `make build-linux-arm64`. Ou execute `make build-pi-zero` para compilar ambos.
|
||||
**Raspberry Pi Zero 2 W:** Use o binário que corresponde ao seu SO: Raspberry Pi OS 32-bit -> `make build-linux-arm`; 64-bit -> `make build-linux-arm64`. Ou execute `make build-pi-zero` para compilar ambos.
|
||||
|
||||
## 📚 Documentação
|
||||
## 🚀 Guia de Início Rápido
|
||||
|
||||
Para guias detalhados, consulte a documentação abaixo. Este README cobre apenas o início rápido.
|
||||
### 🌐 WebUI Launcher (Recomendado para Desktop)
|
||||
|
||||
| Tópico | Descrição |
|
||||
|--------|-----------|
|
||||
| 🐳 [Docker & Início Rápido](docs/pt-br/docker.md) | Configuração Docker Compose, modos Launcher/Agent, configuração de Início Rápido |
|
||||
| 💬 [Apps de Chat](docs/pt-br/chat-apps.md) | Telegram, Discord, WhatsApp, Matrix, QQ, Slack, IRC, DingTalk, LINE, Feishu, WeCom e mais |
|
||||
| ⚙️ [Configuração](docs/pt-br/configuration.md) | Variáveis de ambiente, estrutura do workspace, fontes de skills, sandbox de segurança, heartbeat |
|
||||
| 🔌 [Provedores & Modelos](docs/pt-br/providers.md) | 20+ provedores LLM, roteamento de modelos, configuração model_list, arquitetura de provedores |
|
||||
| 🔄 [Spawn & Tarefas Assíncronas](docs/pt-br/spawn-tasks.md) | Tarefas rápidas, tarefas longas com spawn, orquestração assíncrona de sub-agentes |
|
||||
| 🐛 [Solução de Problemas](docs/pt-br/troubleshooting.md) | Problemas comuns e soluções |
|
||||
| 🔧 [Configuração de Ferramentas](docs/pt-br/tools_configuration.md) | Habilitar/desabilitar por ferramenta, políticas de execução |
|
||||
O WebUI Launcher fornece uma interface baseada em navegador para configuração e chat. Esta é a maneira mais fácil de começar — sem necessidade de conhecimento de linha de comando.
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> Junte-se à Rede Social de Agentes
|
||||
**Opção 1: Duplo clique (Desktop)**
|
||||
|
||||
Conecte o PicoClaw à Rede Social de Agentes simplesmente enviando uma única mensagem via CLI ou qualquer App de Chat integrado.
|
||||
Após baixar de [picoclaw.io](https://picoclaw.io), dê duplo clique em `picoclaw-launcher` (ou `picoclaw-launcher.exe` no Windows). Seu navegador abrirá automaticamente em `http://localhost:18800`.
|
||||
|
||||
**Opção 2: Linha de comando**
|
||||
|
||||
```bash
|
||||
picoclaw-launcher
|
||||
# Abra http://localhost:18800 no seu navegador
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **Acesso remoto / Docker / VM:** Adicione a flag `-public` para escutar em todas as interfaces:
|
||||
> ```bash
|
||||
> picoclaw-launcher -public
|
||||
> ```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-webui.jpg" alt="WebUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Primeiros passos:**
|
||||
|
||||
Abra o WebUI e então: **1)** Configure um Provider (adicione sua API key de LLM) -> **2)** Configure um Channel (ex.: Telegram) -> **3)** Inicie o Gateway -> **4)** Converse!
|
||||
|
||||
Para documentação detalhada do WebUI, veja [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
<details>
|
||||
<summary><b>Docker (alternativa)</b></summary>
|
||||
|
||||
```bash
|
||||
# 1. Clone este repositório
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
|
||||
# 2. Primeira execução — gera automaticamente docker/data/config.json e encerra
|
||||
# (só é acionado quando config.json e workspace/ estão ausentes)
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up
|
||||
# O container imprime "First-run setup complete." e para.
|
||||
|
||||
# 3. Configure suas API keys
|
||||
vim docker/data/config.json
|
||||
|
||||
# 4. Iniciar
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
# Abra http://localhost:18800
|
||||
```
|
||||
|
||||
> **Usuários de Docker / VM:** O Gateway escuta em `127.0.0.1` por padrão. Defina `PICOCLAW_GATEWAY_HOST=0.0.0.0` ou use a flag `-public` para torná-lo acessível pelo host.
|
||||
|
||||
```bash
|
||||
# Verificar logs
|
||||
docker compose -f docker/docker-compose.yml logs -f
|
||||
|
||||
# Parar
|
||||
docker compose -f docker/docker-compose.yml --profile launcher down
|
||||
|
||||
# Atualizar
|
||||
docker compose -f docker/docker-compose.yml pull
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Aviso de segurança no primeiro lançamento</b></summary>
|
||||
|
||||
O macOS pode bloquear o `picoclaw-launcher` no primeiro lançamento porque ele foi baixado da internet e não é notarizado pela Mac App Store.
|
||||
|
||||
**Passo 1:** Dê um duplo clique em `picoclaw-launcher`. Você verá um aviso de segurança:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Aviso do macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" não foi aberto — A Apple não conseguiu verificar se "picoclaw-launcher" está livre de malware que possa prejudicar seu Mac ou comprometer sua privacidade.*
|
||||
|
||||
**Passo 2:** Abra **Configurações do Sistema** → **Privacidade e Segurança** → role até a seção **Segurança** → clique em **Abrir Mesmo Assim** → confirme clicando em **Abrir Mesmo Assim** na caixa de diálogo.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Privacidade e Segurança — Abrir Mesmo Assim" width="600">
|
||||
</p>
|
||||
|
||||
Após esta etapa única, o `picoclaw-launcher` abrirá normalmente nos lançamentos seguintes.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Recomendado para Headless / SSH)
|
||||
|
||||
O TUI (Terminal UI) Launcher fornece uma interface de terminal completa para configuração e gerenciamento. Ideal para servidores, Raspberry Pi e outros ambientes headless.
|
||||
|
||||
```bash
|
||||
picoclaw-launcher-tui
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-tui.jpg" alt="TUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Primeiros passos:**
|
||||
|
||||
Use os menus do TUI para: **1)** Configurar um Provider -> **2)** Configurar um Channel -> **3)** Iniciar o Gateway -> **4)** Conversar!
|
||||
|
||||
Para documentação detalhada do TUI, veja [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
### 📱 Android
|
||||
|
||||
Dê uma segunda vida ao seu celular de uma década! Transforme-o em um Assistente de IA inteligente com o PicoClaw.
|
||||
|
||||
**Opção 1: Termux (disponível agora)**
|
||||
|
||||
1. Instale o [Termux](https://github.com/termux/termux-app) (baixe nas [GitHub Releases](https://github.com/termux/termux-app/releases), ou pesquise no F-Droid / Google Play)
|
||||
2. Execute os seguintes comandos:
|
||||
|
||||
```bash
|
||||
# Baixar a versão mais recente
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard # chroot fornece um layout padrão de sistema de arquivos Linux
|
||||
```
|
||||
|
||||
Em seguida, siga a seção Terminal Launcher abaixo para concluir a configuração.
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**Opção 2: Instalação via APK (em breve)**
|
||||
|
||||
Um APK Android independente com WebUI integrado está em desenvolvimento. Fique ligado!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (para ambientes com recursos limitados)</b></summary>
|
||||
|
||||
Para ambientes mínimos onde apenas o binário principal `picoclaw` está disponível (sem Launcher UI), você pode configurar tudo via linha de comando e um arquivo de configuração JSON.
|
||||
|
||||
**1. Inicializar**
|
||||
|
||||
```bash
|
||||
picoclaw onboard
|
||||
```
|
||||
|
||||
Isso cria `~/.picoclaw/config.json` e o diretório workspace.
|
||||
|
||||
**2. Configurar** (`~/.picoclaw/config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-api-key"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Veja `config/config.example.json` no repositório para um template de configuração completo com todas as opções disponíveis.
|
||||
|
||||
**3. Conversar**
|
||||
|
||||
```bash
|
||||
# Pergunta única
|
||||
picoclaw agent -m "What is 2+2?"
|
||||
|
||||
# Modo interativo
|
||||
picoclaw agent
|
||||
|
||||
# Iniciar gateway para integração com app de chat
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 🔌 Providers (LLM)
|
||||
|
||||
O PicoClaw suporta mais de 30 providers de LLM através da configuração `model_list`. Use o formato `protocolo/modelo`:
|
||||
|
||||
| Provider | Protocolo | API Key | Notas |
|
||||
|----------|-----------|---------|-------|
|
||||
| [OpenAI](https://platform.openai.com/api-keys) | `openai/` | Obrigatória | GPT-5.4, GPT-4o, o3, etc. |
|
||||
| [Anthropic](https://console.anthropic.com/settings/keys) | `anthropic/` | Obrigatória | Claude Opus 4.6, Sonnet 4.6, etc. |
|
||||
| [Google Gemini](https://aistudio.google.com/apikey) | `gemini/` | Obrigatória | Gemini 3 Flash, 2.5 Pro, etc. |
|
||||
| [OpenRouter](https://openrouter.ai/keys) | `openrouter/` | Obrigatória | 200+ modelos, API unificada |
|
||||
| [Zhipu (GLM)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | `zhipu/` | Obrigatória | GLM-4.7, GLM-5, etc. |
|
||||
| [DeepSeek](https://platform.deepseek.com/api_keys) | `deepseek/` | Obrigatória | DeepSeek-V3, DeepSeek-R1 |
|
||||
| [Volcengine](https://console.volcengine.com) | `volcengine/` | Obrigatória | Modelos Doubao, Ark |
|
||||
| [Qwen](https://dashscope.console.aliyun.com/apiKey) | `qwen/` | Obrigatória | Qwen3, Qwen-Max, etc. |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/` | Obrigatória | Inferência rápida (Llama, Mixtral) |
|
||||
| [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | Obrigatória | Modelos Kimi |
|
||||
| [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | Obrigatória | Modelos MiniMax |
|
||||
| [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | Obrigatória | Mistral Large, Codestral |
|
||||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Obrigatória | Modelos hospedados pela NVIDIA |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Obrigatória | Inferência rápida |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Obrigatória | Vários modelos abertos |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Obrigatória | Modelos MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Não necessária | Modelos locais, self-hosted |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Não necessária | Implantação local, compatível com OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Varia | Proxy para 100+ providers |
|
||||
| [Azure OpenAI](https://portal.azure.com/) | `azure/` | Obrigatória | Implantação Azure Enterprise |
|
||||
| [GitHub Copilot](https://github.com/features/copilot) | `github-copilot/` | OAuth | Login por código de dispositivo |
|
||||
| [Antigravity](https://console.cloud.google.com/) | `antigravity/` | OAuth | Google Cloud AI |
|
||||
|
||||
<details>
|
||||
<summary><b>Implantação local (Ollama, vLLM, etc.)</b></summary>
|
||||
|
||||
**Ollama:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-llama",
|
||||
"model": "ollama/llama3.1:8b",
|
||||
"api_base": "http://localhost:11434/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**vLLM:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-vllm",
|
||||
"model": "vllm/your-model",
|
||||
"api_base": "http://localhost:8000/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Para detalhes completos de configuração de providers, veja [Providers & Models](docs/pt-br/providers.md).
|
||||
|
||||
</details>
|
||||
|
||||
## 💬 Channels (Apps de Chat)
|
||||
|
||||
Converse com seu PicoClaw por meio de mais de 17 plataformas de mensagens:
|
||||
|
||||
| Channel | Configuração | Protocolo | Docs |
|
||||
|---------|--------------|-----------|------|
|
||||
| **Telegram** | Fácil (bot token) | Long polling | [Guia](docs/channels/telegram/README.pt-br.md) |
|
||||
| **Discord** | Fácil (bot token + intents) | WebSocket | [Guia](docs/channels/discord/README.pt-br.md) |
|
||||
| **WhatsApp** | Fácil (QR scan ou bridge URL) | Nativo / Bridge | [Guia](docs/pt-br/chat-apps.md#whatsapp) |
|
||||
| **Weixin** | Fácil (scan QR nativo) | iLink API | [Guia](docs/pt-br/chat-apps.md#weixin) |
|
||||
| **QQ** | Fácil (AppID + AppSecret) | WebSocket | [Guia](docs/channels/qq/README.pt-br.md) |
|
||||
| **Slack** | Fácil (bot + app token) | Socket Mode | [Guia](docs/channels/slack/README.pt-br.md) |
|
||||
| **Matrix** | Médio (homeserver + token) | Sync API | [Guia](docs/channels/matrix/README.pt-br.md) |
|
||||
| **DingTalk** | Médio (credenciais do cliente) | Stream | [Guia](docs/channels/dingtalk/README.pt-br.md) |
|
||||
| **Feishu / Lark** | Médio (App ID + Secret) | WebSocket/SDK | [Guia](docs/channels/feishu/README.pt-br.md) |
|
||||
| **LINE** | Médio (credenciais + webhook) | Webhook | [Guia](docs/channels/line/README.pt-br.md) |
|
||||
| **WeCom** | Fácil (login QR ou manual) | WebSocket | [Guia](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Médio (servidor + nick) | Protocolo IRC | [Guia](docs/pt-br/chat-apps.md#irc) |
|
||||
| **OneBot** | Médio (WebSocket URL) | OneBot v11 | [Guia](docs/channels/onebot/README.pt-br.md) |
|
||||
| **MaixCam** | Fácil (habilitar) | TCP socket | [Guia](docs/channels/maixcam/README.pt-br.md) |
|
||||
| **Pico** | Fácil (habilitar) | Protocolo nativo | Integrado |
|
||||
| **Pico Client** | Fácil (WebSocket URL) | WebSocket | Integrado |
|
||||
|
||||
> Todos os channels baseados em webhook compartilham um único servidor HTTP do Gateway (`gateway.host`:`gateway.port`, padrão `127.0.0.1:18790`). O Feishu usa modo WebSocket/SDK e não utiliza o servidor HTTP compartilhado.
|
||||
|
||||
> A verbosidade dos logs é controlada por `gateway.log_level` (padrão: `warn`). Valores suportados: `debug`, `info`, `warn`, `error`, `fatal`. Também pode ser definido via `PICOCLAW_LOG_LEVEL`. Veja [Configuração](docs/pt-br/configuration.md#nível-de-log-do-gateway) para detalhes.
|
||||
|
||||
Para instruções detalhadas de configuração de channels, veja [Configuração de Apps de Chat](docs/pt-br/chat-apps.md).
|
||||
|
||||
## 🔧 Ferramentas
|
||||
|
||||
### 🔍 Busca na Web
|
||||
|
||||
O PicoClaw pode pesquisar na web para fornecer informações atualizadas. Configure em `tools.web`:
|
||||
|
||||
| Motor de Busca | API Key | Nível Gratuito | Link |
|
||||
|----------------|---------|----------------|------|
|
||||
| DuckDuckGo | Não necessária | Ilimitado | Fallback integrado |
|
||||
| [Baidu Search](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5) | Obrigatória | 1000 consultas/dia | IA, otimizado para chinês |
|
||||
| [Tavily](https://tavily.com) | Obrigatória | 1000 consultas/mês | Otimizado para AI Agents |
|
||||
| [Brave Search](https://brave.com/search/api) | Obrigatória | 2000 consultas/mês | Rápido e privado |
|
||||
| [Perplexity](https://www.perplexity.ai) | Obrigatória | Pago | Busca com IA |
|
||||
| [SearXNG](https://github.com/searxng/searxng) | Não necessária | Self-hosted | Metabuscador gratuito |
|
||||
| [GLM Search](https://open.bigmodel.cn/) | Obrigatória | Varia | Busca web Zhipu |
|
||||
|
||||
### ⚙️ Outras Ferramentas
|
||||
|
||||
O PicoClaw inclui ferramentas integradas para operações de arquivo, execução de código, agendamento e mais. Veja [Configuração de Ferramentas](docs/pt-br/tools_configuration.md) para detalhes.
|
||||
|
||||
## 🎯 Skills
|
||||
|
||||
Skills são capacidades modulares que estendem seu Agent. Elas são carregadas a partir de arquivos `SKILL.md` no seu workspace.
|
||||
|
||||
**Instalar skills do ClawHub:**
|
||||
|
||||
```bash
|
||||
picoclaw skills search "web scraping"
|
||||
picoclaw skills install <skill-name>
|
||||
```
|
||||
|
||||
**Configurar token do ClawHub** (opcional, para limites de taxa mais altos):
|
||||
|
||||
Adicione ao seu `config.json`:
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"skills": {
|
||||
"registries": {
|
||||
"clawhub": {
|
||||
"auth_token": "your-clawhub-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Para mais detalhes, veja [Configuração de Ferramentas - Skills](docs/pt-br/tools_configuration.md#skills-tool).
|
||||
|
||||
## 🔗 MCP (Model Context Protocol)
|
||||
|
||||
O PicoClaw suporta nativamente o [MCP](https://modelcontextprotocol.io/) — conecte qualquer servidor MCP para estender as capacidades do seu Agent com ferramentas externas e fontes de dados.
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"servers": {
|
||||
"filesystem": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Para configuração completa de MCP (transportes stdio, SSE, HTTP, Tool Discovery), veja [Configuração de Ferramentas - MCP](docs/pt-br/tools_configuration.md#mcp-tool).
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> Junte-se à Rede Social de Agents
|
||||
|
||||
Conecte o PicoClaw à Rede Social de Agents simplesmente enviando uma única mensagem via CLI ou qualquer App de Chat integrado.
|
||||
|
||||
**Leia `https://clawdchat.ai/skill.md` e siga as instruções para entrar no [ClawdChat.ai](https://clawdchat.ai)**
|
||||
|
||||
## 🖥️ Referência CLI
|
||||
|
||||
| Comando | Descrição |
|
||||
| ------------------------- | ----------------------------- |
|
||||
| `picoclaw onboard` | Inicializar configuração & workspace |
|
||||
| `picoclaw agent -m "..."` | Conversar com o agente |
|
||||
| `picoclaw agent` | Modo de chat interativo |
|
||||
| `picoclaw gateway` | Iniciar o gateway |
|
||||
| `picoclaw status` | Mostrar status |
|
||||
| `picoclaw version` | Mostrar informações de versão |
|
||||
| `picoclaw cron list` | Listar todas as tarefas agendadas |
|
||||
| `picoclaw cron add ...` | Adicionar uma tarefa agendada |
|
||||
| `picoclaw cron disable` | Desabilitar uma tarefa agendada |
|
||||
| `picoclaw cron remove` | Remover uma tarefa agendada |
|
||||
| `picoclaw skills list` | Listar skills instaladas |
|
||||
| `picoclaw skills install` | Instalar uma skill |
|
||||
| `picoclaw migrate` | Migrar dados de versões anteriores |
|
||||
| `picoclaw auth login` | Autenticar com provedores |
|
||||
| Comando | Descrição |
|
||||
| ------------------------- | -------------------------------------- |
|
||||
| `picoclaw onboard` | Inicializar config e workspace |
|
||||
| `picoclaw auth weixin` | Conectar conta WeChat via QR |
|
||||
| `picoclaw agent -m "..."` | Conversar com o agent |
|
||||
| `picoclaw agent` | Modo de chat interativo |
|
||||
| `picoclaw gateway` | Iniciar o gateway |
|
||||
| `picoclaw status` | Exibir status |
|
||||
| `picoclaw version` | Exibir informações de versão |
|
||||
| `picoclaw model` | Ver ou trocar o modelo padrão |
|
||||
| `picoclaw cron list` | Listar todos os jobs agendados |
|
||||
| `picoclaw cron add ...` | Adicionar um job agendado |
|
||||
| `picoclaw cron disable` | Desabilitar um job agendado |
|
||||
| `picoclaw cron remove` | Remover um job agendado |
|
||||
| `picoclaw skills list` | Listar skills instaladas |
|
||||
| `picoclaw skills install` | Instalar uma skill |
|
||||
| `picoclaw migrate` | Migrar dados de versões anteriores |
|
||||
| `picoclaw auth login` | Autenticar com providers |
|
||||
|
||||
### Tarefas Agendadas / Lembretes
|
||||
### ⏰ Tarefas Agendadas / Lembretes
|
||||
|
||||
O PicoClaw suporta lembretes agendados e tarefas recorrentes por meio da ferramenta `cron`:
|
||||
O PicoClaw suporta lembretes agendados e tarefas recorrentes através da ferramenta `cron`:
|
||||
|
||||
* **Lembretes únicos**: "Me lembre em 10 minutos" → dispara uma vez após 10min
|
||||
* **Tarefas recorrentes**: "Me lembre a cada 2 horas" → dispara a cada 2 horas
|
||||
* **Expressões Cron**: "Me lembre às 9h todos os dias" → usa expressão cron
|
||||
* **Lembretes únicos**: "Lembre-me em 10 minutos" -> dispara uma vez após 10min
|
||||
* **Tarefas recorrentes**: "Lembre-me a cada 2 horas" -> dispara a cada 2 horas
|
||||
* **Expressões cron**: "Lembre-me às 9h diariamente" -> usa expressão cron
|
||||
|
||||
## 📚 Documentação
|
||||
|
||||
Para guias detalhados além deste README:
|
||||
|
||||
| Tópico | Descrição |
|
||||
|--------|-----------|
|
||||
| [Docker & Início Rápido](docs/pt-br/docker.md) | Configuração do Docker Compose, modos Launcher/Agent |
|
||||
| [Apps de Chat](docs/pt-br/chat-apps.md) | Guias de configuração para todos os 17+ channels |
|
||||
| [Configuração](docs/pt-br/configuration.md) | Variáveis de ambiente, layout do workspace, sandbox de segurança |
|
||||
| [Providers & Models](docs/pt-br/providers.md) | 30+ providers de LLM, roteamento de modelos, configuração de model_list |
|
||||
| [Spawn & Tarefas Assíncronas](docs/pt-br/spawn-tasks.md) | Tarefas rápidas, tarefas longas com spawn, orquestração assíncrona de sub-agents |
|
||||
| [Hooks](docs/hooks/README.md) | Sistema de hooks orientado a eventos: observadores, interceptores, hooks de aprovação |
|
||||
| [Steering](docs/steering.md) | Injetar mensagens em um loop de agente em execução |
|
||||
| [SubTurn](docs/subturn.md) | Coordenação de subagentes, controle de concorrência, ciclo de vida |
|
||||
| [Solução de Problemas](docs/pt-br/troubleshooting.md) | Problemas comuns e soluções |
|
||||
| [Configuração de Ferramentas](docs/pt-br/tools_configuration.md) | Habilitar/desabilitar por ferramenta, políticas de exec, MCP, Skills |
|
||||
| [Compatibilidade de Hardware](docs/pt-br/hardware-compatibility.md) | Placas testadas, requisitos mínimos |
|
||||
|
||||
## 🤝 Contribuir & Roadmap
|
||||
|
||||
PRs são bem-vindos! O código-fonte é intencionalmente pequeno e legível. 🤗
|
||||
PRs são bem-vindos! O código-fonte é intencionalmente pequeno e legível.
|
||||
|
||||
Veja nosso [Roadmap da Comunidade](https://github.com/sipeed/picoclaw/blob/main/ROADMAP.md) completo.
|
||||
Veja nosso [Roadmap da Comunidade](https://github.com/sipeed/picoclaw/issues/988) e [CONTRIBUTING.md](CONTRIBUTING.md) para diretrizes.
|
||||
|
||||
Grupo de desenvolvedores em formação. Junte-se após seu primeiro PR com merge!
|
||||
Grupo de desenvolvedores em formação, entre após seu primeiro PR mesclado!
|
||||
|
||||
Grupos de usuários:
|
||||
Grupos de Usuários:
|
||||
|
||||
discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
Discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
|
||||
<img src="assets/wechat.png" alt="PicoClaw" width="512">
|
||||
WeChat:
|
||||
<img src="assets/wechat.png" alt="WeChat group QR code" width="512">
|
||||
|
|
|
|||
629
README.vi.md
|
|
@ -1,9 +1,9 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: Trợ lý AI Siêu Nhẹ viết bằng Go</h1>
|
||||
<h1>PicoClaw: Trợ lý AI Siêu Nhẹ viết bằng Go</h1>
|
||||
|
||||
<h3>Phần cứng $10 · <10MB RAM · Khởi động <1 giây · Nào, xuất phát!</h3>
|
||||
<h3>Phần cứng $10 · RAM 10MB · Khởi động ms · Let's Go, PicoClaw!</h3>
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go&logoColor=white" alt="Go">
|
||||
<img src="https://img.shields.io/badge/Arch-x86__64%2C%20ARM64%2C%20MIPS%2C%20RISC--V%2C%20LoongArch-blue" alt="Hardware">
|
||||
|
|
@ -23,147 +23,143 @@
|
|||
|
||||
---
|
||||
|
||||
> **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!
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> [!CAUTION]
|
||||
> **🚨 TUYÊN BỐ BẢO MẬT & KÊNH CHÍNH THỨC**
|
||||
> **Thông báo Bảo mật**
|
||||
>
|
||||
> * **KHÔNG CÓ CRYPTO:** PicoClaw **KHÔNG** có bất kỳ token/coin chính thức nào. Mọi thông tin trên `pump.fun` hoặc các sàn giao dịch khác đều là **LỪA ĐẢO**.
|
||||
>
|
||||
> * **DOMAIN CHÍNH THỨC:** Website chính thức **DUY NHẤT** là **[picoclaw.io](https://picoclaw.io)**, website công ty là **[sipeed.com](https://sipeed.com)**
|
||||
> * **Cảnh báo:** Nhiều tên miền `.ai/.org/.com/.net/...` đã bị bên thứ ba đăng ký.
|
||||
> * **Cảnh báo:** PicoClaw đang trong giai đoạn phát triển sớm và có thể còn các vấn đề bảo mật mạng chưa được giải quyết. Không nên triển khai lên môi trường production trước phiên bản v1.0.
|
||||
> * **Lưu ý:** PicoClaw gần đây đã merge nhiều PR, dẫn đến bộ nhớ sử dụng có thể lớn hơn (10–20MB) ở các phiên bản mới nhất. Chúng tôi sẽ ưu tiên tối ưu tài nguyên khi bộ tính năng đã ổn định.
|
||||
> * **KHÔNG CÓ CRYPTO:** PicoClaw **chưa** phát hành bất kỳ token hay tiền điện tử chính thức nào. Mọi thông tin trên `pump.fun` hoặc các nền tảng giao dịch khác đều là **lừa đảo**.
|
||||
> * **DOMAIN CHÍNH THỨC:** Website chính thức **DUY NHẤT** là **[picoclaw.io](https://picoclaw.io)**, và website công ty là **[sipeed.com](https://sipeed.com)**
|
||||
> * **CẢNH BÁO:** Nhiều domain `.ai/.org/.com/.net/...` đã bị bên thứ ba đăng ký. Đừng tin tưởng chúng.
|
||||
> * **LƯU Ý:** PicoClaw đang trong giai đoạn phát triển nhanh. Có thể còn các vấn đề bảo mật chưa được giải quyết. Không triển khai lên môi trường production trước v1.0.
|
||||
> * **LƯU Ý:** PicoClaw gần đây đã merge nhiều PR. Các bản build gần đây có thể dùng 10-20MB RAM. Tối ưu hóa tài nguyên được lên kế hoạch sau khi tính năng ổn định.
|
||||
|
||||
## 📢 Tin tức
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 Phát hành!** Giao diện khay hệ thống (Windows & Linux), theo dõi trạng thái sub-agent (`spawn_status`), hot-reload gateway thử nghiệm, cổng bảo mật cron và 2 bản vá bảo mật. PicoClaw đạt **25K ⭐**!
|
||||
2026-03-25 🚀 **v0.2.4 đã phát hành!** Tái cấu trúc kiến trúc Agent (SubTurn, Hooks, Steering, EventBus), tích hợp WeChat/WeCom, tăng cường bảo mật (.security.yml, lọc dữ liệu nhạy cảm), provider mới (AWS Bedrock, Azure, Xiaomi MiMo) và 35 bản vá lỗi. PicoClaw đã đạt **26K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Bản cập nhật lớn nhất!** Hỗ trợ giao thức MCP, 4 kênh mới (Matrix/IRC/WeCom/Discord Proxy), 3 nhà cung cấp mới (Kimi/Minimax/Avian), pipeline xử lý hình ảnh, bộ nhớ JSONL và định tuyến mô hình.
|
||||
2026-03-17 🚀 **v0.2.3 đã phát hành!** Giao diện system tray (Windows & Linux), truy vấn trạng thái sub-agent (`spawn_status`), thử nghiệm Gateway hot-reload, bảo mật Cron, và 2 bản vá bảo mật. PicoClaw đã đạt **25K Stars**!
|
||||
|
||||
2026-02-28 📦 **v0.2.0** phát hành với hỗ trợ Docker Compose và launcher Web UI.
|
||||
2026-03-09 🎉 **v0.2.1 — Bản cập nhật lớn nhất từ trước đến nay!** Hỗ trợ giao thức MCP, 4 Channel mới (Matrix/IRC/WeCom/Discord Proxy), 3 Provider mới (Kimi/Minimax/Avian), pipeline thị giác, bộ nhớ JSONL, định tuyến mô hình.
|
||||
|
||||
2026-02-26 🎉 PicoClaw đạt **20K stars** chỉ trong 17 ngày! Tự động điều phối kênh và giao diện năng lực đã được triển khai.
|
||||
2026-02-28 📦 **v0.2.0** phát hành với hỗ trợ Docker Compose và Web UI Launcher.
|
||||
|
||||
<details>
|
||||
<summary>Tin tức cũ hơn...</summary>
|
||||
<summary>Tin tức trước đó...</summary>
|
||||
|
||||
2026-02-16 🎉 PicoClaw đạt 12K stars chỉ trong một tuần! Vai trò maintainer cộng đồng và [roadmap](ROADMAP.md) đã được công bố chính thức.
|
||||
2026-02-26 🎉 PicoClaw đạt **20K Stars** chỉ trong 17 ngày! Tự động điều phối Channel và giao diện khả năng đã hoạt động.
|
||||
|
||||
2026-02-13 🎉 PicoClaw đạt 5000 stars trong 4 ngày! Lộ trình dự án và Nhóm phát triển đang được thiết lập.
|
||||
2026-02-16 🎉 PicoClaw vượt 12K Stars trong một tuần! Vai trò người duy trì cộng đồng và [Lộ trình](ROADMAP.md) chính thức ra mắt.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw chính thức ra mắt!** Được xây dựng trong 1 ngày để mang AI Agent đến phần cứng $10 với RAM <10MB. 🦐 PicoClaw, Lên Đường!
|
||||
2026-02-13 🎉 PicoClaw vượt 5000 Stars trong 4 ngày! Lộ trình dự án và nhóm nhà phát triển đang được xây dựng.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw ra mắt!** Được xây dựng trong 1 ngày để đưa AI Agent lên phần cứng $10 với <10MB RAM. Let's Go, PicoClaw!
|
||||
|
||||
</details>
|
||||
|
||||
## ✨ Tính năng nổi bật
|
||||
## ✨ Tính năng
|
||||
|
||||
🪶 **Siêu nhẹ**: Bộ nhớ sử dụng <10MB — nhỏ hơn 99% so với OpenClaw (chức năng cốt lõi).*
|
||||
🪶 **Siêu nhẹ**: Bộ nhớ lõi <10MB — nhỏ hơn 99% so với OpenClaw.*
|
||||
|
||||
💰 **Chi phí tối thiểu**: Đủ hiệu quả để chạy trên phần cứng $10 — rẻ hơn 98% so với Mac mini.
|
||||
|
||||
⚡️ **Khởi động siêu nhanh**: Nhanh gấp 400 lần, khởi động trong <1 giây ngay cả trên CPU đơn nhân 0.6GHz.
|
||||
⚡️ **Khởi động cực nhanh**: Khởi động nhanh hơn 400 lần. Khởi động trong <1 giây ngay cả trên bộ xử lý đơn nhân 0.6GHz.
|
||||
|
||||
🌍 **Di động thực sự**: Một file binary duy nhất chạy trên RISC-V, ARM, MIPS và x86. Một click là chạy!
|
||||
🌍 **Thực sự di động**: Một binary duy nhất cho các kiến trúc RISC-V, ARM, MIPS và x86. Một binary, chạy mọi nơi!
|
||||
|
||||
🤖 **AI tự xây dựng**: Triển khai Go-native tự động — 95% mã nguồn cốt lõi được Agent tạo ra, với sự tinh chỉnh của con người.
|
||||
🤖 **Được AI khởi động**: Triển khai Go thuần túy — 95% mã lõi được tạo bởi Agent và tinh chỉnh qua quy trình human-in-the-loop.
|
||||
|
||||
🔌 **Hỗ trợ MCP**: Tích hợp [Model Context Protocol](https://modelcontextprotocol.io/) gốc — kết nối bất kỳ máy chủ MCP nào để mở rộng khả năng của agent.
|
||||
🔌 **Hỗ trợ MCP**: Tích hợp [Model Context Protocol](https://modelcontextprotocol.io/) gốc — kết nối bất kỳ MCP server nào để mở rộng khả năng Agent.
|
||||
|
||||
👁️ **Pipeline Xử lý Hình ảnh**: Gửi hình ảnh và tệp trực tiếp cho agent — tự động mã hóa base64 cho các LLM đa phương thức.
|
||||
👁️ **Pipeline thị giác**: Gửi hình ảnh và tệp trực tiếp đến Agent — tự động mã hóa base64 cho LLM đa phương thức.
|
||||
|
||||
🧠 **Định tuyến Thông minh**: Định tuyến mô hình dựa trên quy tắc — truy vấn đơn giản chuyển đến mô hình nhẹ, tiết kiệm chi phí API.
|
||||
🧠 **Định tuyến thông minh**: Định tuyến mô hình dựa trên quy tắc — các truy vấn đơn giản đến mô hình nhẹ, tiết kiệm chi phí API.
|
||||
|
||||
_*Các phiên bản gần đây có thể sử dụng 10–20MB do merge tính năng nhanh chóng. Tối ưu tài nguyên đang được lên kế hoạch. So sánh thời gian khởi động dựa trên benchmark đơn nhân 0.8GHz (xem bảng bên dưới)._
|
||||
_*Các bản build gần đây có thể dùng 10-20MB do merge PR nhanh. Tối ưu hóa tài nguyên đang được lên kế hoạch. So sánh tốc độ khởi động dựa trên benchmark lõi đơn 0.8GHz (xem bảng bên dưới)._
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ----------------------------- | ------------- | ------------------------ | ----------------------------------------- |
|
||||
| **Ngôn ngữ** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **Thời gian khởi động**</br>(CPU 0.8GHz) | >500s | >30s | **<1s** |
|
||||
| **Chi phí** | Mac Mini $599 | Hầu hết SBC Linux ~$50 | **Mọi bo mạch Linux**</br>**Chỉ từ $10** |
|
||||
<div align="center">
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ------------------------------ | ------------- | ------------------------ | -------------------------------------- |
|
||||
| **Ngôn ngữ** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **Thời gian khởi động**</br>(lõi 0.8GHz) | >500s | >30s | **<1s** |
|
||||
| **Chi phí** | Mac Mini $599 | Hầu hết board Linux ~$50 | **Bất kỳ board Linux**</br>**từ $10** |
|
||||
|
||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
## 🦾 Demo
|
||||
</div>
|
||||
|
||||
### 🛠️ Quy trình trợ lý tiêu chuẩn
|
||||
> **[Danh sách Tương thích Phần cứng](docs/vi/hardware-compatibility.md)** — Xem tất cả các board đã được kiểm tra, từ RISC-V $5 đến Raspberry Pi đến điện thoại Android. Board của bạn chưa có trong danh sách? Gửi PR!
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/hardware-banner.jpg" alt="PicoClaw Hardware Compatibility" width="100%">
|
||||
</p>
|
||||
|
||||
## 🦾 Minh họa
|
||||
|
||||
### 🛠️ Quy trình Trợ lý Tiêu chuẩn
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<th><p align="center">🧩 Lập trình Full-Stack</p></th>
|
||||
<th><p align="center">🗂️ Quản lý Nhật ký & Kế hoạch</p></th>
|
||||
<th><p align="center">🔎 Tìm kiếm Web & Học hỏi</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Phát triển • Triển khai • Mở rộng</td>
|
||||
<td align="center">Lên lịch • Tự động hóa • Ghi nhớ</td>
|
||||
<td align="center">Khám phá • Phân tích • Xu hướng</td>
|
||||
</tr>
|
||||
<tr align="center">
|
||||
<th><p align="center">Chế độ Kỹ sư Full-Stack</p></th>
|
||||
<th><p align="center">Ghi nhật ký & Lập kế hoạch</p></th>
|
||||
<th><p align="center">Tìm kiếm Web & Học tập</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Phát triển · Triển khai · Mở rộng</td>
|
||||
<td align="center">Lên lịch · Tự động hóa · Ghi nhớ</td>
|
||||
<td align="center">Khám phá · Thông tin · Xu hướng</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 📱 Chạy trên điện thoại Android cũ
|
||||
### 🐜 Triển khai Sáng tạo với Dấu chân Nhỏ
|
||||
|
||||
Hãy cho chiếc điện thoại cũ một cuộc sống mới! Biến nó thành trợ lý AI thông minh với PicoClaw. Bắt đầu nhanh:
|
||||
PicoClaw có thể được triển khai trên hầu hết mọi thiết bị Linux!
|
||||
|
||||
1. **Cài đặt [Termux](https://github.com/termux/termux-app)** (Tải từ [GitHub Releases](https://github.com/termux/termux-app/releases), hoặc tìm trên F-Droid / Google Play).
|
||||
2. **Chạy các lệnh**
|
||||
|
||||
```bash
|
||||
# Tải phiên bản mới nhất từ https://github.com/sipeed/picoclaw/releases
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard
|
||||
```
|
||||
|
||||
Sau đó làm theo hướng dẫn trong phần "Bắt đầu nhanh" để hoàn tất cấu hình!
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
### 🐜 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
|
||||
|
||||
<https://private-user-images.githubusercontent.com/83055338/547056448-e7b031ff-d6f5-4468-bcca-5726b6fecb5c.mp4>
|
||||
|
||||
🌟 Nhiều hình thức triển khai hơn đang chờ bạn khám phá!
|
||||
🌟 Còn nhiều trường hợp triển khai đang chờ đón!
|
||||
|
||||
## 📦 Cài đặt
|
||||
|
||||
### Cài đặt bằng binary biên dịch sẵn
|
||||
### Tải xuống từ picoclaw.io (Khuyến nghị)
|
||||
|
||||
Tải file binary cho nền tảng của bạn từ [trang Releases](https://github.com/sipeed/picoclaw/releases).
|
||||
Truy cập **[picoclaw.io](https://picoclaw.io)** — website chính thức tự động phát hiện nền tảng của bạn và cung cấp tải xuống một cú nhấp. Không cần chọn kiến trúc thủ công.
|
||||
|
||||
### Cài đặt từ mã nguồn (có tính năng mới nhất, khuyên dùng cho phát triển)
|
||||
### Tải xuống binary đã biên dịch sẵn
|
||||
|
||||
Ngoài ra, tải binary cho nền tảng của bạn từ trang [GitHub Releases](https://github.com/sipeed/picoclaw/releases).
|
||||
|
||||
### Xây dựng từ mã nguồn (để phát triển)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
|
|
@ -171,78 +167,437 @@ git clone https://github.com/sipeed/picoclaw.git
|
|||
cd picoclaw
|
||||
make deps
|
||||
|
||||
# Build (không cần cài đặt)
|
||||
# Build core binary
|
||||
make build
|
||||
|
||||
# Build cho nhiều nền tảng
|
||||
# Build Web UI Launcher (required for WebUI mode)
|
||||
make build-launcher
|
||||
|
||||
# Build for multiple platforms
|
||||
make build-all
|
||||
|
||||
# Build cho Raspberry Pi Zero 2 W (32-bit: make build-linux-arm; 64-bit: make build-linux-arm64)
|
||||
# Build for Raspberry Pi Zero 2 W (32-bit: make build-linux-arm; 64-bit: make build-linux-arm64)
|
||||
make build-pi-zero
|
||||
|
||||
# Build và cài đặt
|
||||
# Build and install
|
||||
make install
|
||||
```
|
||||
|
||||
**Raspberry Pi Zero 2 W:** Sử dụng binary phù hợp với hệ điều hành: Raspberry Pi OS 32-bit → `make build-linux-arm`; 64-bit → `make build-linux-arm64`. Hoặc chạy `make build-pi-zero` để build cả hai.
|
||||
**Raspberry Pi Zero 2 W:** Sử dụng binary phù hợp với hệ điều hành của bạn: Raspberry Pi OS 32-bit -> `make build-linux-arm`; 64-bit -> `make build-linux-arm64`. Hoặc chạy `make build-pi-zero` để xây dựng cả hai.
|
||||
|
||||
## 📚 Tài liệu
|
||||
## 🚀 Hướng dẫn Khởi động Nhanh
|
||||
|
||||
Để xem hướng dẫn chi tiết, tham khảo tài liệu bên dưới. README này chỉ bao gồm phần bắt đầu nhanh.
|
||||
### 🌐 WebUI Launcher (Khuyến nghị cho Desktop)
|
||||
|
||||
| Chủ đề | Mô tả |
|
||||
|--------|-------|
|
||||
| 🐳 [Docker & Bắt đầu nhanh](docs/vi/docker.md) | Thiết lập Docker Compose, chế độ Launcher/Agent, cấu hình Bắt đầu nhanh |
|
||||
| 💬 [Ứng dụng Chat](docs/vi/chat-apps.md) | Telegram, Discord, WhatsApp, Matrix, QQ, Slack, IRC, DingTalk, LINE, Feishu, WeCom và nhiều hơn |
|
||||
| ⚙️ [Cấu hình](docs/vi/configuration.md) | Biến môi trường, cấu trúc workspace, nguồn skill, sandbox bảo mật, heartbeat |
|
||||
| 🔌 [Nhà cung cấp & Mô hình](docs/vi/providers.md) | 20+ nhà cung cấp LLM, định tuyến mô hình, cấu hình model_list, kiến trúc nhà cung cấp |
|
||||
| 🔄 [Spawn & Tác vụ bất đồng bộ](docs/vi/spawn-tasks.md) | Tác vụ nhanh, tác vụ dài với spawn, điều phối sub-agent bất đồng bộ |
|
||||
| 🐛 [Xử lý sự cố](docs/vi/troubleshooting.md) | Các vấn đề thường gặp và giải pháp |
|
||||
| 🔧 [Cấu hình Công cụ](docs/vi/tools_configuration.md) | Bật/tắt từng công cụ, chính sách thực thi |
|
||||
WebUI Launcher cung cấp giao diện dựa trên trình duyệt để cấu hình và trò chuyện. Đây là cách dễ nhất để bắt đầu — không cần kiến thức dòng lệnh.
|
||||
|
||||
**Tùy chọn 1: Nhấp đúp (Desktop)**
|
||||
|
||||
Sau khi tải xuống từ [picoclaw.io](https://picoclaw.io), nhấp đúp vào `picoclaw-launcher` (hoặc `picoclaw-launcher.exe` trên Windows). Trình duyệt của bạn sẽ tự động mở tại `http://localhost:18800`.
|
||||
|
||||
**Tùy chọn 2: Dòng lệnh**
|
||||
|
||||
```bash
|
||||
picoclaw-launcher
|
||||
# Mở http://localhost:18800 trong trình duyệt của bạn
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **Truy cập từ xa / Docker / VM:** Thêm cờ `-public` để lắng nghe trên tất cả giao diện:
|
||||
> ```bash
|
||||
> picoclaw-launcher -public
|
||||
> ```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-webui.jpg" alt="WebUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Bắt đầu:**
|
||||
|
||||
Mở WebUI, sau đó: **1)** Cấu hình Provider (thêm API key LLM của bạn) -> **2)** Cấu hình Channel (ví dụ: Telegram) -> **3)** Khởi động Gateway -> **4)** Trò chuyện!
|
||||
|
||||
Để biết tài liệu WebUI chi tiết, xem [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
<details>
|
||||
<summary><b>Docker (thay thế)</b></summary>
|
||||
|
||||
```bash
|
||||
# 1. Clone this repo
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
|
||||
# 2. First run — auto-generates docker/data/config.json then exits
|
||||
# (only triggers when both config.json and workspace/ are missing)
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up
|
||||
# The container prints "First-run setup complete." and stops.
|
||||
|
||||
# 3. Set your API keys
|
||||
vim docker/data/config.json
|
||||
|
||||
# 4. Start
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
# Open http://localhost:18800
|
||||
```
|
||||
|
||||
> **Người dùng Docker / VM:** Gateway lắng nghe trên `127.0.0.1` theo mặc định. Đặt `PICOCLAW_GATEWAY_HOST=0.0.0.0` hoặc dùng cờ `-public` để có thể truy cập từ host.
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
docker compose -f docker/docker-compose.yml logs -f
|
||||
|
||||
# Stop
|
||||
docker compose -f docker/docker-compose.yml --profile launcher down
|
||||
|
||||
# Update
|
||||
docker compose -f docker/docker-compose.yml pull
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Cảnh báo bảo mật khi khởi chạy lần đầu</b></summary>
|
||||
|
||||
macOS có thể chặn `picoclaw-launcher` khi khởi chạy lần đầu vì nó được tải từ internet và chưa được công chứng qua Mac App Store.
|
||||
|
||||
**Bước 1:** Nhấp đúp vào `picoclaw-launcher`. Bạn sẽ thấy cảnh báo bảo mật:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Cảnh báo macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" Không Mở Được — Apple không thể xác minh "picoclaw-launcher" không chứa phần mềm độc hại có thể gây hại cho Mac hoặc xâm phạm quyền riêng tư của bạn.*
|
||||
|
||||
**Bước 2:** Mở **Cài đặt Hệ thống** → **Quyền riêng tư & Bảo mật** → cuộn xuống phần **Bảo mật** → nhấp **Vẫn Mở** → xác nhận bằng cách nhấp **Vẫn Mở** trong hộp thoại.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Quyền riêng tư & Bảo mật — Vẫn Mở" width="600">
|
||||
</p>
|
||||
|
||||
Sau bước này, `picoclaw-launcher` sẽ mở bình thường trong các lần khởi chạy tiếp theo.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Khuyến nghị cho Headless / SSH)
|
||||
|
||||
TUI (Terminal UI) Launcher cung cấp giao diện terminal đầy đủ tính năng để cấu hình và quản lý. Lý tưởng cho máy chủ, Raspberry Pi và các môi trường headless khác.
|
||||
|
||||
```bash
|
||||
picoclaw-launcher-tui
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-tui.jpg" alt="TUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**Bắt đầu:**
|
||||
|
||||
Sử dụng menu TUI để: **1)** Cấu hình Provider -> **2)** Cấu hình Channel -> **3)** Khởi động Gateway -> **4)** Trò chuyện!
|
||||
|
||||
Để biết tài liệu TUI chi tiết, xem [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
### 📱 Android
|
||||
|
||||
Hãy cho chiếc điện thoại cũ của bạn một cuộc sống mới! Biến nó thành Trợ lý AI thông minh với PicoClaw.
|
||||
|
||||
**Tùy chọn 1: Termux (có sẵn ngay)**
|
||||
|
||||
1. Cài đặt [Termux](https://github.com/termux/termux-app) (tải từ [GitHub Releases](https://github.com/termux/termux-app/releases), hoặc tìm kiếm trong F-Droid / Google Play)
|
||||
2. Chạy các lệnh sau:
|
||||
|
||||
```bash
|
||||
# Download the latest release
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard # chroot provides a standard Linux filesystem layout
|
||||
```
|
||||
|
||||
Sau đó làm theo phần Terminal Launcher bên dưới để hoàn tất cấu hình.
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**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ờ!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (cho môi trường hạn chế tài nguyên)</b></summary>
|
||||
|
||||
Đối với các môi trường tối giản chỉ có binary lõi `picoclaw` (không có Launcher UI), bạn có thể cấu hình mọi thứ qua dòng lệnh và tệp cấu hình JSON.
|
||||
|
||||
**1. Khởi tạo**
|
||||
|
||||
```bash
|
||||
picoclaw onboard
|
||||
```
|
||||
|
||||
Lệnh này tạo `~/.picoclaw/config.json` và thư mục workspace.
|
||||
|
||||
**2. Cấu hình** (`~/.picoclaw/config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-api-key"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Xem `config/config.example.json` trong repo để có mẫu cấu hình đầy đủ với tất cả các tùy chọn có sẵn.
|
||||
|
||||
**3. Trò chuyện**
|
||||
|
||||
```bash
|
||||
# One-shot question
|
||||
picoclaw agent -m "What is 2+2?"
|
||||
|
||||
# Interactive mode
|
||||
picoclaw agent
|
||||
|
||||
# Start gateway for chat app integration
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 🔌 Providers (LLM)
|
||||
|
||||
PicoClaw hỗ trợ 30+ Provider LLM thông qua cấu hình `model_list`. Sử dụng định dạng `protocol/model`:
|
||||
|
||||
| Provider | Protocol | API Key | Ghi chú |
|
||||
|----------|----------|---------|---------|
|
||||
| [OpenAI](https://platform.openai.com/api-keys) | `openai/` | Bắt buộc | GPT-5.4, GPT-4o, o3, v.v. |
|
||||
| [Anthropic](https://console.anthropic.com/settings/keys) | `anthropic/` | Bắt buộc | Claude Opus 4.6, Sonnet 4.6, v.v. |
|
||||
| [Google Gemini](https://aistudio.google.com/apikey) | `gemini/` | Bắt buộc | Gemini 3 Flash, 2.5 Pro, v.v. |
|
||||
| [OpenRouter](https://openrouter.ai/keys) | `openrouter/` | Bắt buộc | 200+ mô hình, API thống nhất |
|
||||
| [Zhipu (GLM)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | `zhipu/` | Bắt buộc | GLM-4.7, GLM-5, v.v. |
|
||||
| [DeepSeek](https://platform.deepseek.com/api_keys) | `deepseek/` | Bắt buộc | DeepSeek-V3, DeepSeek-R1 |
|
||||
| [Volcengine](https://console.volcengine.com) | `volcengine/` | Bắt buộc | Doubao, Ark models |
|
||||
| [Qwen](https://dashscope.console.aliyun.com/apiKey) | `qwen/` | Bắt buộc | Qwen3, Qwen-Max, v.v. |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/` | Bắt buộc | Suy luận nhanh (Llama, Mixtral) |
|
||||
| [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | Bắt buộc | Kimi models |
|
||||
| [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | Bắt buộc | MiniMax models |
|
||||
| [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | Bắt buộc | Mistral Large, Codestral |
|
||||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Bắt buộc | Mô hình do NVIDIA lưu trữ |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Bắt buộc | Suy luận nhanh |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Bắt buộc | Nhiều mô hình mở |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Bắt buộc | Mô hình MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Không cần | Mô hình cục bộ, tự lưu trữ |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Không cần | Triển khai cục bộ, tương thích OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Tùy | Proxy cho 100+ provider |
|
||||
| [Azure OpenAI](https://portal.azure.com/) | `azure/` | Bắt buộc | Triển khai Azure doanh nghiệp |
|
||||
| [GitHub Copilot](https://github.com/features/copilot) | `github-copilot/` | OAuth | Đăng nhập bằng device code |
|
||||
| [Antigravity](https://console.cloud.google.com/) | `antigravity/` | OAuth | Google Cloud AI |
|
||||
|
||||
<details>
|
||||
<summary><b>Triển khai cục bộ (Ollama, vLLM, v.v.)</b></summary>
|
||||
|
||||
**Ollama:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-llama",
|
||||
"model": "ollama/llama3.1:8b",
|
||||
"api_base": "http://localhost:11434/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**vLLM:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-vllm",
|
||||
"model": "vllm/your-model",
|
||||
"api_base": "http://localhost:8000/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Để biết chi tiết cấu hình provider đầy đủ, xem [Providers & Models](docs/vi/providers.md).
|
||||
|
||||
</details>
|
||||
|
||||
## 💬 Channels (Ứng dụng Chat)
|
||||
|
||||
Trò chuyện với PicoClaw của bạn qua 17+ nền tảng nhắn tin:
|
||||
|
||||
| Channel | Thiết lập | Protocol | Tài liệu |
|
||||
|---------|-----------|----------|----------|
|
||||
| **Telegram** | Dễ (bot token) | Long polling | [Hướng dẫn](docs/channels/telegram/README.vi.md) |
|
||||
| **Discord** | Dễ (bot token + intents) | WebSocket | [Hướng dẫn](docs/channels/discord/README.vi.md) |
|
||||
| **WhatsApp** | Dễ (quét QR hoặc bridge URL) | Native / Bridge | [Hướng dẫn](docs/vi/chat-apps.md#whatsapp) |
|
||||
| **Weixin** | Dễ (quét QR gốc) | iLink API | [Hướng dẫn](docs/vi/chat-apps.md#weixin) |
|
||||
| **QQ** | Dễ (AppID + AppSecret) | WebSocket | [Hướng dẫn](docs/channels/qq/README.vi.md) |
|
||||
| **Slack** | Dễ (bot + app token) | Socket Mode | [Hướng dẫn](docs/channels/slack/README.vi.md) |
|
||||
| **Matrix** | Trung bình (homeserver + token) | Sync API | [Hướng dẫn](docs/channels/matrix/README.vi.md) |
|
||||
| **DingTalk** | Trung bình (client credentials) | Stream | [Hướng dẫn](docs/channels/dingtalk/README.vi.md) |
|
||||
| **Feishu / Lark** | Trung bình (App ID + Secret) | WebSocket/SDK | [Hướng dẫn](docs/channels/feishu/README.vi.md) |
|
||||
| **LINE** | Trung bình (credentials + webhook) | Webhook | [Hướng dẫn](docs/channels/line/README.vi.md) |
|
||||
| **WeCom** | Dễ (đăng nhập QR hoặc thủ công) | WebSocket | [Hướng dẫn](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Trung bình (server + nick) | IRC protocol | [Hướng dẫn](docs/vi/chat-apps.md#irc) |
|
||||
| **OneBot** | Trung bình (WebSocket URL) | OneBot v11 | [Hướng dẫn](docs/channels/onebot/README.vi.md) |
|
||||
| **MaixCam** | Dễ (bật) | TCP socket | [Hướng dẫn](docs/channels/maixcam/README.vi.md) |
|
||||
| **Pico** | Dễ (bật) | Native protocol | Tích hợp sẵn |
|
||||
| **Pico Client** | Dễ (WebSocket URL) | WebSocket | Tích hợp sẵn |
|
||||
|
||||
> Tất cả các Channel dựa trên webhook dùng chung một Gateway HTTP server (`gateway.host`:`gateway.port`, mặc định `127.0.0.1:18790`). Feishu sử dụng chế độ WebSocket/SDK và không dùng HTTP server chung.
|
||||
|
||||
> Mức độ chi tiết log được kiểm soát bởi `gateway.log_level` (mặc định: `warn`). Các giá trị được hỗ trợ: `debug`, `info`, `warn`, `error`, `fatal`. Cũng có thể đặt qua `PICOCLAW_LOG_LEVEL`. Xem [Cấu hình](docs/vi/configuration.md#mức-log-của-gateway) để biết thêm chi tiết.
|
||||
|
||||
Để biết hướng dẫn thiết lập Channel chi tiết, xem [Cấu hình Ứng dụng Chat](docs/vi/chat-apps.md).
|
||||
|
||||
## 🔧 Tools
|
||||
|
||||
### 🔍 Tìm kiếm Web
|
||||
|
||||
PicoClaw có thể tìm kiếm web để cung cấp thông tin cập nhật. Cấu hình trong `tools.web`:
|
||||
|
||||
| Công cụ Tìm kiếm | API Key | Gói miễn phí | Liên kết |
|
||||
|------------------|---------|--------------|----------|
|
||||
| DuckDuckGo | Không cần | Không giới hạn | Dự phòng tích hợp sẵn |
|
||||
| [Baidu Search](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5) | Bắt buộc | 1000 truy vấn/ngày | AI, tối ưu cho tiếng Trung |
|
||||
| [Tavily](https://tavily.com) | Bắt buộc | 1000 truy vấn/tháng | Tối ưu cho AI Agent |
|
||||
| [Brave Search](https://brave.com/search/api) | Bắt buộc | 2000 truy vấn/tháng | Nhanh và riêng tư |
|
||||
| [Perplexity](https://www.perplexity.ai) | Bắt buộc | Trả phí | Tìm kiếm hỗ trợ AI |
|
||||
| [SearXNG](https://github.com/searxng/searxng) | Không cần | Tự lưu trữ | Metasearch engine miễn phí |
|
||||
| [GLM Search](https://open.bigmodel.cn/) | Bắt buộc | Tùy | Tìm kiếm web Zhipu |
|
||||
|
||||
### ⚙️ Các Tools Khác
|
||||
|
||||
PicoClaw bao gồm các tool tích hợp sẵn cho thao tác tệp, thực thi mã, lên lịch và nhiều hơn nữa. Xem [Cấu hình Tools](docs/vi/tools_configuration.md) để biết chi tiết.
|
||||
|
||||
## 🎯 Skills
|
||||
|
||||
Skills là các khả năng mô-đun mở rộng Agent của bạn. Chúng được tải từ các tệp `SKILL.md` trong workspace của bạn.
|
||||
|
||||
**Cài đặt Skills từ ClawHub:**
|
||||
|
||||
```bash
|
||||
picoclaw skills search "web scraping"
|
||||
picoclaw skills install <skill-name>
|
||||
```
|
||||
|
||||
**Cấu hình token ClawHub** (tùy chọn, để có giới hạn tốc độ cao hơn):
|
||||
|
||||
Thêm vào `config.json` của bạn:
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"skills": {
|
||||
"registries": {
|
||||
"clawhub": {
|
||||
"auth_token": "your-clawhub-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Để biết thêm chi tiết, xem [Cấu hình Tools - Skills](docs/vi/tools_configuration.md#skills-tool).
|
||||
|
||||
## 🔗 MCP (Model Context Protocol)
|
||||
|
||||
PicoClaw hỗ trợ [MCP](https://modelcontextprotocol.io/) gốc — kết nối bất kỳ MCP server nào để mở rộng khả năng Agent của bạn với các tool và nguồn dữ liệu bên ngoài.
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"servers": {
|
||||
"filesystem": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Để biết cấu hình MCP đầy đủ (stdio, SSE, HTTP transports, Tool Discovery), xem [Cấu hình Tools - MCP](docs/vi/tools_configuration.md#mcp-tool).
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> Tham gia Mạng xã hội Agent
|
||||
|
||||
Kết nối PicoClaw với Mạng xã hội Agent chỉ bằng cách gửi một tin nhắn qua CLI hoặc bất kỳ ứng dụng Chat nào đã tích hợp.
|
||||
Kết nối PicoClaw với Mạng xã hội Agent chỉ bằng cách gửi một tin nhắn duy nhất qua CLI hoặc bất kỳ Ứng dụng Chat nào đã tích hợp.
|
||||
|
||||
**Đọc `https://clawdchat.ai/skill.md` và làm theo hướng dẫn để tham gia [ClawdChat.ai](https://clawdchat.ai)**
|
||||
|
||||
## 🖥️ Tham chiếu CLI
|
||||
|
||||
| Lệnh | Mô tả |
|
||||
| -------------------------- | ------------------------------ |
|
||||
| `picoclaw onboard` | Khởi tạo cấu hình & workspace |
|
||||
| `picoclaw agent -m "..."` | Trò chuyện với agent |
|
||||
| `picoclaw agent` | Chế độ chat tương tác |
|
||||
| `picoclaw gateway` | Khởi động gateway |
|
||||
| `picoclaw status` | Hiển thị trạng thái |
|
||||
| `picoclaw version` | Hiển thị thông tin phiên bản |
|
||||
| `picoclaw cron list` | Liệt kê tất cả tác vụ định kỳ |
|
||||
| `picoclaw cron add ...` | Thêm tác vụ định kỳ |
|
||||
| `picoclaw cron disable` | Tắt tác vụ định kỳ |
|
||||
| `picoclaw cron remove` | Xóa tác vụ định kỳ |
|
||||
| `picoclaw skills list` | Liệt kê các skill đã cài |
|
||||
| `picoclaw skills install` | Cài đặt một skill |
|
||||
| `picoclaw migrate` | Di chuyển dữ liệu từ phiên bản cũ |
|
||||
| `picoclaw auth login` | Xác thực với nhà cung cấp |
|
||||
| Lệnh | Mô tả |
|
||||
| ------------------------- | ---------------------------------------- |
|
||||
| `picoclaw onboard` | Khởi tạo cấu hình & workspace |
|
||||
| `picoclaw auth weixin` | Kết nối tài khoản WeChat qua QR |
|
||||
| `picoclaw agent -m "..."` | Trò chuyện với agent |
|
||||
| `picoclaw agent` | Chế độ trò chuyện tương tác |
|
||||
| `picoclaw gateway` | Khởi động gateway |
|
||||
| `picoclaw status` | Hiển thị trạng thái |
|
||||
| `picoclaw version` | Hiển thị thông tin phiên bản |
|
||||
| `picoclaw model` | Xem hoặc chuyển đổi mô hình mặc định |
|
||||
| `picoclaw cron list` | Liệt kê tất cả công việc đã lên lịch |
|
||||
| `picoclaw cron add ...` | Thêm công việc đã lên lịch |
|
||||
| `picoclaw cron disable` | Vô hiệu hóa công việc đã lên lịch |
|
||||
| `picoclaw cron remove` | Xóa công việc đã lên lịch |
|
||||
| `picoclaw skills list` | Liệt kê các Skill đã cài đặt |
|
||||
| `picoclaw skills install` | Cài đặt một Skill |
|
||||
| `picoclaw migrate` | Di chuyển dữ liệu từ các phiên bản cũ |
|
||||
| `picoclaw auth login` | Xác thực với các provider |
|
||||
|
||||
### Tác vụ định kỳ / Nhắc nhở
|
||||
### ⏰ Tác vụ Đã lên lịch / Nhắc nhở
|
||||
|
||||
PicoClaw hỗ trợ nhắc nhở theo lịch và tác vụ lặp lại thông qua công cụ `cron`:
|
||||
PicoClaw hỗ trợ nhắc nhở đã lên lịch và tác vụ định kỳ thông qua tool `cron`:
|
||||
|
||||
* **Nhắc nhở một lần**: "Nhắc tôi sau 10 phút" → kích hoạt một lần sau 10 phút
|
||||
* **Tác vụ lặp lại**: "Nhắc tôi mỗi 2 giờ" → kích hoạt mỗi 2 giờ
|
||||
* **Biểu thức Cron**: "Nhắc tôi lúc 9 giờ sáng mỗi ngày" → sử dụng biểu thức cron
|
||||
* **Nhắc nhở một lần**: "Nhắc tôi sau 10 phút" -> kích hoạt một lần sau 10 phút
|
||||
* **Tác vụ định kỳ**: "Nhắc tôi mỗi 2 giờ" -> kích hoạt mỗi 2 giờ
|
||||
* **Biểu thức Cron**: "Nhắc tôi lúc 9 giờ sáng hàng ngày" -> sử dụng biểu thức cron
|
||||
|
||||
## 📚 Tài liệu
|
||||
|
||||
Để biết các hướng dẫn chi tiết ngoài README này:
|
||||
|
||||
| Chủ đề | Mô tả |
|
||||
|--------|-------|
|
||||
| [Docker & Khởi động Nhanh](docs/vi/docker.md) | Thiết lập Docker Compose, chế độ Launcher/Agent |
|
||||
| [Ứng dụng Chat](docs/vi/chat-apps.md) | Hướng dẫn thiết lập 17+ Channel |
|
||||
| [Cấu hình](docs/vi/configuration.md) | Biến môi trường, bố cục workspace, sandbox bảo mật |
|
||||
| [Providers & Models](docs/vi/providers.md) | 30+ Provider LLM, định tuyến mô hình, cấu hình model_list |
|
||||
| [Spawn & Tác vụ Bất đồng bộ](docs/vi/spawn-tasks.md) | Tác vụ nhanh, tác vụ dài với spawn, điều phối sub-agent bất đồng bộ |
|
||||
| [Hooks](docs/hooks/README.md) | Hệ thống hook hướng sự kiện: observer, interceptor, approval hook |
|
||||
| [Steering](docs/steering.md) | Chèn tin nhắn vào vòng lặp agent đang chạy |
|
||||
| [SubTurn](docs/subturn.md) | Điều phối subagent, kiểm soát đồng thời, vòng đời |
|
||||
| [Khắc phục sự cố](docs/vi/troubleshooting.md) | Các vấn đề thường gặp và giải pháp |
|
||||
| [Cấu hình Tools](docs/vi/tools_configuration.md) | Bật/tắt từng tool, chính sách exec, MCP, Skills |
|
||||
| [Tương thích Phần cứng](docs/vi/hardware-compatibility.md) | Các board đã kiểm tra, yêu cầu tối thiểu |
|
||||
|
||||
## 🤝 Đóng góp & Lộ trình
|
||||
|
||||
Chào đón mọi PR! Mã nguồn được thiết kế nhỏ gọn và dễ đọc. 🤗
|
||||
PR luôn được chào đón! Codebase được thiết kế nhỏ gọn và dễ đọc.
|
||||
|
||||
Xem [Lộ trình Cộng đồng](https://github.com/sipeed/picoclaw/blob/main/ROADMAP.md) đầy đủ.
|
||||
Xem [Lộ trình Cộng đồng](https://github.com/sipeed/picoclaw/issues/988) và [CONTRIBUTING.md](CONTRIBUTING.md) để biết hướng dẫn.
|
||||
|
||||
Nhóm phát triển đang được xây dựng. Tham gia sau khi có PR đầu tiên được merge!
|
||||
Nhóm nhà phát triển đang được xây dựng, tham gia sau khi PR đầu tiên của bạn được merge!
|
||||
|
||||
Nhóm người dùng:
|
||||
Nhóm Người dùng:
|
||||
|
||||
discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
Discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
|
||||
<img src="assets/wechat.png" alt="PicoClaw" width="512">
|
||||
WeChat:
|
||||
<img src="assets/wechat.png" alt="WeChat group QR code" width="512">
|
||||
|
|
|
|||
447
README.zh.md
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<h1>PicoClaw: 基于Go语言的超高效 AI 助手</h1>
|
||||
|
||||
<h3>$10 硬件 · <10MB 内存 · <1s 启动 · 皮皮虾,我们走!</h3>
|
||||
<h3>$10 硬件 · 10MB 内存 · 毫秒启动 · 皮皮虾,我们走!</h3>
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go&logoColor=white" alt="Go">
|
||||
<img src="https://img.shields.io/badge/Arch-x86__64%2C%20ARM64%2C%20MIPS%2C%20RISC--V%2C%20LoongArch-blue" alt="Hardware">
|
||||
|
|
@ -56,17 +56,19 @@
|
|||
|
||||
## 📢 新闻
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 发布!** Agent 架构全面重构(SubTurn、Hook、Steering、EventBus)、微信/企业微信深度集成、安全体系升级(.security.yml、敏感数据过滤)、新增 Provider(AWS Bedrock、Azure、小米 MiMo),以及 35 项 Bug 修复。PicoClaw 已达 **26K ⭐**!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 发布!** 系统托盘 UI(Windows & Linux)、子 Agent 状态查询 (`spawn_status`)、实验性 Gateway 热重载、Cron 安全门控,以及 2 项安全修复。PicoClaw 已达 **25K ⭐**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — 史上最大更新!** MCP 协议支持、4 个新频道 (Matrix/IRC/WeCom/Discord Proxy)、3 个新 Provider (Kimi/Minimax/Avian)、视觉管线、JSONL 记忆存储、模型路由。
|
||||
|
||||
2026-02-28 📦 **v0.2.0** 发布,支持 Docker Compose 和 Web UI 启动器。
|
||||
|
||||
2026-02-26 🎉 PicoClaw 仅 17 天突破 **20K Stars**!频道自动编排和能力接口上线。
|
||||
|
||||
<details>
|
||||
<summary>更早的新闻...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw 仅 17 天突破 **20K Stars**!频道自动编排和能力接口上线。
|
||||
|
||||
2026-02-16 🎉 PicoClaw 一周内突破 12K Stars!社区维护者角色和 [路线图](ROADMAP.md) 正式发布。
|
||||
|
||||
2026-02-13 🎉 PicoClaw 4 天内突破 5000 Stars!项目路线图和开发者群组筹建中。
|
||||
|
|
@ -95,6 +97,8 @@
|
|||
|
||||
_*近期版本因快速合并 PR 可能占用 10–20MB,资源优化已列入计划。启动速度对比基于 0.8GHz 单核实测(见下方对比表)。_
|
||||
|
||||
<div align="center">
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ------------------------------ | ------------- | ------------------------ | -------------------------------------- |
|
||||
| **语言** | TypeScript | Python | **Go** |
|
||||
|
|
@ -104,6 +108,14 @@ _*近期版本因快速合并 PR 可能占用 10–20MB,资源优化已列入
|
|||
|
||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
</div>
|
||||
|
||||
> 📋 **[硬件兼容列表](docs/zh/hardware-compatibility.md)** — 查看所有已测试的板卡,从 $5 RISC-V 到树莓派到安卓手机。你的板卡没在列表中?欢迎提交 PR!
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/hardware-banner.jpg" alt="PicoClaw Hardware Compatibility" width="100%">
|
||||
</p>
|
||||
|
||||
## 🦾 演示
|
||||
|
||||
### 🛠️ 标准助手工作流
|
||||
|
|
@ -126,25 +138,6 @@ _*近期版本因快速合并 PR 可能占用 10–20MB,资源优化已列入
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
### 📱 在手机上轻松运行
|
||||
|
||||
PicoClaw 可以将你 10 年前的老旧手机废物利用,变身成为你的 AI 助理!快速指南:
|
||||
|
||||
1. 安装 [Termux](https://github.com/termux/termux-app)(可从 [GitHub Releases](https://github.com/termux/termux-app/releases) 下载,或在 F-Droid 等应用商店搜索)
|
||||
2. 打开后执行指令
|
||||
|
||||
```bash
|
||||
# 从 Release 页面下载最新版本
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard
|
||||
```
|
||||
|
||||
然后跟随下面的"快速开始"章节继续配置 PicoClaw 即可使用!
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
### 🐜 创新的低占用部署
|
||||
|
||||
PicoClaw 几乎可以部署在任何 Linux 设备上!
|
||||
|
|
@ -159,11 +152,15 @@ PicoClaw 几乎可以部署在任何 Linux 设备上!
|
|||
|
||||
## 📦 安装
|
||||
|
||||
### 使用预编译二进制文件安装
|
||||
### 从 picoclaw.io 下载(推荐)
|
||||
|
||||
从 [Release 页面](https://github.com/sipeed/picoclaw/releases) 下载适用于您平台的二进制文件。
|
||||
访问 **[picoclaw.io](https://picoclaw.io)** — 官网自动检测你的平台,提供一键下载,无需手动选择架构。
|
||||
|
||||
### 从源码安装(获取最新特性,开发推荐)
|
||||
### 下载预编译二进制文件
|
||||
|
||||
也可以从 [GitHub Releases](https://github.com/sipeed/picoclaw/releases) 页面手动下载对应平台的二进制文件。
|
||||
|
||||
### 从源码构建(开发用)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
|
|
@ -171,9 +168,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
|
||||
|
||||
|
|
@ -186,19 +186,354 @@ 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) | 工具启用/禁用、执行策略 |
|
||||
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
|
||||
> ```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-webui.jpg" alt="WebUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**开始使用:**
|
||||
|
||||
打开 WebUI,然后:**1)** 配置 Provider(填入 LLM API Key)-> **2)** 配置 Channel(如 Telegram)-> **3)** 启动 Gateway -> **4)** 开始聊天!
|
||||
|
||||
详细 WebUI 文档请参阅 [docs.picoclaw.io](https://docs.picoclaw.io)。
|
||||
|
||||
<details>
|
||||
<summary><b>Docker(备选方案)</b></summary>
|
||||
|
||||
```bash
|
||||
# 1. 克隆本仓库
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
|
||||
# 2. 首次运行——自动生成 docker/data/config.json 后退出
|
||||
# (仅在 config.json 和 workspace/ 均不存在时触发)
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up
|
||||
# 容器打印 "First-run setup complete." 后停止。
|
||||
|
||||
# 3. 填写 API Key
|
||||
vim docker/data/config.json
|
||||
|
||||
# 4. 启动
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
# 打开 http://localhost:18800
|
||||
```
|
||||
|
||||
> **Docker / 虚拟机用户:** Gateway 默认监听 `127.0.0.1`。设置 `PICOCLAW_GATEWAY_HOST=0.0.0.0` 或使用 `-public` 参数以允许从宿主机访问。
|
||||
|
||||
```bash
|
||||
# 查看日志
|
||||
docker compose -f docker/docker-compose.yml logs -f
|
||||
|
||||
# 停止
|
||||
docker compose -f docker/docker-compose.yml --profile launcher down
|
||||
|
||||
# 更新
|
||||
docker compose -f docker/docker-compose.yml pull
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — 首次启动安全警告</b></summary>
|
||||
|
||||
macOS 可能会在首次启动时拦截 `picoclaw-launcher`,因为它从互联网下载,未经 Mac App Store 公证。
|
||||
|
||||
**第一步:** 双击 `picoclaw-launcher`,会出现安全警告:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="macOS Gatekeeper 警告" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" 无法打开 — Apple 无法验证 "picoclaw-launcher" 不含可能损害 Mac 或危及隐私的恶意软件。*
|
||||
|
||||
**第二步:** 打开**系统设置** → **隐私与安全性** → 向下滚动找到**安全性**部分 → 点击**仍要打开** → 在弹窗中再次点击**打开**。
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS 隐私与安全性 — 仍要打开" width="600">
|
||||
</p>
|
||||
|
||||
完成这一次操作后,后续启动 `picoclaw-launcher` 将不再弹出警告。
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher(推荐无头环境 / SSH)
|
||||
|
||||
TUI(终端 UI)Launcher 提供功能完整的终端配置与管理界面,适合服务器、树莓派等无显示器环境。
|
||||
|
||||
```bash
|
||||
picoclaw-launcher-tui
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-tui.jpg" alt="TUI Launcher" width="600">
|
||||
</p>
|
||||
|
||||
**开始使用:**
|
||||
|
||||
通过 TUI 菜单:**1)** 配置 Provider -> **2)** 配置 Channel -> **3)** 启动 Gateway -> **4)** 开始聊天!
|
||||
|
||||
详细 TUI 文档请参阅 [docs.picoclaw.io](https://docs.picoclaw.io)。
|
||||
|
||||
### 📱 Android
|
||||
|
||||
让你十年前的旧手机焕发新生!将它变成你的 AI 助手。
|
||||
|
||||
**方式一:Termux(现已可用)**
|
||||
|
||||
1. 安装 [Termux](https://github.com/termux/termux-app)(可从 [GitHub Releases](https://github.com/termux/termux-app/releases) 下载,或在 F-Droid / Google Play 中搜索)
|
||||
2. 执行以下命令:
|
||||
|
||||
```bash
|
||||
# 从 Release 页面下载最新版本
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard # chroot 提供标准 Linux 文件系统布局
|
||||
```
|
||||
|
||||
然后跟随下面的"Terminal Launcher"章节继续配置。
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**方式二:APK 安装(即将推出)**
|
||||
|
||||
内置 WebUI 的独立 Android APK 正在开发中,敬请期待!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher(适用于资源受限环境)</b></summary>
|
||||
|
||||
对于只有 `picoclaw` 核心二进制文件的极简环境(无 Launcher UI),可通过命令行和 JSON 配置文件完成所有配置。
|
||||
|
||||
**1. 初始化**
|
||||
|
||||
```bash
|
||||
picoclaw onboard
|
||||
```
|
||||
|
||||
此命令会创建 `~/.picoclaw/config.json` 和工作区目录。
|
||||
|
||||
**2. 配置** (`~/.picoclaw/config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-api-key"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> 完整配置模板请参阅仓库中的 `config/config.example.json`。
|
||||
|
||||
**3. 开始聊天**
|
||||
|
||||
```bash
|
||||
# 单次提问
|
||||
picoclaw agent -m "What is 2+2?"
|
||||
|
||||
# 交互式对话模式
|
||||
picoclaw agent
|
||||
|
||||
# 启动 Gateway 以接入聊天应用
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 🔌 Providers (LLM)
|
||||
|
||||
PicoClaw 通过 `model_list` 配置支持 30+ LLM Provider,使用 `协议/模型` 格式:
|
||||
|
||||
| Provider | 协议 | API Key | 备注 |
|
||||
|----------|------|---------|------|
|
||||
| [OpenAI](https://platform.openai.com/api-keys) | `openai/` | 必填 | GPT-5.4、GPT-4o、o3 等 |
|
||||
| [Anthropic](https://console.anthropic.com/settings/keys) | `anthropic/` | 必填 | Claude Opus 4.6、Sonnet 4.6 等 |
|
||||
| [Google Gemini](https://aistudio.google.com/apikey) | `gemini/` | 必填 | Gemini 3 Flash、2.5 Pro 等 |
|
||||
| [OpenRouter](https://openrouter.ai/keys) | `openrouter/` | 必填 | 200+ 模型,统一 API |
|
||||
| [智谱 (GLM)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | `zhipu/` | 必填 | GLM-4.7、GLM-5 等 |
|
||||
| [DeepSeek](https://platform.deepseek.com/api_keys) | `deepseek/` | 必填 | DeepSeek-V3、DeepSeek-R1 |
|
||||
| [火山引擎](https://console.volcengine.com) | `volcengine/` | 必填 | 豆包、Ark 系列模型 |
|
||||
| [Qwen](https://dashscope.console.aliyun.com/apiKey) | `qwen/` | 必填 | Qwen3、Qwen-Max 等 |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/` | 必填 | 快速推理(Llama、Mixtral) |
|
||||
| [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | 必填 | Kimi 系列模型 |
|
||||
| [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | 必填 | MiniMax 系列模型 |
|
||||
| [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | 必填 | Mistral Large、Codestral |
|
||||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | 必填 | NVIDIA 托管模型 |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | 必填 | 快速推理 |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | 必填 | 多种开源模型 |
|
||||
| [小米 MiMo](https://platform.xiaomimimo.com/) | `mimo/` | 必填 | MiMo 系列模型 |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | 无需 | 本地模型,自托管 |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | 无需 | 本地部署,兼容 OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | 视情况 | 100+ Provider 代理 |
|
||||
| [Azure OpenAI](https://portal.azure.com/) | `azure/` | 必填 | 企业级 Azure 部署 |
|
||||
| [GitHub Copilot](https://github.com/features/copilot) | `github-copilot/` | OAuth | 设备码登录 |
|
||||
| [Antigravity](https://console.cloud.google.com/) | `antigravity/` | OAuth | Google Cloud AI |
|
||||
|
||||
<details>
|
||||
<summary><b>本地部署(Ollama、vLLM 等)</b></summary>
|
||||
|
||||
**Ollama:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-llama",
|
||||
"model": "ollama/llama3.1:8b",
|
||||
"api_base": "http://localhost:11434/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**vLLM:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-vllm",
|
||||
"model": "vllm/your-model",
|
||||
"api_base": "http://localhost:8000/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
完整 Provider 配置详情请参阅 [Providers & Models](docs/zh/providers.md)。
|
||||
|
||||
</details>
|
||||
|
||||
## 💬 Channels(聊天应用)
|
||||
|
||||
通过 17+ 消息平台与你的 PicoClaw 对话:
|
||||
|
||||
| Channel | 配置难度 | 协议 | 文档 |
|
||||
|---------|----------|------|------|
|
||||
| **Telegram** | 简单(bot token) | 长轮询 | [指南](docs/channels/telegram/README.zh.md) |
|
||||
| **Discord** | 简单(bot token + intents) | WebSocket | [指南](docs/channels/discord/README.zh.md) |
|
||||
| **WhatsApp** | 简单(扫码或 bridge URL) | 原生 / Bridge | [指南](docs/zh/chat-apps.md#whatsapp) |
|
||||
| **微信 (Weixin)** | 简单(扫码登录) | iLink API | [指南](docs/zh/chat-apps.md#weixin) |
|
||||
| **QQ** | 简单(AppID + AppSecret) | WebSocket | [指南](docs/channels/qq/README.zh.md) |
|
||||
| **Slack** | 简单(bot + app token) | Socket Mode | [指南](docs/channels/slack/README.zh.md) |
|
||||
| **Matrix** | 中等(homeserver + token) | Sync API | [指南](docs/channels/matrix/README.zh.md) |
|
||||
| **钉钉** | 中等(client credentials) | Stream | [指南](docs/channels/dingtalk/README.zh.md) |
|
||||
| **飞书 / Lark** | 中等(App ID + Secret) | WebSocket/SDK | [指南](docs/channels/feishu/README.zh.md) |
|
||||
| **LINE** | 中等(credentials + webhook) | Webhook | [指南](docs/channels/line/README.zh.md) |
|
||||
| **企业微信** | 简单(扫码登录或手动配置) | WebSocket | [指南](docs/channels/wecom/README.zh.md) |
|
||||
| **IRC** | 中等(server + nick) | IRC 协议 | [指南](docs/zh/chat-apps.md#irc) |
|
||||
| **OneBot** | 中等(WebSocket URL) | OneBot v11 | [指南](docs/channels/onebot/README.zh.md) |
|
||||
| **MaixCam** | 简单(启用即可) | TCP socket | [指南](docs/channels/maixcam/README.zh.md) |
|
||||
| **Pico** | 简单(启用即可) | 原生协议 | 内置 |
|
||||
| **Pico Client** | 简单(WebSocket URL) | WebSocket | 内置 |
|
||||
|
||||
> 所有基于 Webhook 的 Channel 共用同一个 Gateway HTTP 服务器(`gateway.host`:`gateway.port`,默认 `127.0.0.1:18790`)。飞书使用 WebSocket/SDK 模式,不使用共享 HTTP 服务器。
|
||||
|
||||
> 日志详细程度通过 `gateway.log_level` 控制(默认:`warn`)。支持的值:`debug`、`info`、`warn`、`error`、`fatal`。也可通过 `PICOCLAW_LOG_LEVEL` 环境变量设置。详见[配置指南](docs/zh/configuration.md#gateway-日志等级)。
|
||||
|
||||
详细 Channel 配置说明请参阅 [聊天应用配置](docs/zh/chat-apps.md)。
|
||||
|
||||
## 🔧 Tools
|
||||
|
||||
### 🔍 网络搜索
|
||||
|
||||
PicoClaw 可以搜索网络以提供最新信息。在 `tools.web` 中配置:
|
||||
|
||||
| 搜索引擎 | API Key | 免费额度 | 链接 |
|
||||
|---------|---------|---------|------|
|
||||
| [百度搜索](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5) | 必填 | 1000 次/天 | AI 搜索,国内首选 |
|
||||
| [Tavily](https://tavily.com) | 必填 | 1000 次/月 | 专为 AI Agent 优化 |
|
||||
| [GLM Search](https://open.bigmodel.cn/) | 必填 | 视情况 | 智谱网络搜索 |
|
||||
| DuckDuckGo | 无需 | 无限制 | 内置备用(国内访问困难) |
|
||||
| [Perplexity](https://www.perplexity.ai) | 必填 | 付费 | AI 驱动搜索(国内访问困难) |
|
||||
| [Brave Search](https://brave.com/search/api) | 必填 | 2000 次/月 | 快速且注重隐私(国内访问困难) |
|
||||
| [SearXNG](https://github.com/searxng/searxng) | 无需 | 自托管 | 免费元搜索引擎 |
|
||||
|
||||
### ⚙️ 其他工具
|
||||
|
||||
PicoClaw 内置文件操作、代码执行、定时任务等工具。详情请参阅 [工具配置](docs/zh/tools_configuration.md)。
|
||||
|
||||
## 🎯 Skills
|
||||
|
||||
Skills 是扩展 Agent 能力的模块化插件,从工作区的 `SKILL.md` 文件加载。
|
||||
|
||||
**从 ClawHub 安装 Skills:**
|
||||
|
||||
```bash
|
||||
picoclaw skills search "web scraping"
|
||||
picoclaw skills install <skill-name>
|
||||
```
|
||||
|
||||
**配置 ClawHub token**(可选,用于提高速率限制):
|
||||
|
||||
在 `config.json` 中添加:
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"skills": {
|
||||
"registries": {
|
||||
"clawhub": {
|
||||
"auth_token": "your-clawhub-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
更多详情请参阅 [工具配置 - Skills](docs/zh/tools_configuration.md#skills-tool)。
|
||||
|
||||
## 🔗 MCP (Model Context Protocol)
|
||||
|
||||
PicoClaw 原生支持 [MCP](https://modelcontextprotocol.io/) — 连接任意 MCP 服务器,通过外部工具和数据源扩展 Agent 能力。
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"servers": {
|
||||
"filesystem": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
完整 MCP 配置(stdio、SSE、HTTP 传输、Tool Discovery)请参阅 [工具配置 - MCP](docs/zh/tools_configuration.md#mcp-tool)。
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> 加入 Agent 社交网络
|
||||
|
||||
|
|
@ -211,21 +546,23 @@ make install
|
|||
| 命令 | 说明 |
|
||||
| ------------------------- | ---------------------- |
|
||||
| `picoclaw onboard` | 初始化配置与工作区 |
|
||||
| `picoclaw auth weixin` | 扫码连接微信个人号 |
|
||||
| `picoclaw agent -m "..."` | 与 Agent 对话 |
|
||||
| `picoclaw agent` | 交互式对话模式 |
|
||||
| `picoclaw gateway` | 启动网关 |
|
||||
| `picoclaw status` | 查看状态 |
|
||||
| `picoclaw version` | 查看版本信息 |
|
||||
| `picoclaw model` | 查看或切换默认模型 |
|
||||
| `picoclaw cron list` | 列出所有定时任务 |
|
||||
| `picoclaw cron add ...` | 添加定时任务 |
|
||||
| `picoclaw cron disable` | 禁用定时任务 |
|
||||
| `picoclaw cron remove` | 删除定时任务 |
|
||||
| `picoclaw skills list` | 列出已安装技能 |
|
||||
| `picoclaw skills install` | 安装技能 |
|
||||
| `picoclaw skills list` | 列出已安装 Skills |
|
||||
| `picoclaw skills install` | 安装 Skill |
|
||||
| `picoclaw migrate` | 从旧版本迁移数据 |
|
||||
| `picoclaw auth login` | 认证提供商 |
|
||||
| `picoclaw auth login` | 认证 Provider |
|
||||
|
||||
### 定时任务 / 提醒
|
||||
### ⏰ 定时任务 / 提醒
|
||||
|
||||
PicoClaw 通过 `cron` 工具支持定时提醒和重复任务:
|
||||
|
||||
|
|
@ -233,11 +570,29 @@ PicoClaw 通过 `cron` 工具支持定时提醒和重复任务:
|
|||
* **重复任务**: "每2小时提醒我" → 每2小时触发
|
||||
* **Cron 表达式**: "每天上午9点提醒我" → 使用 cron 表达式
|
||||
|
||||
## 📚 文档
|
||||
|
||||
详细指南请参阅以下文档,README 仅涵盖快速入门。
|
||||
|
||||
| 主题 | 说明 |
|
||||
|------|------|
|
||||
| 🐳 [Docker 与快速开始](docs/zh/docker.md) | Docker Compose 配置、Launcher/Agent 模式、快速开始 |
|
||||
| 💬 [聊天应用配置](docs/zh/chat-apps.md) | 全部 17+ Channel 配置指南 |
|
||||
| ⚙️ [配置指南](docs/zh/configuration.md) | 环境变量、工作区布局、安全沙箱 |
|
||||
| 🔌 [提供商与模型配置](docs/zh/providers.md) | 30+ LLM Provider、模型路由、model_list 配置 |
|
||||
| 🔄 [异步任务与 Spawn](docs/zh/spawn-tasks.md) | 快速任务、长任务与 Spawn、异步子 Agent 编排 |
|
||||
| 🪝 [Hook 系统](docs/hooks/README.zh.md) | 事件驱动 Hook:观察者、拦截器、审批 Hook |
|
||||
| 🎯 [Steering](docs/steering.md) | 在工具调用间向运行中的 Agent 注入消息 |
|
||||
| 🔀 [SubTurn](docs/subturn.md) | 子 Agent 协调、并发控制、生命周期管理 |
|
||||
| 🐛 [疑难解答](docs/zh/troubleshooting.md) | 常见问题与解决方案 |
|
||||
| 🔧 [工具配置](docs/zh/tools_configuration.md) | 工具启用/禁用、执行策略、MCP、Skills |
|
||||
| 📋 [硬件兼容列表](docs/zh/hardware-compatibility.md) | 已测试板卡、最低要求 |
|
||||
|
||||
## 🤝 贡献与路线图
|
||||
|
||||
欢迎提交 PR!代码库刻意保持小巧和可读。🤗
|
||||
|
||||
查看完整的 [社区路线图](https://github.com/sipeed/picoclaw/blob/main/ROADMAP.md)。
|
||||
查看完整的 [社区路线图](https://github.com/sipeed/picoclaw/issues/988) 和 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
||||
|
||||
开发者群组正在组建中,入群门槛:至少合并过 1 个 PR。
|
||||
|
||||
|
|
@ -245,4 +600,10 @@ PicoClaw 通过 `cron` 工具支持定时提醒和重复任务:
|
|||
|
||||
Discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
|
||||
<img src="assets/wechat.png" alt="PicoClaw" width="512">
|
||||
WeChat:
|
||||
<img src="assets/wechat.png" alt="WeChat group QR code" width="512">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
BIN
assets/hardware-banner.jpg
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
assets/launcher-tui.jpg
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
assets/launcher-webui.jpg
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
assets/macos-gatekeeper-allow.jpg
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
assets/macos-gatekeeper-warning.jpg
Normal file
|
After Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 321 KiB |
BIN
assets/wecom-qr-binding.jpg
Normal file
|
After Width: | Height: | Size: 233 KiB |
69
cmd/picoclaw-launcher-tui/README.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Picoclaw Launcher TUI
|
||||
|
||||
This directory contains the terminal-based TUI launcher for `picoclaw`.
|
||||
It provides a lightweight, terminal-native user interface for managing, configuring, and interacting with the core `picoclaw` engine, without requiring a web browser or graphical environment.
|
||||
|
||||
## Architecture
|
||||
|
||||
The TUI launcher is implemented purely in Go with no external runtime dependencies:
|
||||
* **`main.go`**: Application entry point, handles initialization and main event loop
|
||||
* **`ui/`**: TUI interface components built on tview + tcell framework:
|
||||
- `home.go`: Main dashboard with navigation menu
|
||||
- `schemes.go`: AI model scheme management
|
||||
- `users.go`: User and API key management for model providers
|
||||
- `channels.go`: Communication channel (Telegram/Discord/WeChat etc.) configuration editor
|
||||
- `gateway.go`: PicoClaw gateway daemon lifecycle management (start/stop/status)
|
||||
- `app.go`: Core TUI application framework and navigation logic
|
||||
- `models.go`: Data structures and state management
|
||||
* **`config/`**: Configuration management layer, integrates with the core picoclaw configuration system
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* Go 1.25+
|
||||
* Terminal with 256-color support (most modern terminals are compatible)
|
||||
|
||||
### Development
|
||||
|
||||
Run the TUI launcher directly in development mode:
|
||||
|
||||
```bash
|
||||
# From project root
|
||||
go run ./cmd/picoclaw-launcher-tui
|
||||
|
||||
# Or from this directory
|
||||
go run .
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
Build the standalone TUI launcher binary:
|
||||
|
||||
```bash
|
||||
# From project root (recommended)
|
||||
make build-launcher-tui
|
||||
|
||||
# Output will be at:
|
||||
# build/picoclaw-launcher-tui-<platform>-<arch>
|
||||
# with symlink build/picoclaw-launcher-tui
|
||||
|
||||
# Or build directly from this directory
|
||||
go build -o picoclaw-launcher-tui .
|
||||
```
|
||||
|
||||
### Key Features
|
||||
|
||||
* 🖥️ Terminal-native interface - works over SSH, on headless servers, and in low-resource environments
|
||||
* ⚙️ AI model scheme and API key management
|
||||
* 📱 Communication channel configuration editor (Telegram/Discord/WeChat etc.)
|
||||
* 🔄 PicoClaw gateway daemon management (start/stop/status monitoring)
|
||||
* 💬 One-click launch of interactive AI chat session
|
||||
* 🎯 Keyboard-first design with intuitive shortcuts
|
||||
|
||||
### Other Commands
|
||||
|
||||
```bash
|
||||
# Run with custom config file path
|
||||
go run . /path/to/custom/config.json
|
||||
```
|
||||
236
cmd/picoclaw-launcher-tui/config/config.go
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
// License: MIT
|
||||
//
|
||||
// Copyright (c) 2026 PicoClaw contributors
|
||||
|
||||
// Package config provides types and I/O for ~/.picoclaw/tui.toml.
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/fileutil"
|
||||
)
|
||||
|
||||
// DefaultConfigPath returns the default path to the tui.toml config file.
|
||||
func DefaultConfigPath() string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
home = "."
|
||||
}
|
||||
return filepath.Join(home, ".picoclaw", "tui.toml")
|
||||
}
|
||||
|
||||
// TUIConfig is the top-level structure of ~/.picoclaw/tui.toml.
|
||||
type TUIConfig struct {
|
||||
Version string `toml:"version"`
|
||||
Model Model `toml:"model"`
|
||||
Provider Provider `toml:"provider"`
|
||||
}
|
||||
|
||||
type Model struct {
|
||||
Type string `toml:"type"` // "provider" (default) | "manual"
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
Schemes []Scheme `toml:"schemes"`
|
||||
Users []User `toml:"users"`
|
||||
Current ProviderCurrent `toml:"current"`
|
||||
}
|
||||
|
||||
type Scheme struct {
|
||||
Name string `toml:"name"` // unique key
|
||||
BaseURL string `toml:"baseURL"` // required
|
||||
Type string `toml:"type"` // "openai-compatible" (default) | "anthropic"
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string `toml:"name"`
|
||||
Scheme string `toml:"scheme"` // references Scheme.Name; (Name+Scheme) is unique
|
||||
Type string `toml:"type"` // "key" (default) | "OAuth"
|
||||
Key string `toml:"key"`
|
||||
}
|
||||
|
||||
type ProviderCurrent struct {
|
||||
Scheme string `toml:"scheme"` // references Scheme.Name
|
||||
User string `toml:"user"` // references User.Name where User.Scheme == Scheme
|
||||
Model string `toml:"model"` // from GET <baseURL>/models
|
||||
}
|
||||
|
||||
// DefaultConfig returns a minimal valid TUIConfig.
|
||||
func DefaultConfig() *TUIConfig {
|
||||
return &TUIConfig{
|
||||
Version: "1.0",
|
||||
Model: Model{Type: "provider"},
|
||||
Provider: Provider{
|
||||
Schemes: []Scheme{},
|
||||
Users: []User{},
|
||||
Current: ProviderCurrent{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Load reads the TUI config from path. Returns a default config if the file does not exist.
|
||||
func Load(path string) (*TUIConfig, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if os.IsNotExist(err) {
|
||||
return DefaultConfig(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read config file %q: %w", path, err)
|
||||
}
|
||||
|
||||
cfg := DefaultConfig()
|
||||
if _, err := toml.Decode(string(data), cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse config file %q: %w", path, err)
|
||||
}
|
||||
|
||||
applyDefaults(cfg)
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Save writes cfg to path atomically (safe for flash / SD storage).
|
||||
func Save(path string, cfg *TUIConfig) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
return fmt.Errorf("failed to create config directory: %w", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
enc := toml.NewEncoder(&buf)
|
||||
if err := enc.Encode(cfg); err != nil {
|
||||
return fmt.Errorf("failed to encode config: %w", err)
|
||||
}
|
||||
if err := fileutil.WriteFileAtomic(path, buf.Bytes(), 0o600); err != nil {
|
||||
return fmt.Errorf("failed to write config file %q: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyDefaults(cfg *TUIConfig) {
|
||||
if cfg.Version == "" {
|
||||
cfg.Version = "1.0"
|
||||
}
|
||||
if cfg.Model.Type == "" {
|
||||
cfg.Model.Type = "provider"
|
||||
}
|
||||
for i := range cfg.Provider.Schemes {
|
||||
if cfg.Provider.Schemes[i].Type == "" {
|
||||
cfg.Provider.Schemes[i].Type = "openai-compatible"
|
||||
}
|
||||
}
|
||||
for i := range cfg.Provider.Users {
|
||||
if cfg.Provider.Users[i].Type == "" {
|
||||
cfg.Provider.Users[i].Type = "key"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SchemeByName returns the first Scheme whose Name matches, or nil.
|
||||
func (p *Provider) SchemeByName(name string) *Scheme {
|
||||
for i := range p.Schemes {
|
||||
if p.Schemes[i].Name == name {
|
||||
return &p.Schemes[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UsersForScheme returns all users whose Scheme field matches schemeName.
|
||||
func (p *Provider) UsersForScheme(schemeName string) []User {
|
||||
var out []User
|
||||
for _, u := range p.Users {
|
||||
if u.Scheme == schemeName {
|
||||
out = append(out, u)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// SyncSelectedModelToMainConfig syncs the currently selected model to ~/.picoclaw/config.json
|
||||
// Adds/replaces a "tui-prefer" model entry and sets it as the default model.
|
||||
// Preserves all other existing fields in the config file unchanged.
|
||||
func SyncSelectedModelToMainConfig(scheme Scheme, user User, modelID string) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
home = "."
|
||||
}
|
||||
mainConfigPath := filepath.Join(home, ".picoclaw", "config.json")
|
||||
|
||||
var cfg map[string]any
|
||||
if data, readErr := os.ReadFile(mainConfigPath); readErr == nil {
|
||||
if unmarshalErr := json.Unmarshal(data, &cfg); unmarshalErr != nil {
|
||||
cfg = make(map[string]any)
|
||||
}
|
||||
} else {
|
||||
cfg = make(map[string]any)
|
||||
}
|
||||
|
||||
if _, ok := cfg["agents"]; !ok {
|
||||
cfg["agents"] = make(map[string]any)
|
||||
}
|
||||
agents, ok := cfg["agents"].(map[string]any)
|
||||
if ok {
|
||||
if _, ok := agents["defaults"]; !ok {
|
||||
agents["defaults"] = make(map[string]any)
|
||||
}
|
||||
defaults, ok := agents["defaults"].(map[string]any)
|
||||
if ok {
|
||||
defaults["model"] = "tui-prefer"
|
||||
}
|
||||
}
|
||||
|
||||
tuiModel := map[string]any{
|
||||
"model_name": "tui-prefer",
|
||||
"model": modelID,
|
||||
"api_key": user.Key,
|
||||
"api_base": scheme.BaseURL,
|
||||
}
|
||||
|
||||
modelList := []any{}
|
||||
if ml, ok := cfg["model_list"].([]any); ok {
|
||||
modelList = ml
|
||||
}
|
||||
|
||||
found := false
|
||||
for i, m := range modelList {
|
||||
if entry, ok := m.(map[string]any); ok {
|
||||
if name, ok := entry["model_name"].(string); ok && name == "tui-prefer" {
|
||||
modelList[i] = tuiModel
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
modelList = append(modelList, tuiModel)
|
||||
}
|
||||
cfg["model_list"] = modelList
|
||||
|
||||
data, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(mainConfigPath), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(mainConfigPath, data, 0o600)
|
||||
}
|
||||
|
||||
func (cfg *TUIConfig) CurrentModelLabel() string {
|
||||
cur := cfg.Provider.Current
|
||||
if cur.Model == "" {
|
||||
return "(not configured)"
|
||||
}
|
||||
label := cur.Scheme
|
||||
if label != "" {
|
||||
label += " / "
|
||||
}
|
||||
return label + cur.Model
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
package configstore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
picoclawconfig "github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
const (
|
||||
configDirName = ".picoclaw"
|
||||
configFileName = "config.json"
|
||||
)
|
||||
|
||||
func ConfigPath() (string, error) {
|
||||
dir, err := ConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(dir, configFileName), nil
|
||||
}
|
||||
|
||||
func ConfigDir() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, configDirName), nil
|
||||
}
|
||||
|
||||
func Load() (*picoclawconfig.Config, error) {
|
||||
path, err := ConfigPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return picoclawconfig.LoadConfig(path)
|
||||
}
|
||||
|
||||
func Save(cfg *picoclawconfig.Config) error {
|
||||
if cfg == nil {
|
||||
return errors.New("config is nil")
|
||||
}
|
||||
path, err := ConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return picoclawconfig.SaveConfig(path, cfg)
|
||||
}
|
||||
|
|
@ -1,522 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
configstore "github.com/sipeed/picoclaw/cmd/picoclaw-launcher-tui/internal/config"
|
||||
picoclawconfig "github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
type appState struct {
|
||||
app *tview.Application
|
||||
pages *tview.Pages
|
||||
stack []string
|
||||
config *picoclawconfig.Config
|
||||
configPath string
|
||||
gatewayCmd *exec.Cmd
|
||||
menus map[string]*Menu
|
||||
original []byte
|
||||
hasOriginal bool
|
||||
backupPath string
|
||||
dirty bool
|
||||
logPath string
|
||||
}
|
||||
|
||||
func Run() error {
|
||||
applyStyles()
|
||||
cfg, err := configstore.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path, err := configstore.ConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg == nil {
|
||||
cfg = picoclawconfig.DefaultConfig()
|
||||
}
|
||||
|
||||
originalData, hasOriginal := loadOriginalConfig(path)
|
||||
backupPath := path + ".bak"
|
||||
if hasOriginal {
|
||||
_ = writeBackupConfig(backupPath, originalData)
|
||||
}
|
||||
|
||||
logPath := filepath.Join(filepath.Dir(path), "gateway.log")
|
||||
state := &appState{
|
||||
app: tview.NewApplication(),
|
||||
pages: tview.NewPages(),
|
||||
config: cfg,
|
||||
configPath: path,
|
||||
menus: map[string]*Menu{},
|
||||
original: originalData,
|
||||
hasOriginal: hasOriginal,
|
||||
backupPath: backupPath,
|
||||
logPath: logPath,
|
||||
}
|
||||
|
||||
state.push("main", state.mainMenu())
|
||||
|
||||
root := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
root.AddItem(bannerView(), 6, 0, false)
|
||||
root.AddItem(state.pages, 0, 1, true)
|
||||
root.AddItem(footerView(), 1, 0, false)
|
||||
|
||||
if err := state.app.SetRoot(root, true).EnableMouse(false).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *appState) push(name string, primitive tview.Primitive) {
|
||||
s.pages.AddPage(name, primitive, true, true)
|
||||
s.stack = append(s.stack, name)
|
||||
s.pages.SwitchToPage(name)
|
||||
if menu, ok := primitive.(*Menu); ok {
|
||||
s.menus[name] = menu
|
||||
}
|
||||
}
|
||||
|
||||
func (s *appState) pop() {
|
||||
if len(s.stack) == 0 {
|
||||
return
|
||||
}
|
||||
last := s.stack[len(s.stack)-1]
|
||||
s.pages.RemovePage(last)
|
||||
s.stack = s.stack[:len(s.stack)-1]
|
||||
if len(s.stack) == 0 {
|
||||
s.app.Stop()
|
||||
return
|
||||
}
|
||||
current := s.stack[len(s.stack)-1]
|
||||
s.pages.SwitchToPage(current)
|
||||
if menu, ok := s.menus[current]; ok {
|
||||
s.refreshMenu(current, menu)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *appState) mainMenu() tview.Primitive {
|
||||
menu := NewMenu("Menu", nil)
|
||||
refreshMainMenu(menu, s)
|
||||
menu.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Key() {
|
||||
case tcell.KeyEsc:
|
||||
s.requestExit()
|
||||
return nil
|
||||
}
|
||||
|
||||
return event
|
||||
})
|
||||
|
||||
return menu
|
||||
}
|
||||
|
||||
func (s *appState) refreshMenu(name string, menu *Menu) {
|
||||
switch name {
|
||||
case "main":
|
||||
refreshMainMenu(menu, s)
|
||||
case "model":
|
||||
refreshModelMenuFromState(menu, s)
|
||||
case "channel":
|
||||
refreshChannelMenuFromState(menu, s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *appState) countChannels() (enabled int, total int) {
|
||||
c := s.config.Channels
|
||||
entries := []bool{
|
||||
c.Telegram.Enabled,
|
||||
c.Discord.Enabled,
|
||||
c.QQ.Enabled,
|
||||
c.MaixCam.Enabled,
|
||||
c.WhatsApp.Enabled,
|
||||
c.Feishu.Enabled,
|
||||
c.DingTalk.Enabled,
|
||||
c.Slack.Enabled,
|
||||
c.Matrix.Enabled,
|
||||
c.LINE.Enabled,
|
||||
c.OneBot.Enabled,
|
||||
c.WeCom.Enabled,
|
||||
c.WeComApp.Enabled,
|
||||
}
|
||||
total = len(entries)
|
||||
for _, v := range entries {
|
||||
if v {
|
||||
enabled++
|
||||
}
|
||||
}
|
||||
return enabled, total
|
||||
}
|
||||
|
||||
func refreshMainMenuIfPresent(s *appState) {
|
||||
if menu, ok := s.menus["main"]; ok {
|
||||
refreshMainMenu(menu, s)
|
||||
}
|
||||
}
|
||||
|
||||
func refreshMainMenu(menu *Menu, s *appState) {
|
||||
selectedModel := s.selectedModelName()
|
||||
modelReady := selectedModel != ""
|
||||
channelReady := s.hasEnabledChannel()
|
||||
enabledCount, totalChannels := s.countChannels()
|
||||
gatewayRunning := s.gatewayCmd != nil || s.isGatewayRunning()
|
||||
|
||||
gatewayLabel := "Start Gateway"
|
||||
gatewayDescription := "Launch gateway for channels"
|
||||
if gatewayRunning {
|
||||
gatewayLabel = "Stop Gateway"
|
||||
gatewayDescription = "Gateway running"
|
||||
}
|
||||
|
||||
items := []MenuItem{
|
||||
{
|
||||
Label: rootModelLabel(selectedModel),
|
||||
Description: rootModelDescription(),
|
||||
Action: func() {
|
||||
s.push("model", s.modelMenu())
|
||||
},
|
||||
MainColor: func() *tcell.Color {
|
||||
if modelReady {
|
||||
return nil
|
||||
}
|
||||
color := tcell.ColorGray
|
||||
return &color
|
||||
}(),
|
||||
},
|
||||
{
|
||||
Label: rootChannelLabel(channelReady),
|
||||
Description: fmt.Sprintf("%d/%d enabled", enabledCount, totalChannels),
|
||||
Action: func() {
|
||||
s.push("channel", s.channelMenu())
|
||||
},
|
||||
MainColor: func() *tcell.Color {
|
||||
if channelReady {
|
||||
return nil
|
||||
}
|
||||
color := tcell.ColorGray
|
||||
return &color
|
||||
}(),
|
||||
},
|
||||
{
|
||||
Label: "Start Talk",
|
||||
Description: "Open picoclaw agent in terminal",
|
||||
Action: func() {
|
||||
s.requestStartTalk()
|
||||
},
|
||||
Disabled: !modelReady,
|
||||
},
|
||||
{
|
||||
Label: gatewayLabel,
|
||||
Description: gatewayDescription,
|
||||
Action: func() {
|
||||
if gatewayRunning {
|
||||
s.stopGateway()
|
||||
} else {
|
||||
s.requestStartGateway()
|
||||
}
|
||||
refreshMainMenu(menu, s)
|
||||
},
|
||||
Disabled: !gatewayRunning && (!modelReady || !channelReady),
|
||||
},
|
||||
{
|
||||
Label: "View Gateway Log",
|
||||
Description: "Open gateway.log",
|
||||
Action: func() {
|
||||
s.viewGatewayLog()
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Exit",
|
||||
Description: "Exit the TUI",
|
||||
Action: func() {
|
||||
s.requestExit()
|
||||
},
|
||||
},
|
||||
}
|
||||
menu.applyItems(items)
|
||||
}
|
||||
|
||||
func (s *appState) applyChangesValidated() bool {
|
||||
if err := s.config.ValidateModelList(); err != nil {
|
||||
s.showMessage("Validation failed", err.Error())
|
||||
return false
|
||||
}
|
||||
if err := s.validateAgentModel(); err != nil {
|
||||
s.showMessage("Validation failed", err.Error())
|
||||
return false
|
||||
}
|
||||
if err := configstore.Save(s.config); err != nil {
|
||||
s.showMessage("Save failed", err.Error())
|
||||
return false
|
||||
}
|
||||
if data, err := os.ReadFile(s.configPath); err == nil {
|
||||
s.original = data
|
||||
s.hasOriginal = true
|
||||
_ = writeBackupConfig(s.backupPath, data)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *appState) requestExit() {
|
||||
if s.dirty {
|
||||
s.confirmApplyOrDiscard(func() {
|
||||
s.app.Stop()
|
||||
}, func() {
|
||||
s.discardChanges()
|
||||
s.app.Stop()
|
||||
})
|
||||
return
|
||||
}
|
||||
s.app.Stop()
|
||||
}
|
||||
|
||||
func (s *appState) requestStartTalk() {
|
||||
if s.dirty {
|
||||
s.confirmApplyOrDiscard(func() {
|
||||
s.startTalk()
|
||||
}, func() {
|
||||
s.startTalk()
|
||||
})
|
||||
return
|
||||
}
|
||||
s.startTalk()
|
||||
}
|
||||
|
||||
func (s *appState) requestStartGateway() {
|
||||
if s.dirty {
|
||||
s.confirmApplyOrDiscard(func() {
|
||||
s.startGateway()
|
||||
}, func() {
|
||||
s.startGateway()
|
||||
})
|
||||
return
|
||||
}
|
||||
s.startGateway()
|
||||
}
|
||||
|
||||
func (s *appState) viewGatewayLog() {
|
||||
data, err := os.ReadFile(s.logPath)
|
||||
if err != nil {
|
||||
s.showMessage("Log not found", "gateway.log not found")
|
||||
return
|
||||
}
|
||||
text := tview.NewTextView()
|
||||
text.SetBorder(true).SetTitle("Gateway Log")
|
||||
text.SetText(string(data))
|
||||
text.SetDoneFunc(func(key tcell.Key) {
|
||||
s.pages.RemovePage("log")
|
||||
})
|
||||
text.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEsc {
|
||||
s.pages.RemovePage("log")
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
s.pages.AddPage("log", text, true, true)
|
||||
}
|
||||
|
||||
func (s *appState) selectedModelName() string {
|
||||
modelName := strings.TrimSpace(s.config.Agents.Defaults.Model)
|
||||
if modelName == "" {
|
||||
return ""
|
||||
}
|
||||
if !s.isActiveModelValid() {
|
||||
return ""
|
||||
}
|
||||
return modelName
|
||||
}
|
||||
|
||||
func rootModelLabel(selected string) string {
|
||||
if selected == "" {
|
||||
return "Model (None)"
|
||||
}
|
||||
return "Model (" + selected + ")"
|
||||
}
|
||||
|
||||
func rootModelDescription() string {
|
||||
return "Using SPACE to choose your model"
|
||||
}
|
||||
|
||||
func rootChannelLabel(valid bool) string {
|
||||
if !valid {
|
||||
return "Channel (no channel enabled)"
|
||||
}
|
||||
return "Channel"
|
||||
}
|
||||
|
||||
func (s *appState) startTalk() {
|
||||
if !s.isActiveModelValid() {
|
||||
s.showMessage("Model required", "Select a valid model before starting talk")
|
||||
return
|
||||
}
|
||||
if !s.applyChangesValidated() {
|
||||
return
|
||||
}
|
||||
s.app.Suspend(func() {
|
||||
cmd := exec.Command("picoclaw", "agent")
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
_ = cmd.Run()
|
||||
})
|
||||
}
|
||||
|
||||
func (s *appState) startGateway() {
|
||||
if !s.isActiveModelValid() {
|
||||
s.showMessage("Model required", "Select a valid model before starting gateway")
|
||||
return
|
||||
}
|
||||
if !s.hasEnabledChannel() {
|
||||
s.showMessage("Channel required", "Enable at least one channel before starting gateway")
|
||||
return
|
||||
}
|
||||
if !s.applyChangesValidated() {
|
||||
return
|
||||
}
|
||||
_ = stopGatewayProcess()
|
||||
cmd := exec.Command("picoclaw", "gateway")
|
||||
logFile, err := os.OpenFile(s.logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||
if err != nil {
|
||||
s.showMessage("Gateway failed", err.Error())
|
||||
return
|
||||
}
|
||||
cmd.Stdout = logFile
|
||||
cmd.Stderr = logFile
|
||||
if err := cmd.Start(); err != nil {
|
||||
s.showMessage("Gateway failed", err.Error())
|
||||
_ = logFile.Close()
|
||||
return
|
||||
}
|
||||
_ = logFile.Close()
|
||||
s.gatewayCmd = cmd
|
||||
}
|
||||
|
||||
func (s *appState) stopGateway() {
|
||||
_ = stopGatewayProcess()
|
||||
if s.gatewayCmd != nil && s.gatewayCmd.Process != nil {
|
||||
_ = s.gatewayCmd.Process.Kill()
|
||||
}
|
||||
s.gatewayCmd = nil
|
||||
}
|
||||
|
||||
func (s *appState) isGatewayRunning() bool {
|
||||
return isGatewayProcessRunning()
|
||||
}
|
||||
|
||||
func (s *appState) validateAgentModel() error {
|
||||
modelName := strings.TrimSpace(s.config.Agents.Defaults.Model)
|
||||
if modelName == "" {
|
||||
return nil
|
||||
}
|
||||
_, err := s.config.GetModelConfig(modelName)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *appState) isActiveModelValid() bool {
|
||||
modelName := strings.TrimSpace(s.config.Agents.Defaults.Model)
|
||||
if modelName == "" {
|
||||
return false
|
||||
}
|
||||
cfg, err := s.config.GetModelConfig(modelName)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
hasKey := strings.TrimSpace(cfg.APIKey) != "" || strings.TrimSpace(cfg.AuthMethod) == "oauth"
|
||||
hasModel := strings.TrimSpace(cfg.Model) != ""
|
||||
return hasKey && hasModel
|
||||
}
|
||||
|
||||
func (s *appState) hasEnabledChannel() bool {
|
||||
c := s.config.Channels
|
||||
return c.Telegram.Enabled || c.Discord.Enabled || c.QQ.Enabled || c.MaixCam.Enabled ||
|
||||
c.WhatsApp.Enabled || c.Feishu.Enabled || c.DingTalk.Enabled || c.Slack.Enabled ||
|
||||
c.Matrix.Enabled || c.LINE.Enabled || c.OneBot.Enabled || c.WeCom.Enabled || c.WeComApp.Enabled
|
||||
}
|
||||
|
||||
func (s *appState) confirmApplyOrDiscard(onApply func(), onDiscard func()) {
|
||||
if s.pages.HasPage("apply") {
|
||||
return
|
||||
}
|
||||
modal := tview.NewModal().
|
||||
SetText("Apply changes or discard before continuing?").
|
||||
AddButtons([]string{"Cancel", "Discard", "Apply"}).
|
||||
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||
s.pages.RemovePage("apply")
|
||||
switch buttonLabel {
|
||||
case "Discard":
|
||||
s.discardChanges()
|
||||
if onDiscard != nil {
|
||||
onDiscard()
|
||||
}
|
||||
case "Apply":
|
||||
if s.applyChangesValidated() {
|
||||
s.dirty = false
|
||||
if onApply != nil {
|
||||
onApply()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
modal.SetBorder(true)
|
||||
s.pages.AddPage("apply", modal, true, true)
|
||||
}
|
||||
|
||||
func (s *appState) discardChanges() {
|
||||
if s.hasOriginal {
|
||||
_ = writeOriginalConfig(s.configPath, s.original)
|
||||
} else {
|
||||
_ = os.Remove(s.configPath)
|
||||
}
|
||||
_ = os.Remove(s.backupPath)
|
||||
if cfg, err := configstore.Load(); err == nil && cfg != nil {
|
||||
s.config = cfg
|
||||
}
|
||||
s.dirty = false
|
||||
refreshMainMenuIfPresent(s)
|
||||
}
|
||||
|
||||
func (s *appState) showMessage(title, message string) {
|
||||
if s.pages.HasPage("message") {
|
||||
return
|
||||
}
|
||||
modal := tview.NewModal().
|
||||
SetText(strings.TrimSpace(message)).
|
||||
AddButtons([]string{"OK"}).
|
||||
SetDoneFunc(func(_ int, _ string) {
|
||||
s.pages.RemovePage("message")
|
||||
})
|
||||
modal.SetTitle(title).SetBorder(true)
|
||||
modal.SetBackgroundColor(tview.Styles.ContrastBackgroundColor)
|
||||
modal.SetTextColor(tview.Styles.PrimaryTextColor)
|
||||
modal.SetButtonBackgroundColor(tcell.NewRGBColor(112, 102, 255))
|
||||
modal.SetButtonTextColor(tview.Styles.PrimaryTextColor)
|
||||
s.pages.AddPage("message", modal, true, true)
|
||||
}
|
||||
|
||||
func loadOriginalConfig(path string) ([]byte, bool) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, false
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
return data, true
|
||||
}
|
||||
|
||||
func writeOriginalConfig(path string, data []byte) error {
|
||||
return os.WriteFile(path, data, 0o600)
|
||||
}
|
||||
|
||||
func writeBackupConfig(path string, data []byte) error {
|
||||
return os.WriteFile(path, data, 0o600)
|
||||
}
|
||||
|
|
@ -1,433 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
picoclawconfig "github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
func (s *appState) buildChannelMenuItems() []MenuItem {
|
||||
return []MenuItem{
|
||||
channelItem(
|
||||
"Telegram",
|
||||
"Telegram bot settings",
|
||||
s.config.Channels.Telegram.Enabled,
|
||||
func() { s.push("channel-telegram", s.telegramForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"Discord",
|
||||
"Discord bot settings",
|
||||
s.config.Channels.Discord.Enabled,
|
||||
func() { s.push("channel-discord", s.discordForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"QQ",
|
||||
"QQ bot settings",
|
||||
s.config.Channels.QQ.Enabled,
|
||||
func() { s.push("channel-qq", s.qqForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"MaixCam",
|
||||
"MaixCam gateway",
|
||||
s.config.Channels.MaixCam.Enabled,
|
||||
func() { s.push("channel-maixcam", s.maixcamForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"WhatsApp",
|
||||
"WhatsApp bridge",
|
||||
s.config.Channels.WhatsApp.Enabled,
|
||||
func() { s.push("channel-whatsapp", s.whatsappForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"Feishu",
|
||||
"Feishu bot settings",
|
||||
s.config.Channels.Feishu.Enabled,
|
||||
func() { s.push("channel-feishu", s.feishuForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"DingTalk",
|
||||
"DingTalk bot settings",
|
||||
s.config.Channels.DingTalk.Enabled,
|
||||
func() { s.push("channel-dingtalk", s.dingtalkForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"Slack",
|
||||
"Slack bot settings",
|
||||
s.config.Channels.Slack.Enabled,
|
||||
func() { s.push("channel-slack", s.slackForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"Matrix",
|
||||
"Matrix bot settings",
|
||||
s.config.Channels.Matrix.Enabled,
|
||||
func() { s.push("channel-matrix", s.matrixForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"LINE",
|
||||
"LINE bot settings",
|
||||
s.config.Channels.LINE.Enabled,
|
||||
func() { s.push("channel-line", s.lineForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"OneBot",
|
||||
"OneBot settings",
|
||||
s.config.Channels.OneBot.Enabled,
|
||||
func() { s.push("channel-onebot", s.onebotForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"WeCom",
|
||||
"WeCom bot settings",
|
||||
s.config.Channels.WeCom.Enabled,
|
||||
func() { s.push("channel-wecom", s.wecomForm()) },
|
||||
),
|
||||
channelItem(
|
||||
"WeCom App",
|
||||
"WeCom App settings",
|
||||
s.config.Channels.WeComApp.Enabled,
|
||||
func() { s.push("channel-wecomapp", s.wecomAppForm()) },
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *appState) channelMenu() tview.Primitive {
|
||||
menu := NewMenu("Channels", s.buildChannelMenuItems())
|
||||
menu.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEsc {
|
||||
s.pop()
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
return menu
|
||||
}
|
||||
|
||||
func refreshChannelMenuFromState(menu *Menu, s *appState) {
|
||||
menu.applyItems(s.buildChannelMenuItems())
|
||||
}
|
||||
|
||||
func (s *appState) telegramForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.Telegram
|
||||
form := baseChannelForm("Telegram", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("Token", cfg.Token, 128, nil, func(text string) {
|
||||
cfg.Token = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Proxy", cfg.Proxy, 128, nil, func(text string) {
|
||||
cfg.Proxy = strings.TrimSpace(text)
|
||||
})
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) discordForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.Discord
|
||||
form := baseChannelForm("Discord", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("Token", cfg.Token, 128, nil, func(text string) {
|
||||
cfg.Token = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddCheckbox("Mention Only", cfg.MentionOnly, func(checked bool) {
|
||||
cfg.MentionOnly = checked
|
||||
})
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) qqForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.QQ
|
||||
form := baseChannelForm("QQ", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("App ID", cfg.AppID, 64, nil, func(text string) {
|
||||
cfg.AppID = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("App Secret", cfg.AppSecret, 128, nil, func(text string) {
|
||||
cfg.AppSecret = strings.TrimSpace(text)
|
||||
})
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) maixcamForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.MaixCam
|
||||
form := baseChannelForm("MaixCam", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("Host", cfg.Host, 64, nil, func(text string) {
|
||||
cfg.Host = strings.TrimSpace(text)
|
||||
})
|
||||
addIntField(form, "Port", cfg.Port, func(value int) { cfg.Port = value })
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) whatsappForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.WhatsApp
|
||||
form := baseChannelForm("WhatsApp", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("Bridge URL", cfg.BridgeURL, 128, nil, func(text string) {
|
||||
cfg.BridgeURL = strings.TrimSpace(text)
|
||||
})
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) feishuForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.Feishu
|
||||
form := baseChannelForm("Feishu", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("App ID", cfg.AppID, 64, nil, func(text string) {
|
||||
cfg.AppID = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("App Secret", cfg.AppSecret, 128, nil, func(text string) {
|
||||
cfg.AppSecret = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Encrypt Key", cfg.EncryptKey, 128, nil, func(text string) {
|
||||
cfg.EncryptKey = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Verification Token", cfg.VerificationToken, 128, nil, func(text string) {
|
||||
cfg.VerificationToken = strings.TrimSpace(text)
|
||||
})
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) dingtalkForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.DingTalk
|
||||
form := baseChannelForm("DingTalk", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("Client ID", cfg.ClientID, 64, nil, func(text string) {
|
||||
cfg.ClientID = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Client Secret", cfg.ClientSecret, 128, nil, func(text string) {
|
||||
cfg.ClientSecret = strings.TrimSpace(text)
|
||||
})
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) slackForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.Slack
|
||||
form := baseChannelForm("Slack", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("Bot Token", cfg.BotToken, 128, nil, func(text string) {
|
||||
cfg.BotToken = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("App Token", cfg.AppToken, 128, nil, func(text string) {
|
||||
cfg.AppToken = strings.TrimSpace(text)
|
||||
})
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) lineForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.LINE
|
||||
form := baseChannelForm("LINE", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("Channel Secret", cfg.ChannelSecret, 128, nil, func(text string) {
|
||||
cfg.ChannelSecret = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Channel Access Token", cfg.ChannelAccessToken, 128, nil, func(text string) {
|
||||
cfg.ChannelAccessToken = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Webhook Host", cfg.WebhookHost, 64, nil, func(text string) {
|
||||
cfg.WebhookHost = strings.TrimSpace(text)
|
||||
})
|
||||
addIntField(form, "Webhook Port", cfg.WebhookPort, func(value int) { cfg.WebhookPort = value })
|
||||
form.AddInputField("Webhook Path", cfg.WebhookPath, 64, nil, func(text string) {
|
||||
cfg.WebhookPath = strings.TrimSpace(text)
|
||||
})
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) matrixForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.Matrix
|
||||
form := baseChannelForm("Matrix", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("Homeserver", cfg.Homeserver, 128, nil, func(text string) {
|
||||
cfg.Homeserver = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("User ID", cfg.UserID, 128, nil, func(text string) {
|
||||
cfg.UserID = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Access Token", cfg.AccessToken, 128, nil, func(text string) {
|
||||
cfg.AccessToken = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Device ID", cfg.DeviceID, 128, nil, func(text string) {
|
||||
cfg.DeviceID = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddCheckbox("Join On Invite", cfg.JoinOnInvite, func(checked bool) {
|
||||
cfg.JoinOnInvite = checked
|
||||
})
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) onebotForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.OneBot
|
||||
form := baseChannelForm("OneBot", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("WS URL", cfg.WSUrl, 128, nil, func(text string) {
|
||||
cfg.WSUrl = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Access Token", cfg.AccessToken, 128, nil, func(text string) {
|
||||
cfg.AccessToken = strings.TrimSpace(text)
|
||||
})
|
||||
addIntField(
|
||||
form,
|
||||
"Reconnect Interval",
|
||||
cfg.ReconnectInterval,
|
||||
func(value int) { cfg.ReconnectInterval = value },
|
||||
)
|
||||
form.AddInputField(
|
||||
"Group Trigger Prefix",
|
||||
strings.Join(cfg.GroupTriggerPrefix, ","),
|
||||
128,
|
||||
nil,
|
||||
func(text string) {
|
||||
cfg.GroupTriggerPrefix = splitCSV(text)
|
||||
},
|
||||
)
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) wecomForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.WeCom
|
||||
form := baseChannelForm("WeCom", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("Token", cfg.Token, 128, nil, func(text string) {
|
||||
cfg.Token = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Encoding AES Key", cfg.EncodingAESKey, 128, nil, func(text string) {
|
||||
cfg.EncodingAESKey = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Webhook URL", cfg.WebhookURL, 128, nil, func(text string) {
|
||||
cfg.WebhookURL = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Webhook Host", cfg.WebhookHost, 64, nil, func(text string) {
|
||||
cfg.WebhookHost = strings.TrimSpace(text)
|
||||
})
|
||||
addIntField(form, "Webhook Port", cfg.WebhookPort, func(value int) { cfg.WebhookPort = value })
|
||||
form.AddInputField("Webhook Path", cfg.WebhookPath, 64, nil, func(text string) {
|
||||
cfg.WebhookPath = strings.TrimSpace(text)
|
||||
})
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
addIntField(
|
||||
form,
|
||||
"Reply Timeout",
|
||||
cfg.ReplyTimeout,
|
||||
func(value int) { cfg.ReplyTimeout = value },
|
||||
)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) wecomAppForm() tview.Primitive {
|
||||
cfg := &s.config.Channels.WeComApp
|
||||
form := baseChannelForm("WeCom App", cfg.Enabled, s.makeChannelOnEnabled(&cfg.Enabled))
|
||||
form.AddInputField("Corp ID", cfg.CorpID, 64, nil, func(text string) {
|
||||
cfg.CorpID = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Corp Secret", cfg.CorpSecret, 128, nil, func(text string) {
|
||||
cfg.CorpSecret = strings.TrimSpace(text)
|
||||
})
|
||||
addInt64Field(form, "Agent ID", cfg.AgentID, func(value int64) { cfg.AgentID = value })
|
||||
form.AddInputField("Token", cfg.Token, 128, nil, func(text string) {
|
||||
cfg.Token = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Encoding AES Key", cfg.EncodingAESKey, 128, nil, func(text string) {
|
||||
cfg.EncodingAESKey = strings.TrimSpace(text)
|
||||
})
|
||||
form.AddInputField("Webhook Host", cfg.WebhookHost, 64, nil, func(text string) {
|
||||
cfg.WebhookHost = strings.TrimSpace(text)
|
||||
})
|
||||
addIntField(form, "Webhook Port", cfg.WebhookPort, func(value int) { cfg.WebhookPort = value })
|
||||
form.AddInputField("Webhook Path", cfg.WebhookPath, 64, nil, func(text string) {
|
||||
cfg.WebhookPath = strings.TrimSpace(text)
|
||||
})
|
||||
addAllowFromField(form, &cfg.AllowFrom)
|
||||
addIntField(
|
||||
form,
|
||||
"Reply Timeout",
|
||||
cfg.ReplyTimeout,
|
||||
func(value int) { cfg.ReplyTimeout = value },
|
||||
)
|
||||
return wrapWithBack(form, s)
|
||||
}
|
||||
|
||||
func (s *appState) makeChannelOnEnabled(enabledPtr *bool) func(bool) {
|
||||
return func(v bool) {
|
||||
*enabledPtr = v
|
||||
s.dirty = true
|
||||
refreshMainMenuIfPresent(s)
|
||||
if menu, ok := s.menus["channel"]; ok {
|
||||
refreshChannelMenuFromState(menu, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addAllowFromField(form *tview.Form, allowFrom *picoclawconfig.FlexibleStringSlice) {
|
||||
form.AddInputField("Allow From", strings.Join(*allowFrom, ","), 128, nil, func(text string) {
|
||||
*allowFrom = splitCSV(text)
|
||||
})
|
||||
}
|
||||
|
||||
func baseChannelForm(title string, enabled bool, onEnabled func(bool)) *tview.Form {
|
||||
form := tview.NewForm()
|
||||
form.SetBorder(true).SetTitle(fmt.Sprintf("Channel: %s", title))
|
||||
form.SetButtonBackgroundColor(tcell.NewRGBColor(80, 250, 123))
|
||||
form.SetButtonTextColor(tcell.NewRGBColor(12, 13, 22))
|
||||
form.AddCheckbox("Enabled", enabled, func(checked bool) {
|
||||
onEnabled(checked)
|
||||
})
|
||||
return form
|
||||
}
|
||||
|
||||
func wrapWithBack(form *tview.Form, s *appState) tview.Primitive {
|
||||
form.AddButton("Back", func() {
|
||||
s.pop()
|
||||
})
|
||||
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEsc {
|
||||
s.pop()
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
return form
|
||||
}
|
||||
|
||||
func splitCSV(input string) picoclawconfig.FlexibleStringSlice {
|
||||
parts := strings.Split(strings.TrimSpace(input), ",")
|
||||
cleaned := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
value := strings.TrimSpace(part)
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
cleaned = append(cleaned, value)
|
||||
}
|
||||
return cleaned
|
||||
}
|
||||
|
||||
func addIntField(form *tview.Form, label string, value int, onChange func(int)) {
|
||||
form.AddInputField(label, fmt.Sprintf("%d", value), 16, nil, func(text string) {
|
||||
var parsed int
|
||||
if _, err := fmt.Sscanf(strings.TrimSpace(text), "%d", &parsed); err == nil {
|
||||
onChange(parsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func addInt64Field(form *tview.Form, label string, value int64, onChange func(int64)) {
|
||||
form.AddInputField(label, fmt.Sprintf("%d", value), 16, nil, func(text string) {
|
||||
var parsed int64
|
||||
if _, err := fmt.Sscanf(strings.TrimSpace(text), "%d", &parsed); err == nil {
|
||||
onChange(parsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func channelItem(label, description string, enabled bool, action MenuAction) MenuItem {
|
||||
item := MenuItem{
|
||||
Label: label,
|
||||
Description: description,
|
||||
Action: action,
|
||||
}
|
||||
if !enabled {
|
||||
color := tcell.ColorGray
|
||||
item.MainColor = &color
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package ui
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func isGatewayProcessRunning() bool {
|
||||
cmd := exec.Command("sh", "-c", "pgrep -f 'picoclaw\\s+gateway' >/dev/null 2>&1")
|
||||
return cmd.Run() == nil
|
||||
}
|
||||
|
||||
func stopGatewayProcess() error {
|
||||
cmd := exec.Command("sh", "-c", "pkill -f 'picoclaw\\s+gateway' >/dev/null 2>&1")
|
||||
return cmd.Run()
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package ui
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func isGatewayProcessRunning() bool {
|
||||
cmd := exec.Command("tasklist", "/FI", "IMAGENAME eq picoclaw.exe")
|
||||
return cmd.Run() == nil
|
||||
}
|
||||
|
||||
func stopGatewayProcess() error {
|
||||
cmd := exec.Command("taskkill", "/F", "/IM", "picoclaw.exe")
|
||||
return cmd.Run()
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type MenuAction func()
|
||||
|
||||
type MenuItem struct {
|
||||
Label string
|
||||
Description string
|
||||
Action MenuAction
|
||||
Disabled bool
|
||||
MainColor *tcell.Color
|
||||
DescColor *tcell.Color
|
||||
}
|
||||
|
||||
type Menu struct {
|
||||
*tview.Table
|
||||
items []MenuItem
|
||||
}
|
||||
|
||||
func NewMenu(title string, items []MenuItem) *Menu {
|
||||
table := tview.NewTable().SetSelectable(true, false)
|
||||
table.SetBorder(true).SetTitle(title)
|
||||
table.SetBorders(false)
|
||||
menu := &Menu{Table: table, items: items}
|
||||
menu.applyItems(items)
|
||||
menu.SetSelectedFunc(func(row, _ int) {
|
||||
if row < 0 || row >= len(menu.items) {
|
||||
return
|
||||
}
|
||||
item := menu.items[row]
|
||||
if item.Disabled || item.Action == nil {
|
||||
return
|
||||
}
|
||||
item.Action()
|
||||
})
|
||||
menu.SetSelectedStyle(
|
||||
tcell.StyleDefault.Foreground(tview.Styles.InverseTextColor).
|
||||
Background(tcell.NewRGBColor(189, 147, 249)),
|
||||
)
|
||||
return menu
|
||||
}
|
||||
|
||||
func (m *Menu) applyItems(items []MenuItem) {
|
||||
m.items = items
|
||||
m.Clear()
|
||||
for row, item := range items {
|
||||
label := item.Label
|
||||
if item.Disabled && label != "" {
|
||||
label = label + " (disabled)"
|
||||
}
|
||||
left := tview.NewTableCell(label)
|
||||
right := tview.NewTableCell(item.Description).SetAlign(tview.AlignRight)
|
||||
if item.MainColor != nil {
|
||||
left.SetTextColor(*item.MainColor)
|
||||
}
|
||||
if item.DescColor != nil {
|
||||
right.SetTextColor(*item.DescColor)
|
||||
} else {
|
||||
right.SetTextColor(tview.Styles.TertiaryTextColor)
|
||||
}
|
||||
if item.Disabled {
|
||||
left.SetTextColor(tcell.ColorGray)
|
||||
right.SetTextColor(tcell.ColorGray)
|
||||
}
|
||||
m.SetCell(row, 0, left)
|
||||
m.SetCell(row, 1, right)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,399 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
picoclawconfig "github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
func (s *appState) modelMenu() tview.Primitive {
|
||||
items := make([]MenuItem, 0, 1+len(s.config.ModelList))
|
||||
currentModel := strings.TrimSpace(s.config.Agents.Defaults.Model)
|
||||
for i := range s.config.ModelList {
|
||||
index := i
|
||||
model := s.config.ModelList[i]
|
||||
isValid := isModelValid(model)
|
||||
desc := model.APIBase
|
||||
if desc == "" {
|
||||
desc = model.AuthMethod
|
||||
}
|
||||
if desc == "" {
|
||||
desc = "api_key required"
|
||||
}
|
||||
label := fmt.Sprintf("%s (%s)", model.ModelName, model.Model)
|
||||
if model.ModelName == currentModel && currentModel != "" {
|
||||
label = "* " + label
|
||||
}
|
||||
isSelected := model.ModelName == currentModel && currentModel != ""
|
||||
items = append(items, MenuItem{
|
||||
Label: label,
|
||||
Description: desc,
|
||||
MainColor: modelStatusColor(isValid, isSelected),
|
||||
Action: func() {
|
||||
s.push(fmt.Sprintf("model-%d", index), s.modelForm(index))
|
||||
},
|
||||
})
|
||||
}
|
||||
// Add model entry appended at the end so the models map to rows 1..N
|
||||
items = append(items,
|
||||
MenuItem{
|
||||
Label: "**Add model**",
|
||||
Description: "Append a new model entry",
|
||||
Action: func() {
|
||||
newName := s.nextAvailableModelName("new-model")
|
||||
s.addModel(
|
||||
picoclawconfig.ModelConfig{ModelName: newName, Model: "openai/gpt-5.4"},
|
||||
)
|
||||
s.push(
|
||||
fmt.Sprintf("model-%d", len(s.config.ModelList)-1),
|
||||
s.modelForm(len(s.config.ModelList)-1),
|
||||
)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
menu := NewMenu("Models", items)
|
||||
menu.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEsc {
|
||||
s.pop()
|
||||
return nil
|
||||
}
|
||||
|
||||
if event.Rune() == ' ' {
|
||||
row, _ := menu.GetSelection()
|
||||
if row >= 0 && row < len(s.config.ModelList) {
|
||||
model := s.config.ModelList[row]
|
||||
if !isModelValid(model) {
|
||||
s.showMessage(
|
||||
"Invalid model",
|
||||
"Select a model with api_key or oauth auth_method",
|
||||
)
|
||||
return nil
|
||||
}
|
||||
s.config.Agents.Defaults.Model = model.ModelName
|
||||
s.dirty = true
|
||||
refreshModelMenu(menu, s.config.Agents.Defaults.Model, s.config.ModelList)
|
||||
refreshMainMenuIfPresent(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
return menu
|
||||
}
|
||||
|
||||
func (s *appState) modelForm(index int) tview.Primitive {
|
||||
model := &s.config.ModelList[index]
|
||||
form := tview.NewForm()
|
||||
form.SetBorder(true).SetTitle(fmt.Sprintf("Model: %s", model.ModelName))
|
||||
|
||||
addInput(form, "Model Name", model.ModelName, func(value string) {
|
||||
if value == "" {
|
||||
s.showMessage("Invalid model name", "Model Name cannot be empty")
|
||||
return
|
||||
}
|
||||
if s.modelNameExists(value, index) {
|
||||
s.showMessage("Duplicate model name", fmt.Sprintf("Model Name '%s' already exists", value))
|
||||
return
|
||||
}
|
||||
oldName := model.ModelName
|
||||
model.ModelName = value
|
||||
if s.config.Agents.Defaults.Model == oldName {
|
||||
s.config.Agents.Defaults.Model = value
|
||||
}
|
||||
s.dirty = true
|
||||
form.SetTitle(fmt.Sprintf("Model: %s", model.ModelName))
|
||||
refreshMainMenuIfPresent(s)
|
||||
if menu, ok := s.menus["model"]; ok {
|
||||
refreshModelMenuFromState(menu, s)
|
||||
}
|
||||
})
|
||||
addInput(form, "Model", model.Model, func(value string) {
|
||||
model.Model = value
|
||||
s.dirty = true
|
||||
refreshMainMenuIfPresent(s)
|
||||
if menu, ok := s.menus["model"]; ok {
|
||||
refreshModelMenuFromState(menu, s)
|
||||
}
|
||||
})
|
||||
addInput(form, "API Base", model.APIBase, func(value string) {
|
||||
model.APIBase = value
|
||||
s.dirty = true
|
||||
refreshMainMenuIfPresent(s)
|
||||
if menu, ok := s.menus["model"]; ok {
|
||||
refreshModelMenuFromState(menu, s)
|
||||
}
|
||||
})
|
||||
addInput(form, "API Key", model.APIKey, func(value string) {
|
||||
model.APIKey = value
|
||||
s.dirty = true
|
||||
refreshMainMenuIfPresent(s)
|
||||
if menu, ok := s.menus["model"]; ok {
|
||||
refreshModelMenuFromState(menu, s)
|
||||
}
|
||||
})
|
||||
addInput(form, "Proxy", model.Proxy, func(value string) {
|
||||
model.Proxy = value
|
||||
})
|
||||
addInput(form, "Auth Method", model.AuthMethod, func(value string) {
|
||||
model.AuthMethod = value
|
||||
s.dirty = true
|
||||
refreshMainMenuIfPresent(s)
|
||||
if menu, ok := s.menus["model"]; ok {
|
||||
refreshModelMenuFromState(menu, s)
|
||||
}
|
||||
})
|
||||
addInput(form, "Connect Mode", model.ConnectMode, func(value string) {
|
||||
model.ConnectMode = value
|
||||
})
|
||||
addInput(form, "Workspace", model.Workspace, func(value string) {
|
||||
model.Workspace = value
|
||||
})
|
||||
addInput(form, "Max Tokens Field", model.MaxTokensField, func(value string) {
|
||||
model.MaxTokensField = value
|
||||
})
|
||||
addIntInput(form, "RPM", model.RPM, func(value int) {
|
||||
model.RPM = value
|
||||
})
|
||||
addIntInput(form, "Request Timeout", model.RequestTimeout, func(value int) {
|
||||
model.RequestTimeout = value
|
||||
})
|
||||
|
||||
form.AddButton("Delete", func() {
|
||||
pageName := "confirm-delete-model"
|
||||
if s.pages.HasPage(pageName) {
|
||||
return
|
||||
}
|
||||
modal := tview.NewModal().
|
||||
SetText("Are you sure you want to delete this model?").
|
||||
AddButtons([]string{"Cancel", "Delete"}).
|
||||
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||
s.pages.RemovePage(pageName)
|
||||
if buttonLabel == "Delete" {
|
||||
s.deleteModel(index)
|
||||
}
|
||||
})
|
||||
modal.SetTitle("Confirm Delete").SetBorder(true)
|
||||
s.pages.AddPage(pageName, modal, true, true)
|
||||
})
|
||||
form.AddButton("Test", func() {
|
||||
s.testModel(model)
|
||||
})
|
||||
form.AddButton("Back", func() {
|
||||
s.pop()
|
||||
})
|
||||
|
||||
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEsc {
|
||||
s.pop()
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
return form
|
||||
}
|
||||
|
||||
func addInput(form *tview.Form, label, value string, onChange func(string)) {
|
||||
form.AddInputField(label, value, 128, nil, func(text string) {
|
||||
onChange(strings.TrimSpace(text))
|
||||
})
|
||||
}
|
||||
|
||||
func addIntInput(form *tview.Form, label string, value int, onChange func(int)) {
|
||||
form.AddInputField(label, fmt.Sprintf("%d", value), 16, nil, func(text string) {
|
||||
var parsed int
|
||||
if _, err := fmt.Sscanf(strings.TrimSpace(text), "%d", &parsed); err == nil {
|
||||
onChange(parsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *appState) addModel(model picoclawconfig.ModelConfig) {
|
||||
s.config.ModelList = append(s.config.ModelList, model)
|
||||
}
|
||||
|
||||
func (s *appState) deleteModel(index int) {
|
||||
if index < 0 || index >= len(s.config.ModelList) {
|
||||
return
|
||||
}
|
||||
s.config.ModelList = append(s.config.ModelList[:index], s.config.ModelList[index+1:]...)
|
||||
s.pop()
|
||||
}
|
||||
|
||||
func modelStatusColor(valid bool, selected bool) *tcell.Color {
|
||||
if valid {
|
||||
color := tview.Styles.PrimaryTextColor
|
||||
return &color
|
||||
}
|
||||
color := tcell.ColorGray
|
||||
return &color
|
||||
}
|
||||
|
||||
func refreshModelMenu(menu *Menu, currentModel string, models []picoclawconfig.ModelConfig) {
|
||||
for i, model := range models {
|
||||
row := i
|
||||
label := fmt.Sprintf("%s (%s)", model.ModelName, model.Model)
|
||||
isValid := isModelValid(model)
|
||||
if model.ModelName == currentModel && currentModel != "" {
|
||||
label = "* " + label
|
||||
}
|
||||
cell := menu.GetCell(row, 0)
|
||||
if cell != nil {
|
||||
cell.SetText(label)
|
||||
isSelected := model.ModelName == currentModel && currentModel != ""
|
||||
color := modelStatusColor(isValid, isSelected)
|
||||
if color != nil {
|
||||
cell.SetTextColor(*color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshModelMenuFromState(menu *Menu, s *appState) {
|
||||
items := make([]MenuItem, 0, 1+len(s.config.ModelList))
|
||||
currentModel := strings.TrimSpace(s.config.Agents.Defaults.Model)
|
||||
for i := range s.config.ModelList {
|
||||
index := i
|
||||
model := s.config.ModelList[i]
|
||||
isValid := isModelValid(model)
|
||||
desc := model.APIBase
|
||||
if desc == "" {
|
||||
desc = model.AuthMethod
|
||||
}
|
||||
if desc == "" {
|
||||
desc = "api_key required"
|
||||
}
|
||||
label := fmt.Sprintf("%s (%s)", model.ModelName, model.Model)
|
||||
if model.ModelName == currentModel && currentModel != "" {
|
||||
label = "* " + label
|
||||
}
|
||||
isSelected := model.ModelName == currentModel && currentModel != ""
|
||||
items = append(items, MenuItem{
|
||||
Label: label,
|
||||
Description: desc,
|
||||
MainColor: modelStatusColor(isValid, isSelected),
|
||||
Action: func() {
|
||||
s.push(fmt.Sprintf("model-%d", index), s.modelForm(index))
|
||||
},
|
||||
})
|
||||
}
|
||||
items = append(items,
|
||||
MenuItem{
|
||||
Label: "**Add Model**",
|
||||
Description: "Append a new model entry",
|
||||
Action: func() {
|
||||
newName := s.nextAvailableModelName("new-model")
|
||||
s.addModel(
|
||||
picoclawconfig.ModelConfig{ModelName: newName, Model: "openai/gpt-5.4"},
|
||||
)
|
||||
s.push(fmt.Sprintf("model-%d", len(s.config.ModelList)-1), s.modelForm(len(s.config.ModelList)-1))
|
||||
},
|
||||
},
|
||||
)
|
||||
menu.applyItems(items)
|
||||
}
|
||||
|
||||
func isModelValid(model picoclawconfig.ModelConfig) bool {
|
||||
hasKey := strings.TrimSpace(model.APIKey) != "" ||
|
||||
strings.TrimSpace(model.AuthMethod) == "oauth"
|
||||
hasModel := strings.TrimSpace(model.Model) != ""
|
||||
return hasKey && hasModel
|
||||
}
|
||||
|
||||
func (s *appState) modelNameExists(name string, excludeIndex int) bool {
|
||||
target := strings.TrimSpace(name)
|
||||
if target == "" {
|
||||
return false
|
||||
}
|
||||
for i := range s.config.ModelList {
|
||||
if i == excludeIndex {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(s.config.ModelList[i].ModelName) == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *appState) nextAvailableModelName(base string) string {
|
||||
name := strings.TrimSpace(base)
|
||||
if name == "" {
|
||||
name = "new-model"
|
||||
}
|
||||
if !s.modelNameExists(name, -1) {
|
||||
return name
|
||||
}
|
||||
for i := 2; ; i++ {
|
||||
candidate := fmt.Sprintf("%s-%d", name, i)
|
||||
if !s.modelNameExists(candidate, -1) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *appState) testModel(model *picoclawconfig.ModelConfig) {
|
||||
if model == nil {
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(model.APIKey) == "" {
|
||||
s.showMessage("Missing API Key", "Set api_key before testing")
|
||||
return
|
||||
}
|
||||
base := strings.TrimSpace(model.APIBase)
|
||||
if base == "" {
|
||||
s.showMessage("Missing API Base", "Set api_base before testing")
|
||||
return
|
||||
}
|
||||
modelID := strings.TrimSpace(model.Model)
|
||||
if modelID == "" {
|
||||
s.showMessage("Missing Model", "Set model before testing")
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(modelID, "openai/") {
|
||||
s.showMessage("Unsupported model", "Only openai/* models are supported for test")
|
||||
return
|
||||
}
|
||||
modelName := strings.TrimPrefix(modelID, "openai/")
|
||||
endpoint := strings.TrimRight(base, "/") + "/chat/completions"
|
||||
|
||||
payload := fmt.Sprintf(
|
||||
`{"model":"%s","messages":[{"role":"user","content":"ping"}],"max_tokens":1}`,
|
||||
modelName,
|
||||
)
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
request, err := http.NewRequest("POST", endpoint, strings.NewReader(payload))
|
||||
if err != nil {
|
||||
s.showMessage("Test failed", err.Error())
|
||||
return
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("Authorization", "Bearer "+strings.TrimSpace(model.APIKey))
|
||||
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
s.showMessage("Test failed", err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
s.showMessage("Test OK", resp.Status)
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 2048))
|
||||
if err != nil {
|
||||
s.showMessage("Test failed", fmt.Sprintf("failed to read response: %v", err))
|
||||
return
|
||||
}
|
||||
s.showMessage(
|
||||
"Test failed",
|
||||
fmt.Sprintf("%s: %s", resp.Status, strings.TrimSpace(string(body))),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const (
|
||||
colorBlue = "[#3e5db9]"
|
||||
colorRed = "[#d54646]"
|
||||
banner = "\r\n[::b]" +
|
||||
colorBlue + "██████╗ ██╗ ██████╗ ██████╗ " + colorRed + " ██████╗██╗ █████╗ ██╗ ██╗\n" +
|
||||
colorBlue + "██╔══██╗██║██╔════╝██╔═══██╗" + colorRed + "██╔════╝██║ ██╔══██╗██║ ██║\n" +
|
||||
colorBlue + "██████╔╝██║██║ ██║ ██║" + colorRed + "██║ ██║ ███████║██║ █╗ ██║\n" +
|
||||
colorBlue + "██╔═══╝ ██║██║ ██║ ██║" + colorRed + "██║ ██║ ██╔══██║██║███╗██║\n" +
|
||||
colorBlue + "██║ ██║╚██████╗╚██████╔╝" + colorRed + "╚██████╗███████╗██║ ██║╚███╔███╔╝\n" +
|
||||
colorBlue + "╚═╝ ╚═╝ ╚═════╝ ╚═════╝ " + colorRed + " ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝\n " +
|
||||
"[:]"
|
||||
)
|
||||
|
||||
func applyStyles() {
|
||||
tview.Styles.PrimitiveBackgroundColor = tcell.NewRGBColor(12, 13, 22)
|
||||
tview.Styles.ContrastBackgroundColor = tcell.NewRGBColor(34, 19, 53)
|
||||
tview.Styles.MoreContrastBackgroundColor = tcell.NewRGBColor(18, 18, 32)
|
||||
tview.Styles.BorderColor = tcell.NewRGBColor(112, 102, 255)
|
||||
tview.Styles.TitleColor = tcell.NewRGBColor(255, 121, 198)
|
||||
tview.Styles.GraphicsColor = tcell.NewRGBColor(139, 233, 253)
|
||||
tview.Styles.PrimaryTextColor = tcell.NewRGBColor(241, 250, 255)
|
||||
tview.Styles.SecondaryTextColor = tcell.NewRGBColor(80, 250, 123)
|
||||
tview.Styles.TertiaryTextColor = tcell.NewRGBColor(139, 233, 253)
|
||||
tview.Styles.InverseTextColor = tcell.NewRGBColor(12, 13, 22)
|
||||
tview.Styles.ContrastSecondaryTextColor = tcell.NewRGBColor(189, 147, 249)
|
||||
}
|
||||
|
||||
func bannerView() *tview.TextView {
|
||||
text := tview.NewTextView()
|
||||
text.SetDynamicColors(true)
|
||||
text.SetTextAlign(tview.AlignCenter)
|
||||
text.SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
|
||||
text.SetText(banner)
|
||||
text.SetBorder(false)
|
||||
return text
|
||||
}
|
||||
|
||||
const footerText = "Esc: Back/Exit | Enter: Enter | ←↓↑→ : Move | Space: Select | Tab/Shift+Tab: Switch"
|
||||
|
||||
func footerView() *tview.TextView {
|
||||
text := tview.NewTextView()
|
||||
text.SetTextAlign(tview.AlignCenter)
|
||||
text.SetText(footerText)
|
||||
text.SetBackgroundColor(tview.Styles.MoreContrastBackgroundColor)
|
||||
text.SetTextColor(tview.Styles.PrimaryTextColor)
|
||||
text.SetBorder(false)
|
||||
return text
|
||||
}
|
||||
|
|
@ -1,15 +1,48 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
// License: MIT
|
||||
//
|
||||
// Copyright (c) 2026 PicoClaw contributors
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sipeed/picoclaw/cmd/picoclaw-launcher-tui/internal/ui"
|
||||
tuicfg "github.com/sipeed/picoclaw/cmd/picoclaw-launcher-tui/config"
|
||||
"github.com/sipeed/picoclaw/cmd/picoclaw-launcher-tui/ui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := ui.Run(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
configPath := tuicfg.DefaultConfigPath()
|
||||
if len(os.Args) > 1 {
|
||||
configPath = os.Args[1]
|
||||
}
|
||||
|
||||
configDir := filepath.Dir(configPath)
|
||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
||||
cmd := exec.Command("picoclaw", "onboard")
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
_ = cmd.Run()
|
||||
}
|
||||
|
||||
cfg, err := tuicfg.Load(configPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "picoclaw-launcher-tui: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
app := ui.New(cfg, configPath)
|
||||
// Bind model selection hook to sync to main config
|
||||
app.OnModelSelected = func(scheme tuicfg.Scheme, user tuicfg.User, modelID string) {
|
||||
_ = tuicfg.SyncSelectedModelToMainConfig(scheme, user, modelID)
|
||||
}
|
||||
if err := app.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "picoclaw-launcher-tui: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
325
cmd/picoclaw-launcher-tui/ui/app.go
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
// License: MIT
|
||||
//
|
||||
// Copyright (c) 2026 PicoClaw contributors
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
tuicfg "github.com/sipeed/picoclaw/cmd/picoclaw-launcher-tui/config"
|
||||
)
|
||||
|
||||
// App is the root TUI application.
|
||||
type App struct {
|
||||
tapp *tview.Application
|
||||
pages *tview.Pages
|
||||
pageStack []string
|
||||
cfg *tuicfg.TUIConfig
|
||||
configPath string
|
||||
pageRefreshFns map[string]func()
|
||||
headerModelTV *tview.TextView
|
||||
modalOpen map[string]bool
|
||||
|
||||
// OnModelSelected is called when a model is selected in the UI.
|
||||
// Can be nil to disable.
|
||||
OnModelSelected func(scheme tuicfg.Scheme, user tuicfg.User, modelID string)
|
||||
|
||||
modelCache map[string][]modelEntry
|
||||
modelCacheMu sync.RWMutex
|
||||
refreshMu sync.Mutex
|
||||
}
|
||||
|
||||
// cacheKey returns the map key for a (scheme, user) pair.
|
||||
func cacheKey(schemeName, userName string) string {
|
||||
return fmt.Sprintf("%s/%s", schemeName, userName)
|
||||
}
|
||||
|
||||
// cachedModels returns a defensive copy of the cached model list for a user (may be nil).
|
||||
func (a *App) cachedModels(schemeName, userName string) []modelEntry {
|
||||
a.modelCacheMu.RLock()
|
||||
defer a.modelCacheMu.RUnlock()
|
||||
entries := a.modelCache[cacheKey(schemeName, userName)]
|
||||
return append([]modelEntry(nil), entries...)
|
||||
}
|
||||
|
||||
// refreshModelCache fetches models for every user in the config concurrently.
|
||||
// Serialized by refreshMu so concurrent calls don't race on the cache map.
|
||||
// When all fetches complete it calls onDone via QueueUpdateDraw.
|
||||
func (a *App) refreshModelCache(onDone func()) {
|
||||
go func() {
|
||||
a.refreshMu.Lock()
|
||||
defer a.refreshMu.Unlock()
|
||||
|
||||
users := a.cfg.Provider.Users
|
||||
schemes := a.cfg.Provider.Schemes
|
||||
|
||||
schemeURL := make(map[string]string, len(schemes))
|
||||
for _, s := range schemes {
|
||||
schemeURL[s.Name] = s.BaseURL
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, u := range users {
|
||||
baseURL, ok := schemeURL[u.Scheme]
|
||||
if !ok || baseURL == "" {
|
||||
continue
|
||||
}
|
||||
if u.Key == "" {
|
||||
a.modelCacheMu.Lock()
|
||||
if a.modelCache == nil {
|
||||
a.modelCache = make(map[string][]modelEntry)
|
||||
}
|
||||
a.modelCache[cacheKey(u.Scheme, u.Name)] = nil
|
||||
a.modelCacheMu.Unlock()
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
bURL := baseURL
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
entries, err := fetchModels(bURL, u.Key)
|
||||
a.modelCacheMu.Lock()
|
||||
if a.modelCache == nil {
|
||||
a.modelCache = make(map[string][]modelEntry)
|
||||
}
|
||||
if err != nil || len(entries) == 0 {
|
||||
a.modelCache[cacheKey(u.Scheme, u.Name)] = nil
|
||||
} else {
|
||||
a.modelCache[cacheKey(u.Scheme, u.Name)] = entries
|
||||
}
|
||||
a.modelCacheMu.Unlock()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if onDone != nil {
|
||||
a.tapp.QueueUpdateDraw(onDone)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// New creates and wires up the TUI application.
|
||||
func New(cfg *tuicfg.TUIConfig, configPath string) *App {
|
||||
// Cyberpunk Theme Colors
|
||||
// Dark background
|
||||
tview.Styles.PrimitiveBackgroundColor = tcell.NewHexColor(0x050510) // Deep Void
|
||||
tview.Styles.ContrastBackgroundColor = tcell.NewHexColor(0x1a1a2e) // Dark Indigo
|
||||
tview.Styles.MoreContrastBackgroundColor = tcell.NewHexColor(0x2a2a40)
|
||||
|
||||
// Borders and Titles
|
||||
tview.Styles.BorderColor = tcell.NewHexColor(0x00f0ff) // Neon Cyan
|
||||
tview.Styles.TitleColor = tcell.NewHexColor(0x00f0ff) // Neon Cyan
|
||||
tview.Styles.GraphicsColor = tcell.NewHexColor(0xff00ff) // Neon Magenta
|
||||
|
||||
// Text
|
||||
tview.Styles.PrimaryTextColor = tcell.NewHexColor(0xe0e0e0) // Off-white
|
||||
tview.Styles.SecondaryTextColor = tcell.NewHexColor(0x00f0ff) // Neon Cyan
|
||||
tview.Styles.TertiaryTextColor = tcell.NewHexColor(0x39ff14) // Neon Lime
|
||||
tview.Styles.InverseTextColor = tcell.NewHexColor(0x000000) // Black
|
||||
tview.Styles.ContrastSecondaryTextColor = tcell.NewHexColor(0xff00ff) // Neon Magenta
|
||||
|
||||
a := &App{
|
||||
tapp: tview.NewApplication(),
|
||||
pages: tview.NewPages(),
|
||||
pageStack: []string{},
|
||||
cfg: cfg,
|
||||
configPath: configPath,
|
||||
pageRefreshFns: make(map[string]func()),
|
||||
modalOpen: make(map[string]bool),
|
||||
}
|
||||
|
||||
a.tapp.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
if len(a.modalOpen) > 0 {
|
||||
return event
|
||||
}
|
||||
return a.goBack()
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
a.buildPages()
|
||||
return a
|
||||
}
|
||||
|
||||
// Run starts the TUI event loop.
|
||||
func (a *App) Run() error {
|
||||
return a.tapp.SetRoot(a.pages, true).EnableMouse(true).Run()
|
||||
}
|
||||
|
||||
func (a *App) buildPages() {
|
||||
a.pages.AddPage("home", a.newHomePage(), true, true)
|
||||
a.pageStack = []string{"home"}
|
||||
}
|
||||
|
||||
func (a *App) navigateTo(name string, page tview.Primitive) {
|
||||
a.pages.RemovePage(name)
|
||||
a.pages.AddPage(name, page, true, false)
|
||||
a.pageStack = append(a.pageStack, name)
|
||||
a.pages.SwitchToPage(name)
|
||||
}
|
||||
|
||||
func (a *App) goBack() *tcell.EventKey {
|
||||
if len(a.pageStack) <= 1 {
|
||||
return nil
|
||||
}
|
||||
popped := a.pageStack[len(a.pageStack)-1]
|
||||
a.pageStack = a.pageStack[:len(a.pageStack)-1]
|
||||
a.pages.RemovePage(popped)
|
||||
prev := a.pageStack[len(a.pageStack)-1]
|
||||
if fn, ok := a.pageRefreshFns[prev]; ok {
|
||||
fn()
|
||||
}
|
||||
if prev == "home" && a.headerModelTV != nil {
|
||||
a.headerModelTV.SetText(a.cfg.CurrentModelLabel() + " ")
|
||||
}
|
||||
a.pages.SwitchToPage(prev)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) showModal(name string, primitive tview.Primitive) {
|
||||
a.modalOpen[name] = true
|
||||
a.pages.AddPage(name, primitive, true, true)
|
||||
}
|
||||
|
||||
func (a *App) hideModal(name string) {
|
||||
delete(a.modalOpen, name)
|
||||
a.pages.HidePage(name)
|
||||
a.pages.RemovePage(name)
|
||||
}
|
||||
|
||||
func (a *App) save() {
|
||||
if err := tuicfg.Save(a.configPath, a.cfg); err != nil {
|
||||
a.showError("save failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) showError(msg string) {
|
||||
modal := tview.NewModal().
|
||||
SetText(" [red::b]ERROR[-::-]\n\n" + msg).
|
||||
AddButtons([]string{"OK"}).
|
||||
SetDoneFunc(func(_ int, _ string) {
|
||||
a.hideModal("error")
|
||||
})
|
||||
// Cyberpunk Modal Style
|
||||
modal.SetBackgroundColor(tcell.NewHexColor(0x1a1a2e)) // Deep Indigo
|
||||
modal.SetTextColor(tcell.NewHexColor(0xffffff)) // White
|
||||
modal.SetButtonBackgroundColor(tcell.NewHexColor(0xff2a2a)) // Neon Red
|
||||
modal.SetButtonTextColor(tcell.NewHexColor(0xffffff)) // White
|
||||
a.showModal("error", modal)
|
||||
}
|
||||
|
||||
func (a *App) confirmDelete(label string, onConfirm func()) {
|
||||
modal := tview.NewModal().
|
||||
SetText(" [red::b]DELETE WARNING[-::-]\n\nDelete " + label + "?\n[gray]This action cannot be undone.[-]").
|
||||
AddButtons([]string{"Delete", "Cancel"}).
|
||||
SetDoneFunc(func(_ int, buttonLabel string) {
|
||||
a.hideModal("confirm-delete")
|
||||
if buttonLabel == "Delete" {
|
||||
onConfirm()
|
||||
}
|
||||
})
|
||||
// Cyberpunk Modal Style
|
||||
modal.SetBackgroundColor(tcell.NewHexColor(0x1a1a2e)) // Deep Indigo
|
||||
modal.SetTextColor(tcell.NewHexColor(0xffffff)) // White
|
||||
modal.SetButtonBackgroundColor(tcell.NewHexColor(0xff2a2a)) // Neon Red for danger
|
||||
modal.SetButtonTextColor(tcell.NewHexColor(0xffffff)) // White
|
||||
a.showModal("confirm-delete", modal)
|
||||
}
|
||||
|
||||
func centeredForm(form *tview.Form, widthPct, height int) tview.Primitive {
|
||||
return tview.NewFlex().
|
||||
AddItem(tview.NewBox(), 0, 1, false).
|
||||
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
|
||||
AddItem(tview.NewBox(), 0, 1, false).
|
||||
AddItem(form, height, 1, true).
|
||||
AddItem(tview.NewBox(), 0, 1, false), 0, widthPct, true).
|
||||
AddItem(tview.NewBox(), 0, 1, false)
|
||||
}
|
||||
|
||||
func hintBar(text string) *tview.TextView {
|
||||
tv := tview.NewTextView().
|
||||
SetText(text).
|
||||
SetDynamicColors(true).
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetTextColor(tcell.NewHexColor(0x00f0ff)) // Neon Cyan
|
||||
tv.SetBackgroundColor(tcell.NewHexColor(0x2a2a40)) // Darker Indigo
|
||||
return tv
|
||||
}
|
||||
|
||||
func (a *App) buildShell(pageID string, content tview.Primitive, hint string) tview.Primitive {
|
||||
var modelTV *tview.TextView
|
||||
if pageID == "home" {
|
||||
if a.headerModelTV == nil {
|
||||
a.headerModelTV = tview.NewTextView()
|
||||
a.headerModelTV.SetTextAlign(tview.AlignRight).
|
||||
SetTextColor(tcell.NewHexColor(0x39ff14)). // Neon Lime
|
||||
SetDynamicColors(true).
|
||||
SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
}
|
||||
modelTV = a.headerModelTV
|
||||
modelTV.SetText("MODEL: " + a.cfg.CurrentModelLabel() + " ")
|
||||
} else {
|
||||
modelTV = tview.NewTextView()
|
||||
modelTV.SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
}
|
||||
|
||||
headerLeft := tview.NewTextView().
|
||||
SetText(" [#ff00ff::b]///[#00f0ff] PICOCLAW LAUNCHER [#ff00ff]///").
|
||||
SetDynamicColors(true).
|
||||
SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
|
||||
header := tview.NewFlex().
|
||||
AddItem(headerLeft, 0, 1, false).
|
||||
AddItem(modelTV, 0, 1, false)
|
||||
|
||||
sidebar := tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetWrap(false)
|
||||
sidebar.SetBackgroundColor(tcell.NewHexColor(0x1a1a2e)) // Deep Indigo
|
||||
|
||||
// Cyberpunk Sidebar Styling
|
||||
activePrefix := "[#39ff14::b]>> " // Neon Lime arrow
|
||||
activeSuffix := "[-]"
|
||||
inactivePrefix := "[#808080] "
|
||||
inactiveSuffix := "[-]"
|
||||
|
||||
sbText := "\n\n" // Top padding
|
||||
|
||||
menuItem := func(id, label string) string {
|
||||
if pageID == id {
|
||||
return activePrefix + label + activeSuffix + "\n\n"
|
||||
}
|
||||
return inactivePrefix + label + inactiveSuffix + "\n\n"
|
||||
}
|
||||
|
||||
sbText += menuItem("home", "HOME")
|
||||
sbText += menuItem("schemes", "SCHEMES")
|
||||
sbText += menuItem("users", "USERS")
|
||||
sbText += menuItem("models", "MODELS")
|
||||
sbText += menuItem("channels", "CHANNELS")
|
||||
sbText += menuItem("gateway", "GATEWAY")
|
||||
|
||||
sidebar.SetText(sbText)
|
||||
|
||||
footer := hintBar(hint)
|
||||
|
||||
grid := tview.NewGrid().
|
||||
SetRows(1, 0, 1).
|
||||
SetColumns(20, 0). // Slightly wider sidebar
|
||||
AddItem(header, 0, 0, 1, 2, 0, 0, false).
|
||||
AddItem(sidebar, 1, 0, 1, 1, 0, 0, false).
|
||||
AddItem(content, 1, 1, 1, 1, 0, 0, true).
|
||||
AddItem(footer, 2, 0, 1, 2, 0, 0, false)
|
||||
|
||||
// Add a border around the content area if possible, or ensure content has its own border
|
||||
// grid.SetBorders(false) // Grid borders usually look bad, handled by components
|
||||
|
||||
return grid
|
||||
}
|
||||
202
cmd/picoclaw-launcher-tui/ui/channels.go
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
// License: MIT
|
||||
//
|
||||
// Copyright (c) 2026 PicoClaw contributors
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
func (a *App) newChannelsPage() tview.Primitive {
|
||||
list := tview.NewList()
|
||||
list.SetBorder(true).
|
||||
SetTitle(" [#00f0ff::b] COMMUNICATION CHANNELS ").
|
||||
SetTitleColor(tcell.NewHexColor(0x00f0ff)).
|
||||
SetBorderColor(tcell.NewHexColor(0x00f0ff))
|
||||
list.SetMainTextColor(tcell.NewHexColor(0xe0e0e0))
|
||||
list.SetSecondaryTextColor(tcell.NewHexColor(0x808080))
|
||||
list.SetSelectedStyle(
|
||||
tcell.StyleDefault.Background(tcell.NewHexColor(0xff00ff)).Foreground(tcell.NewHexColor(0x050510)),
|
||||
)
|
||||
list.SetHighlightFullLine(true)
|
||||
list.SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
|
||||
rebuild := func() {
|
||||
sel := list.GetCurrentItem()
|
||||
list.Clear()
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
home = "."
|
||||
}
|
||||
configPath := filepath.Join(home, ".picoclaw", "config.json")
|
||||
|
||||
var cfg map[string]any
|
||||
if data, err := os.ReadFile(configPath); err == nil {
|
||||
_ = json.Unmarshal(data, &cfg)
|
||||
}
|
||||
|
||||
if chRaw, ok := cfg["channels"].(map[string]any); ok {
|
||||
for name, ch := range chRaw {
|
||||
chMap, ok := ch.(map[string]any)
|
||||
enabled := "disabled"
|
||||
if ok {
|
||||
if e, ok := chMap["enabled"].(bool); ok && e {
|
||||
enabled = "enabled"
|
||||
}
|
||||
}
|
||||
list.AddItem(name, fmt.Sprintf("Status: %s", enabled), 0, func() {
|
||||
a.showChannelEditForm(configPath, name, chMap)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if sel >= 0 && sel < list.GetItemCount() {
|
||||
list.SetCurrentItem(sel)
|
||||
}
|
||||
}
|
||||
rebuild()
|
||||
|
||||
a.pageRefreshFns["channels"] = rebuild
|
||||
|
||||
list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
return a.goBack()
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
return a.buildShell("channels", list, " [#ff00ff]Enter:[-] edit [#ff2a2a]ESC:[-] back ")
|
||||
}
|
||||
|
||||
func (a *App) showChannelEditForm(configPath, channelName string, existing map[string]any) {
|
||||
form := tview.NewForm()
|
||||
form.SetBorder(true).
|
||||
SetTitle(" [::b]EDIT CHANNEL ").
|
||||
SetTitleColor(tcell.NewHexColor(0x39ff14)).
|
||||
SetBorderColor(tcell.NewHexColor(0x00f0ff))
|
||||
form.SetBackgroundColor(tcell.NewHexColor(0x1a1a2e))
|
||||
form.SetFieldBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
form.SetFieldTextColor(tcell.NewHexColor(0x00f0ff))
|
||||
form.SetLabelColor(tcell.NewHexColor(0xe0e0e0))
|
||||
form.SetButtonBackgroundColor(tcell.NewHexColor(0xff00ff))
|
||||
form.SetButtonTextColor(tcell.NewHexColor(0xffffff))
|
||||
|
||||
fields := make(map[string]*tview.InputField)
|
||||
var nameField *tview.InputField
|
||||
|
||||
if channelName == "" {
|
||||
nameField = tview.NewInputField().
|
||||
SetLabel("Channel Name").
|
||||
SetText("").
|
||||
SetFieldWidth(28)
|
||||
form.AddFormItem(nameField)
|
||||
}
|
||||
|
||||
for k, v := range existing {
|
||||
if reflect.ValueOf(v).Kind() == reflect.Map || reflect.ValueOf(v).Kind() == reflect.Slice {
|
||||
continue
|
||||
}
|
||||
valStr := fmt.Sprintf("%v", v)
|
||||
field := tview.NewInputField().
|
||||
SetLabel(k).
|
||||
SetText(valStr).
|
||||
SetFieldWidth(28)
|
||||
form.AddFormItem(field)
|
||||
fields[k] = field
|
||||
}
|
||||
|
||||
form.AddButton("SAVE", func() {
|
||||
var cfg map[string]any
|
||||
if data, err := os.ReadFile(configPath); err == nil {
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
cfg = make(map[string]any)
|
||||
}
|
||||
} else {
|
||||
cfg = make(map[string]any)
|
||||
}
|
||||
|
||||
if _, ok := cfg["channels"]; !ok {
|
||||
cfg["channels"] = make(map[string]any)
|
||||
}
|
||||
channels, ok := cfg["channels"].(map[string]any)
|
||||
if !ok {
|
||||
channels = make(map[string]any)
|
||||
cfg["channels"] = channels
|
||||
}
|
||||
|
||||
finalName := channelName
|
||||
if channelName == "" {
|
||||
if nameField == nil || nameField.GetText() == "" {
|
||||
a.showError("Channel name is required")
|
||||
return
|
||||
}
|
||||
finalName = nameField.GetText()
|
||||
}
|
||||
|
||||
updated := make(map[string]any)
|
||||
if existing != nil {
|
||||
for k, v := range existing {
|
||||
updated[k] = v
|
||||
}
|
||||
}
|
||||
for k, field := range fields {
|
||||
val := field.GetText()
|
||||
if val == "true" {
|
||||
updated[k] = true
|
||||
} else if val == "false" {
|
||||
updated[k] = false
|
||||
} else if num, err := strconv.Atoi(val); err == nil {
|
||||
updated[k] = num
|
||||
} else {
|
||||
updated[k] = val
|
||||
}
|
||||
}
|
||||
|
||||
if channelName != "" && finalName != channelName {
|
||||
delete(channels, channelName)
|
||||
}
|
||||
channels[finalName] = updated
|
||||
|
||||
data, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
a.showError(fmt.Sprintf("Failed to save config: %v", err))
|
||||
return
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(configPath), 0o700); err != nil {
|
||||
a.showError(fmt.Sprintf("Failed to create config directory: %v", err))
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(configPath, data, 0o600); err != nil {
|
||||
a.showError(fmt.Sprintf("Failed to write config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
a.hideModal("channel-edit")
|
||||
a.goBack()
|
||||
})
|
||||
|
||||
form.AddButton("CANCEL", func() {
|
||||
a.hideModal("channel-edit")
|
||||
})
|
||||
|
||||
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
a.hideModal("channel-edit")
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
a.showModal("channel-edit", centeredForm(form, 4, 20))
|
||||
}
|
||||
229
cmd/picoclaw-launcher-tui/ui/gateway.go
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
// License: MIT
|
||||
//
|
||||
// Copyright (c) 2026 PicoClaw contributors
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
ppid "github.com/sipeed/picoclaw/pkg/pid"
|
||||
)
|
||||
|
||||
type gatewayStatus struct {
|
||||
running bool
|
||||
pid int
|
||||
version string
|
||||
}
|
||||
|
||||
func picoHome() string {
|
||||
return config.GetHome()
|
||||
}
|
||||
|
||||
func getGatewayStatus() gatewayStatus {
|
||||
data := ppid.ReadPidFileWithCheck(picoHome())
|
||||
if data == nil {
|
||||
return gatewayStatus{running: false}
|
||||
}
|
||||
return gatewayStatus{
|
||||
running: true,
|
||||
pid: data.PID,
|
||||
version: data.Version,
|
||||
}
|
||||
}
|
||||
|
||||
func startGateway() error {
|
||||
status := getGatewayStatus()
|
||||
if status.running {
|
||||
return fmt.Errorf("gateway is already running (PID: %d)", status.pid)
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.Command("cmd", "/C", "start /B picoclaw gateway > NUL 2>&1")
|
||||
} else {
|
||||
cmd = exec.Command("sh", "-c", "nohup picoclaw gateway > /dev/null 2>&1 &")
|
||||
}
|
||||
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd := exec.Command(
|
||||
"wmic",
|
||||
"process",
|
||||
"where",
|
||||
"name='picoclaw.exe' and commandline like '%gateway%'",
|
||||
"get",
|
||||
"processid",
|
||||
)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get gateway PID: %w", err)
|
||||
}
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines[1:] {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
_, err := strconv.Atoi(line)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = getGatewayStatus()
|
||||
if !status.running {
|
||||
return fmt.Errorf("failed to start gateway")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stopGateway() error {
|
||||
status := getGatewayStatus()
|
||||
if !status.running {
|
||||
return fmt.Errorf("gateway is not running")
|
||||
}
|
||||
|
||||
var err error
|
||||
if runtime.GOOS == "windows" {
|
||||
err = exec.Command("taskkill", "/F", "/PID", strconv.Itoa(status.pid)).Run()
|
||||
} else {
|
||||
err = exec.Command("kill", strconv.Itoa(status.pid)).Run()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for process to stop (ReadPidFileWithCheck cleans up stale pid file)
|
||||
for i := 0; i < 5; i++ {
|
||||
if !getGatewayStatus().running {
|
||||
break
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) newGatewayPage() tview.Primitive {
|
||||
flex := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
flex.SetBorder(true).
|
||||
SetTitle(" [#00f0ff::b] GATEWAY MANAGEMENT ").
|
||||
SetTitleColor(tcell.NewHexColor(0x00f0ff)).
|
||||
SetBorderColor(tcell.NewHexColor(0x00f0ff))
|
||||
flex.SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
|
||||
statusTV := tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetText("Checking status...")
|
||||
statusTV.SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
|
||||
var updateStatus func()
|
||||
|
||||
// 使用List作为按钮,保证显示和交互正常
|
||||
buttons := tview.NewList()
|
||||
buttons.SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
buttons.SetMainTextColor(tcell.ColorWhite)
|
||||
buttons.SetSelectedBackgroundColor(tcell.NewHexColor(0xff00ff))
|
||||
buttons.SetSelectedTextColor(tcell.ColorBlack)
|
||||
|
||||
buttons.AddItem(" [lime]START[white] ", "", 0, func() {
|
||||
if !getGatewayStatus().running {
|
||||
err := startGateway()
|
||||
if err != nil {
|
||||
a.showError(err.Error())
|
||||
}
|
||||
updateStatus()
|
||||
}
|
||||
})
|
||||
buttons.AddItem(" [red]STOP[white] ", "", 0, func() {
|
||||
if getGatewayStatus().running {
|
||||
err := stopGateway()
|
||||
if err != nil {
|
||||
a.showError(err.Error())
|
||||
}
|
||||
updateStatus()
|
||||
}
|
||||
})
|
||||
|
||||
buttonFlex := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
buttonFlex.
|
||||
AddItem(tview.NewBox(), 0, 1, false).
|
||||
AddItem(buttons, 20, 1, true).
|
||||
AddItem(tview.NewBox(), 0, 1, false)
|
||||
|
||||
flex.
|
||||
AddItem(tview.NewBox(), 0, 1, false).
|
||||
AddItem(statusTV, 3, 1, false).
|
||||
AddItem(tview.NewBox(), 0, 1, false).
|
||||
AddItem(buttonFlex, 4, 1, true).
|
||||
AddItem(tview.NewBox(), 0, 1, false)
|
||||
|
||||
updateStatus = func() {
|
||||
status := getGatewayStatus()
|
||||
if status.running {
|
||||
versionInfo := ""
|
||||
if status.version != "" {
|
||||
versionInfo = fmt.Sprintf("\nVersion: %s", status.version)
|
||||
}
|
||||
statusTV.SetText(fmt.Sprintf("[#39ff14::b]GATEWAY RUNNING[-]\n\nPID: %d%s", status.pid, versionInfo))
|
||||
buttons.SetItemText(0, " [gray]START[white] ", "")
|
||||
buttons.SetItemText(1, " [red]STOP[white] ", "")
|
||||
} else {
|
||||
statusTV.SetText("[#ff2a2a::b]GATEWAY STOPPED[-]\n\nPID: N/A")
|
||||
buttons.SetItemText(0, " [lime]START[white] ", "")
|
||||
buttons.SetItemText(1, " [gray]STOP[white] ", "")
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus()
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
a.tapp.QueueUpdateDraw(updateStatus)
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
originalInputCapture := flex.GetInputCapture()
|
||||
flex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
close(done)
|
||||
return a.goBack()
|
||||
}
|
||||
if originalInputCapture != nil {
|
||||
return originalInputCapture(event)
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
a.pageRefreshFns["gateway"] = updateStatus
|
||||
|
||||
return a.buildShell("gateway", flex, " [#39ff14]Enter:[-] select [#ff2a2a]ESC:[-] back ")
|
||||
}
|
||||
70
cmd/picoclaw-launcher-tui/ui/home.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
// License: MIT
|
||||
//
|
||||
// Copyright (c) 2026 PicoClaw contributors
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
func (a *App) newHomePage() tview.Primitive {
|
||||
list := tview.NewList()
|
||||
list.SetBorder(true).
|
||||
SetTitle(" [#00f0ff::b] ACTIVE CONFIGURATION ").
|
||||
SetTitleColor(tcell.NewHexColor(0x00f0ff)).
|
||||
SetBorderColor(tcell.NewHexColor(0x00f0ff))
|
||||
list.SetMainTextColor(tcell.NewHexColor(0xe0e0e0))
|
||||
list.SetSecondaryTextColor(tcell.NewHexColor(0x808080))
|
||||
list.SetSelectedStyle(
|
||||
tcell.StyleDefault.Background(tcell.NewHexColor(0x39ff14)).Foreground(tcell.NewHexColor(0x050510)),
|
||||
)
|
||||
list.SetHighlightFullLine(true)
|
||||
list.SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
|
||||
rebuildList := func() {
|
||||
sel := list.GetCurrentItem()
|
||||
list.Clear()
|
||||
list.AddItem("MODEL: "+a.cfg.CurrentModelLabel(), "Select to configure AI model", 'm', func() {
|
||||
a.navigateTo("schemes", a.newSchemesPage())
|
||||
})
|
||||
list.AddItem(
|
||||
"CHANNELS: Configure communication channels",
|
||||
"Manage Telegram/Discord/WeChat channels",
|
||||
'n',
|
||||
func() {
|
||||
a.navigateTo("channels", a.newChannelsPage())
|
||||
},
|
||||
)
|
||||
list.AddItem("GATEWAY MANAGEMENT", "Manage PicoClaw gateway daemon", 'g', func() {
|
||||
a.navigateTo("gateway", a.newGatewayPage())
|
||||
})
|
||||
list.AddItem("CHAT: Start AI agent chat", "Launch interactive chat session", 'c', func() {
|
||||
a.tapp.Suspend(func() {
|
||||
cmd := exec.Command("picoclaw", "agent")
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
_ = cmd.Run()
|
||||
})
|
||||
})
|
||||
list.AddItem("QUIT SYSTEM", "Exit PicoClaw Launcher", 'q', func() { a.tapp.Stop() })
|
||||
if sel >= 0 && sel < list.GetItemCount() {
|
||||
list.SetCurrentItem(sel)
|
||||
}
|
||||
}
|
||||
rebuildList()
|
||||
|
||||
a.pageRefreshFns["home"] = rebuildList
|
||||
|
||||
return a.buildShell(
|
||||
"home",
|
||||
list,
|
||||
" [#00f0ff]m:[-] model [#00f0ff]n:[-] channels [#00f0ff]g:[-] gateway [#00f0ff]c:[-] chat [#ff2a2a]q:[-] quit ",
|
||||
)
|
||||
}
|
||||
200
cmd/picoclaw-launcher-tui/ui/models.go
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
// License: MIT
|
||||
//
|
||||
// Copyright (c) 2026 PicoClaw contributors
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
tuicfg "github.com/sipeed/picoclaw/cmd/picoclaw-launcher-tui/config"
|
||||
)
|
||||
|
||||
type modelsAPIResponse struct {
|
||||
Data []modelEntry `json:"data"`
|
||||
}
|
||||
|
||||
type modelEntry struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func (a *App) newModelsPage(schemeName, userName, baseURL string) tview.Primitive {
|
||||
table := tview.NewTable().
|
||||
SetBorders(false).
|
||||
SetSelectable(true, false).
|
||||
SetFixed(0, 0)
|
||||
table.SetBorder(true).
|
||||
SetTitle(fmt.Sprintf(" [#00f0ff::b] MODELS · %s / %s ", schemeName, userName)).
|
||||
SetTitleColor(tcell.NewHexColor(0x00f0ff)).
|
||||
SetBorderColor(tcell.NewHexColor(0x00f0ff))
|
||||
table.SetSelectedStyle(
|
||||
tcell.StyleDefault.Background(tcell.NewHexColor(0xff00ff)).Foreground(tcell.NewHexColor(0xffffff)),
|
||||
)
|
||||
table.SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
|
||||
var modelIDs []string
|
||||
|
||||
status := tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetDynamicColors(true).
|
||||
SetText("[#ffff00]FETCHING MODELS...[-]")
|
||||
status.SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
|
||||
flex := tview.NewFlex().
|
||||
SetDirection(tview.FlexRow).
|
||||
AddItem(status, 1, 0, false).
|
||||
AddItem(table, 0, 1, false)
|
||||
|
||||
apiKey := a.resolveKey(schemeName, userName)
|
||||
|
||||
go func() {
|
||||
var entries []modelEntry
|
||||
var err error
|
||||
if apiKey == "" {
|
||||
err = fmt.Errorf("key is required")
|
||||
} else {
|
||||
entries, err = fetchModels(baseURL, apiKey)
|
||||
}
|
||||
|
||||
a.modelCacheMu.Lock()
|
||||
if a.modelCache == nil {
|
||||
a.modelCache = make(map[string][]modelEntry)
|
||||
}
|
||||
if err == nil && len(entries) > 0 {
|
||||
a.modelCache[cacheKey(schemeName, userName)] = entries
|
||||
} else {
|
||||
a.modelCache[cacheKey(schemeName, userName)] = nil
|
||||
}
|
||||
a.modelCacheMu.Unlock()
|
||||
|
||||
a.tapp.QueueUpdateDraw(func() {
|
||||
if err != nil {
|
||||
status.SetText(fmt.Sprintf("[#ff2a2a]ERROR: %s[-]", err.Error()))
|
||||
table.SetCell(0, 0, tview.NewTableCell(" (failed to load models)"))
|
||||
a.tapp.SetFocus(table)
|
||||
return
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
status.SetText("[#ff2a2a]NO MODELS RETURNED[-]")
|
||||
table.SetCell(0, 0, tview.NewTableCell(" (no models available)"))
|
||||
a.tapp.SetFocus(table)
|
||||
return
|
||||
}
|
||||
|
||||
status.SetText(fmt.Sprintf("[#39ff14]%d MODEL(S) LOADED[-]", len(entries)))
|
||||
for i, m := range entries {
|
||||
modelIDs = append(modelIDs, m.ID)
|
||||
table.SetCell(i, 0,
|
||||
tview.NewTableCell(fmt.Sprintf("%3d", i+1)).
|
||||
SetAlign(tview.AlignRight).
|
||||
SetTextColor(tcell.NewHexColor(0x808080)).
|
||||
SetSelectable(false),
|
||||
)
|
||||
table.SetCell(i, 1,
|
||||
tview.NewTableCell(" "+m.ID).
|
||||
SetAlign(tview.AlignLeft).
|
||||
SetExpansion(1).
|
||||
SetTextColor(tcell.NewHexColor(0xe0e0e0)),
|
||||
)
|
||||
}
|
||||
a.tapp.SetFocus(table)
|
||||
})
|
||||
}()
|
||||
|
||||
table.SetSelectedFunc(func(row, _ int) {
|
||||
if row < 0 || row >= len(modelIDs) {
|
||||
return
|
||||
}
|
||||
a.cfg.Provider.Current = tuicfg.ProviderCurrent{
|
||||
Scheme: schemeName,
|
||||
User: userName,
|
||||
Model: modelIDs[row],
|
||||
}
|
||||
a.save()
|
||||
|
||||
// Trigger model selected callback if set
|
||||
if a.OnModelSelected != nil && a.cfg.Model.Type == "provider" {
|
||||
scheme := a.cfg.Provider.SchemeByName(schemeName)
|
||||
if scheme == nil {
|
||||
a.goBack()
|
||||
return
|
||||
}
|
||||
var user tuicfg.User
|
||||
for _, u := range a.cfg.Provider.Users {
|
||||
if u.Scheme == schemeName && u.Name == userName {
|
||||
user = u
|
||||
break
|
||||
}
|
||||
}
|
||||
a.OnModelSelected(*scheme, user, modelIDs[row])
|
||||
}
|
||||
|
||||
a.goBack()
|
||||
})
|
||||
|
||||
return a.buildShell("models", flex, " [#39ff14]Enter:[-] select [#ff00ff]ESC:[-] back ")
|
||||
}
|
||||
|
||||
func (a *App) resolveKey(schemeName, userName string) string {
|
||||
for _, u := range a.cfg.Provider.Users {
|
||||
if u.Scheme == schemeName && u.Name == userName {
|
||||
return u.Key
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func fetchModels(baseURL, apiKey string) ([]modelEntry, error) {
|
||||
url := strings.TrimRight(baseURL, "/") + "/models"
|
||||
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build request: %w", err)
|
||||
}
|
||||
if apiKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
|
||||
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response: %w", err)
|
||||
}
|
||||
|
||||
var result modelsAPIResponse
|
||||
if err := json.Unmarshal(body, &result); err == nil && len(result.Data) > 0 {
|
||||
return result.Data, nil
|
||||
}
|
||||
|
||||
var arr []modelEntry
|
||||
if err := json.Unmarshal(body, &arr); err == nil {
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(
|
||||
"decode response: unrecognized shape: %s",
|
||||
strings.TrimSpace(string(body[:min(len(body), 256)])),
|
||||
)
|
||||
}
|
||||
252
cmd/picoclaw-launcher-tui/ui/schemes.go
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
// License: MIT
|
||||
//
|
||||
// Copyright (c) 2026 PicoClaw contributors
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
tuicfg "github.com/sipeed/picoclaw/cmd/picoclaw-launcher-tui/config"
|
||||
)
|
||||
|
||||
func (a *App) newSchemesPage() tview.Primitive {
|
||||
table := tview.NewTable().
|
||||
SetBorders(false).
|
||||
SetSelectable(true, false)
|
||||
table.SetBorder(true).
|
||||
SetTitle(" [#00f0ff::b] PROVIDER SCHEMES ").
|
||||
SetTitleColor(tcell.NewHexColor(0x00f0ff)).
|
||||
SetBorderColor(tcell.NewHexColor(0x00f0ff))
|
||||
table.SetSelectedStyle(
|
||||
tcell.StyleDefault.Background(tcell.NewHexColor(0xff00ff)).Foreground(tcell.NewHexColor(0xffffff)),
|
||||
)
|
||||
table.SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
|
||||
rowToIdx := func(row int) int { return row / 2 }
|
||||
|
||||
selectedSchemeName := func() string {
|
||||
row, _ := table.GetSelection()
|
||||
idx := rowToIdx(row)
|
||||
schemes := a.cfg.Provider.Schemes
|
||||
if idx >= 0 && idx < len(schemes) {
|
||||
return schemes[idx].Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
rebuild := func() {
|
||||
selName := selectedSchemeName()
|
||||
table.Clear()
|
||||
schemes := a.cfg.Provider.Schemes
|
||||
for i, s := range schemes {
|
||||
nameRow := i * 2
|
||||
detailRow := nameRow + 1
|
||||
|
||||
table.SetCell(nameRow, 0,
|
||||
tview.NewTableCell(" "+s.Name).
|
||||
SetTextColor(tcell.NewHexColor(0xe0e0e0)).
|
||||
SetExpansion(1).
|
||||
SetSelectable(true),
|
||||
)
|
||||
|
||||
users := a.cfg.Provider.UsersForScheme(s.Name)
|
||||
n := len(users)
|
||||
m := 0
|
||||
for _, u := range users {
|
||||
if models := a.cachedModels(s.Name, u.Name); len(models) > 0 {
|
||||
m++
|
||||
}
|
||||
}
|
||||
table.SetCell(detailRow, 0,
|
||||
tview.NewTableCell(fmt.Sprintf(" [#808080](%d/%d) %s", m, n, s.BaseURL)).
|
||||
SetTextColor(tcell.NewHexColor(0x808080)).
|
||||
SetExpansion(1).
|
||||
SetSelectable(false),
|
||||
)
|
||||
table.SetCell(detailRow, 1,
|
||||
tview.NewTableCell("[#00f0ff]"+s.Type+" ").
|
||||
SetAlign(tview.AlignRight).
|
||||
SetSelectable(false),
|
||||
)
|
||||
}
|
||||
if selName != "" {
|
||||
for i, s := range schemes {
|
||||
if s.Name == selName {
|
||||
table.Select(i*2, 0)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if table.GetRowCount() > 0 {
|
||||
table.Select(0, 0)
|
||||
}
|
||||
}
|
||||
rebuild()
|
||||
|
||||
a.refreshModelCache(rebuild)
|
||||
a.pageRefreshFns["schemes"] = func() { a.refreshModelCache(rebuild) }
|
||||
|
||||
table.SetSelectedFunc(func(row, _ int) {
|
||||
idx := rowToIdx(row)
|
||||
schemes := a.cfg.Provider.Schemes
|
||||
if idx < 0 || idx >= len(schemes) {
|
||||
return
|
||||
}
|
||||
name := schemes[idx].Name
|
||||
a.navigateTo("users", a.newUsersPage(name))
|
||||
})
|
||||
|
||||
table.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
row, _ := table.GetSelection()
|
||||
idx := rowToIdx(row)
|
||||
schemes := a.cfg.Provider.Schemes
|
||||
switch event.Rune() {
|
||||
case 'a':
|
||||
a.showSchemeForm(nil, func(s tuicfg.Scheme) {
|
||||
a.cfg.Provider.Schemes = append(a.cfg.Provider.Schemes, s)
|
||||
a.save()
|
||||
a.refreshModelCache(rebuild)
|
||||
})
|
||||
return nil
|
||||
case 'e':
|
||||
if idx < 0 || idx >= len(schemes) {
|
||||
return nil
|
||||
}
|
||||
origName := schemes[idx].Name
|
||||
orig := schemes[idx]
|
||||
a.showSchemeForm(&orig, func(s tuicfg.Scheme) {
|
||||
current := a.cfg.Provider.Schemes
|
||||
for i, sc := range current {
|
||||
if sc.Name == origName {
|
||||
a.cfg.Provider.Schemes[i] = s
|
||||
break
|
||||
}
|
||||
}
|
||||
a.save()
|
||||
a.refreshModelCache(func() {
|
||||
rebuild()
|
||||
for i, sc := range a.cfg.Provider.Schemes {
|
||||
if sc.Name == s.Name {
|
||||
table.Select(i*2, 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
return nil
|
||||
case 'd':
|
||||
if idx < 0 || idx >= len(schemes) {
|
||||
return nil
|
||||
}
|
||||
name := schemes[idx].Name
|
||||
a.confirmDelete(fmt.Sprintf("scheme %q", name), func() {
|
||||
current := a.cfg.Provider.Schemes
|
||||
newSchemes := make([]tuicfg.Scheme, 0, len(current))
|
||||
for _, sc := range current {
|
||||
if sc.Name != name {
|
||||
newSchemes = append(newSchemes, sc)
|
||||
}
|
||||
}
|
||||
a.cfg.Provider.Schemes = newSchemes
|
||||
|
||||
existing := a.cfg.Provider.Users
|
||||
filtered := make([]tuicfg.User, 0, len(existing))
|
||||
for _, u := range existing {
|
||||
if u.Scheme != name {
|
||||
filtered = append(filtered, u)
|
||||
}
|
||||
}
|
||||
a.cfg.Provider.Users = filtered
|
||||
|
||||
a.save()
|
||||
a.refreshModelCache(rebuild)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
return a.buildShell(
|
||||
"schemes",
|
||||
table,
|
||||
" [#00f0ff]a:[-] add [#00f0ff]e:[-] edit [#ff2a2a]d:[-] delete [#39ff14]Enter:[-] open [#ff00ff]ESC:[-] back ",
|
||||
)
|
||||
}
|
||||
|
||||
func (a *App) showSchemeForm(existing *tuicfg.Scheme, onSave func(tuicfg.Scheme)) {
|
||||
name := ""
|
||||
baseURL := ""
|
||||
schemeType := "openai-compatible"
|
||||
title := " ADD SCHEME "
|
||||
|
||||
if existing != nil {
|
||||
name = existing.Name
|
||||
baseURL = existing.BaseURL
|
||||
schemeType = existing.Type
|
||||
title = " EDIT SCHEME "
|
||||
}
|
||||
|
||||
typeOptions := []string{"openai-compatible", "anthropic"}
|
||||
typeIdx := 0
|
||||
for i, t := range typeOptions {
|
||||
if t == schemeType {
|
||||
typeIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
form := tview.NewForm()
|
||||
|
||||
form.
|
||||
AddInputField("Name", name, 20, nil, func(text string) { name = text }).
|
||||
AddInputField("Base URL", baseURL, 28, nil, func(text string) { baseURL = text }).
|
||||
AddDropDown("Type", typeOptions, typeIdx, func(option string, _ int) { schemeType = option }).
|
||||
AddButton("SAVE", func() {
|
||||
if name == "" {
|
||||
a.showError("Name is required")
|
||||
return
|
||||
}
|
||||
if baseURL == "" {
|
||||
a.showError("Base URL is required")
|
||||
return
|
||||
}
|
||||
if existing == nil {
|
||||
for _, s := range a.cfg.Provider.Schemes {
|
||||
if s.Name == name {
|
||||
a.showError(fmt.Sprintf("Scheme name %q already exists", name))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
a.hideModal("scheme-form")
|
||||
onSave(tuicfg.Scheme{Name: name, BaseURL: baseURL, Type: schemeType})
|
||||
}).
|
||||
AddButton("CANCEL", func() {
|
||||
a.hideModal("scheme-form")
|
||||
})
|
||||
|
||||
form.SetBorder(true).
|
||||
SetTitle(" [::b]" + title + " ").
|
||||
SetTitleColor(tcell.NewHexColor(0x39ff14)).
|
||||
SetBorderColor(tcell.NewHexColor(0x00f0ff))
|
||||
form.SetBackgroundColor(tcell.NewHexColor(0x1a1a2e))
|
||||
form.SetFieldBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
form.SetFieldTextColor(tcell.NewHexColor(0x00f0ff))
|
||||
form.SetLabelColor(tcell.NewHexColor(0xe0e0e0))
|
||||
form.SetButtonBackgroundColor(tcell.NewHexColor(0xff00ff))
|
||||
form.SetButtonTextColor(tcell.NewHexColor(0xffffff))
|
||||
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
a.hideModal("scheme-form")
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
a.showModal("scheme-form", centeredForm(form, 4, 12))
|
||||
}
|
||||
261
cmd/picoclaw-launcher-tui/ui/users.go
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
// PicoClaw - Ultra-lightweight personal AI agent
|
||||
// License: MIT
|
||||
//
|
||||
// Copyright (c) 2026 PicoClaw contributors
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
tuicfg "github.com/sipeed/picoclaw/cmd/picoclaw-launcher-tui/config"
|
||||
)
|
||||
|
||||
func (a *App) newUsersPage(schemeName string) tview.Primitive {
|
||||
table := tview.NewTable().
|
||||
SetBorders(false).
|
||||
SetSelectable(true, false)
|
||||
table.SetBorder(true).
|
||||
SetTitle(fmt.Sprintf(" [#00f0ff::b] USERS · %s ", schemeName)).
|
||||
SetTitleColor(tcell.NewHexColor(0x00f0ff)).
|
||||
SetBorderColor(tcell.NewHexColor(0x00f0ff))
|
||||
table.SetSelectedStyle(
|
||||
tcell.StyleDefault.Background(tcell.NewHexColor(0xff00ff)).Foreground(tcell.NewHexColor(0xffffff)),
|
||||
)
|
||||
table.SetBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
|
||||
visibleUsers := func() []tuicfg.User {
|
||||
var out []tuicfg.User
|
||||
for _, u := range a.cfg.Provider.Users {
|
||||
if u.Scheme == schemeName {
|
||||
out = append(out, u)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
findUserGlobalIdx := func(userName string) int {
|
||||
for i, u := range a.cfg.Provider.Users {
|
||||
if u.Scheme == schemeName && u.Name == userName {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
rowToVisIdx := func(row int) int { return row / 2 }
|
||||
|
||||
selectedUserName := func() string {
|
||||
row, _ := table.GetSelection()
|
||||
users := visibleUsers()
|
||||
visIdx := rowToVisIdx(row)
|
||||
if visIdx >= 0 && visIdx < len(users) {
|
||||
return users[visIdx].Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
rebuild := func() {
|
||||
selName := selectedUserName()
|
||||
table.Clear()
|
||||
users := visibleUsers()
|
||||
for i, u := range users {
|
||||
nameRow := i * 2
|
||||
detailRow := nameRow + 1
|
||||
|
||||
table.SetCell(nameRow, 0,
|
||||
tview.NewTableCell(" "+u.Name).
|
||||
SetTextColor(tcell.NewHexColor(0xe0e0e0)).
|
||||
SetExpansion(1).
|
||||
SetSelectable(true),
|
||||
)
|
||||
table.SetCell(nameRow, 1,
|
||||
tview.NewTableCell("").
|
||||
SetSelectable(false),
|
||||
)
|
||||
|
||||
models := a.cachedModels(schemeName, u.Name)
|
||||
var detailText string
|
||||
if len(models) > 0 {
|
||||
detailText = fmt.Sprintf(" [#39ff14]%d models available[-]", len(models))
|
||||
} else {
|
||||
detailText = " [#ff2a2a]Inactive / No Access[-]"
|
||||
}
|
||||
table.SetCell(detailRow, 0,
|
||||
tview.NewTableCell(detailText).
|
||||
SetTextColor(tcell.NewHexColor(0x808080)).
|
||||
SetExpansion(1).
|
||||
SetSelectable(false),
|
||||
)
|
||||
table.SetCell(detailRow, 1,
|
||||
tview.NewTableCell("[#00f0ff]"+u.Type+" ").
|
||||
SetAlign(tview.AlignRight).
|
||||
SetSelectable(false),
|
||||
)
|
||||
}
|
||||
if selName != "" {
|
||||
for i, u := range users {
|
||||
if u.Name == selName {
|
||||
table.Select(i*2, 0)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if table.GetRowCount() > 0 {
|
||||
table.Select(0, 0)
|
||||
}
|
||||
}
|
||||
rebuild()
|
||||
|
||||
a.refreshModelCache(rebuild)
|
||||
a.pageRefreshFns["users"] = func() { a.refreshModelCache(rebuild) }
|
||||
|
||||
table.SetSelectedFunc(func(row, _ int) {
|
||||
visIdx := rowToVisIdx(row)
|
||||
users := visibleUsers()
|
||||
if visIdx < 0 || visIdx >= len(users) {
|
||||
return
|
||||
}
|
||||
uName := users[visIdx].Name
|
||||
scheme := a.cfg.Provider.SchemeByName(schemeName)
|
||||
if scheme == nil {
|
||||
a.showError(fmt.Sprintf("Scheme %q not found", schemeName))
|
||||
return
|
||||
}
|
||||
a.navigateTo("models", a.newModelsPage(schemeName, uName, scheme.BaseURL))
|
||||
})
|
||||
|
||||
table.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
row, _ := table.GetSelection()
|
||||
visIdx := rowToVisIdx(row)
|
||||
users := visibleUsers()
|
||||
switch event.Rune() {
|
||||
case 'a':
|
||||
a.showUserForm(schemeName, nil, func(u tuicfg.User) {
|
||||
a.cfg.Provider.Users = append(a.cfg.Provider.Users, u)
|
||||
a.save()
|
||||
a.refreshModelCache(rebuild)
|
||||
})
|
||||
return nil
|
||||
case 'e':
|
||||
if visIdx < 0 || visIdx >= len(users) {
|
||||
return nil
|
||||
}
|
||||
origName := users[visIdx].Name
|
||||
orig := a.cfg.Provider.Users[findUserGlobalIdx(origName)]
|
||||
a.showUserForm(schemeName, &orig, func(u tuicfg.User) {
|
||||
cfgIdx := findUserGlobalIdx(origName)
|
||||
if cfgIdx < 0 {
|
||||
a.showError(fmt.Sprintf("User %q no longer exists", origName))
|
||||
return
|
||||
}
|
||||
a.cfg.Provider.Users[cfgIdx] = u
|
||||
a.save()
|
||||
a.refreshModelCache(func() {
|
||||
rebuild()
|
||||
for i, usr := range visibleUsers() {
|
||||
if usr.Name == u.Name {
|
||||
table.Select(i*2, 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
return nil
|
||||
case 'd':
|
||||
if visIdx < 0 || visIdx >= len(users) {
|
||||
return nil
|
||||
}
|
||||
uName := users[visIdx].Name
|
||||
a.confirmDelete(fmt.Sprintf("user %q", uName), func() {
|
||||
cfgIdx := findUserGlobalIdx(uName)
|
||||
if cfgIdx < 0 {
|
||||
return
|
||||
}
|
||||
all := a.cfg.Provider.Users
|
||||
a.cfg.Provider.Users = append(all[:cfgIdx], all[cfgIdx+1:]...)
|
||||
a.save()
|
||||
a.refreshModelCache(rebuild)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
return a.buildShell(
|
||||
"users",
|
||||
table,
|
||||
" [#00f0ff]a:[-] add [#00f0ff]e:[-] edit [#ff2a2a]d:[-] delete [#39ff14]Enter:[-] models [#ff00ff]ESC:[-] back ",
|
||||
)
|
||||
}
|
||||
|
||||
func (a *App) showUserForm(schemeName string, existing *tuicfg.User, onSave func(tuicfg.User)) {
|
||||
name := ""
|
||||
userType := "key"
|
||||
key := ""
|
||||
title := " ADD USER "
|
||||
|
||||
if existing != nil {
|
||||
name = existing.Name
|
||||
userType = existing.Type
|
||||
key = existing.Key
|
||||
title = " EDIT USER "
|
||||
}
|
||||
|
||||
typeOptions := []string{"key", "OAuth"}
|
||||
typeIdx := 0
|
||||
for i, t := range typeOptions {
|
||||
if t == userType {
|
||||
typeIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
form := tview.NewForm()
|
||||
form.
|
||||
AddInputField("Name", name, 20, nil, func(text string) { name = text }).
|
||||
AddDropDown("Type", typeOptions, typeIdx, func(option string, _ int) { userType = option }).
|
||||
AddPasswordField("Key", key, 28, '*', func(text string) { key = text }).
|
||||
AddButton("SAVE", func() {
|
||||
if name == "" {
|
||||
a.showError("Name is required")
|
||||
return
|
||||
}
|
||||
if existing == nil {
|
||||
for _, u := range a.cfg.Provider.Users {
|
||||
if u.Scheme == schemeName && u.Name == name {
|
||||
a.showError(fmt.Sprintf("User name %q already exists for this scheme", name))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
a.hideModal("user-form")
|
||||
onSave(tuicfg.User{Name: name, Scheme: schemeName, Type: userType, Key: key})
|
||||
}).
|
||||
AddButton("CANCEL", func() {
|
||||
a.hideModal("user-form")
|
||||
})
|
||||
|
||||
form.SetBorder(true).
|
||||
SetTitle(" [::b]" + title + " ").
|
||||
SetTitleColor(tcell.NewHexColor(0x39ff14)).
|
||||
SetBorderColor(tcell.NewHexColor(0x00f0ff))
|
||||
form.SetBackgroundColor(tcell.NewHexColor(0x1a1a2e))
|
||||
form.SetFieldBackgroundColor(tcell.NewHexColor(0x050510))
|
||||
form.SetFieldTextColor(tcell.NewHexColor(0x00f0ff))
|
||||
form.SetLabelColor(tcell.NewHexColor(0xe0e0e0))
|
||||
form.SetButtonBackgroundColor(tcell.NewHexColor(0xff00ff))
|
||||
form.SetButtonTextColor(tcell.NewHexColor(0xffffff))
|
||||
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
a.hideModal("user-form")
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
a.showModal("user-form", centeredForm(form, 4, 13))
|
||||
}
|
||||
64
cmd/picoclaw/dns_noresolv.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// 仅在 /etc/resolv.conf 不存在时才覆盖(即 Android 环境)
|
||||
if _, err := os.Stat("/etc/resolv.conf"); err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 从环境变量获取 DNS server 列表,多个用 ; 隔开
|
||||
// 例如: PICOCLAW_DNS_SERVER="8.8.8.8:53;1.1.1.1:53;223.5.5.5:53"
|
||||
dnsEnv := os.Getenv("PICOCLAW_DNS_SERVER")
|
||||
if dnsEnv == "" {
|
||||
dnsEnv = "8.8.8.8:53;1.1.1.1:53"
|
||||
}
|
||||
|
||||
var dnsServers []string
|
||||
for _, s := range strings.Split(dnsEnv, ";") {
|
||||
s = strings.TrimSpace(s)
|
||||
if s != "" {
|
||||
// 如果没有带端口号,自动补上 :53
|
||||
if _, _, err := net.SplitHostPort(s); err != nil {
|
||||
s = s + ":53"
|
||||
}
|
||||
dnsServers = append(dnsServers, s)
|
||||
}
|
||||
}
|
||||
|
||||
// 轮询索引,在多个 DNS 服务器之间轮转
|
||||
var idx uint64
|
||||
|
||||
customResolver := &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := net.Dialer{Timeout: 5 * time.Second}
|
||||
// Round-robin: 依次尝试不同的 DNS 服务器
|
||||
server := dnsServers[atomic.AddUint64(&idx, 1)%uint64(len(dnsServers))]
|
||||
return d.DialContext(ctx, "udp", server)
|
||||
},
|
||||
}
|
||||
|
||||
// 覆盖全局 DefaultResolver
|
||||
net.DefaultResolver = customResolver
|
||||
|
||||
// 覆盖 http.DefaultTransport 使用自定义 DNS 解析的 DialContext
|
||||
dialer := &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
Resolver: customResolver,
|
||||
}
|
||||
|
||||
if tr, ok := http.DefaultTransport.(*http.Transport); ok {
|
||||
tr.DialContext = dialer.DialContext
|
||||
}
|
||||
}
|
||||
|
|
@ -23,16 +23,18 @@ 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)
|
||||
}
|
||||
|
||||
logger.ConfigureFromEnv()
|
||||
|
||||
if debug {
|
||||
logger.SetLevel(logger.DEBUG)
|
||||
fmt.Println("🔍 Debug mode enabled")
|
||||
}
|
||||
|
||||
if model != "" {
|
||||
cfg.Agents.Defaults.ModelName = model
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ func NewAuthCommand() *cobra.Command {
|
|||
newLogoutCommand(),
|
||||
newStatusCommand(),
|
||||
newModelsCommand(),
|
||||
newWeixinCommand(),
|
||||
newWeComCommand(),
|
||||
)
|
||||
|
||||
return cmd
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ func TestNewAuthCommand(t *testing.T) {
|
|||
"logout",
|
||||
"status",
|
||||
"models",
|
||||
"weixin",
|
||||
"wecom",
|
||||
}
|
||||
|
||||
subcommands := cmd.Commands()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
407
cmd/picoclaw/internal/auth/wecom.go
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mdp/qrterminal/v3"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
const (
|
||||
wecomQRSourceID = "picoclaw"
|
||||
wecomQRGenerateEndpoint = "https://work.weixin.qq.com/ai/qc/generate"
|
||||
wecomQRQueryEndpoint = "https://work.weixin.qq.com/ai/qc/query_result"
|
||||
wecomQRPageEndpoint = "https://work.weixin.qq.com/ai/qc/gen"
|
||||
wecomQRHTTPTimeout = 15 * time.Second
|
||||
wecomQRPollInterval = 3 * time.Second
|
||||
wecomQRPollTimeout = 5 * time.Minute
|
||||
wecomDefaultWebSocketURL = "wss://openws.work.weixin.qq.com"
|
||||
)
|
||||
|
||||
type wecomQRScanner func(context.Context, wecomQRFlowOptions) (wecomQRBotInfo, error)
|
||||
|
||||
type wecomQRFlowOptions struct {
|
||||
HTTPClient *http.Client
|
||||
GenerateURL string
|
||||
QueryURL string
|
||||
QRCodePageURL string
|
||||
SourceID string
|
||||
PollInterval time.Duration
|
||||
PollTimeout time.Duration
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
type wecomQRBotInfo struct {
|
||||
BotID string
|
||||
Secret string
|
||||
}
|
||||
|
||||
type wecomQRSession struct {
|
||||
SCode string
|
||||
AuthURL string
|
||||
}
|
||||
|
||||
type wecomQRGenerateResponse struct {
|
||||
ErrCode int `json:"errcode,omitempty"`
|
||||
ErrMsg string `json:"errmsg,omitempty"`
|
||||
Data struct {
|
||||
SCode string `json:"scode"`
|
||||
AuthURL string `json:"auth_url"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type wecomQRQueryResponse struct {
|
||||
ErrCode int `json:"errcode,omitempty"`
|
||||
ErrMsg string `json:"errmsg,omitempty"`
|
||||
Data struct {
|
||||
Status string `json:"status"`
|
||||
BotInfo struct {
|
||||
BotID string `json:"botid"`
|
||||
Secret string `json:"secret"`
|
||||
} `json:"bot_info"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func newWeComCommand() *cobra.Command {
|
||||
var timeout time.Duration
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "wecom",
|
||||
Short: "Scan a WeCom QR code and configure channels.wecom",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return authWeComCmd(timeout)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().DurationVar(&timeout, "timeout", wecomQRPollTimeout, "How long to wait for QR confirmation")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func authWeComCmd(timeout time.Duration) error {
|
||||
return authWeComCmdWithScanner(context.Background(), os.Stdout, timeout, scanWeComQRCodeInteractive)
|
||||
}
|
||||
|
||||
func authWeComCmdWithScanner(
|
||||
ctx context.Context,
|
||||
writer io.Writer,
|
||||
timeout time.Duration,
|
||||
scanner wecomQRScanner,
|
||||
) error {
|
||||
if scanner == nil {
|
||||
return fmt.Errorf("wecom QR scanner is nil")
|
||||
}
|
||||
if writer == nil {
|
||||
writer = os.Stdout
|
||||
}
|
||||
|
||||
cfg, err := internal.LoadConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
|
||||
opts := defaultWeComQRFlowOptions(timeout)
|
||||
opts.Writer = writer
|
||||
|
||||
botInfo, err := scanner(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
applyWeComAuthResult(cfg, botInfo)
|
||||
|
||||
if saveErr := config.SaveConfig(internal.GetConfigPath(), cfg); saveErr != nil {
|
||||
return fmt.Errorf("failed to save config: %w", saveErr)
|
||||
}
|
||||
|
||||
fmt.Fprintln(writer)
|
||||
fmt.Fprintln(writer, "WeCom connected.")
|
||||
fmt.Fprintf(writer, "Bot ID: %s\n", botInfo.BotID)
|
||||
fmt.Fprintf(writer, "Config: %s\n", internal.GetConfigPath())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func defaultWeComQRFlowOptions(timeout time.Duration) wecomQRFlowOptions {
|
||||
if timeout <= 0 {
|
||||
timeout = wecomQRPollTimeout
|
||||
}
|
||||
|
||||
return wecomQRFlowOptions{
|
||||
HTTPClient: &http.Client{Timeout: wecomQRHTTPTimeout},
|
||||
GenerateURL: wecomQRGenerateEndpoint,
|
||||
QueryURL: wecomQRQueryEndpoint,
|
||||
QRCodePageURL: wecomQRPageEndpoint,
|
||||
SourceID: wecomQRSourceID,
|
||||
PollInterval: wecomQRPollInterval,
|
||||
PollTimeout: timeout,
|
||||
Writer: os.Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
func applyWeComAuthResult(cfg *config.Config, botInfo wecomQRBotInfo) {
|
||||
cfg.Channels.WeCom.Enabled = true
|
||||
cfg.Channels.WeCom.BotID = botInfo.BotID
|
||||
cfg.Channels.WeCom.SetSecret(botInfo.Secret)
|
||||
if strings.TrimSpace(cfg.Channels.WeCom.WebSocketURL) == "" {
|
||||
cfg.Channels.WeCom.WebSocketURL = wecomDefaultWebSocketURL
|
||||
}
|
||||
}
|
||||
|
||||
func scanWeComQRCodeInteractive(ctx context.Context, opts wecomQRFlowOptions) (wecomQRBotInfo, error) {
|
||||
opts = normalizeWeComQRFlowOptions(opts)
|
||||
|
||||
fmt.Fprintln(opts.Writer, "Requesting WeCom QR code...")
|
||||
|
||||
session, err := fetchWeComQRCode(ctx, opts)
|
||||
if err != nil {
|
||||
return wecomQRBotInfo{}, err
|
||||
}
|
||||
|
||||
fmt.Fprintln(opts.Writer)
|
||||
fmt.Fprintln(opts.Writer, "=======================================================")
|
||||
fmt.Fprintln(opts.Writer, "Please scan the following QR code with WeCom:")
|
||||
fmt.Fprintln(opts.Writer, "=======================================================")
|
||||
fmt.Fprintln(opts.Writer)
|
||||
|
||||
qrterminal.GenerateWithConfig(session.AuthURL, qrterminal.Config{
|
||||
Level: qrterminal.L,
|
||||
Writer: opts.Writer,
|
||||
HalfBlocks: true,
|
||||
})
|
||||
|
||||
pageURL, err := buildWeComQRCodePageURL(opts.QRCodePageURL, opts.SourceID, session.SCode)
|
||||
if err != nil {
|
||||
return wecomQRBotInfo{}, err
|
||||
}
|
||||
|
||||
fmt.Fprintln(opts.Writer)
|
||||
fmt.Fprintf(opts.Writer, "QR Code Link: %s\n", pageURL)
|
||||
fmt.Fprintln(opts.Writer)
|
||||
fmt.Fprintln(opts.Writer, "Waiting for scan...")
|
||||
|
||||
return pollWeComQRCodeResult(ctx, opts, session.SCode)
|
||||
}
|
||||
|
||||
func normalizeWeComQRFlowOptions(opts wecomQRFlowOptions) wecomQRFlowOptions {
|
||||
if opts.HTTPClient == nil {
|
||||
opts.HTTPClient = &http.Client{Timeout: wecomQRHTTPTimeout}
|
||||
}
|
||||
if strings.TrimSpace(opts.GenerateURL) == "" {
|
||||
opts.GenerateURL = wecomQRGenerateEndpoint
|
||||
}
|
||||
if strings.TrimSpace(opts.QueryURL) == "" {
|
||||
opts.QueryURL = wecomQRQueryEndpoint
|
||||
}
|
||||
if strings.TrimSpace(opts.QRCodePageURL) == "" {
|
||||
opts.QRCodePageURL = wecomQRPageEndpoint
|
||||
}
|
||||
if strings.TrimSpace(opts.SourceID) == "" {
|
||||
opts.SourceID = wecomQRSourceID
|
||||
}
|
||||
if opts.PollInterval <= 0 {
|
||||
opts.PollInterval = wecomQRPollInterval
|
||||
}
|
||||
if opts.PollTimeout <= 0 {
|
||||
opts.PollTimeout = wecomQRPollTimeout
|
||||
}
|
||||
if opts.Writer == nil {
|
||||
opts.Writer = os.Stdout
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func fetchWeComQRCode(ctx context.Context, opts wecomQRFlowOptions) (wecomQRSession, error) {
|
||||
generateURL, err := buildWeComQRGenerateURL(opts.GenerateURL, opts.SourceID, wecomPlatformCode())
|
||||
if err != nil {
|
||||
return wecomQRSession{}, err
|
||||
}
|
||||
|
||||
var resp wecomQRGenerateResponse
|
||||
if err := doWeComJSONGet(ctx, opts.HTTPClient, generateURL, &resp); err != nil {
|
||||
return wecomQRSession{}, fmt.Errorf("failed to get WeCom QR code: %w", err)
|
||||
}
|
||||
if resp.ErrCode != 0 {
|
||||
return wecomQRSession{}, fmt.Errorf(
|
||||
"failed to get WeCom QR code: errcode=%d errmsg=%s",
|
||||
resp.ErrCode,
|
||||
resp.ErrMsg,
|
||||
)
|
||||
}
|
||||
if resp.Data.SCode == "" || resp.Data.AuthURL == "" {
|
||||
return wecomQRSession{}, fmt.Errorf("failed to get WeCom QR code: response missing scode or auth_url")
|
||||
}
|
||||
|
||||
return wecomQRSession{
|
||||
SCode: resp.Data.SCode,
|
||||
AuthURL: resp.Data.AuthURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func pollWeComQRCodeResult(ctx context.Context, opts wecomQRFlowOptions, scode string) (wecomQRBotInfo, error) {
|
||||
if strings.TrimSpace(scode) == "" {
|
||||
return wecomQRBotInfo{}, fmt.Errorf("missing WeCom QR scode")
|
||||
}
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, opts.PollTimeout)
|
||||
defer cancel()
|
||||
|
||||
var scannedPrinted bool
|
||||
|
||||
for {
|
||||
status, err := queryWeComQRCodeStatus(timeoutCtx, opts, scode)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) || errors.Is(timeoutCtx.Err(), context.DeadlineExceeded) {
|
||||
return wecomQRBotInfo{}, fmt.Errorf("WeCom QR scan timed out after %s", opts.PollTimeout)
|
||||
}
|
||||
return wecomQRBotInfo{}, err
|
||||
}
|
||||
|
||||
switch strings.ToLower(status.Data.Status) {
|
||||
case "success":
|
||||
if status.Data.BotInfo.BotID == "" || status.Data.BotInfo.Secret == "" {
|
||||
return wecomQRBotInfo{}, fmt.Errorf("WeCom QR scan succeeded but bot credentials are missing")
|
||||
}
|
||||
return wecomQRBotInfo{
|
||||
BotID: status.Data.BotInfo.BotID,
|
||||
Secret: status.Data.BotInfo.Secret,
|
||||
}, nil
|
||||
case "expired":
|
||||
return wecomQRBotInfo{}, fmt.Errorf("WeCom QR code expired, please retry")
|
||||
case "scaned", "scanned":
|
||||
if !scannedPrinted {
|
||||
fmt.Fprintln(opts.Writer, "QR code scanned. Confirm the login in WeCom.")
|
||||
scannedPrinted = true
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-timeoutCtx.Done():
|
||||
if errors.Is(timeoutCtx.Err(), context.DeadlineExceeded) {
|
||||
return wecomQRBotInfo{}, fmt.Errorf("WeCom QR scan timed out after %s", opts.PollTimeout)
|
||||
}
|
||||
return wecomQRBotInfo{}, timeoutCtx.Err()
|
||||
case <-time.After(opts.PollInterval):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func queryWeComQRCodeStatus(ctx context.Context, opts wecomQRFlowOptions, scode string) (wecomQRQueryResponse, error) {
|
||||
queryURL, err := buildWeComQRQueryURL(opts.QueryURL, scode)
|
||||
if err != nil {
|
||||
return wecomQRQueryResponse{}, err
|
||||
}
|
||||
|
||||
var resp wecomQRQueryResponse
|
||||
if err := doWeComJSONGet(ctx, opts.HTTPClient, queryURL, &resp); err != nil {
|
||||
return wecomQRQueryResponse{}, fmt.Errorf("failed to query WeCom QR result: %w", err)
|
||||
}
|
||||
if resp.ErrCode != 0 {
|
||||
return wecomQRQueryResponse{}, fmt.Errorf(
|
||||
"failed to query WeCom QR result: errcode=%d errmsg=%s",
|
||||
resp.ErrCode,
|
||||
resp.ErrMsg,
|
||||
)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func buildWeComQRGenerateURL(baseURL, sourceID string, platformCode int) (string, error) {
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid WeCom QR generate URL: %w", err)
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
query.Set("source", sourceID)
|
||||
query.Set("sourceID", sourceID)
|
||||
query.Set("plat", strconv.Itoa(platformCode))
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func buildWeComQRQueryURL(baseURL, scode string) (string, error) {
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid WeCom QR query URL: %w", err)
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
query.Set("scode", scode)
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func buildWeComQRCodePageURL(baseURL, sourceID, scode string) (string, error) {
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid WeCom QR page URL: %w", err)
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
query.Set("source", sourceID)
|
||||
query.Set("sourceID", sourceID)
|
||||
query.Set("scode", scode)
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func doWeComJSONGet(ctx context.Context, client *http.Client, targetURL string, out any) error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 8192))
|
||||
if readErr != nil {
|
||||
return fmt.Errorf("unexpected status %s", resp.Status)
|
||||
}
|
||||
return fmt.Errorf("unexpected status %s: %s", resp.Status, strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
||||
return fmt.Errorf("decode JSON response: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func wecomPlatformCode() int {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return 1
|
||||
case "windows":
|
||||
return 2
|
||||
case "linux":
|
||||
return 3
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
157
cmd/picoclaw/internal/auth/wecom_test.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
func TestNewWeComCommand(t *testing.T) {
|
||||
cmd := newWeComCommand()
|
||||
|
||||
require.NotNil(t, cmd)
|
||||
assert.Equal(t, "wecom", cmd.Use)
|
||||
assert.Equal(t, "Scan a WeCom QR code and configure channels.wecom", cmd.Short)
|
||||
assert.NotNil(t, cmd.Flags().Lookup("timeout"))
|
||||
}
|
||||
|
||||
func TestBuildWeComQRGenerateURL(t *testing.T) {
|
||||
rawURL, err := buildWeComQRGenerateURL("https://example.com/ai/qc/generate", wecomQRSourceID, 3)
|
||||
require.NoError(t, err)
|
||||
|
||||
parsed, err := url.Parse(rawURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, wecomQRSourceID, parsed.Query().Get("source"))
|
||||
assert.Equal(t, wecomQRSourceID, parsed.Query().Get("sourceID"))
|
||||
assert.Equal(t, "3", parsed.Query().Get("plat"))
|
||||
}
|
||||
|
||||
func TestBuildWeComQRCodePageURL(t *testing.T) {
|
||||
rawURL, err := buildWeComQRCodePageURL("https://example.com/ai/qc/gen", wecomQRSourceID, "scode-1")
|
||||
require.NoError(t, err)
|
||||
|
||||
parsed, err := url.Parse(rawURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, wecomQRSourceID, parsed.Query().Get("source"))
|
||||
assert.Equal(t, wecomQRSourceID, parsed.Query().Get("sourceID"))
|
||||
assert.Equal(t, "scode-1", parsed.Query().Get("scode"))
|
||||
}
|
||||
|
||||
func TestFetchWeComQRCode(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/generate", r.URL.Path)
|
||||
assert.Equal(t, wecomQRSourceID, r.URL.Query().Get("source"))
|
||||
assert.Equal(t, wecomQRSourceID, r.URL.Query().Get("sourceID"))
|
||||
assert.Equal(t, strconv.Itoa(wecomPlatformCode()), r.URL.Query().Get("plat"))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"data":{"scode":"scode-1","auth_url":"https://example.com/qr"}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
opts := normalizeWeComQRFlowOptions(wecomQRFlowOptions{
|
||||
HTTPClient: server.Client(),
|
||||
GenerateURL: server.URL + "/generate",
|
||||
Writer: bytes.NewBuffer(nil),
|
||||
})
|
||||
|
||||
session, err := fetchWeComQRCode(context.Background(), opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "scode-1", session.SCode)
|
||||
assert.Equal(t, "https://example.com/qr", session.AuthURL)
|
||||
}
|
||||
|
||||
func TestPollWeComQRCodeResult(t *testing.T) {
|
||||
var calls atomic.Int32
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
call := calls.Add(1)
|
||||
assert.Equal(t, "/query", r.URL.Path)
|
||||
assert.Equal(t, "scode-1", r.URL.Query().Get("scode"))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch call {
|
||||
case 1:
|
||||
_, _ = w.Write([]byte(`{"data":{"status":"wait"}}`))
|
||||
case 2:
|
||||
_, _ = w.Write([]byte(`{"data":{"status":"scaned"}}`))
|
||||
default:
|
||||
_, _ = w.Write([]byte(`{"data":{"status":"success","bot_info":{"botid":"bot-1","secret":"secret-1"}}}`))
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
var output bytes.Buffer
|
||||
opts := normalizeWeComQRFlowOptions(wecomQRFlowOptions{
|
||||
HTTPClient: server.Client(),
|
||||
QueryURL: server.URL + "/query",
|
||||
PollInterval: time.Millisecond,
|
||||
PollTimeout: time.Second,
|
||||
Writer: &output,
|
||||
})
|
||||
|
||||
botInfo, err := pollWeComQRCodeResult(context.Background(), opts, "scode-1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "bot-1", botInfo.BotID)
|
||||
assert.Equal(t, "secret-1", botInfo.Secret)
|
||||
assert.Contains(t, output.String(), "QR code scanned. Confirm the login in WeCom.")
|
||||
}
|
||||
|
||||
func TestApplyWeComAuthResult(t *testing.T) {
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Channels.WeCom.WebSocketURL = ""
|
||||
|
||||
applyWeComAuthResult(cfg, wecomQRBotInfo{
|
||||
BotID: "bot-1",
|
||||
Secret: "secret-1",
|
||||
})
|
||||
|
||||
assert.True(t, cfg.Channels.WeCom.Enabled)
|
||||
assert.Equal(t, "bot-1", cfg.Channels.WeCom.BotID)
|
||||
assert.Equal(t, "secret-1", cfg.Channels.WeCom.Secret.String())
|
||||
assert.Equal(t, wecomDefaultWebSocketURL, cfg.Channels.WeCom.WebSocketURL)
|
||||
}
|
||||
|
||||
func TestAuthWeComCmdWithScanner(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "config.json")
|
||||
|
||||
t.Setenv(config.EnvHome, tmpDir)
|
||||
t.Setenv(config.EnvConfig, configPath)
|
||||
|
||||
var output bytes.Buffer
|
||||
err := authWeComCmdWithScanner(
|
||||
context.Background(),
|
||||
&output,
|
||||
time.Second,
|
||||
func(_ context.Context, opts wecomQRFlowOptions) (wecomQRBotInfo, error) {
|
||||
assert.Equal(t, wecomQRSourceID, opts.SourceID)
|
||||
return wecomQRBotInfo{
|
||||
BotID: "bot-1",
|
||||
Secret: "secret-1",
|
||||
}, nil
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg, err := config.LoadConfig(internal.GetConfigPath())
|
||||
require.NoError(t, err)
|
||||
assert.True(t, cfg.Channels.WeCom.Enabled)
|
||||
assert.Equal(t, "bot-1", cfg.Channels.WeCom.BotID)
|
||||
assert.Equal(t, "secret-1", cfg.Channels.WeCom.Secret.String())
|
||||
assert.Equal(t, wecomDefaultWebSocketURL, cfg.Channels.WeCom.WebSocketURL)
|
||||
assert.Contains(t, output.String(), "WeCom connected.")
|
||||
}
|
||||
124
cmd/picoclaw/internal/auth/weixin.go
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
package auth
|
||||
|
||||
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 auth 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(` }`)
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@ func newAddCommand(storePath func() string) *cobra.Command {
|
|||
message string
|
||||
every int64
|
||||
cronExp string
|
||||
deliver bool
|
||||
channel string
|
||||
to string
|
||||
)
|
||||
|
|
@ -37,7 +36,7 @@ func newAddCommand(storePath func() string) *cobra.Command {
|
|||
}
|
||||
|
||||
cs := cron.NewCronService(storePath(), nil)
|
||||
job, err := cs.AddJob(name, schedule, message, deliver, channel, to)
|
||||
job, err := cs.AddJob(name, schedule, message, channel, to)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding job: %w", err)
|
||||
}
|
||||
|
|
@ -52,7 +51,6 @@ func newAddCommand(storePath func() string) *cobra.Command {
|
|||
cmd.Flags().StringVarP(&message, "message", "m", "", "Message for agent")
|
||||
cmd.Flags().Int64VarP(&every, "every", "e", 0, "Run every N seconds")
|
||||
cmd.Flags().StringVarP(&cronExp, "cron", "c", "", "Cron expression (e.g. '0 9 * * *')")
|
||||
cmd.Flags().BoolVarP(&deliver, "deliver", "d", false, "Deliver response to channel")
|
||||
cmd.Flags().StringVar(&to, "to", "", "Recipient for delivery")
|
||||
cmd.Flags().StringVar(&channel, "channel", "", "Channel for delivery")
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ func TestNewAddSubcommand(t *testing.T) {
|
|||
|
||||
assert.NotNil(t, cmd.Flags().Lookup("every"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("cron"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("deliver"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("to"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("channel"))
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,19 +4,17 @@ 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
|
||||
func GetPicoclawHome() string {
|
||||
if home := os.Getenv(config.EnvHome); home != "" {
|
||||
return home
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(home, ".picoclaw")
|
||||
return config.GetHome()
|
||||
}
|
||||
|
||||
func GetConfigPath() string {
|
||||
|
|
@ -27,7 +25,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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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.Enabled {
|
||||
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.Enabled && 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 {
|
||||
|
|
|
|||
|
|
@ -64,9 +64,19 @@ func TestShowCurrentModel_WithDefaultModel(t *testing.T) {
|
|||
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",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "claude-3",
|
||||
Model: "anthropic/claude-3",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -85,11 +95,15 @@ func TestShowCurrentModel_NoDefaultModel(t *testing.T) {
|
|||
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",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -101,26 +115,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() {
|
||||
|
|
@ -137,10 +134,20 @@ func TestListAvailableModels_WithModels(t *testing.T) {
|
|||
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",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "claude-3",
|
||||
Model: "anthropic/claude-3",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{ModelName: "no-key-model", Model: "openai/test"},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -163,9 +170,19 @@ func TestSetDefaultModel_ValidModel(t *testing.T) {
|
|||
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",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "old-model",
|
||||
Model: "openai/old-model",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -180,29 +197,6 @@ 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) {
|
||||
|
|
@ -214,8 +208,13 @@ func TestSetDefaultModel_InvalidModel(t *testing.T) {
|
|||
ModelName: "existing-model",
|
||||
},
|
||||
},
|
||||
ModelList: []config.ModelConfig{
|
||||
{ModelName: "existing-model", Model: "openai/existing", APIKey: "test"},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{
|
||||
ModelName: "existing-model",
|
||||
Model: "openai/existing",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -231,9 +230,14 @@ func TestSetDefaultModel_ModelWithoutAPIKey(t *testing.T) {
|
|||
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",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{ModelName: "no-key-model", Model: "openai/nokey"},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -250,8 +254,13 @@ func TestSetDefaultModel_SaveConfigError(t *testing.T) {
|
|||
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",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -291,8 +300,13 @@ func TestModelCommandExecution_Show(t *testing.T) {
|
|||
ModelName: "test-model",
|
||||
},
|
||||
},
|
||||
ModelList: []config.ModelConfig{
|
||||
{ModelName: "test-model", Model: "openai/test", APIKey: "test"},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{
|
||||
ModelName: "test-model",
|
||||
Model: "openai/test",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -318,9 +332,19 @@ func TestModelCommandExecution_Set(t *testing.T) {
|
|||
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",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "new-model",
|
||||
Model: "openai/new",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -352,10 +376,25 @@ func TestListAvailableModels_MarkerLogic(t *testing.T) {
|
|||
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",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "middle-model",
|
||||
Model: "openai/middle",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "last-model",
|
||||
Model: "openai/last",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,13 @@ func NewOnboardCommand() *cobra.Command {
|
|||
Use: "onboard",
|
||||
Aliases: []string{"o"},
|
||||
Short: "Initialize picoclaw configuration and workspace",
|
||||
// Run without subcommands → original onboard flow
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
onboard(encrypt)
|
||||
if len(args) == 0 {
|
||||
onboard(encrypt)
|
||||
} else {
|
||||
_ = cmd.Help()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,11 @@ func onboard(encrypt bool) {
|
|||
fmt.Println("")
|
||||
fmt.Println(" See README.md for 17+ supported providers.")
|
||||
fmt.Println("")
|
||||
fmt.Println(" 3. Chat: picoclaw agent -m \"Hello!\"")
|
||||
if encrypt {
|
||||
fmt.Println(" 3. Chat: picoclaw agent -m \"Hello!\"")
|
||||
} else {
|
||||
fmt.Println(" 2. Chat: picoclaw agent -m \"Hello!\"")
|
||||
}
|
||||
}
|
||||
|
||||
// promptPassphrase reads the encryption passphrase twice from the terminal
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.String(),
|
||||
cfg.Tools.Skills.Github.Proxy,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -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.String(),
|
||||
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.String(),
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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:")
|
||||
|
|
|
|||
|
|
@ -5,10 +5,16 @@
|
|||
"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,
|
||||
"summarize_token_percent": 75
|
||||
"summarize_token_percent": 75,
|
||||
"split_on_marker": false,
|
||||
"tool_feedback": {
|
||||
"enabled": false,
|
||||
"max_args_length": 300
|
||||
}
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
|
|
@ -80,7 +86,10 @@
|
|||
"proxy": "",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"use_markdown_v2": false,
|
||||
"reasoning_channel_id": ""
|
||||
"reasoning_channel_id": "",
|
||||
"streaming": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"discord": {
|
||||
"enabled": false,
|
||||
|
|
@ -121,6 +130,10 @@
|
|||
"encrypt_key": "",
|
||||
"verification_token": "",
|
||||
"allow_from": [],
|
||||
"placeholder": {
|
||||
"enabled": true,
|
||||
"text": ["Thinking...", "Processing...", "Typing..."]
|
||||
},
|
||||
"reasoning_channel_id": "",
|
||||
"random_reaction_emoji": [],
|
||||
"is_lark": false
|
||||
|
|
@ -152,9 +165,11 @@
|
|||
},
|
||||
"placeholder": {
|
||||
"enabled": true,
|
||||
"text": "Thinking... 💭"
|
||||
"text": ["Thinking...", "Processing...", "Typing..."]
|
||||
},
|
||||
"reasoning_channel_id": ""
|
||||
"reasoning_channel_id": "",
|
||||
"crypto_database_path": "",
|
||||
"crypto_passphrase": "YOUR_MATRIX_CRYPTO_PICKLE_KEY"
|
||||
},
|
||||
"line": {
|
||||
"enabled": false,
|
||||
|
|
@ -174,38 +189,33 @@
|
|||
"reasoning_channel_id": ""
|
||||
},
|
||||
"wecom": {
|
||||
"_comment": "WeCom Bot - Easier setup, supports group chats",
|
||||
"_comment": "WeCom AI Bot over WebSocket.",
|
||||
"enabled": false,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
|
||||
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
|
||||
"webhook_path": "/webhook/wecom",
|
||||
"bot_id": "YOUR_BOT_ID",
|
||||
"secret": "YOUR_SECRET",
|
||||
"websocket_url": "wss://openws.work.weixin.qq.com",
|
||||
"send_thinking_message": true,
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5,
|
||||
"reasoning_channel_id": ""
|
||||
},
|
||||
"wecom_app": {
|
||||
"_comment": "WeCom App (自建应用) - More features, proactive messaging, private chat only.",
|
||||
"pico": {
|
||||
"enabled": false,
|
||||
"corp_id": "YOUR_CORP_ID",
|
||||
"corp_secret": "YOUR_CORP_SECRET",
|
||||
"agent_id": 1000002,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-app",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5,
|
||||
"reasoning_channel_id": ""
|
||||
"token": "YOUR_PICO_TOKEN",
|
||||
"allow_token_query": false,
|
||||
"allow_origins": [],
|
||||
"ping_interval": 30,
|
||||
"read_timeout": 60,
|
||||
"max_connections": 100,
|
||||
"allow_from": []
|
||||
},
|
||||
"wecom_aibot": {
|
||||
"_comment": "WeCom AI Bot (智能机器人) - Official WeCom AI Bot integration, supports proactive messaging and private chats.",
|
||||
"pico_client": {
|
||||
"enabled": false,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-aibot",
|
||||
"max_steps": 10,
|
||||
"welcome_message": "Hello! I'm your AI assistant. How can I help you today?",
|
||||
"reasoning_channel_id": ""
|
||||
"url": "wss://remote-pico-server/pico/ws",
|
||||
"token": "YOUR_PICO_TOKEN",
|
||||
"session_id": "",
|
||||
"ping_interval": 30,
|
||||
"read_timeout": 60,
|
||||
"allow_from": []
|
||||
},
|
||||
"irc": {
|
||||
"enabled": false,
|
||||
|
|
@ -218,13 +228,8 @@
|
|||
"nickserv_password": "",
|
||||
"sasl_user": "",
|
||||
"sasl_password": "",
|
||||
"channels": [
|
||||
"#mychannel"
|
||||
],
|
||||
"request_caps": [
|
||||
"server-time",
|
||||
"message-tags"
|
||||
],
|
||||
"channels": ["#mychannel"],
|
||||
"request_caps": ["server-time", "message-tags"],
|
||||
"allow_from": [],
|
||||
"group_trigger": {
|
||||
"mention_only": true
|
||||
|
|
@ -235,79 +240,6 @@
|
|||
"reasoning_channel_id": ""
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"_comment": "DEPRECATED: Use model_list instead. This will be removed in a future version",
|
||||
"anthropic": {
|
||||
"api_key": "",
|
||||
"api_base": ""
|
||||
},
|
||||
"openai": {
|
||||
"api_key": "",
|
||||
"api_base": "",
|
||||
"web_search": true
|
||||
},
|
||||
"openrouter": {
|
||||
"api_key": "sk-or-v1-xxx",
|
||||
"api_base": ""
|
||||
},
|
||||
"groq": {
|
||||
"api_key": "gsk_xxx",
|
||||
"api_base": ""
|
||||
},
|
||||
"zhipu": {
|
||||
"api_key": "YOUR_ZHIPU_API_KEY",
|
||||
"api_base": ""
|
||||
},
|
||||
"gemini": {
|
||||
"api_key": "",
|
||||
"api_base": ""
|
||||
},
|
||||
"vllm": {
|
||||
"api_key": "",
|
||||
"api_base": ""
|
||||
},
|
||||
"nvidia": {
|
||||
"api_key": "nvapi-xxx",
|
||||
"api_base": "",
|
||||
"proxy": "http://127.0.0.1:7890"
|
||||
},
|
||||
"moonshot": {
|
||||
"api_key": "sk-xxx",
|
||||
"api_base": ""
|
||||
},
|
||||
"qwen": {
|
||||
"api_key": "sk-xxx",
|
||||
"api_base": ""
|
||||
},
|
||||
"ollama": {
|
||||
"api_key": "",
|
||||
"api_base": "http://localhost:11434/v1"
|
||||
},
|
||||
"cerebras": {
|
||||
"api_key": "",
|
||||
"api_base": ""
|
||||
},
|
||||
"volcengine": {
|
||||
"api_key": "",
|
||||
"api_base": ""
|
||||
},
|
||||
"mistral": {
|
||||
"api_key": "",
|
||||
"api_base": "https://api.mistral.ai/v1"
|
||||
},
|
||||
"avian": {
|
||||
"api_key": "",
|
||||
"api_base": "https://api.avian.io/v1"
|
||||
},
|
||||
"longcat": {
|
||||
"api_key": "",
|
||||
"api_base": "https://api.longcat.chat/openai"
|
||||
},
|
||||
"modelscope": {
|
||||
"api_key": "",
|
||||
"api_base": "https://api-inference.modelscope.cn/v1"
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"allow_read_paths": null,
|
||||
"allow_write_paths": null,
|
||||
|
|
@ -319,9 +251,7 @@
|
|||
"brave": {
|
||||
"enabled": false,
|
||||
"api_key": "YOUR_BRAVE_API_KEY",
|
||||
"api_keys": [
|
||||
"YOUR_BRAVE_API_KEY"
|
||||
],
|
||||
"api_keys": ["YOUR_BRAVE_API_KEY"],
|
||||
"max_results": 5
|
||||
},
|
||||
"tavily": {
|
||||
|
|
@ -337,9 +267,7 @@
|
|||
"perplexity": {
|
||||
"enabled": false,
|
||||
"api_key": "pplx-xxx",
|
||||
"api_keys": [
|
||||
"pplx-xxx"
|
||||
],
|
||||
"api_keys": ["pplx-xxx"],
|
||||
"max_results": 5
|
||||
},
|
||||
"searxng": {
|
||||
|
|
@ -354,6 +282,12 @@
|
|||
"search_engine": "search_std",
|
||||
"max_results": 5
|
||||
},
|
||||
"baidu_search": {
|
||||
"enabled": false,
|
||||
"api_key": "",
|
||||
"base_url": "https://qianfan.baidubce.com/v2/ai_search/web_search",
|
||||
"max_results": 10
|
||||
},
|
||||
"fetch_limit_bytes": 10485760,
|
||||
"private_host_whitelist": []
|
||||
},
|
||||
|
|
@ -382,19 +316,12 @@
|
|||
"filesystem": {
|
||||
"enabled": false,
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"/tmp"
|
||||
]
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
},
|
||||
"github": {
|
||||
"enabled": false,
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-github"
|
||||
],
|
||||
"args": ["-y", "@modelcontextprotocol/server-github"],
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_GITHUB_TOKEN"
|
||||
}
|
||||
|
|
@ -402,10 +329,7 @@
|
|||
"brave-search": {
|
||||
"enabled": false,
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-brave-search"
|
||||
],
|
||||
"args": ["-y", "@modelcontextprotocol/server-brave-search"],
|
||||
"env": {
|
||||
"BRAVE_API_KEY": "YOUR_BRAVE_API_KEY"
|
||||
}
|
||||
|
|
@ -422,10 +346,7 @@
|
|||
"slack": {
|
||||
"enabled": false,
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-slack"
|
||||
],
|
||||
"args": ["-y", "@modelcontextprotocol/server-slack"],
|
||||
"env": {
|
||||
"SLACK_BOT_TOKEN": "YOUR_SLACK_BOT_TOKEN",
|
||||
"SLACK_TEAM_ID": "YOUR_SLACK_TEAM_ID"
|
||||
|
|
@ -518,11 +439,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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
67
docker/Dockerfile.heavy
Normal file
|
|
@ -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"]
|
||||
|
|
@ -24,7 +24,7 @@ services:
|
|||
picoclaw-gateway:
|
||||
image: docker.io/sipeed/picoclaw:latest
|
||||
container_name: picoclaw-gateway
|
||||
restart: on-failure
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- gateway
|
||||
# Uncomment to access host network; leave commented unless needed.
|
||||
|
|
@ -40,7 +40,7 @@ services:
|
|||
picoclaw-launcher:
|
||||
image: docker.io/sipeed/picoclaw:launcher
|
||||
container_name: picoclaw-launcher
|
||||
restart: on-failure
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- launcher
|
||||
environment:
|
||||
|
|
|
|||
|
|
@ -438,7 +438,7 @@ type ProviderAuthResult = {
|
|||
|
||||
### 1. Required Environment/Dependencies
|
||||
|
||||
- Go ≥ 1.21
|
||||
- Go ≥ 1.25
|
||||
- PicoClaw codebase (`pkg/providers/` and `pkg/auth/`)
|
||||
- `crypto` and `net/http` standard library packages
|
||||
|
||||
|
|
@ -584,7 +584,7 @@ Each SSE message (`data: {...}`) is wrapped in a `response` field:
|
|||
],
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": "gemini-flash"
|
||||
"model_name": "gemini-flash"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -674,7 +674,7 @@ Add a default entry in `pkg/config/defaults.go`:
|
|||
|
||||
#### 5. Add Auth Support (Optional)
|
||||
|
||||
If your provider requires OAuth or special authentication, add a case to `cmd/picoclaw/cmd_auth.go`:
|
||||
If your provider requires OAuth or special authentication, add a case to `cmd/picoclaw/internal/auth/helpers.go`:
|
||||
|
||||
```go
|
||||
case "your-provider":
|
||||
|
|
@ -736,7 +736,7 @@ export PICOCLAW_MODEL_LIST='[{"model_name":"your-model","model":"your-provider/m
|
|||
- `pkg/auth/store.go` - Auth credential storage (`~/.picoclaw/auth.json`)
|
||||
- `pkg/providers/factory.go` - Provider factory and protocol routing
|
||||
- `pkg/providers/types.go` - Provider interface definitions
|
||||
- `cmd/picoclaw/cmd_auth.go` - Auth CLI commands
|
||||
- `cmd/picoclaw/internal/auth/helpers.go` - Auth CLI commands
|
||||
|
||||
- **Documentation:**
|
||||
- `docs/ANTIGRAVITY_USAGE.md` - Antigravity usage guide
|
||||
|
|
|
|||
164
docs/agent-refactor/context.md
Normal file
|
|
@ -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
|
||||
35
docs/channels/dingtalk/README.fr.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> Retour au [README](../../../README.fr.md)
|
||||
|
||||
# DingTalk
|
||||
|
||||
DingTalk est la plateforme de communication d'entreprise d'Alibaba, très populaire dans les milieux professionnels chinois. Elle utilise un SDK de streaming pour maintenir des connexions persistantes.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"dingtalk": {
|
||||
"enabled": true,
|
||||
"client_id": "YOUR_CLIENT_ID",
|
||||
"client_secret": "YOUR_CLIENT_SECRET",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Champ | Type | Requis | Description |
|
||||
| ------------- | ------ | ------ | ---------------------------------------------------------------- |
|
||||
| enabled | bool | Oui | Activer ou non le canal DingTalk |
|
||||
| client_id | string | Oui | Client ID de l'application DingTalk |
|
||||
| client_secret | string | Oui | Client Secret de l'application DingTalk |
|
||||
| allow_from | array | Non | Liste blanche d'ID utilisateurs ; vide signifie tous les utilisateurs |
|
||||
|
||||
## Procédure de configuration
|
||||
|
||||
1. Rendez-vous sur la [plateforme ouverte DingTalk](https://open.dingtalk.com/)
|
||||
2. Créez une application interne d'entreprise
|
||||
3. Obtenez le Client ID et le Client Secret depuis les paramètres de l'application
|
||||
4. Configurez OAuth et les abonnements aux événements (si nécessaire)
|
||||
5. Renseignez le Client ID et le Client Secret dans le fichier de configuration
|
||||
35
docs/channels/dingtalk/README.ja.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> [README](../../../README.ja.md) に戻る
|
||||
|
||||
# DingTalk
|
||||
|
||||
DingTalkはアリババの企業向けコミュニケーションプラットフォームで、中国のビジネス環境で広く利用されています。ストリーミング SDK を使用して持続的な接続を維持します。
|
||||
|
||||
## 設定
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"dingtalk": {
|
||||
"enabled": true,
|
||||
"client_id": "YOUR_CLIENT_ID",
|
||||
"client_secret": "YOUR_CLIENT_SECRET",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 型 | 必須 | 説明 |
|
||||
| ------------- | ------ | ---- | -------------------------------------------- |
|
||||
| enabled | bool | はい | DingTalk チャンネルを有効にするかどうか |
|
||||
| client_id | string | はい | DingTalk アプリケーションの Client ID |
|
||||
| client_secret | string | はい | DingTalk アプリケーションの Client Secret |
|
||||
| allow_from | array | いいえ | ユーザーIDのホワイトリスト。空の場合は全ユーザーを許可 |
|
||||
|
||||
## セットアップ手順
|
||||
|
||||
1. [DingTalk オープンプラットフォーム](https://open.dingtalk.com/) にアクセスする
|
||||
2. 企業内部アプリケーションを作成する
|
||||
3. アプリケーション設定から Client ID と Client Secret を取得する
|
||||
4. OAuth とイベントサブスクリプションを設定する(必要な場合)
|
||||
5. Client ID と Client Secret を設定ファイルに入力する
|
||||
35
docs/channels/dingtalk/README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> Back to [README](../../../README.md)
|
||||
|
||||
# DingTalk
|
||||
|
||||
DingTalk is Alibaba's enterprise communication platform, widely used in Chinese workplaces. It uses a streaming SDK to maintain persistent connections.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"dingtalk": {
|
||||
"enabled": true,
|
||||
"client_id": "YOUR_CLIENT_ID",
|
||||
"client_secret": "YOUR_CLIENT_SECRET",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ------------- | ------ | -------- | -------------------------------------------------------- |
|
||||
| enabled | bool | Yes | Whether to enable the DingTalk channel |
|
||||
| client_id | string | Yes | Client ID of the DingTalk application |
|
||||
| client_secret | string | Yes | Client Secret of the DingTalk application |
|
||||
| allow_from | array | No | User ID whitelist; empty means all users are allowed |
|
||||
|
||||
## Setup
|
||||
|
||||
1. Go to the [DingTalk Open Platform](https://open.dingtalk.com/)
|
||||
2. Create an internal enterprise application
|
||||
3. Obtain the Client ID and Client Secret from the application settings
|
||||
4. Configure OAuth and event subscriptions (if needed)
|
||||
5. Fill in the Client ID and Client Secret in the configuration file
|
||||
35
docs/channels/dingtalk/README.pt-br.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> Voltar ao [README](../../../README.pt-br.md)
|
||||
|
||||
# DingTalk
|
||||
|
||||
DingTalk é a plataforma de comunicação empresarial da Alibaba, amplamente utilizada no ambiente corporativo chinês. Ela usa um SDK de streaming para manter conexões persistentes.
|
||||
|
||||
## Configuração
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"dingtalk": {
|
||||
"enabled": true,
|
||||
"client_id": "YOUR_CLIENT_ID",
|
||||
"client_secret": "YOUR_CLIENT_SECRET",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
| ------------- | ------ | ----------- | ---------------------------------------------------------------- |
|
||||
| enabled | bool | Sim | Se o canal DingTalk deve ser habilitado |
|
||||
| client_id | string | Sim | Client ID do aplicativo DingTalk |
|
||||
| client_secret | string | Sim | Client Secret do aplicativo DingTalk |
|
||||
| allow_from | array | Não | Lista de permissão de IDs de usuário; vazio permite todos |
|
||||
|
||||
## Configuração passo a passo
|
||||
|
||||
1. Acesse a [Plataforma Aberta DingTalk](https://open.dingtalk.com/)
|
||||
2. Crie um aplicativo interno corporativo
|
||||
3. Obtenha o Client ID e o Client Secret nas configurações do aplicativo
|
||||
4. Configure OAuth e assinaturas de eventos (se necessário)
|
||||
5. Preencha o Client ID e o Client Secret no arquivo de configuração
|
||||
35
docs/channels/dingtalk/README.vi.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> Quay lại [README](../../../README.vi.md)
|
||||
|
||||
# DingTalk
|
||||
|
||||
DingTalk là nền tảng giao tiếp doanh nghiệp của Alibaba, được sử dụng rộng rãi trong môi trường làm việc tại Trung Quốc. Nền tảng này sử dụng SDK streaming để duy trì kết nối liên tục.
|
||||
|
||||
## Cấu hình
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"dingtalk": {
|
||||
"enabled": true,
|
||||
"client_id": "YOUR_CLIENT_ID",
|
||||
"client_secret": "YOUR_CLIENT_SECRET",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Trường | Kiểu | Bắt buộc | Mô tả |
|
||||
| ------------- | ------ | -------- | ---------------------------------------------------------------- |
|
||||
| enabled | bool | Có | Có bật kênh DingTalk hay không |
|
||||
| client_id | string | Có | Client ID của ứng dụng DingTalk |
|
||||
| client_secret | string | Có | Client Secret của ứng dụng DingTalk |
|
||||
| allow_from | array | Không | Danh sách trắng ID người dùng; để trống cho phép tất cả |
|
||||
|
||||
## Quy trình thiết lập
|
||||
|
||||
1. Truy cập [Nền tảng mở DingTalk](https://open.dingtalk.com/)
|
||||
2. Tạo một ứng dụng nội bộ doanh nghiệp
|
||||
3. Lấy Client ID và Client Secret từ cài đặt ứng dụng
|
||||
4. Cấu hình OAuth và đăng ký sự kiện (nếu cần)
|
||||
5. Điền Client ID và Client Secret vào file cấu hình
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
> 返回 [README](../../../README.zh.md)
|
||||
|
||||
# 钉钉
|
||||
|
||||
钉钉是阿里巴巴的企业通讯平台,在中国职场中广受欢迎。它采用流式 SDK 来维持持久连接。
|
||||
|
|
|
|||
39
docs/channels/discord/README.fr.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
> Retour au [README](../../../README.fr.md)
|
||||
|
||||
# Discord
|
||||
|
||||
Discord est une application gratuite de chat vocal, vidéo et textuel conçue pour les communautés. PicoClaw se connecte aux serveurs Discord via l'API Bot Discord, avec prise en charge de la réception et de l'envoi de messages.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"discord": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"group_trigger": {
|
||||
"mention_only": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Champ | Type | Requis | Description |
|
||||
| ------------- | ------ | ------ | --------------------------------------------------------------------------- |
|
||||
| enabled | bool | Oui | Activer ou non le canal Discord |
|
||||
| token | string | Oui | Token du bot Discord |
|
||||
| allow_from | array | Non | Liste blanche d'identifiants utilisateur ; vide signifie tous les utilisateurs |
|
||||
| group_trigger | object | Non | Paramètres de déclenchement de groupe (exemple : { "mention_only": false }) |
|
||||
|
||||
## Configuration initiale
|
||||
|
||||
1. Accéder au [Portail des développeurs Discord](https://discord.com/developers/applications) et créer une nouvelle application
|
||||
2. Activer les Intents :
|
||||
- Message Content Intent
|
||||
- Server Members Intent
|
||||
3. Obtenir le Token du bot
|
||||
4. Renseigner le Token du bot dans le fichier de configuration
|
||||
5. Inviter le bot sur le serveur et lui accorder les permissions nécessaires (ex. envoyer des messages, lire l'historique des messages)
|
||||
39
docs/channels/discord/README.ja.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
> [README](../../../README.ja.md) に戻る
|
||||
|
||||
# Discord
|
||||
|
||||
Discord はコミュニティ向けに設計された無料の音声・ビデオ・テキストチャットアプリケーションです。PicoClaw は Discord Bot API を通じて Discord サーバーに接続し、メッセージの受信と送信をサポートします。
|
||||
|
||||
## 設定
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"discord": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"group_trigger": {
|
||||
"mention_only": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 型 | 必須 | 説明 |
|
||||
| ------------- | ------ | ------ | ----------------------------------------------------------------- |
|
||||
| enabled | bool | はい | Discord チャンネルを有効にするかどうか |
|
||||
| token | string | はい | Discord ボットトークン |
|
||||
| allow_from | array | いいえ | 許可するユーザーIDのリスト。空の場合はすべてのユーザーを許可 |
|
||||
| group_trigger | object | いいえ | グループトリガー設定(例: { "mention_only": false }) |
|
||||
|
||||
## セットアップ手順
|
||||
|
||||
1. [Discord 開発者ポータル](https://discord.com/developers/applications) にアクセスして新しいアプリケーションを作成する
|
||||
2. Intents を有効にする:
|
||||
- Message Content Intent
|
||||
- Server Members Intent
|
||||
3. Bot トークンを取得する
|
||||
4. 設定ファイルに Bot トークンを入力する
|
||||
5. ボットをサーバーに招待し、必要な権限を付与する(例: メッセージの送信、メッセージ履歴の読み取りなど)
|
||||
39
docs/channels/discord/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
> Back to [README](../../../README.md)
|
||||
|
||||
# Discord
|
||||
|
||||
Discord is a free voice, video, and text chat application designed for communities. PicoClaw connects to Discord servers via the Discord Bot API, supporting both receiving and sending messages.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"discord": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"group_trigger": {
|
||||
"mention_only": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ------------- | ------ | -------- | --------------------------------------------------------------------------- |
|
||||
| enabled | bool | Yes | Whether to enable the Discord channel |
|
||||
| token | string | Yes | Discord Bot Token |
|
||||
| allow_from | array | No | Allowlist of user IDs; empty means all users are allowed |
|
||||
| group_trigger | object | No | Group trigger settings (example: { "mention_only": false }) |
|
||||
|
||||
## Setup
|
||||
|
||||
1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) and create a new application
|
||||
2. Enable Intents:
|
||||
- Message Content Intent
|
||||
- Server Members Intent
|
||||
3. Obtain the Bot Token
|
||||
4. Fill in the Bot Token in the configuration file
|
||||
5. Invite the bot to your server and grant the necessary permissions (e.g. Send Messages, Read Message History)
|
||||
39
docs/channels/discord/README.pt-br.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
> Voltar ao [README](../../../README.pt-br.md)
|
||||
|
||||
# Discord
|
||||
|
||||
Discord é um aplicativo gratuito de chat de voz, vídeo e texto projetado para comunidades. O PicoClaw se conecta a servidores Discord via Discord Bot API, com suporte para receber e enviar mensagens.
|
||||
|
||||
## Configuração
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"discord": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"group_trigger": {
|
||||
"mention_only": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
| ------------- | ------ | ----------- | --------------------------------------------------------------------------- |
|
||||
| enabled | bool | Sim | Se o canal Discord deve ser habilitado |
|
||||
| token | string | Sim | Token do Bot Discord |
|
||||
| allow_from | array | Não | Lista de IDs de usuários permitidos; vazio significa todos os usuários |
|
||||
| group_trigger | object | Não | Configurações de gatilho de grupo (exemplo: { "mention_only": false }) |
|
||||
|
||||
## Configuração inicial
|
||||
|
||||
1. Acesse o [Portal de Desenvolvedores do Discord](https://discord.com/developers/applications) e crie uma nova aplicação
|
||||
2. Habilite os Intents:
|
||||
- Message Content Intent
|
||||
- Server Members Intent
|
||||
3. Obtenha o Token do Bot
|
||||
4. Preencha o Token do Bot no arquivo de configuração
|
||||
5. Convide o bot para o servidor e conceda as permissões necessárias (ex. enviar mensagens, ler histórico de mensagens)
|
||||
39
docs/channels/discord/README.vi.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
> Quay lại [README](../../../README.vi.md)
|
||||
|
||||
# Discord
|
||||
|
||||
Discord là ứng dụng chat thoại, video và văn bản miễn phí được thiết kế cho cộng đồng. PicoClaw kết nối với máy chủ Discord qua Discord Bot API, hỗ trợ nhận và gửi tin nhắn.
|
||||
|
||||
## Cấu hình
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"discord": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"group_trigger": {
|
||||
"mention_only": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Trường | Kiểu | Bắt buộc | Mô tả |
|
||||
| ------------- | ------ | -------- | --------------------------------------------------------------------------- |
|
||||
| enabled | bool | Có | Có bật kênh Discord hay không |
|
||||
| token | string | Có | Token Bot Discord |
|
||||
| allow_from | array | Không | Danh sách trắng ID người dùng; để trống nghĩa là cho phép tất cả |
|
||||
| group_trigger | object | Không | Cài đặt kích hoạt nhóm (ví dụ: { "mention_only": false }) |
|
||||
|
||||
## Hướng dẫn thiết lập
|
||||
|
||||
1. Truy cập [Discord Developer Portal](https://discord.com/developers/applications) và tạo ứng dụng mới
|
||||
2. Bật các Intents:
|
||||
- Message Content Intent
|
||||
- Server Members Intent
|
||||
3. Lấy Bot Token
|
||||
4. Điền Bot Token vào file cấu hình
|
||||
5. Mời bot vào máy chủ và cấp các quyền cần thiết (ví dụ: gửi tin nhắn, đọc lịch sử tin nhắn)
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
> 返回 [README](../../../README.zh.md)
|
||||
|
||||
# Discord
|
||||
|
||||
Discord 是一个专为社区设计的免费语音、视频和文本聊天应用。PicoClaw 通过 Discord Bot API 连接到 Discord 服务器,支持接收和发送消息。
|
||||
|
|
|
|||
52
docs/channels/feishu/README.fr.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
> Retour au [README](../../../README.fr.md)
|
||||
|
||||
# Feishu
|
||||
|
||||
Feishu (nom international : Lark) est une plateforme de collaboration d'entreprise de ByteDance. Elle prend en charge les marchés chinois et mondiaux via des connexions WebSocket pilotées par événements.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"feishu": {
|
||||
"enabled": true,
|
||||
"app_id": "cli_xxx",
|
||||
"app_secret": "xxx",
|
||||
"encrypt_key": "",
|
||||
"verification_token": "",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Champ | Type | Requis | Description |
|
||||
| --------------------- | ------ | ------ | --------------------------------------------------------------------------- |
|
||||
| enabled | bool | Oui | Activer ou non le canal Feishu |
|
||||
| app_id | string | Oui | App ID de l'application Feishu (commence par `cli_`) |
|
||||
| app_secret | string | Oui | App Secret de l'application Feishu |
|
||||
| encrypt_key | string | Non | Clé de chiffrement pour les callbacks d'événements |
|
||||
| verification_token | string | Non | Token utilisé pour la vérification des événements Webhook |
|
||||
| allow_from | array | Non | Liste blanche d'identifiants utilisateur ; vide signifie tous les utilisateurs |
|
||||
| random_reaction_emoji | array | Non | Liste d'emojis de réaction aléatoires ; vide utilise le "Pin" par défaut |
|
||||
|
||||
## Configuration initiale
|
||||
|
||||
1. Accéder à la [plateforme ouverte Feishu](https://open.feishu.cn/) et créer une application
|
||||
2. Activer la capacité **Bot** dans les paramètres de l'application
|
||||
3. Créer une version et publier l'application (la configuration prend effet après la publication)
|
||||
4. Obtenir l'**App ID** (commence par `cli_`) et l'**App Secret**
|
||||
5. Renseigner l'App ID et l'App Secret dans le fichier de configuration PicoClaw
|
||||
6. Exécuter `picoclaw gateway` pour démarrer le service
|
||||
7. Rechercher le nom du bot dans Feishu et commencer une conversation
|
||||
|
||||
> PicoClaw se connecte à Feishu en mode WebSocket/SDK — aucune adresse de callback publique ni URL Webhook n'est requise.
|
||||
>
|
||||
> `encrypt_key` et `verification_token` sont optionnels ; l'activation du chiffrement des événements est recommandée pour les environnements de production.
|
||||
>
|
||||
> Pour les références d'emojis personnalisés, voir : [Liste des emojis Feishu](https://open.larkoffice.com/document/server-docs/im-v1/message-reaction/emojis-introduce)
|
||||
|
||||
## Limitations de plateforme
|
||||
|
||||
> ⚠️ **Le canal Feishu ne prend pas en charge les appareils 32 bits.** Le SDK Feishu ne fournit que des builds 64 bits. Les architectures 32 bits (armv6, armv7, mipsle, etc.) ne peuvent pas utiliser le canal Feishu. Pour la messagerie sur des appareils 32 bits, utilisez Telegram, Discord ou OneBot.
|
||||
52
docs/channels/feishu/README.ja.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
> [README](../../../README.ja.md) に戻る
|
||||
|
||||
# 飛書(Feishu)
|
||||
|
||||
飛書(国際名:Lark)は ByteDance が提供するエンタープライズコラボレーションプラットフォームです。イベント駆動型の WebSocket 接続を通じて、中国および世界市場の両方をサポートします。
|
||||
|
||||
## 設定
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"feishu": {
|
||||
"enabled": true,
|
||||
"app_id": "cli_xxx",
|
||||
"app_secret": "xxx",
|
||||
"encrypt_key": "",
|
||||
"verification_token": "",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 型 | 必須 | 説明 |
|
||||
| --------------------- | ------ | ------ | ----------------------------------------------------------------- |
|
||||
| enabled | bool | はい | 飛書チャンネルを有効にするかどうか |
|
||||
| app_id | string | はい | 飛書アプリケーションの App ID(`cli_` で始まる) |
|
||||
| app_secret | string | はい | 飛書アプリケーションの App Secret |
|
||||
| encrypt_key | string | いいえ | イベントコールバックの暗号化キー |
|
||||
| verification_token | string | いいえ | Webhook イベント検証に使用するトークン |
|
||||
| allow_from | array | いいえ | 許可するユーザーIDのリスト。空の場合はすべてのユーザーを許可 |
|
||||
| random_reaction_emoji | array | いいえ | ランダムに追加する絵文字のリスト。空の場合はデフォルトの "Pin" を使用 |
|
||||
|
||||
## セットアップ手順
|
||||
|
||||
1. [飛書オープンプラットフォーム](https://open.feishu.cn/) にアクセスしてアプリケーションを作成する
|
||||
2. アプリケーション設定で**ボット**機能を有効にする
|
||||
3. バージョンを作成してアプリケーションを公開する(公開後に設定が有効になる)
|
||||
4. **App ID**(`cli_` で始まる)と **App Secret** を取得する
|
||||
5. PicoClaw 設定ファイルに App ID と App Secret を入力する
|
||||
6. `picoclaw gateway` を実行してサービスを起動する
|
||||
7. 飛書でボット名を検索して会話を始める
|
||||
|
||||
> PicoClaw は WebSocket/SDK モードで飛書に接続するため、公開コールバックアドレスや Webhook URL の設定は不要です。
|
||||
>
|
||||
> `encrypt_key` と `verification_token` はオプションですが、本番環境ではイベント暗号化を有効にすることを推奨します。
|
||||
>
|
||||
> カスタム絵文字の参考:[飛書絵文字リスト](https://open.larkoffice.com/document/server-docs/im-v1/message-reaction/emojis-introduce)
|
||||
|
||||
## プラットフォーム制限
|
||||
|
||||
> ⚠️ **飛書チャネルは 32 ビットデバイスをサポートしていません。** 飛書 SDK は 64 ビットビルドのみ提供しています。armv6 / armv7 / mipsle などの 32 ビットアーキテクチャでは飛書チャネルを使用できません。32 ビットデバイスでのメッセージングには、Telegram、Discord、または OneBot をご利用ください。
|
||||
52
docs/channels/feishu/README.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
> Back to [README](../../../README.md)
|
||||
|
||||
# Feishu
|
||||
|
||||
Feishu (international name: Lark) is an enterprise collaboration platform by ByteDance. It supports both Chinese and global markets through event-driven WebSocket connections.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"feishu": {
|
||||
"enabled": true,
|
||||
"app_id": "cli_xxx",
|
||||
"app_secret": "xxx",
|
||||
"encrypt_key": "",
|
||||
"verification_token": "",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------------------------------ |
|
||||
| enabled | bool | Yes | Whether to enable the Feishu channel |
|
||||
| app_id | string | Yes | App ID of the Feishu application (starts with `cli_`) |
|
||||
| app_secret | string | Yes | App Secret of the Feishu application |
|
||||
| encrypt_key | string | No | Encryption key for event callbacks |
|
||||
| verification_token | string | No | Token used for Webhook event verification |
|
||||
| allow_from | array | No | Allowlist of user IDs; empty means all users are allowed |
|
||||
| random_reaction_emoji | array | No | List of random reaction emojis; empty uses the default "Pin" |
|
||||
|
||||
## Setup
|
||||
|
||||
1. Go to the [Feishu Open Platform](https://open.feishu.cn/) and create an application
|
||||
2. Enable the **Bot** capability in the application settings
|
||||
3. Create a version and publish the application (configuration takes effect only after publishing)
|
||||
4. Obtain the **App ID** (starts with `cli_`) and **App Secret**
|
||||
5. Fill in the App ID and App Secret in the PicoClaw configuration file
|
||||
6. Run `picoclaw gateway` to start the service
|
||||
7. Search for the bot name in Feishu and start a conversation
|
||||
|
||||
> PicoClaw connects to Feishu using WebSocket/SDK mode — no public callback address or Webhook URL is required.
|
||||
>
|
||||
> `encrypt_key` and `verification_token` are optional; enabling event encryption is recommended for production environments.
|
||||
>
|
||||
> For custom emoji references, see: [Feishu Emoji List](https://open.larkoffice.com/document/server-docs/im-v1/message-reaction/emojis-introduce)
|
||||
|
||||
## Platform Limitations
|
||||
|
||||
> ⚠️ **Feishu channel does not support 32-bit devices.** The Feishu SDK only provides 64-bit builds. Devices running armv6, armv7, mipsle, or other 32-bit architectures cannot use the Feishu channel. For messaging on 32-bit devices, use Telegram, Discord, or OneBot instead.
|
||||
52
docs/channels/feishu/README.pt-br.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
> Voltar ao [README](../../../README.pt-br.md)
|
||||
|
||||
# Feishu
|
||||
|
||||
Feishu (nome internacional: Lark) é uma plataforma de colaboração empresarial da ByteDance. Suporta os mercados chinês e global por meio de conexões WebSocket orientadas a eventos.
|
||||
|
||||
## Configuração
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"feishu": {
|
||||
"enabled": true,
|
||||
"app_id": "cli_xxx",
|
||||
"app_secret": "xxx",
|
||||
"encrypt_key": "",
|
||||
"verification_token": "",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
| --------------------- | ------ | ----------- | -------------------------------------------------------------------------- |
|
||||
| enabled | bool | Sim | Se o canal Feishu deve ser habilitado |
|
||||
| app_id | string | Sim | App ID da aplicação Feishu (começa com `cli_`) |
|
||||
| app_secret | string | Sim | App Secret da aplicação Feishu |
|
||||
| encrypt_key | string | Não | Chave de criptografia para callbacks de eventos |
|
||||
| verification_token | string | Não | Token usado para verificação de eventos Webhook |
|
||||
| allow_from | array | Não | Lista de IDs de usuários permitidos; vazio significa todos os usuários |
|
||||
| random_reaction_emoji | array | Não | Lista de emojis de reação aleatórios; vazio usa o "Pin" padrão |
|
||||
|
||||
## Configuração inicial
|
||||
|
||||
1. Acesse a [Plataforma Aberta Feishu](https://open.feishu.cn/) e crie uma aplicação
|
||||
2. Habilite a capacidade de **Bot** nas configurações da aplicação
|
||||
3. Crie uma versão e publique a aplicação (a configuração entra em vigor após a publicação)
|
||||
4. Obtenha o **App ID** (começa com `cli_`) e o **App Secret**
|
||||
5. Preencha o App ID e o App Secret no arquivo de configuração do PicoClaw
|
||||
6. Execute `picoclaw gateway` para iniciar o serviço
|
||||
7. Pesquise o nome do bot no Feishu e inicie uma conversa
|
||||
|
||||
> O PicoClaw se conecta ao Feishu usando o modo WebSocket/SDK — nenhum endereço de callback público ou URL de Webhook é necessário.
|
||||
>
|
||||
> `encrypt_key` e `verification_token` são opcionais; recomenda-se habilitar a criptografia de eventos em ambientes de produção.
|
||||
>
|
||||
> Para referências de emojis personalizados, consulte: [Lista de Emojis do Feishu](https://open.larkoffice.com/document/server-docs/im-v1/message-reaction/emojis-introduce)
|
||||
|
||||
## Limitações de Plataforma
|
||||
|
||||
> ⚠️ **O canal Feishu não suporta dispositivos 32 bits.** O SDK do Feishu fornece apenas builds 64 bits. Arquiteturas 32 bits (armv6, armv7, mipsle, etc.) não podem usar o canal Feishu. Para mensagens em dispositivos 32 bits, use Telegram, Discord ou OneBot.
|
||||
52
docs/channels/feishu/README.vi.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
> Quay lại [README](../../../README.vi.md)
|
||||
|
||||
# Feishu
|
||||
|
||||
Feishu (tên quốc tế: Lark) là nền tảng cộng tác doanh nghiệp của ByteDance. Hỗ trợ cả thị trường Trung Quốc và toàn cầu thông qua kết nối WebSocket theo hướng sự kiện.
|
||||
|
||||
## Cấu hình
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"feishu": {
|
||||
"enabled": true,
|
||||
"app_id": "cli_xxx",
|
||||
"app_secret": "xxx",
|
||||
"encrypt_key": "",
|
||||
"verification_token": "",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Trường | Kiểu | Bắt buộc | Mô tả |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------------------------------------ |
|
||||
| enabled | bool | Có | Có bật kênh Feishu hay không |
|
||||
| app_id | string | Có | App ID của ứng dụng Feishu (bắt đầu bằng `cli_`) |
|
||||
| app_secret | string | Có | App Secret của ứng dụng Feishu |
|
||||
| encrypt_key | string | Không | Khóa mã hóa cho callback sự kiện |
|
||||
| verification_token | string | Không | Token dùng để xác minh sự kiện Webhook |
|
||||
| allow_from | array | Không | Danh sách trắng ID người dùng; để trống nghĩa là cho phép tất cả |
|
||||
| random_reaction_emoji | array | Không | Danh sách emoji phản ứng ngẫu nhiên; để trống dùng "Pin" mặc định |
|
||||
|
||||
## Hướng dẫn thiết lập
|
||||
|
||||
1. Truy cập [Nền tảng Mở Feishu](https://open.feishu.cn/) và tạo ứng dụng
|
||||
2. Bật khả năng **Bot** trong cài đặt ứng dụng
|
||||
3. Tạo phiên bản và xuất bản ứng dụng (cấu hình có hiệu lực sau khi xuất bản)
|
||||
4. Lấy **App ID** (bắt đầu bằng `cli_`) và **App Secret**
|
||||
5. Điền App ID và App Secret vào file cấu hình PicoClaw
|
||||
6. Chạy `picoclaw gateway` để khởi động dịch vụ
|
||||
7. Tìm kiếm tên bot trong Feishu và bắt đầu trò chuyện
|
||||
|
||||
> PicoClaw kết nối với Feishu bằng chế độ WebSocket/SDK — không cần cấu hình địa chỉ callback công khai hay Webhook URL.
|
||||
>
|
||||
> `encrypt_key` và `verification_token` là tùy chọn; nên bật mã hóa sự kiện trong môi trường sản xuất.
|
||||
>
|
||||
> Tham khảo emoji tùy chỉnh: [Danh sách Emoji Feishu](https://open.larkoffice.com/document/server-docs/im-v1/message-reaction/emojis-introduce)
|
||||
|
||||
## Giới hạn nền tảng
|
||||
|
||||
> ⚠️ **Kênh Feishu không hỗ trợ thiết bị 32 bit.** SDK Feishu chỉ cung cấp bản build 64 bit. Các kiến trúc 32 bit (armv6, armv7, mipsle, v.v.) không thể sử dụng kênh Feishu. Để nhắn tin trên thiết bị 32 bit, hãy dùng Telegram, Discord hoặc OneBot.
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
> 返回 [README](../../../README.zh.md)
|
||||
|
||||
# 飞书
|
||||
|
||||
飞书(国际版名称:Lark)是字节跳动旗下的企业协作平台。它通过事件驱动的 Webhook 同时支持中国和全球市场。
|
||||
|
|
@ -39,3 +41,7 @@
|
|||
4. 设置加密(可选,生产环境建议启用)
|
||||
5. 将 App ID、App Secret、Encrypt Key 和 Verification Token(如果启用加密) 填入配置文件中
|
||||
6. 自定义你希望 PicoClaw react 你消息时的表情(可选, Reference URL: [Feishu Emoji List](https://open.larkoffice.com/document/server-docs/im-v1/message-reaction/emojis-introduce))
|
||||
|
||||
## 平台限制
|
||||
|
||||
> ⚠️ **飞书通道不支持 32 位设备。** 飞书官方 SDK 仅提供 64 位构建,armv6 / armv7 / mipsle 等 32 位架构无法使用飞书通道。如需在 32 位设备上接入即时通讯,请改用 Telegram、Discord 或 OneBot 等通道。
|
||||
|
|
|
|||
40
docs/channels/line/README.fr.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
> Retour au [README](../../../README.fr.md)
|
||||
|
||||
# Line
|
||||
|
||||
PicoClaw prend en charge LINE via l'API LINE Messaging avec des callbacks webhook.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"line": {
|
||||
"enabled": true,
|
||||
"channel_secret": "YOUR_CHANNEL_SECRET",
|
||||
"channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN",
|
||||
"webhook_path": "/webhook/line",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Champ | Type | Requis | Description |
|
||||
| -------------------- | ------ | ------ | ------------------------------------------------------------------------ |
|
||||
| enabled | bool | Oui | Activer ou non le canal LINE |
|
||||
| channel_secret | string | Oui | Channel Secret de l'API LINE Messaging |
|
||||
| channel_access_token | string | Oui | Channel Access Token de l'API LINE Messaging |
|
||||
| webhook_path | string | Non | Chemin du webhook (par défaut : /webhook/line) |
|
||||
| allow_from | array | Non | Liste blanche d'ID utilisateurs ; vide signifie tous les utilisateurs |
|
||||
|
||||
## Procédure de configuration
|
||||
|
||||
1. Rendez-vous sur la [LINE Developers Console](https://developers.line.biz/console/) et créez un fournisseur de services ainsi qu'un canal Messaging API
|
||||
2. Obtenez le Channel Secret et le Channel Access Token
|
||||
3. Configurez le webhook :
|
||||
- LINE exige que les webhooks utilisent HTTPS. Vous devez donc déployer un serveur compatible HTTPS ou utiliser un outil de proxy inverse comme ngrok pour exposer votre serveur local sur Internet
|
||||
- PicoClaw utilise un serveur HTTP Gateway partagé pour recevoir les callbacks webhook de tous les canaux, écoutant par défaut sur 127.0.0.1:18790
|
||||
- Définissez l'URL du webhook sur `https://your-domain.com/webhook/line`, puis configurez un proxy inverse de votre domaine externe vers le Gateway local (port par défaut 18790)
|
||||
- Activez le webhook et vérifiez l'URL
|
||||
4. Renseignez le Channel Secret et le Channel Access Token dans le fichier de configuration
|
||||
40
docs/channels/line/README.ja.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
> [README](../../../README.ja.md) に戻る
|
||||
|
||||
# Line
|
||||
|
||||
PicoClaw は LINE Messaging API と Webhook コールバックを通じて LINE をサポートします。
|
||||
|
||||
## 設定
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"line": {
|
||||
"enabled": true,
|
||||
"channel_secret": "YOUR_CHANNEL_SECRET",
|
||||
"channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN",
|
||||
"webhook_path": "/webhook/line",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 型 | 必須 | 説明 |
|
||||
| -------------------- | ------ | ------ | ------------------------------------------------------------------ |
|
||||
| enabled | bool | はい | LINE チャンネルを有効にするかどうか |
|
||||
| channel_secret | string | はい | LINE Messaging API の Channel Secret |
|
||||
| channel_access_token | string | はい | LINE Messaging API の Channel Access Token |
|
||||
| webhook_path | string | いいえ | Webhook のパス(デフォルト: /webhook/line) |
|
||||
| allow_from | array | いいえ | ユーザーIDのホワイトリスト。空の場合は全ユーザーを許可 |
|
||||
|
||||
## セットアップ手順
|
||||
|
||||
1. [LINE Developers Console](https://developers.line.biz/console/) にアクセスし、サービスプロバイダーと Messaging API チャンネルを作成する
|
||||
2. Channel Secret と Channel Access Token を取得する
|
||||
3. Webhook を設定する:
|
||||
- LINE は Webhook に HTTPS が必要なため、HTTPS 対応サーバーをデプロイするか、ngrok などのリバースプロキシツールを使用してローカルサーバーをインターネットに公開する必要があります
|
||||
- PicoClaw は共有の Gateway HTTP サーバーを使用してすべてのチャンネルの Webhook コールバックを受信します。デフォルトのリッスンアドレスは 127.0.0.1:18790 です
|
||||
- Webhook URL を `https://your-domain.com/webhook/line` に設定し、外部ドメインをローカルの Gateway(デフォルトポート 18790)にリバースプロキシする
|
||||
- Webhook を有効にして URL を検証する
|
||||
4. Channel Secret と Channel Access Token を設定ファイルに入力する
|
||||
40
docs/channels/line/README.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
> Back to [README](../../../README.md)
|
||||
|
||||
# Line
|
||||
|
||||
PicoClaw supports LINE through the LINE Messaging API with webhook callbacks.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"line": {
|
||||
"enabled": true,
|
||||
"channel_secret": "YOUR_CHANNEL_SECRET",
|
||||
"channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN",
|
||||
"webhook_path": "/webhook/line",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| -------------------- | ------ | -------- | ------------------------------------------------------------------ |
|
||||
| enabled | bool | Yes | Whether to enable the LINE channel |
|
||||
| channel_secret | string | Yes | Channel Secret for the LINE Messaging API |
|
||||
| channel_access_token | string | Yes | Channel Access Token for the LINE Messaging API |
|
||||
| webhook_path | string | No | Webhook path (default: /webhook/line) |
|
||||
| allow_from | array | No | User ID whitelist; empty means all users are allowed |
|
||||
|
||||
## Setup
|
||||
|
||||
1. Go to the [LINE Developers Console](https://developers.line.biz/console/) and create a provider and a Messaging API channel
|
||||
2. Obtain the Channel Secret and Channel Access Token
|
||||
3. Configure the webhook:
|
||||
- LINE requires webhooks to use HTTPS, so you need to deploy a server with HTTPS support, or use a reverse proxy tool like ngrok to expose your local server to the internet
|
||||
- PicoClaw uses a shared Gateway HTTP server to receive webhook callbacks for all channels, listening on 127.0.0.1:18790 by default
|
||||
- Set the Webhook URL to `https://your-domain.com/webhook/line`, then reverse-proxy your external domain to the local Gateway (default port 18790)
|
||||
- Enable the webhook and verify the URL
|
||||
4. Fill in the Channel Secret and Channel Access Token in the configuration file
|
||||
40
docs/channels/line/README.pt-br.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
> Voltar ao [README](../../../README.pt-br.md)
|
||||
|
||||
# Line
|
||||
|
||||
O PicoClaw suporta o LINE por meio da LINE Messaging API com callbacks de webhook.
|
||||
|
||||
## Configuração
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"line": {
|
||||
"enabled": true,
|
||||
"channel_secret": "YOUR_CHANNEL_SECRET",
|
||||
"channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN",
|
||||
"webhook_path": "/webhook/line",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
| -------------------- | ------ | ----------- | ---------------------------------------------------------------------- |
|
||||
| enabled | bool | Sim | Se o canal LINE deve ser habilitado |
|
||||
| channel_secret | string | Sim | Channel Secret da LINE Messaging API |
|
||||
| channel_access_token | string | Sim | Channel Access Token da LINE Messaging API |
|
||||
| webhook_path | string | Não | Caminho do webhook (padrão: /webhook/line) |
|
||||
| allow_from | array | Não | Lista de permissão de IDs de usuário; vazio permite todos |
|
||||
|
||||
## Configuração passo a passo
|
||||
|
||||
1. Acesse o [LINE Developers Console](https://developers.line.biz/console/) e crie um provedor de serviços e um canal Messaging API
|
||||
2. Obtenha o Channel Secret e o Channel Access Token
|
||||
3. Configure o webhook:
|
||||
- O LINE exige que os webhooks usem HTTPS, portanto é necessário implantar um servidor com suporte a HTTPS ou usar uma ferramenta de proxy reverso como o ngrok para expor seu servidor local à internet
|
||||
- O PicoClaw usa um servidor HTTP Gateway compartilhado para receber callbacks de webhook de todos os canais, escutando em 127.0.0.1:18790 por padrão
|
||||
- Defina a URL do webhook como `https://your-domain.com/webhook/line` e configure um proxy reverso do seu domínio externo para o Gateway local (porta padrão 18790)
|
||||
- Ative o webhook e verifique a URL
|
||||
4. Preencha o Channel Secret e o Channel Access Token no arquivo de configuração
|
||||
40
docs/channels/line/README.vi.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
> Quay lại [README](../../../README.vi.md)
|
||||
|
||||
# Line
|
||||
|
||||
PicoClaw hỗ trợ LINE thông qua LINE Messaging API kết hợp với webhook callback.
|
||||
|
||||
## Cấu hình
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"line": {
|
||||
"enabled": true,
|
||||
"channel_secret": "YOUR_CHANNEL_SECRET",
|
||||
"channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN",
|
||||
"webhook_path": "/webhook/line",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Trường | Kiểu | Bắt buộc | Mô tả |
|
||||
| -------------------- | ------ | -------- | ---------------------------------------------------------------------- |
|
||||
| enabled | bool | Có | Có bật kênh LINE hay không |
|
||||
| channel_secret | string | Có | Channel Secret của LINE Messaging API |
|
||||
| channel_access_token | string | Có | Channel Access Token của LINE Messaging API |
|
||||
| webhook_path | string | Không | Đường dẫn webhook (mặc định: /webhook/line) |
|
||||
| allow_from | array | Không | Danh sách trắng ID người dùng; để trống cho phép tất cả |
|
||||
|
||||
## Quy trình thiết lập
|
||||
|
||||
1. Truy cập [LINE Developers Console](https://developers.line.biz/console/) và tạo một nhà cung cấp dịch vụ cùng một kênh Messaging API
|
||||
2. Lấy Channel Secret và Channel Access Token
|
||||
3. Cấu hình webhook:
|
||||
- LINE yêu cầu webhook phải sử dụng HTTPS, vì vậy bạn cần triển khai máy chủ hỗ trợ HTTPS hoặc dùng công cụ reverse proxy như ngrok để expose máy chủ cục bộ ra internet
|
||||
- PicoClaw sử dụng máy chủ HTTP Gateway dùng chung để nhận webhook callback cho tất cả các kênh, mặc định lắng nghe tại 127.0.0.1:18790
|
||||
- Đặt Webhook URL thành `https://your-domain.com/webhook/line`, sau đó reverse proxy tên miền bên ngoài về Gateway cục bộ (cổng mặc định 18790)
|
||||
- Bật webhook và xác minh URL
|
||||
4. Điền Channel Secret và Channel Access Token vào file cấu hình
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
> 返回 [README](../../../README.zh.md)
|
||||
|
||||
# Line
|
||||
|
||||
PicoClaw 通过 LINE Messaging API 配合 Webhook 回调功能实现对 LINE 的支持。
|
||||
|
|
|
|||
35
docs/channels/maixcam/README.fr.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> Retour au [README](../../../README.fr.md)
|
||||
|
||||
# MaixCam
|
||||
|
||||
MaixCam est un canal dédié à la connexion aux caméras AI Sipeed MaixCAM et MaixCAM2. Il utilise des sockets TCP pour une communication bidirectionnelle et prend en charge les scénarios de déploiement d'IA en périphérie.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"maixcam": {
|
||||
"enabled": true,
|
||||
"host": "0.0.0.0",
|
||||
"port": 18790,
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Champ | Type | Requis | Description |
|
||||
| ---------- | ------ | ------ | --------------------------------------------------------------------------- |
|
||||
| enabled | bool | Oui | Activer ou non le canal MaixCam |
|
||||
| host | string | Oui | Adresse d'écoute du serveur TCP |
|
||||
| port | int | Oui | Port d'écoute du serveur TCP |
|
||||
| allow_from | array | Non | Liste blanche d'identifiants d'appareils ; vide signifie tous les appareils |
|
||||
|
||||
## Cas d'utilisation
|
||||
|
||||
Le canal MaixCam permet à PicoClaw de fonctionner comme backend IA pour les appareils en périphérie :
|
||||
|
||||
- **Surveillance intelligente** : MaixCAM envoie des images ; PicoClaw les analyse via des modèles de vision
|
||||
- **Contrôle IoT** : Les appareils envoient des données de capteurs ; PicoClaw coordonne les réponses
|
||||
- **IA hors ligne** : Déployer PicoClaw sur un réseau local pour une inférence à faible latence
|
||||
35
docs/channels/maixcam/README.ja.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> [README](../../../README.ja.md) に戻る
|
||||
|
||||
# MaixCam
|
||||
|
||||
MaixCam は、Sipeed MaixCAM および MaixCAM2 AI カメラデバイスへの接続専用チャンネルです。TCP ソケットを使用した双方向通信を実装し、エッジ AI デプロイメントシナリオをサポートします。
|
||||
|
||||
## 設定
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"maixcam": {
|
||||
"enabled": true,
|
||||
"host": "0.0.0.0",
|
||||
"port": 18790,
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 型 | 必須 | 説明 |
|
||||
| ---------- | ------ | ------ | ------------------------------------------------------------- |
|
||||
| enabled | bool | はい | MaixCam チャンネルを有効にするかどうか |
|
||||
| host | string | はい | TCP サーバーのリッスンアドレス |
|
||||
| port | int | はい | TCP サーバーのリッスンポート |
|
||||
| allow_from | array | いいえ | 許可するデバイスIDのリスト。空の場合はすべてのデバイスを許可 |
|
||||
|
||||
## ユースケース
|
||||
|
||||
MaixCam チャンネルにより、PicoClaw はエッジデバイスの AI バックエンドとして機能できます:
|
||||
|
||||
- **スマート監視**:MaixCAM が画像フレームを送信し、PicoClaw がビジョンモデルで分析する
|
||||
- **IoT 制御**:デバイスがセンサーデータを送信し、PicoClaw がレスポンスを調整する
|
||||
- **オフライン AI**:ローカルネットワークに PicoClaw をデプロイして低遅延推論を実現する
|
||||
35
docs/channels/maixcam/README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> Back to [README](../../../README.md)
|
||||
|
||||
# MaixCam
|
||||
|
||||
MaixCam is a dedicated channel for connecting to Sipeed MaixCAM and MaixCAM2 AI camera devices. It uses TCP sockets for bidirectional communication and supports edge AI deployment scenarios.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"maixcam": {
|
||||
"enabled": true,
|
||||
"host": "0.0.0.0",
|
||||
"port": 18790,
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ---------- | ------ | -------- | ---------------------------------------------------------------- |
|
||||
| enabled | bool | Yes | Whether to enable the MaixCam channel |
|
||||
| host | string | Yes | TCP server listening address |
|
||||
| port | int | Yes | TCP server listening port |
|
||||
| allow_from | array | No | Allowlist of device IDs; empty means all devices are allowed |
|
||||
|
||||
## Use Cases
|
||||
|
||||
The MaixCam channel enables PicoClaw to act as an AI backend for edge devices:
|
||||
|
||||
- **Smart Surveillance**: MaixCAM sends image frames; PicoClaw analyzes them using vision models
|
||||
- **IoT Control**: Devices send sensor data; PicoClaw coordinates responses
|
||||
- **Offline AI**: Deploy PicoClaw on a local network for low-latency inference
|
||||
35
docs/channels/maixcam/README.pt-br.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> Voltar ao [README](../../../README.pt-br.md)
|
||||
|
||||
# MaixCam
|
||||
|
||||
MaixCam é um canal dedicado para conectar dispositivos de câmera AI Sipeed MaixCAM e MaixCAM2. Utiliza sockets TCP para comunicação bidirecional e suporta cenários de implantação de IA na borda.
|
||||
|
||||
## Configuração
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"maixcam": {
|
||||
"enabled": true,
|
||||
"host": "0.0.0.0",
|
||||
"port": 18790,
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
| ---------- | ------ | ----------- | -------------------------------------------------------------------------- |
|
||||
| enabled | bool | Sim | Se o canal MaixCam deve ser habilitado |
|
||||
| host | string | Sim | Endereço de escuta do servidor TCP |
|
||||
| port | int | Sim | Porta de escuta do servidor TCP |
|
||||
| allow_from | array | Não | Lista de IDs de dispositivos permitidos; vazio significa todos os dispositivos |
|
||||
|
||||
## Casos de uso
|
||||
|
||||
O canal MaixCam permite que o PicoClaw atue como backend de IA para dispositivos de borda:
|
||||
|
||||
- **Vigilância inteligente**: MaixCAM envia quadros de imagem; PicoClaw os analisa usando modelos de visão
|
||||
- **Controle IoT**: Dispositivos enviam dados de sensores; PicoClaw coordena as respostas
|
||||
- **IA offline**: Implante o PicoClaw em uma rede local para inferência de baixa latência
|
||||
35
docs/channels/maixcam/README.vi.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> Quay lại [README](../../../README.vi.md)
|
||||
|
||||
# MaixCam
|
||||
|
||||
MaixCam là kênh chuyên dụng để kết nối với các thiết bị camera AI Sipeed MaixCAM và MaixCAM2. Sử dụng TCP socket để giao tiếp hai chiều và hỗ trợ các kịch bản triển khai AI tại biên.
|
||||
|
||||
## Cấu hình
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"maixcam": {
|
||||
"enabled": true,
|
||||
"host": "0.0.0.0",
|
||||
"port": 18790,
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Trường | Kiểu | Bắt buộc | Mô tả |
|
||||
| ---------- | ------ | -------- | ------------------------------------------------------------------------ |
|
||||
| enabled | bool | Có | Có bật kênh MaixCam hay không |
|
||||
| host | string | Có | Địa chỉ lắng nghe của máy chủ TCP |
|
||||
| port | int | Có | Cổng lắng nghe của máy chủ TCP |
|
||||
| allow_from | array | Không | Danh sách trắng ID thiết bị; để trống nghĩa là cho phép tất cả thiết bị |
|
||||
|
||||
## Trường hợp sử dụng
|
||||
|
||||
Kênh MaixCam cho phép PicoClaw hoạt động như backend AI cho các thiết bị biên:
|
||||
|
||||
- **Giám sát thông minh**: MaixCAM gửi khung hình ảnh; PicoClaw phân tích bằng mô hình thị giác
|
||||
- **Điều khiển IoT**: Thiết bị gửi dữ liệu cảm biến; PicoClaw điều phối phản hồi
|
||||
- **AI ngoại tuyến**: Triển khai PicoClaw trên mạng nội bộ để suy luận độ trễ thấp
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
> 返回 [README](../../../README.zh.md)
|
||||
|
||||
# MaixCam
|
||||
|
||||
MaixCam 是专用于连接矽速科技 MaixCAM 与 MaixCAM2 AI 摄像设备的通道。它采用 TCP 套接字实现双向通信,支持边缘 AI 部署场景。
|
||||
|
|
@ -9,18 +11,20 @@ MaixCam 是专用于连接矽速科技 MaixCAM 与 MaixCAM2 AI 摄像设备的
|
|||
"channels": {
|
||||
"maixcam": {
|
||||
"enabled": true,
|
||||
"server_address": "0.0.0.0:8899",
|
||||
"host": "0.0.0.0",
|
||||
"port": 18790,
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 描述 |
|
||||
| -------------- | ------ | ---- | -------------------------------- |
|
||||
| enabled | bool | 是 | 是否启用 MaixCam 频道 |
|
||||
| server_address | string | 是 | TCP 服务器监听地址和端口 |
|
||||
| allow_from | array | 否 | 设备ID白名单,空表示允许所有设备 |
|
||||
| 字段 | 类型 | 必填 | 描述 |
|
||||
| ---------- | ------ | ---- | -------------------------------- |
|
||||
| enabled | bool | 是 | 是否启用 MaixCam 频道 |
|
||||
| host | string | 是 | TCP 服务器监听地址 |
|
||||
| port | int | 是 | TCP 服务器监听端口 |
|
||||
| allow_from | array | 否 | 设备ID白名单,空表示允许所有设备 |
|
||||
|
||||
## 使用场景
|
||||
|
||||
|
|
|
|||
64
docs/channels/matrix/README.fr.md
Normal file
|
|
@ -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)
|
||||
64
docs/channels/matrix/README.ja.md
Normal file
|
|
@ -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
|
||||
|
||||
- リッチメディアメタデータの改善(例:画像/動画のサイズとサムネイル)
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
> Back to [README](../../../README.md)
|
||||
|
||||
# Matrix Channel Configuration Guide
|
||||
|
||||
## 1. Example Configuration
|
||||
|
|
@ -20,10 +22,12 @@ Add this to `config.json`:
|
|||
},
|
||||
"placeholder": {
|
||||
"enabled": true,
|
||||
"text": "Thinking..."
|
||||
"text": ["Thinking...", "Processing...", "Typing..."]
|
||||
},
|
||||
"reasoning_channel_id": "",
|
||||
"message_format": "richtext"
|
||||
"message_format": "richtext",
|
||||
"crypto_database_path": "",
|
||||
"crypto_passphrase": "YOUR_MATRIX_CRYPTO_PICKLE_KEY"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,9 +45,18 @@ Add this to `config.json`:
|
|||
| join_on_invite | bool | No | Auto-join invited rooms |
|
||||
| allow_from | []string | No | User whitelist (Matrix user IDs) |
|
||||
| group_trigger | object | No | Group trigger strategy (`mention_only` / `prefixes`) |
|
||||
| placeholder | object | No | Placeholder message config |
|
||||
| placeholder | object | No | Placeholder message config (see below) |
|
||||
| reasoning_channel_id | string | No | Target channel for reasoning output |
|
||||
| message_format | string | No | Output format: `"richtext"` (default) renders markdown as HTML; `"plain"` sends plain text only |
|
||||
| crypto_database_path | string | No | Path to store the crypto database (uses workspace path `~/.picoclaw/workspace` if empty) |
|
||||
| crypto_passphrase | string | No | Serialization key for encrypting session keys in the database; must remain unchanged once set |
|
||||
|
||||
### Placeholder Config
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---------|----------------|----------|-------------|
|
||||
| enabled | bool | No | Enable placeholder messages (default: false) |
|
||||
| text | string/[]string | No | Placeholder text(s). Can be a single string or array of strings. If multiple texts are provided, one is randomly selected at runtime. Default: "Thinking..." |
|
||||
|
||||
## 3. Currently Supported
|
||||
|
||||
|
|
@ -56,6 +69,7 @@ Add this to `config.json`:
|
|||
- Typing state (`m.typing`)
|
||||
- Placeholder message + final reply replacement
|
||||
- Auto-join invited rooms (can be disabled)
|
||||
- End-to-end encryption (E2EE) support for encrypted messages
|
||||
|
||||
## 4. TODO
|
||||
|
||||
|
|
|
|||
64
docs/channels/matrix/README.pt-br.md
Normal file
|
|
@ -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)
|
||||