Files
homeagent-sdk/example/editdoc/plugin.go
JianFeeeee b6a6168e65 chore(module)!: module 路径随托管地迁移 gitcode → github
SDK 仓已迁到 GitHub 作为权威源,go module 路径同步:
`gitcode.com/JianFeeeee/homeagent-sdk` → `github.com/JianFeeeee/homeagentsdk`

## 范围

74 个文件:71 个 Go 源码 + go.mod/go.sum + hmapdev 模板 + CI 与打包脚本。

主仓(HomeAgent)侧对应改动是 6d38a164。本仓这些文件当时**不在工作树上**
(见下),所以那次的批量替换扫不到它们——这里是补偿。

## 为何这批文件当时不在工作树上(事故与恢复)

10-08 07:58 主仓做 checkout/cherry-pick 序列时,把本仓工作树的 20 个文件
误清理了(`example/*/plugin.go` + `plg.json`,10 个示例)。

根因是**结构误判**:本仓是「一目录两仓库」结构下主仓 `third_party/` 里的
**独立 git 仓库**,而主仓 .gitignore 忽略了 `example/`,于是它在主仓
`git status` 里不可见 —— 看着像「不受跟踪的构建产物」,实际是本仓受跟踪的源码。

已全部恢复(`git checkout -- <D 文件>`,只恢复删除、不动迁移改动),
20 个文件与 HEAD 逐字节一致。

**破坏的连锁后果**:恢复出来的 `plugin.go` 仍写着旧 module 路径,
因为迁移时它们已不在工作树上。所以这次补偿是必须的,不是可选优化。

## 保留 gitcode 引用

以下位置的 `gitcode` **有意保留**,不要"顺手清掉":
- README.md / docs 里「二进制实际分发地址目前是 gitcode」的说明
- mkdocs.yml 的 gitcode 镜像链接(国内直连更快)
- `tools/hmapdev/templates.go` 里「SDK 已从 gitcode 迁到 GitHub」的注释

gitcode 仍是**只读镜像**(源码同步;release 附件暂时仍在那边)。

## 验证

- 本仓根 module:`go build ./...` + `go test ./...` 全通过
- 全部 20 个 example:`go vet` 通过(补偿前 10 个因旧 module 路径失败)
- example/qq:`go test` 通过,且能从源码 build 出可用 plugin 产物
2026-10-09 07:25:25 +08:00

