12 Commits

Author SHA1 Message Date
bdad44de7d feat: 优化流式输出,添加按行延迟和配置化
Some checks failed
Release / build (push) Failing after 22s
- 使用 ProcessDirect 替代 ChatStream,支持工具调用结果显示
- 新增 project.config.yml 统一配置(Logo、用户前缀、流式延迟、Markdown等)
- Markdown 渲染支持自动终端宽度换行
- 按行输出文本,每行延迟可配置
- 简化状态栏,只显示耗时(图标颜色 #f0c75e,文字颜色 #2b2e32)
- spinner 动画右移两个字符
- 用户输入前缀可配置化
2026-04-15 06:13:52 +08:00
432a8db938 chore: 更新 agents.md 添加 picoclaw 地址,添加 hxclaw 到 .gitignore,切换到 picoclaw v0.2.6 2026-04-12 03:36:04 +08:00
a536375f41 ci: 切换到 picoclaw v0.2.6 2026-04-12 03:34:19 +08:00
8a631831df ci: 切换到远程 picoclaw 依赖 2026-04-12 03:28:51 +08:00
98d0b5200b ci: 改用 /workspace 目录
Some checks failed
Release / build (push) Failing after 19s
2026-04-12 03:23:52 +08:00
af1c509257 ci: 修复 cp 错误
Some checks failed
Release / build (push) Failing after 1m38s
2026-04-12 03:21:55 +08:00
9b1c8b5566 ci: 添加 clone 步骤
Some checks failed
Release / build (push) Failing after 1m38s
2026-04-12 03:19:58 +08:00
4fca6bc081 ci: 简化工作目录,修复 YAML 格式
Some checks failed
Release / build (push) Failing after 21s
2026-04-12 03:19:12 +08:00
ebee173df9 ci: 修复工作目录为 /workspace/titor/hxclaw
Some checks failed
Release / build (push) Failing after 19s
2026-04-12 03:18:00 +08:00
fa86bd5712 ci: 修复 go mod download 路径
Some checks failed
Release / build (push) Failing after 20s
2026-04-12 03:17:27 +08:00
c15b8c4a80 ci: 修复 workflow 路径为 .gitea/workflows
Some checks failed
Release / build (push) Failing after 24s
2026-04-12 03:15:12 +08:00
3c0d161d1a ci: 添加 Release CI/CD workflow 2026-04-12 03:08:15 +08:00
10 changed files with 384 additions and 128 deletions

View File

@@ -0,0 +1,61 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
container:
image: golang:1.26-alpine
env:
GOPROXY: "https://mirrors.aliyun.com/goproxy/,direct"
steps:
- name: Checkout
run: |
apk add git bash
rm -rf /workspace
git clone https://hub.gaomia.site/titor/hxclaw.git /workspace
- name: Download dependencies
run: cd /workspace && go mod download
- name: Build
run: |
cd /workspace
for p in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64; do
os=${p%/*}
arch=${p#*/}
ext=""
[ "$os" = "windows" ] && ext=".exe"
GOOS=$os GOARCH=$arch go build -o "hxclaw-${os}-${arch}${ext}" ./cmd/hxclaw
done
- name: Checksums
run: cd /workspace && sha256sum hxclaw-* > checksums.txt
- name: Release
env:
GITEA_TOKEN: "${{ secrets.release_token }}"
run: |
cd /workspace
apk add curl jq
TAG_NAME="${GITHUB_REF#refs/tags/}"
RELEASE_BODY=$(git tag -l --format='%(contents)' "$TAG_NAME" 2>/dev/null || git log -1 --format="%s%n%n%b" "$TAG_NAME" 2>/dev/null || echo "Release $TAG_NAME")
RELEASE_RESPONSE=$(curl -s -X POST "https://hub.gaomia.site/api/v1/repos/titor/hxclaw/releases" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${TAG_NAME}\",\"name\":\"${TAG_NAME}\",\"body\":\"${RELEASE_BODY}\"}")
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
for f in hxclaw-* checksums.txt; do
[ -f "$f" ] && curl -s -X POST "https://hub.gaomia.site/api/v1/repos/titor/hxclaw/releases/${RELEASE_ID}/assets" \
-H "Authorization: token ${GITEA_TOKEN}" \
-F "attachment=@$f"
done

57
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
container:
image: golang:1.26-alpine
env:
GOPROXY: "https://mirrors.aliyun.com/goproxy/,direct"
steps:
- name: Checkout
run: |
apk add git bash
- name: Download dependencies
run: go mod download
- name: Build
run: |
for p in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64; do
os=${p%/*}
arch=${p#*/}
ext=""
[ "$os" = "windows" ] && ext=".exe"
GOOS=$os GOARCH=$arch go build -o "hxclaw-${os}-${arch}${ext}" ./cmd/hxclaw
done
- name: Checksums
run: sha256sum hxclaw-* > checksums.txt
- name: Release
env:
GITEA_TOKEN: ${{ secrets.release_token }}
run: |
apk add curl jq
TAG_NAME="${GITHUB_REF#refs/tags/}"
RELEASE_BODY=$(git tag -l --format='%(contents)' "$TAG_NAME" 2>/dev/null || git log -1 --format="%s%n%n%b" "$TAG_NAME" 2>/dev/null || echo "Release $TAG_NAME")
RELEASE_RESPONSE=$(curl -s -X POST "https://hub.gaomia.site/api/v1/repos/titor/hxclaw/releases" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${TAG_NAME}\",\"name\":\"${TAG_NAME}\",\"body\":\"${RELEASE_BODY}\"}")
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
for f in hxclaw-* checksums.txt; do
[ -f "$f" ] && curl -s -X POST "https://hub.gaomia.site/api/v1/repos/titor/hxclaw/releases/${RELEASE_ID}/assets" \
-H "Authorization: token ${GITEA_TOKEN}" \
-F "attachment=@$f"
done

View File

@@ -57,20 +57,48 @@
### v0.1.0 已完成功能 ### v0.1.0 已完成功能
1. **流式输出** 1. **流式输出(新流程)**
- 实时打印 token - 等待 AI 返回完整响应
- Spinner 显示"思考中..." - Markdown 转译
- 第一个 token 到达时停止 spinner - 模拟流式输出(从配置读取速度)
- 效果更好,无残留问题
2. **Markdown 渲染** 2. **Markdown 渲染**
- 使用 glamour 库渲染 Markdown - 使用 glamour 库渲染 Markdown
- 支持多种主题dark, light, dracula, tokyo-night 等) - 支持多种主题dark, light, dracula, tokyo-night 等)
- 通过 GLAMOUR_STYLE 环境变量配置主题 - 通过 project.config.yml 配置主题
3. **重绘逻辑** 3. **项目配置**
- 响应完成后尝试重绘 - 通过 project.config.yml 统一管理配置项
- 使用 termenv 库清除屏幕 - 支持流式速度、渲染主题、Logo 等配置
- ⚠️ 存在轻微残留 bug可接受
---
## 项目配置
### project.config.yml
配置文件位于项目根目录:
```yaml
# hxclaw 项目配置文件
# 模拟流式输出配置
streaming:
simulated_speed_ms: 30 # 模拟流式输出速度(毫秒/字符)
# Markdown 渲染配置
markdown:
glamour_style: dark # 渲染主题dark, light, dracula, tokyo-night 等
# UI 配置
ui:
logo: "🦐"
```
配置加载优先级:
1. 环境变量 `HXCLAW_CONFIG` 指定路径
2. 项目根目录 `project.config.yml`
--- ---
@@ -82,12 +110,14 @@
- `charm.land/lipgloss/v2` - 终端样式 - `charm.land/lipgloss/v2` - 终端样式
- `charm.land/x/term` - 终端控制 - `charm.land/x/term` - 终端控制
- `github.com/muesli/termenv` - 终端环境工具 - `github.com/muesli/termenv` - 终端环境工具
- `gopkg.in/yaml.v3` - 配置文件解析
### 配置文件 ### 配置文件
- `cmd/hxclaw/main.go` - 主入口逻辑 - `cmd/hxclaw/main.go` - 主入口逻辑
- `cmd/hxclaw/internal/markdown.go` - Markdown 渲染器 - `cmd/hxclaw/internal/markdown.go` - Markdown 渲染器
- `cmd/hxclaw/internal/helpers.go` - 辅助函数 - `cmd/hxclaw/internal/helpers.go` - 辅助函数
- `cmd/hxclaw/internal/config.go` - 项目配置加载
--- ---
@@ -100,14 +130,16 @@
## 待优化 ## 待优化
1. 优化重绘逻辑,解决残留问题 1. 优化重绘逻辑,解决残留问题(已通过新流程解决)
2. 添加更多主题支持 2. 添加更多主题支持
3. 添加命令-line 参数支持主题选择 3. 添加命令参数支持主题选择
--- ---
## 构建命令 ## 构建命令
picoclaw GitHub地址https://github.com/sipeed/picoclaw.git
```bash ```bash
go build -o hxclaw ./cmd/hxclaw go build -o hxclaw ./cmd/hxclaw
``` ```
@@ -120,4 +152,4 @@ go build -o hxclaw ./cmd/hxclaw
- 不要修改 picoclaw 源码 - 不要修改 picoclaw 源码
- 保持代码独立,便于后续版本同步 - 保持代码独立,便于后续版本同步
- 优先实现核心功能,再考虑增强功能 - 优先实现核心功能,再考虑增强功能
- 文档和代码同步更新 - 文档和代码同步更新

