package main import ( "fmt" "os" "os/exec" "path/filepath" "strings" "gitcode.com/JianFeeeee/homeagent-sdk/tools/hmapdev/yaegi" ) // tmplLuaDebug is the temporary Lua debug script template const tmplLuaDebug = `-- HomeAgent Lua Plugin Debug -- Generated by hmapdev debug --lua -- sdk.lua 优先用激活 SDK 的权威 mock(HMAPDEV_SDK_LUA),否则回退项目内副本, -- 避免拿一份过期的 sdk.lua 调试出“本地能跑、内核报 nil”的假象。 local sdk_path = os.getenv("HMAPDEV_SDK_LUA") if sdk_path and sdk_path ~= "" then sdk = dofile(sdk_path) else sdk = require("sdk") end local ok, plugin = pcall(dofile, "main.lua") if not ok then print("[debug] ERROR loading main.lua: " .. tostring(plugin)) os.exit(1) end if type(plugin) == "table" then print("[debug] Plugin: " .. tostring(plugin.name or "unnamed")) if plugin.start then print("[debug] Calling plugin.start(sdk) ...") local ok, err = pcall(plugin.start, sdk) if ok then print("[debug] plugin.start() OK") else print("[debug] plugin:start() ERROR: " .. tostring(err)) end end end print("") print("=== Interactive REPL ===") print("sdk, plugin globals are available") local function repl() while true do io.write("> ") io.flush() local line = io.read() if line == nil or line == "exit" or line == "quit" then break end if line == "help" then print(" help - this help") print(" exit/quit - exit debug") print(" sdk - SDK API table") print(" plugin - loaded plugin table") else local fn, err = (loadstring or load)(line) if fn then local ok, result = pcall(fn) if ok and result ~= nil then print(tostring(result)) end if not ok then print("Error: " .. tostring(result)) end else print("Error: " .. tostring(err)) end end end end repl() ` type DebugConfig struct { Dir string Replaces []string } func cmdDebug(args []string) { cfg := DebugConfig{Dir: "."} for i := 0; i < len(args); i++ { switch args[i] { case "--replace", "-R": if i+1 < len(args) { cfg.Replaces = append(cfg.Replaces, args[i+1]); i++ } default: if !strings.HasPrefix(args[i], "-") { cfg.Dir = args[i] } } } dir := cfg.Dir luaPath := filepath.Join(dir, "main.lua") sdkPath := filepath.Join(dir, "sdk.lua") hasGo := false entries, _ := os.ReadDir(dir) for _, e := range entries { if !e.IsDir() && strings.HasSuffix(e.Name(), ".go") && !strings.HasSuffix(e.Name(), "_test.go") { hasGo = true break } } // Merge plg.json replaces with CLI --replace overrides var replaces []string if plg, err := readPlgJSON(filepath.Join(dir, "plg.json")); err == nil { replaces = plg.ReplacesToSlice() } replaces = append(replaces, cfg.Replaces...) if _, err := os.Stat(luaPath); err == nil { debugLua(dir, sdkPath, luaPath) } else if hasGo { debugGo(dir, replaces) } else { fmt.Println("error: no main.lua or .go files found in", dir) os.Exit(1) } } func debugLua(dir, sdkPath, luaPath string) { // check lua interpreter luaBin, err := exec.LookPath("lua") if err != nil { fmt.Println("error: lua interpreter not found in PATH") fmt.Println(" install Lua 5.1+ or use hmapdev build to compile your plugin") os.Exit(1) } fmt.Printf("[debug] Lua interpreter: %s\n", luaBin) fmt.Printf("[debug] Plugin dir: %s\n", dir) // check if sdk.lua exists if _, err := os.Stat(sdkPath); os.IsNotExist(err) { fmt.Println("warning: sdk.lua not found, debug SDK mock will not be available") } // 优先用激活 SDK 里的权威 mock,避免调试用的是项目里可能过期的副本。 env := os.Environ() if root := tryActiveSDKRoot(); root != "" { canonical := filepath.Join(root, "sdk", "lua", "sdk.lua") if _, err := os.Stat(canonical); err == nil { env = append(env, "HMAPDEV_SDK_LUA="+canonical) fmt.Printf("[debug] SDK mock: %s\n", canonical) } } // write temporary debug script debugScript := filepath.Join(dir, "_debug.lua") if err := os.WriteFile(debugScript, []byte(tmplLuaDebug), 0644); err != nil { fmt.Printf("error: write debug script: %v\n", err) os.Exit(1) } defer os.Remove(debugScript) cmd := exec.Command(luaBin, filepath.Base(debugScript)) cmd.Dir = dir cmd.Env = env cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr fmt.Println("[debug] Starting Lua debug session...") fmt.Println() if err := cmd.Run(); err != nil { if exitErr, ok := err.(*exec.ExitError); ok { os.Exit(exitErr.ExitCode()) } fmt.Printf("[debug] error: %v\n", err) os.Exit(1) } } func debugGo(dir string, replaces []string) { debug, err := yaegi.NewYaegiDebugger(dir, replaces) if err != nil { fmt.Printf("error: %v\n", err) os.Exit(1) } fmt.Printf("[debug] Plugin dir: %s\n", dir) if err := debug.LoadPlugin(); err != nil { fmt.Printf("[debug] load plugin: %v\n", err) os.Exit(1) } if err := debug.StartREPL(); err != nil { fmt.Printf("[debug] repl error: %v\n", err) os.Exit(1) } }