164 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"github.com/JianFeeeee/homeagentsdk/sdk"
)
type Plugin struct {
name string
sdk *sdk.PluginSDK
scriptPath string
venvPython string
}
func (p *Plugin) Name() string { return p.name }
func (p *Plugin) Start(s *sdk.PluginSDK) error {
s.SetAutoRestart(true)
p.sdk = s
s.Settings().RegisterDef(sdk.ConfigDef{
Key: "script_path", Default: "", Type: "string",
DisplayName: "编辑脚本路径",
Description: "edit_doc.py 的绝对路径;留空时使用插件可执行文件同目录下的 edit_doc.py",
Category: p.name,
})
s.Settings().RegisterDef(sdk.ConfigDef{
Key: "venv_python", Default: "", Type: "string",
DisplayName: "venv Python 解释器",
Description: "执行 edit_doc.py 使用的 Python 解释器(建议用 venv 内的 python);必须配置,留空将报错",
Category: p.name,
})
if v, err := s.Settings().Get("script_path"); err == nil {
if str, ok := v.(string); ok {
p.scriptPath = str
}
}
if v, err := s.Settings().Get("venv_python"); err == nil {
if str, ok := v.(string); ok {
p.venvPython = str
}
}
s.RegisterTool("edit_document", sdk.ToolDef{
Name: "edit_document",
Description: "编辑 Office 文档内容。支持替换文本、修改单元格等操作。编辑后原文件被覆盖。操作前建议先用 read_document 查看内容。支持 .docx / .xlsx / .pptx。",
NoMemory: true,
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"file": map[string]interface{}{"type": "string", "description": "文档文件路径"},
"operation": map[string]interface{}{"type": "string", "description": "操作: replace_text(查找替换), set_cell(设置单元格), insert_row(插入行)"},
"target": map[string]interface{}{"type": "string", "description": "要查找的文本(replace_text)"},
"replacement": map[string]interface{}{"type": "string", "description": "替换为的文本(replace_text)"},
"sheet": map[string]interface{}{"type": "string", "description": "工作表名称(xlsx,可选)"},
"row": map[string]interface{}{"type": "integer", "description": "行号(set_cell/insert_row)"},
"col": map[string]interface{}{"type": "integer", "description": "列号(set_cell)"},
"value": map[string]interface{}{"type": "string", "description": "单元格值(set_cell)"},
},
"required": []string{"file", "operation"},
},
}, p.handleEditDocument)
return nil
}
func (p *Plugin) Stop() error { return nil }
func (p *Plugin) handleEditDocument(args map[string]interface{}) (interface{}, error) {
file, _ := args["file"].(string)
if file == "" {
return nil, fmt.Errorf("file is required")
}
operation, _ := args["operation"].(string)
if operation == "" {
return nil, fmt.Errorf("operation is required")
}
if _, err := os.Stat(file); os.IsNotExist(err) {
return map[string]interface{}{
"content": fmt.Sprintf("文件不存在: %s", file),
}, nil
}
pyArgs := map[string]interface{}{}
if v, ok := args["target"]; ok {
pyArgs["target"] = v
}
if v, ok := args["replacement"]; ok {
pyArgs["replacement"] = v
}
if v, ok := args["sheet"]; ok {
pyArgs["sheet"] = v
}
if v, ok := args["row"]; ok {
pyArgs["row"] = v
}
if v, ok := args["col"]; ok {
pyArgs["col"] = v
}
if v, ok := args["value"]; ok {
pyArgs["value"] = v
}
pyArgsJSON, _ := json.Marshal(pyArgs)
scriptPath := p.scriptPath
if scriptPath == "" {
scriptPath = filepath.Join(filepath.Dir(os.Args[0]), "edit_doc.py")
log.Printf("[%s] script_path 未配置,使用默认脚本路径: %s", p.name, scriptPath)
}
if _, err := os.Stat(scriptPath); os.IsNotExist(err) {
return nil, fmt.Errorf("edit_doc.py not found at %s(请在插件配置 script_path 中指定脚本路径)", scriptPath)
}
if p.venvPython == "" {
return nil, fmt.Errorf("venv_python 未配置,无法执行脚本;请在插件配置中设置 venv_python(venv 内 python 的绝对路径)")
}
if _, err := os.Stat(p.venvPython); err != nil {
return nil, fmt.Errorf("venv python 不存在: %s(请检查 venv_python 配置)", p.venvPython)
}
var out bytes.Buffer
cmd := exec.Command(p.venvPython, scriptPath, file, operation, string(pyArgsJSON))
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("edit document: %w", err)
}
var result struct {
Ok bool `json:"ok"`
Error string `json:"error,omitempty"`
Count int `json:"count,omitempty"`
}
if err := json.Unmarshal(out.Bytes(), &result); err != nil {
return map[string]interface{}{
"content": fmt.Sprintf("编辑完成,输出: %s", out.String()),
"file": file,
}, nil
}
if !result.Ok {
return map[string]interface{}{
"content": fmt.Sprintf("编辑失败: %s", result.Error),
}, nil
}
msg := fmt.Sprintf("编辑完成,已保存到原文件: %s", file)
if result.Count > 0 {
msg += fmt.Sprintf("\n共处理 %d 处", result.Count)
}
return map[string]interface{}{
"content": msg,
"file": file,
}, nil
}
func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) {
return &Plugin{name: name}, nil
}