View File

@@ -0,0 +1,112 @@
package internal
import (
"os"
"path/filepath"
"sync"
"gopkg.in/yaml.v3"
)
type ProjectConfig struct {
Streaming StreamingConfig `yaml:"streaming"`
Markdown MarkdownConfig `yaml:"markdown"`
UI UIConfig `yaml:"ui"`
}
type StreamingConfig struct {
LineDelayMs int `yaml:"line_delay_ms"`
LastLineDelayMs int `yaml:"last_line_delay_ms"`
}
type MarkdownConfig struct {
GlamourStyle string `yaml:"glamour_style"`
WrapWidth int `yaml:"wrap_width"`
}
type UIConfig struct {
Logo string `yaml:"logo"`
UserPrefix string `yaml:"user_prefix"`
}
var (
defaultCfg = ProjectConfig{
Streaming: StreamingConfig{
LineDelayMs: 1000,
LastLineDelayMs: 600,
},
Markdown: MarkdownConfig{
GlamourStyle: "dark",
WrapWidth: 0,
},
UI: UIConfig{
Logo: "🦐",
UserPrefix: "👀 ",
},
}
projCfg *ProjectConfig
projCfgLock sync.RWMutex
)
func LoadProjectConfig() error {
projCfgLock.Lock()
defer projCfgLock.Unlock()
cfgPath := getConfigPath()
if cfgPath == "" {
projCfg = &defaultCfg
return nil
}
data, err := os.ReadFile(cfgPath)
if err != nil {
if os.IsNotExist(err) {
projCfg = &defaultCfg
return nil
}
return err
}
var cfg ProjectConfig
if err := yaml.Unmarshal(data, &cfg); err != nil {
return err
}
if cfg.Streaming.LineDelayMs <= 0 {
cfg.Streaming.LineDelayMs = defaultCfg.Streaming.LineDelayMs
}
if cfg.Streaming.LastLineDelayMs <= 0 {
cfg.Streaming.LastLineDelayMs = defaultCfg.Streaming.LastLineDelayMs
}
if cfg.Markdown.GlamourStyle == "" {
cfg.Markdown.GlamourStyle = defaultCfg.Markdown.GlamourStyle
}
if cfg.Markdown.WrapWidth < 0 {
cfg.Markdown.WrapWidth = 0
}
if cfg.UI.Logo == "" {
cfg.UI.Logo = defaultCfg.UI.Logo
}
if cfg.UI.UserPrefix == "" {
cfg.UI.UserPrefix = defaultCfg.UI.UserPrefix
}
projCfg = &cfg
return nil
}
func GetProjectConfig() *ProjectConfig {
projCfgLock.RLock()
defer projCfgLock.RUnlock()
if projCfg == nil {
return &defaultCfg
}
return projCfg
}
func getConfigPath() string {
if path := os.Getenv("HXCLAW_CONFIG"); path != "" {
return path
}
return filepath.Join(".", "project.config.yml")
}

