main.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. package main
  2. import (
  3. "context"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "os"
  8. "os/exec"
  9. "os/signal"
  10. "sort"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "syscall"
  15. "time"
  16. "ai-status-light/internal/api"
  17. "ai-status-light/internal/database"
  18. "ai-status-light/internal/discovery"
  19. "ai-status-light/internal/event"
  20. "ai-status-light/internal/logger"
  21. "ai-status-light/internal/monitor"
  22. mqttcli "ai-status-light/internal/mqtt"
  23. )
  24. const defaultDBPath = "./data/config.db"
  25. var Version = "dev"
  26. func main() {
  27. if len(os.Args) < 2 {
  28. printUsage()
  29. return
  30. }
  31. switch os.Args[1] {
  32. case "monitor":
  33. runMonitor(os.Args[2:])
  34. case "config":
  35. runConfig(os.Args[2:])
  36. case "serve":
  37. runServe(os.Args[2:])
  38. case "version":
  39. fmt.Printf("opencode-monitor %s\n", Version)
  40. default:
  41. printUsage()
  42. }
  43. }
  44. func printUsage() {
  45. fmt.Printf("opencode-monitor %s\n\n", Version)
  46. fmt.Println("用法: opencode-monitor <命令> [选项]")
  47. fmt.Println("")
  48. fmt.Println("命令:")
  49. fmt.Println(" monitor 启动监控")
  50. fmt.Println(" config 管理 MQTT 和 BLE 配置")
  51. fmt.Println(" serve 启动 API 服务")
  52. fmt.Println(" version 显示版本信息")
  53. fmt.Println("")
  54. fmt.Println("运行 'opencode-monitor <命令> -h' 查看命令帮助")
  55. }
  56. func runServe(args []string) {
  57. fs := flag.NewFlagSet("serve", flag.ExitOnError)
  58. addr := fs.String("addr", ":8080", "监听地址")
  59. dbPath := fs.String("db", defaultDBPath, "数据库路径")
  60. logFile := fs.String("log-file", "./logs", "日志文件路径(默认 ./logs/monitor.log)")
  61. logLevel := fs.String("log-level", "info", "日志级别 (debug/info/warn/error)")
  62. tls := fs.Bool("tls", false, "启用 HTTPS (使用自签名证书)")
  63. tlsCert := fs.String("tls-cert", "./data/tls/cert.pem", "TLS 证书文件路径")
  64. tlsKey := fs.String("tls-key", "./data/tls/key.pem", "TLS 私钥文件路径")
  65. fs.Parse(args)
  66. logger.SetLevel(logger.ParseLevel(*logLevel))
  67. if err := logger.InitFileLog(*logFile); err != nil {
  68. fmt.Printf("初始化日志文件失败: %v\n", err)
  69. return
  70. }
  71. defer logger.Close()
  72. db, err := database.New(*dbPath)
  73. if err != nil {
  74. logger.Error("打开数据库失败: %v", err)
  75. fmt.Printf("打开数据库失败: %v\n", err)
  76. return
  77. }
  78. defer db.Close()
  79. logger.Info("数据库已连接: %s", *dbPath)
  80. server := api.New(db, *addr)
  81. if *tls {
  82. if err := api.EnsureSelfSignedCert(*tlsCert, *tlsKey); err != nil {
  83. logger.Error("生成自签名证书失败: %v", err)
  84. fmt.Printf("生成自签名证书失败: %v\n", err)
  85. return
  86. }
  87. server.EnableTLS(*tlsCert, *tlsKey)
  88. logger.Info("HTTPS 已启用")
  89. fmt.Println("HTTPS 已启用 (自签名证书)")
  90. } else {
  91. fmt.Printf("API 服务已启动: %s\n", *addr)
  92. }
  93. fmt.Println("接口文档:")
  94. fmt.Println(" GET /api/health - 健康检查")
  95. fmt.Println(" GET /api/mqtt - 获取所有 MQTT 配置")
  96. fmt.Println(" POST /api/mqtt - 创建 MQTT 配置")
  97. fmt.Println(" GET /api/mqtt/:id - 获取单个 MQTT 配置")
  98. fmt.Println(" PUT /api/mqtt/:id - 更新 MQTT 配置")
  99. fmt.Println(" DELETE /api/mqtt/:id - 删除 MQTT 配置")
  100. fmt.Println(" GET /api/ble - 获取所有 BLE 配置")
  101. fmt.Println(" POST /api/ble - 创建 BLE 配置")
  102. fmt.Println(" GET /api/ble/:id - 获取单个 BLE 配置")
  103. fmt.Println(" PUT /api/ble/:id - 更新 BLE 配置")
  104. fmt.Println(" DELETE /api/ble/:id - 删除 BLE 配置")
  105. if err := server.Start(); err != nil {
  106. logger.Error("服务启动失败: %v", err)
  107. fmt.Printf("服务启动失败: %v\n", err)
  108. }
  109. }
  110. func runMonitor(args []string) {
  111. fs := flag.NewFlagSet("monitor", flag.ExitOnError)
  112. host := fs.String("host", "127.0.0.1", "主机地址")
  113. portsFlag := fs.String("ports", "", "端口列表,逗号分隔 (如: 4096,4097,4098)")
  114. scanFlag := fs.String("scan", "", "扫描端口范围 (如: 4096-4100)")
  115. intervalFlag := fs.Int("interval", 1, "动态扫描间隔(秒), 默认1")
  116. dbPath := fs.String("db", defaultDBPath, "数据库路径")
  117. apiAddr := fs.String("api-addr", "", "API 服务地址 (如: :8080)")
  118. tls := fs.Bool("tls", false, "启用 HTTPS (使用自签名证书)")
  119. tlsCert := fs.String("tls-cert", "./data/tls/cert.pem", "TLS 证书文件路径")
  120. tlsKey := fs.String("tls-key", "./data/tls/key.pem", "TLS 私钥文件路径")
  121. logFile := fs.String("log-file", "./logs", "日志文件路径(默认 ./logs/monitor.log)")
  122. logLevel := fs.String("log-level", "info", "日志级别 (debug/info/warn/error)")
  123. fs.Parse(args)
  124. logger.SetLevel(logger.ParseLevel(*logLevel))
  125. if err := logger.InitFileLog(*logFile); err != nil {
  126. fmt.Printf("初始化日志文件失败: %v\n", err)
  127. return
  128. }
  129. defer logger.Close()
  130. var scanRange *[2]int
  131. if *scanFlag != "" {
  132. parts := strings.Split(*scanFlag, "-")
  133. if len(parts) == 2 {
  134. start, err1 := strconv.Atoi(parts[0])
  135. end, err2 := strconv.Atoi(parts[1])
  136. if err1 == nil && err2 == nil {
  137. scanRange = &[2]int{start, end}
  138. }
  139. }
  140. }
  141. ctx, cancel := context.WithCancel(context.Background())
  142. defer cancel()
  143. sigChan := make(chan os.Signal, 1)
  144. signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
  145. db, err := database.New(*dbPath)
  146. if err != nil {
  147. logger.Error("打开数据库失败: %v", err)
  148. fmt.Printf("打开数据库失败: %v\n", err)
  149. return
  150. }
  151. defer db.Close()
  152. logger.Info("数据库已连接: %s", *dbPath)
  153. var mqttClient *mqttcli.Client
  154. cfg, err := db.GetMQTTConfig()
  155. if err != nil {
  156. logger.Error("读取 MQTT 配置失败: %v", err)
  157. fmt.Printf("读取 MQTT 配置失败: %v\n", err)
  158. } else if cfg != nil {
  159. mqttClient = mqttcli.NewFromConfig(cfg)
  160. if err := mqttClient.Connect(); err != nil {
  161. logger.Error("MQTT 连接失败: %v", err)
  162. fmt.Printf("MQTT 连接失败: %v\n", err)
  163. mqttClient = nil
  164. } else {
  165. defer mqttClient.Disconnect()
  166. logger.Info("MQTT 已连接: %s (主题: %s)", cfg.Broker, cfg.Topic)
  167. fmt.Printf("MQTT 已连接: %s (主题: %s)\n", cfg.Broker, cfg.Topic)
  168. }
  169. } else {
  170. logger.Info("未配置 MQTT,跳过 MQTT 连接")
  171. }
  172. var bleStdin io.WriteCloser
  173. bleCfg, err := db.GetBLEConfig()
  174. if err != nil {
  175. logger.Error("读取 BLE 配置失败: %v", err)
  176. fmt.Printf("读取 BLE 配置失败: %v\n", err)
  177. } else if bleCfg != nil {
  178. bleStdin = startBLERelay(bleCfg, ctx)
  179. } else {
  180. logger.Info("未配置 BLE,跳过 BLE 中继")
  181. }
  182. var apiServer *api.Server
  183. if *apiAddr != "" {
  184. apiServer = api.New(db, *apiAddr)
  185. if *tls {
  186. if err := api.EnsureSelfSignedCert(*tlsCert, *tlsKey); err != nil {
  187. logger.Error("生成自签名证书失败: %v", err)
  188. fmt.Printf("生成自签名证书失败: %v\n", err)
  189. return
  190. }
  191. apiServer.EnableTLS(*tlsCert, *tlsKey)
  192. }
  193. go func() {
  194. scheme := "http"
  195. if *tls {
  196. scheme = "https"
  197. }
  198. logger.Info("API 服务已启动: %s://%s", scheme, *apiAddr)
  199. fmt.Printf("API 服务已启动: %s://%s\n", scheme, *apiAddr)
  200. if err := apiServer.Start(); err != nil {
  201. logger.Error("API 服务失败: %v", err)
  202. fmt.Printf("API 服务失败: %v\n", err)
  203. }
  204. }()
  205. }
  206. callback := createCallback(mqttClient, apiServer, bleStdin)
  207. if *portsFlag != "" {
  208. runFixedMode(ctx, *host, *portsFlag, callback, sigChan)
  209. cancel()
  210. return
  211. }
  212. if scanRange != nil {
  213. fmt.Printf("扫描端口范围 %d-%d...\n", scanRange[0], scanRange[1])
  214. } else {
  215. fmt.Println("查找 OpenCode 实例...")
  216. }
  217. runDynamicMode(ctx, *host, scanRange, *intervalFlag, callback, sigChan)
  218. cancel()
  219. }
  220. func runConfig(args []string) {
  221. if len(args) < 1 {
  222. printConfigUsage()
  223. return
  224. }
  225. dbPath := defaultDBPath
  226. logFile := "./logs"
  227. logLevel := "info"
  228. for i, arg := range args {
  229. if arg == "--db" && i+1 < len(args) {
  230. dbPath = args[i+1]
  231. }
  232. if arg == "--log-file" && i+1 < len(args) {
  233. logFile = args[i+1]
  234. }
  235. if arg == "--log-level" && i+1 < len(args) {
  236. logLevel = args[i+1]
  237. }
  238. }
  239. logger.SetLevel(logger.ParseLevel(logLevel))
  240. if err := logger.InitFileLog(logFile); err != nil {
  241. fmt.Printf("初始化日志文件失败: %v\n", err)
  242. return
  243. }
  244. defer logger.Close()
  245. // 过滤全局选项,避免传递给子命令的 FlagSet
  246. var filtered []string
  247. for i := 0; i < len(args); i++ {
  248. switch args[i] {
  249. case "--db", "--log-file", "--log-level":
  250. i++ // 跳过值
  251. default:
  252. filtered = append(filtered, args[i])
  253. }
  254. }
  255. args = filtered
  256. db, err := database.New(dbPath)
  257. if err != nil {
  258. logger.Error("打开数据库失败: %v", err)
  259. fmt.Printf("打开数据库失败: %v\n", err)
  260. return
  261. }
  262. defer db.Close()
  263. logger.Info("数据库已连接: %s", dbPath)
  264. switch args[0] {
  265. case "list":
  266. configs, err := db.ListMQTTConfigs()
  267. if err != nil {
  268. logger.Error("查询配置失败: %v", err)
  269. fmt.Printf("查询失败: %v\n", err)
  270. return
  271. }
  272. if len(configs) == 0 {
  273. fmt.Println("未配置 MQTT")
  274. return
  275. }
  276. logger.Info("查询到 %d 条 MQTT 配置", len(configs))
  277. for _, cfg := range configs {
  278. status := "禁用"
  279. if cfg.Enabled {
  280. status = "启用"
  281. }
  282. auth := ""
  283. if cfg.Username != "" {
  284. auth = fmt.Sprintf(" [认证: %s]", cfg.Username)
  285. }
  286. fmt.Printf("[%d] %s | %s | %s%s | %s\n", cfg.ID, cfg.Broker, cfg.ClientID, cfg.Topic, auth, status)
  287. }
  288. case "set":
  289. fs := flag.NewFlagSet("config set", flag.ExitOnError)
  290. broker := fs.String("broker", "", "MQTT Broker 地址 (如: tcp://127.0.0.1:1883)")
  291. clientID := fs.String("client-id", "opencode-monitor", "MQTT 客户端 ID")
  292. username := fs.String("username", "", "MQTT 用户名")
  293. password := fs.String("password", "", "MQTT 密码")
  294. topic := fs.String("topic", "opencode/status", "MQTT 主题")
  295. enabled := fs.Bool("enabled", true, "是否启用")
  296. fs.Parse(args[1:])
  297. if *broker == "" {
  298. fmt.Println("必须指定 --broker")
  299. return
  300. }
  301. cfg := &database.MQTTConfig{
  302. Broker: *broker,
  303. ClientID: *clientID,
  304. Username: *username,
  305. Password: *password,
  306. Topic: *topic,
  307. Enabled: *enabled,
  308. }
  309. if err := db.SaveMQTTConfig(cfg); err != nil {
  310. logger.Error("保存 MQTT 配置失败: %v", err)
  311. fmt.Printf("保存失败: %v\n", err)
  312. return
  313. }
  314. logger.Info("MQTT 配置已保存: %s (主题: %s)", cfg.Broker, cfg.Topic)
  315. fmt.Println("配置已保存")
  316. case "delete":
  317. if len(args) < 2 {
  318. fmt.Println("必须指定配置 ID")
  319. return
  320. }
  321. id, err := strconv.Atoi(args[1])
  322. if err != nil {
  323. logger.Warn("无效的配置 ID: %s", args[1])
  324. fmt.Println("无效的 ID")
  325. return
  326. }
  327. if err := db.DeleteMQTTConfig(id); err != nil {
  328. logger.Error("删除配置失败: id=%d, %v", id, err)
  329. fmt.Printf("删除失败: %v\n", err)
  330. return
  331. }
  332. logger.Info("MQTT 配置已删除: id=%d", id)
  333. fmt.Println("配置已删除")
  334. case "ble":
  335. runBleConfig(db, args[1:])
  336. default:
  337. printConfigUsage()
  338. }
  339. }
  340. func printConfigUsage() {
  341. fmt.Println("用法: opencode-monitor config <子命令> [选项]")
  342. fmt.Println("")
  343. fmt.Println("子命令:")
  344. fmt.Println(" list 列出所有 MQTT 配置")
  345. fmt.Println(" set 设置 MQTT 配置")
  346. fmt.Println(" delete <id> 删除 MQTT 配置")
  347. fmt.Println(" ble list 列出所有 BLE 配置")
  348. fmt.Println(" ble set 设置 BLE 配置")
  349. fmt.Println(" ble delete <id> 删除 BLE 配置")
  350. fmt.Println("")
  351. fmt.Println("MQTT 选项:")
  352. fmt.Println(" --broker MQTT Broker 地址")
  353. fmt.Println(" --client-id MQTT 客户端 ID")
  354. fmt.Println(" --username MQTT 用户名")
  355. fmt.Println(" --password MQTT 密码")
  356. fmt.Println(" --topic MQTT 主题")
  357. fmt.Println(" --enabled 是否启用 (true/false)")
  358. fmt.Println("")
  359. fmt.Println("BLE 选项:")
  360. fmt.Println(" --device 蓝牙设备名称 (默认: AI-Light)")
  361. fmt.Println(" --service-uuid BLE 服务 UUID")
  362. fmt.Println(" --char-uuid BLE 特征 UUID")
  363. fmt.Println(" --enabled 是否启用 (true/false)")
  364. fmt.Println("")
  365. fmt.Println("全局选项:")
  366. fmt.Println(" --db 数据库路径")
  367. fmt.Println(" --log-file 日志文件路径(默认 ./logs/monitor.log)")
  368. fmt.Println(" --log-level 日志级别 (debug/info/warn/error)")
  369. }
  370. func startBLERelay(bleCfg *database.BLEConfig, ctx context.Context) io.WriteCloser {
  371. args := []string{
  372. "scripts/ble_relay.py",
  373. "--device", bleCfg.DeviceName,
  374. "--service-uuid", bleCfg.ServiceUUID,
  375. "--char-uuid", bleCfg.CharUUID,
  376. }
  377. cmd := exec.CommandContext(ctx, "python", args...)
  378. cmd.Stdout = os.Stdout
  379. cmd.Stderr = os.Stderr
  380. stdin, err := cmd.StdinPipe()
  381. if err != nil {
  382. logger.Error("创建 BLE 中继 stdin 管道失败: %v", err)
  383. return nil
  384. }
  385. if err := cmd.Start(); err != nil {
  386. logger.Error("启动 BLE 中继失败: %v", err)
  387. return nil
  388. }
  389. logger.Info("BLE 中继已启动: %s (PID: %d)", bleCfg.DeviceName, cmd.Process.Pid)
  390. fmt.Printf("BLE 中继已启动: %s (PID: %d)\n", bleCfg.DeviceName, cmd.Process.Pid)
  391. go func() {
  392. if err := cmd.Wait(); err != nil {
  393. logger.Error("BLE 中继退出: %v", err)
  394. }
  395. }()
  396. return stdin
  397. }
  398. func runBleConfig(db *database.DB, args []string) {
  399. if len(args) < 1 {
  400. printBleConfigUsage()
  401. return
  402. }
  403. switch args[0] {
  404. case "list":
  405. configs, err := db.ListBLEConfigs()
  406. if err != nil {
  407. logger.Error("查询 BLE 配置失败: %v", err)
  408. fmt.Printf("查询失败: %v\n", err)
  409. return
  410. }
  411. if len(configs) == 0 {
  412. fmt.Println("未配置 BLE")
  413. return
  414. }
  415. logger.Info("查询到 %d 条 BLE 配置", len(configs))
  416. for _, cfg := range configs {
  417. status := "禁用"
  418. if cfg.Enabled {
  419. status = "启用"
  420. }
  421. fmt.Printf("[%d] %s | %s | %s | %s\n", cfg.ID, cfg.DeviceName, cfg.ServiceUUID, cfg.CharUUID, status)
  422. }
  423. case "set":
  424. fs := flag.NewFlagSet("config ble set", flag.ExitOnError)
  425. deviceName := fs.String("device", "AI-Light", "蓝牙设备名称")
  426. serviceUUID := fs.String("service-uuid", "b8b7e001-7a6b-4f4f-9a8b-11c0ffee0001", "BLE 服务 UUID")
  427. charUUID := fs.String("char-uuid", "b8b7e002-7a6b-4f4f-9a8b-11c0ffee0001", "BLE 特征 UUID")
  428. enabled := fs.Bool("enabled", true, "是否启用")
  429. fs.Parse(args[1:])
  430. cfg := &database.BLEConfig{
  431. DeviceName: *deviceName,
  432. ServiceUUID: *serviceUUID,
  433. CharUUID: *charUUID,
  434. Enabled: *enabled,
  435. }
  436. if err := db.SaveBLEConfig(cfg); err != nil {
  437. logger.Error("保存 BLE 配置失败: %v", err)
  438. fmt.Printf("保存失败: %v\n", err)
  439. return
  440. }
  441. logger.Info("BLE 配置已保存: %s", cfg.DeviceName)
  442. fmt.Println("配置已保存")
  443. case "delete":
  444. if len(args) < 2 {
  445. fmt.Println("必须指定配置 ID")
  446. return
  447. }
  448. id, err := strconv.Atoi(args[1])
  449. if err != nil {
  450. logger.Warn("无效的配置 ID: %s", args[1])
  451. fmt.Println("无效的 ID")
  452. return
  453. }
  454. if err := db.DeleteBLEConfig(id); err != nil {
  455. logger.Error("删除 BLE 配置失败: id=%d, %v", id, err)
  456. fmt.Printf("删除失败: %v\n", err)
  457. return
  458. }
  459. logger.Info("BLE 配置已删除: id=%d", id)
  460. fmt.Println("配置已删除")
  461. default:
  462. printBleConfigUsage()
  463. }
  464. }
  465. func printBleConfigUsage() {
  466. fmt.Println("用法: opencode-monitor config ble <子命令> [选项]")
  467. fmt.Println("")
  468. fmt.Println("子命令:")
  469. fmt.Println(" list 列出所有 BLE 配置")
  470. fmt.Println(" set 设置 BLE 配置")
  471. fmt.Println(" delete <id> 删除 BLE 配置")
  472. fmt.Println("")
  473. fmt.Println("选项:")
  474. fmt.Println(" --device 蓝牙设备名称 (默认: AI-Light)")
  475. fmt.Println(" --service-uuid BLE 服务 UUID")
  476. fmt.Println(" --char-uuid BLE 特征 UUID")
  477. fmt.Println(" --enabled 是否启用 (true/false)")
  478. }
  479. func createCallback(mqttClient *mqttcli.Client, apiServer *api.Server, bleStdin io.Writer) monitor.EventCallback {
  480. lastStatus := make(map[int]string)
  481. var mu sync.Mutex
  482. publish := func(port int, status string, code string) {
  483. if mqttClient != nil {
  484. payload := map[string]interface{}{
  485. "port": port,
  486. "status": status,
  487. "code": code,
  488. "timestamp": time.Now().Format(time.RFC3339),
  489. }
  490. if err := mqttClient.PublishRaw(mqttClient.GetTopic(), payload); err != nil {
  491. logger.Error("MQTT 发送失败: %v", err)
  492. fmt.Printf("MQTT 发送失败: %v\n", err)
  493. }
  494. }
  495. if apiServer != nil {
  496. apiServer.BroadcastStatus(port, status, code)
  497. }
  498. if bleStdin != nil {
  499. msg := fmt.Sprintf(`{"port":%d,"code":"%s"}`+"\n", port, code)
  500. if _, err := bleStdin.Write([]byte(msg)); err != nil {
  501. logger.Error("BLE 发送失败: %v", err)
  502. }
  503. }
  504. }
  505. return func(port int, evt *event.SSEEvent) {
  506. msg := event.FormatEvent(port, evt)
  507. if msg != "" {
  508. fmt.Println(msg)
  509. }
  510. if mqttClient == nil && apiServer == nil && bleStdin == nil {
  511. return
  512. }
  513. var status, code string
  514. switch evt.Type {
  515. case "session.status":
  516. if s, ok := evt.Properties["status"].(map[string]interface{}); ok {
  517. if t, ok := s["type"].(string); ok {
  518. code = t
  519. }
  520. status = event.ParseStatus(s)
  521. }
  522. case "session.idle":
  523. status = "空闲"
  524. code = "idle"
  525. case "message.part.updated":
  526. if part, ok := evt.Properties["part"].(map[string]interface{}); ok {
  527. switch part["type"].(string) {
  528. case "tool":
  529. if st, ok := part["state"].(map[string]interface{}); ok {
  530. if s, ok := st["status"].(string); ok {
  531. code = s
  532. }
  533. status = event.ParseToolState(st)
  534. }
  535. case "reasoning":
  536. status = "思考中"
  537. code = "reasoning"
  538. default:
  539. status = "使用工具中"
  540. code = "using_tool"
  541. }
  542. }
  543. case "permission.updated":
  544. code = "permission"
  545. if title, ok := evt.Properties["title"].(string); ok && title != "" {
  546. status = "等待权限: " + title
  547. } else {
  548. status = "等待权限"
  549. }
  550. case "session.error":
  551. status = "错误"
  552. code = "error"
  553. }
  554. if status != "" {
  555. mu.Lock()
  556. prev := lastStatus[port]
  557. if status == "空闲" && prev != "" && prev != "空闲" {
  558. publish(port, "会话完成", "session_completed")
  559. }
  560. lastStatus[port] = status
  561. mu.Unlock()
  562. publish(port, status, code)
  563. }
  564. }
  565. }
  566. func runFixedMode(ctx context.Context, host string, portsFlag string, callback monitor.EventCallback, sigChan chan os.Signal) {
  567. var ports []int
  568. for _, p := range strings.Split(portsFlag, ",") {
  569. p = strings.TrimSpace(p)
  570. if port, err := strconv.Atoi(p); err == nil {
  571. ports = append(ports, port)
  572. }
  573. }
  574. if len(ports) == 0 {
  575. fmt.Println("未指定端口")
  576. return
  577. }
  578. logger.Info("固定模式启动,监控端口: %v", ports)
  579. fmt.Printf("监控端口: %v\n", ports)
  580. fmt.Println("Ctrl+C 停止")
  581. fmt.Println(strings.Repeat("-", 40))
  582. var wg sync.WaitGroup
  583. for _, port := range ports {
  584. wg.Add(1)
  585. go func(p int) {
  586. defer wg.Done()
  587. logger.Info("开始监控端口: %d", p)
  588. m := monitor.New(host, p, callback)
  589. m.Run(ctx)
  590. logger.Info("端口 %d 监控已停止", p)
  591. }(port)
  592. }
  593. <-sigChan
  594. logger.Info("收到停止信号,正在退出")
  595. fmt.Println("\n已停止")
  596. wg.Wait()
  597. logger.Info("所有监控协程已退出")
  598. }
  599. func runDynamicMode(ctx context.Context, host string, scanRange *[2]int, interval int, callback monitor.EventCallback, sigChan chan os.Signal) {
  600. scanner := discovery.NewScanner(host, scanRange)
  601. monitoredPorts := make(map[int]bool)
  602. runningMonitors := make(map[int]context.CancelFunc)
  603. var mu sync.Mutex
  604. startMonitor := func(port int) {
  605. mu.Lock()
  606. if _, exists := runningMonitors[port]; exists {
  607. mu.Unlock()
  608. return
  609. }
  610. monitorCtx, monitorCancel := context.WithCancel(ctx)
  611. runningMonitors[port] = monitorCancel
  612. mu.Unlock()
  613. go func() {
  614. logger.Info("开始监控端口: %d", port)
  615. fmt.Printf("开始监控端口: %d\n", port)
  616. m := monitor.New(host, port, callback)
  617. m.Run(monitorCtx)
  618. // 连接断开后清理记录,允许重新连接
  619. mu.Lock()
  620. delete(runningMonitors, port)
  621. delete(monitoredPorts, port)
  622. mu.Unlock()
  623. logger.Info("端口 %d 监控已停止,等待重新连接", port)
  624. fmt.Printf("端口 %d 监控已停止,等待重新连接\n", port)
  625. }()
  626. }
  627. initial := scanner.Discover()
  628. if len(initial) == 0 {
  629. logger.Info("未找到运行中的 OpenCode 实例,等待自动检测")
  630. fmt.Println("未找到运行中的 OpenCode 实例")
  631. fmt.Println("请先执行: opencode serve --port 4096")
  632. fmt.Println("启动后会自动检测,等待中...")
  633. }
  634. for _, port := range initial {
  635. monitoredPorts[port] = true
  636. startMonitor(port)
  637. }
  638. if len(monitoredPorts) > 0 {
  639. ports := make([]int, 0, len(monitoredPorts))
  640. for p := range monitoredPorts {
  641. ports = append(ports, p)
  642. }
  643. sort.Ints(ports)
  644. logger.Info("找到 %d 个实例: %v", len(monitoredPorts), ports)
  645. fmt.Printf("找到 %d 个实例: %v\n", len(monitoredPorts), ports)
  646. }
  647. logger.Info("动态模式启动,每 %d 秒扫描新实例", interval)
  648. fmt.Printf("每 %d 秒扫描新实例,Ctrl+C 停止\n", interval)
  649. fmt.Println(strings.Repeat("-", 40))
  650. scanTicker := time.NewTicker(time.Duration(interval) * time.Second)
  651. defer scanTicker.Stop()
  652. go func() {
  653. for {
  654. select {
  655. case <-ctx.Done():
  656. return
  657. case <-scanTicker.C:
  658. newPorts := scanner.Discover()
  659. if len(newPorts) > 0 {
  660. logger.Debug("扫描到 %d 个端口: %v", len(newPorts), newPorts)
  661. }
  662. for _, port := range newPorts {
  663. mu.Lock()
  664. alreadyMonitored := monitoredPorts[port]
  665. if !alreadyMonitored {
  666. monitoredPorts[port] = true
  667. }
  668. mu.Unlock()
  669. if !alreadyMonitored {
  670. logger.Info("发现新实例端口: %d,开始监控", port)
  671. startMonitor(port)
  672. }
  673. }
  674. }
  675. }
  676. }()
  677. <-sigChan
  678. logger.Info("收到停止信号,正在退出")
  679. fmt.Println("\n已停止")
  680. }