View File

@@ -2,9 +2,11 @@ package internal
import ( import (
"os" "os"
"strconv"
"strings" "strings"
"charm.land/glamour/v2" "charm.land/glamour/v2"
"github.com/charmbracelet/x/term"
) )
func RenderMarkdown(md string) string { func RenderMarkdown(md string) string {
@@ -13,10 +15,11 @@ func RenderMarkdown(md string) string {
} }
style := getStyle() style := getStyle()
wrapWidth := getWrapWidth()
r, err := glamour.NewTermRenderer( r, err := glamour.NewTermRenderer(
glamour.WithStandardStyle(style), glamour.WithStandardStyle(style),
glamour.WithWordWrap(80), glamour.WithWordWrap(wrapWidth),
) )
if err != nil { if err != nil {
return md return md
@@ -43,10 +46,11 @@ func RenderParagraph(text string) string {
} }
style := getStyle() style := getStyle()
wrapWidth := getWrapWidth()
r, err := glamour.NewTermRenderer( r, err := glamour.NewTermRenderer(
glamour.WithStandardStyle(style), glamour.WithStandardStyle(style),
glamour.WithWordWrap(80), glamour.WithWordWrap(wrapWidth),
) )
if err != nil { if err != nil {
return text return text
@@ -62,9 +66,33 @@ func RenderParagraph(text string) string {
} }
func getStyle() string { func getStyle() string {
style := "dark" if cfg := GetProjectConfig(); cfg != nil {
if s := os.Getenv("GLAMOUR_STYLE"); s != "" { if cfg.Markdown.GlamourStyle != "" {
style = s return cfg.Markdown.GlamourStyle
}
} }
return style if s := os.Getenv("GLAMOUR_STYLE"); s != "" {
return s
}
return "dark"
}
func getWrapWidth() int {
if cfg := GetProjectConfig(); cfg != nil {
if cfg.Markdown.WrapWidth > 0 {
return cfg.Markdown.WrapWidth
}
}
if cols := os.Getenv("COLUMNS"); cols != "" {
if w, err := strconv.Atoi(cols); err == nil && w > 0 {
return w
}
}
width, _, err := term.GetSize(0)
if err != nil || width <= 0 {
return 80
}
return width
} }

View File

@@ -80,11 +80,11 @@ func (s *Spinner) run() {
} }
func (s *Spinner) render() { func (s *Spinner) render() {
fmt.Printf("\r%s %s", s.spinner.View(), s.text) fmt.Printf("\r %s %s", s.spinner.View(), s.text)
os.Stdout.Sync() os.Stdout.Sync()
} }
func (s *Spinner) clear() { func (s *Spinner) clear() {
fmt.Printf("\r%s 思考完成.\n", s.spinner.View()) fmt.Printf("\r %s 思考完成.\n", s.spinner.View())
os.Stdout.Sync() os.Stdout.Sync()
} }

View File

@@ -18,12 +18,16 @@ import (
"github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/providers"
) )
var totalCompletionTokens int
const Logo = "🦐" const Logo = "🦐"
func main() { func main() {
fmt.Printf("%s HxClaw - PicoClaw 增强版 CLI\n\n", Logo) if err := internal.LoadProjectConfig(); err != nil {
fmt.Fprintf(os.Stderr, "错误:加载项目配置失败: %v\n", err)
os.Exit(1)
}
logo := internal.GetProjectConfig().UI.Logo
fmt.Printf("%s HxClaw - PicoClaw 增强版 CLI\n\n", logo)
cfg, err := internal.LoadConfig() cfg, err := internal.LoadConfig()
if err != nil { if err != nil {
@@ -62,7 +66,7 @@ func main() {
} }
func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) { func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
prompt := fmt.Sprintf("%s You: ", Logo) prompt := internal.GetProjectConfig().UI.UserPrefix
rl, err := internal.NewReadline(prompt) rl, err := internal.NewReadline(prompt)
if err != nil { if err != nil {
@@ -101,7 +105,7 @@ func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) { func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
reader := internal.NewSimpleReader() reader := internal.NewSimpleReader()
for { for {
fmt.Print(fmt.Sprintf("%s You: ", Logo)) fmt.Print(internal.GetProjectConfig().UI.UserPrefix)
line, err := reader.ReadString() line, err := reader.ReadString()
if err != nil { if err != nil {
if err == internal.ErrEOF { if err == internal.ErrEOF {
@@ -126,131 +130,78 @@ func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
} }
} }
// runWithStreaming 尝试使用流式输出,如果 Provider 不支持则回退到普通模式 // runWithStreaming 使用 ProcessDirect 处理请求,支持工具调用和结果显示
func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string) { func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string) {
startTime := time.Now() startTime := time.Now()
agentInstance := agentLoop.GetRegistry().GetDefaultAgent() spinner := internal.NewSpinner("思考中...")
if agentInstance == nil { spinner.Start()
fmt.Println("错误:无法获取 Agent 实例")
resp, err := agentLoop.ProcessDirect(context.Background(), input, sessionKey)
spinner.Stop()
if err != nil {
fmt.Printf("错误: %v\n", err)
return return
} }
provider := agentInstance.Provider rendered := internal.RenderMarkdown(resp)
ctx := context.Background() clearSpinnerLine()
outputLineByLine(rendered)
// 判断是否支持流式 elapsed := time.Since(startTime)
if sp, ok := provider.(providers.StreamingProvider); ok { printElapsed(elapsed)
// 从 session 中获取历史消息 }
history := agentInstance.Sessions.GetHistory(sessionKey)
summary := agentInstance.Sessions.GetSummary(sessionKey)
// 使用 ContextBuilder 构建消息,包含历史 func clearSpinnerLine() {
messages := agentInstance.ContextBuilder.BuildMessages( output := termenv.DefaultOutput()
history, output.ClearLine()
summary, fmt.Print("\r")
input, os.Stdout.Sync()
nil, // media }
"cli", // channel
sessionKey,
"", // senderID
"", // senderDisplayName
)
// 获取工具定义 func outputLineByLine(text string) {
toolDefs := agentInstance.Tools.ToProviderDefs() if text == "" {
return
}
// 启动 spinner显示 "思考中..." lines := strings.Split(text, "\n")
spinner := internal.NewSpinner("思考中...") totalLines := len(lines)
spinner.Start()
fmt.Print("\n") cfg := internal.GetProjectConfig()
var result strings.Builder lineDelay := time.Duration(cfg.Streaming.LineDelayMs) * time.Millisecond
var printedLen int lastLineDelay := time.Duration(cfg.Streaming.LastLineDelayMs) * time.Millisecond
firstToken := true
resp, err := sp.ChatStream(ctx, messages, toolDefs, agentInstance.Model, nil, func(accumulated string) {
if firstToken && len(accumulated) > 0 {
spinner.Stop()
firstToken = false
}
if len(accumulated) > printedLen {
newText := accumulated[printedLen:]
fmt.Print(newText)
os.Stdout.Sync()
result.WriteString(newText)
printedLen = len(accumulated)
}
})
if err != nil { for i, line := range lines {
spinner.Stop() if line == "" {
fmt.Printf("流式调用错误: %v\n", err) fmt.Println()
return continue
} }
if result.Len() > 0 { fmt.Println(line)
allOutput := result.String()
rendered := internal.RenderMarkdown(allOutput)
if rendered != allOutput && rendered != "" {
lines := strings.Count(allOutput, "\n") + 1
output := termenv.DefaultOutput()
output.CursorUp(1)
output.ClearLine()
output.ClearLines(lines)
fmt.Print(rendered)
fmt.Println()
fmt.Println()
} else {
fmt.Println()
fmt.Println()
}
elapsed := time.Since(startTime) if i < totalLines-1 {
printStats(resp, elapsed) time.Sleep(lineDelay)
agentInstance.Sessions.AddMessage(sessionKey, "user", input)
agentInstance.Sessions.AddMessage(sessionKey, "assistant", allOutput)
}
} else {
response, err := agentLoop.ProcessDirect(ctx, input, sessionKey)
if err != nil {
fmt.Printf("错误: %v\n", err)
return
}
rendered := internal.RenderMarkdown(response)
if rendered != "" && rendered != response {
fmt.Printf("\n%s\n\n", rendered)
} else { } else {
fmt.Printf("\n%s %s\n\n", Logo, response) time.Sleep(lastLineDelay)
} }
} }
fmt.Println()
} }
var ( var (
iconStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ffcc80")) iconStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#f0c75e"))
textStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#5c7a9a")) textStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#2b2e32"))
) )
func printStats(resp *providers.LLMResponse, elapsed time.Duration) { func printElapsed(elapsed time.Duration) {
if resp == nil || resp.Usage == nil {
return
}
completionTokens := resp.Usage.CompletionTokens
if completionTokens <= 0 {
return
}
totalCompletionTokens += completionTokens
elapsedSec := math.Round(elapsed.Seconds()*10) / 10 elapsedSec := math.Round(elapsed.Seconds()*10) / 10
thisTokens := formatTokens(completionTokens)
totalTokens := formatTokens(totalCompletionTokens)
elapsedStr := formatDuration(elapsedSec) elapsedStr := formatDuration(elapsedSec)
icon := iconStyle.Render("▣ ") icon := iconStyle.Render("▣ ")
text := textStyle.Render(fmt.Sprintf("Tokens: %s · 耗时: %s · 总Tokens: %s", thisTokens, elapsedStr, totalTokens)) text := textStyle.Render(fmt.Sprintf("耗时: %s", elapsedStr))
fmt.Printf(" %s%s\n\n", icon, text) fmt.Printf(" %s%s\n\n", icon, text)
} }

7
go.mod
View File

@@ -8,7 +8,8 @@ require (
charm.land/lipgloss/v2 v2.0.2 charm.land/lipgloss/v2 v2.0.2
github.com/ergochat/readline v0.1.3 github.com/ergochat/readline v0.1.3
github.com/muesli/termenv v0.16.0 github.com/muesli/termenv v0.16.0
github.com/sipeed/picoclaw v0.0.0 github.com/sipeed/picoclaw v0.2.6
gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
@@ -92,12 +93,8 @@ require (
golang.org/x/term v0.41.0 // indirect golang.org/x/term v0.41.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect
golang.org/x/time v0.15.0 // indirect golang.org/x/time v0.15.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.70.0 // indirect modernc.org/libc v1.70.0 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.48.2 // indirect modernc.org/sqlite v1.48.2 // indirect
) )
// 开发时指向本地 picoclaw
replace github.com/sipeed/picoclaw => /Users/titor/picoclaw

2
go.sum
View File

@@ -159,6 +159,8 @@ github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/sipeed/picoclaw v0.2.6 h1:MqV6hidBW2WVPqE+nmIkjO3sJI/ng0nrAyFDJVHUOBg=
github.com/sipeed/picoclaw v0.2.6/go.mod h1:gl9BuZhxUIvrJM1oQXw4Xa8wlmarGGz1y1z6XdcMu3Y=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=

16
project.config.yml Normal file
View File

@@ -0,0 +1,16 @@
# hxclaw 项目配置文件
# 模拟流式输出配置
streaming:
line_delay_ms: 1000 # 每行输出后的延迟(毫秒)
last_line_delay_ms: 600 # 最后一行延迟(毫秒)
# Markdown 渲染配置
markdown:
glamour_style: dark # 渲染主题dark, light, dracula, tokyo-night 等
wrap_width: 0 # 自动换行宽度0=自动获取终端宽度)
# UI 配置
ui:
logo: "🦐"
user_prefix: "👀 "