Files
fn_registry/cmd/fn/run.go
T
egutierrez 47fac22230 chore: auto-commit (799 archivos)
- .claude/CLAUDE.md
- .claude/commands/subagentes.md
- .claude/rules/INDEX.md
- .mcp.json
- bash/functions/cybersecurity/analyze_dns.md
- bash/functions/cybersecurity/audit_http_headers.md
- bash/functions/cybersecurity/audit_ssh_config.md
- bash/functions/cybersecurity/check_firewall.md
- bash/functions/cybersecurity/detect_suspicious_users.md
- bash/functions/cybersecurity/encrypt_file.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 00:28:20 +02:00

215 lines
6.0 KiB
Go

package main
import (
"database/sql"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
_ "github.com/mattn/go-sqlite3"
"fn-registry/registry"
)
func cmdRun(args []string) {
if len(args) == 0 {
fmt.Fprintln(os.Stderr, "usage: fn run <id_or_name> [args...]")
os.Exit(1)
}
idOrName := args[0]
passArgs := args[1:]
registryRoot := root()
dbPath := filepath.Join(registryRoot, dbName)
db, err := registry.Open(dbPath)
if err != nil {
fmt.Fprintf(os.Stderr, "error: cannot open registry: %v\n", err)
os.Exit(1)
}
defer db.Close()
fn, err := resolveFunction(db, idOrName)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
if fn.FilePath == "" {
fmt.Fprintf(os.Stderr, "error: %s has no file_path in registry\n", fn.ID)
os.Exit(1)
}
absPath := filepath.Join(registryRoot, fn.FilePath)
if _, err := os.Stat(absPath); os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "error: file not found: %s\n", absPath)
os.Exit(1)
}
cmd, err := buildCommand(fn, db, registryRoot, absPath, passArgs)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
fmt.Fprintf(os.Stderr, "[fn run] %s (%s/%s) %s\n", fn.ID, fn.Lang, fn.Kind, strings.Join(passArgs, " "))
t0 := time.Now()
runErr := cmd.Run()
durationMs := time.Since(t0).Milliseconds()
exitCode := 0
errClass := ""
if runErr != nil {
if exitErr, ok := runErr.(*exec.ExitError); ok {
exitCode = exitErr.ExitCode()
errClass = fmt.Sprintf("exit_%d", exitCode)
} else {
exitCode = 1
errClass = "spawn_error"
}
}
logFnRunTelemetry(registryRoot, fn.ID, durationMs, exitCode == 0, errClass)
if runErr != nil {
if errClass == "spawn_error" {
fmt.Fprintf(os.Stderr, "error: %v\n", runErr)
}
os.Exit(exitCode)
}
}
// logFnRunTelemetry inserts a row into call_monitor.operations.db.calls if
// the database is present. No-op silently otherwise — never blocks the run.
func logFnRunTelemetry(registryRoot, functionID string, durationMs int64, success bool, errClass string) {
callDB := filepath.Join(registryRoot, "projects", "fn_monitoring", "apps", "call_monitor", "operations.db")
if _, err := os.Stat(callDB); err != nil {
return
}
conn, err := sql.Open("sqlite3", callDB+"?_journal_mode=WAL&_busy_timeout=100")
if err != nil {
return
}
defer conn.Close()
sessionID := os.Getenv("CLAUDE_SESSION_ID")
successInt := 0
if success {
successInt = 1
}
_, _ = conn.Exec(
`INSERT INTO calls (session_id, function_id, tool_used, args_hash, duration_ms, success, error_class, error_snippet, ts)
VALUES (?, ?, 'fn_run_cli', '', ?, ?, ?, '', CAST(strftime('%s','now') AS INTEGER))`,
sessionID, functionID, durationMs, successInt, errClass,
)
}
func resolveFunction(db *registry.DB, idOrName string) (*registry.Function, error) {
fn, err := db.GetFunction(idOrName)
if err == nil {
return fn, nil
}
fns, err := db.GetFunctionsByName(idOrName)
if err != nil {
return nil, fmt.Errorf("lookup failed: %w", err)
}
if len(fns) == 0 {
return nil, fmt.Errorf("function %q not found (tried as ID and name)", idOrName)
}
if len(fns) == 1 {
return &fns[0], nil
}
var b strings.Builder
fmt.Fprintf(&b, "ambiguous name %q — found %d matches:\n", idOrName, len(fns))
for _, f := range fns {
fmt.Fprintf(&b, " %s (%s/%s)\n", f.ID, f.Lang, f.Kind)
}
fmt.Fprintf(&b, "use the full ID to disambiguate")
return nil, fmt.Errorf("%s", b.String())
}
func buildCommand(fn *registry.Function, db *registry.DB, registryRoot, absPath string, args []string) (*exec.Cmd, error) {
switch fn.Lang {
case "go":
return buildGoCommand(fn, registryRoot, absPath, args)
case "py":
return buildPyRunnerCommand(fn, db, registryRoot, args)
case "bash":
return buildBashCommand(absPath, args)
case "ts":
return buildTsCommand(registryRoot, absPath, args)
case "cpp":
return buildCppCommand(fn, registryRoot, absPath, args)
default:
return nil, fmt.Errorf("unsupported lang %q for execution", fn.Lang)
}
}
func buildGoCommand(fn *registry.Function, registryRoot, absPath string, args []string) (*exec.Cmd, error) {
dir := filepath.Dir(absPath)
env := append(os.Environ(), "CGO_ENABLED=1")
// If directory has main.go → go run . (pipelines and standalone executables)
mainGo := filepath.Join(dir, "main.go")
if _, err := os.Stat(mainGo); err == nil {
cmdArgs := append([]string{"run", "."}, args...)
cmd := exec.Command("go", cmdArgs...)
cmd.Dir = dir
cmd.Env = env
return cmd, nil
}
// Library code: if it has tests → go test
if fn.Tested && fn.TestFilePath != "" {
testAbs := filepath.Join(registryRoot, fn.TestFilePath)
if _, err := os.Stat(testAbs); err == nil {
relPkg, _ := filepath.Rel(registryRoot, dir)
pkgPath := "./" + filepath.ToSlash(relPkg)
cmdArgs := append([]string{"test", "-v", "-count=1", "-tags", "fts5", pkgPath}, args...)
cmd := exec.Command("go", cmdArgs...)
cmd.Dir = registryRoot
cmd.Env = env
return cmd, nil
}
}
// No tests: go vet (compilation check)
relPkg, _ := filepath.Rel(registryRoot, dir)
pkgPath := "./" + filepath.ToSlash(relPkg)
cmdArgs := []string{"vet", "-tags", "fts5", pkgPath}
cmd := exec.Command("go", cmdArgs...)
cmd.Dir = registryRoot
cmd.Env = env
fmt.Fprintf(os.Stderr, "[fn run] %s is library code without tests — running go vet\n", fn.ID)
return cmd, nil
}
func buildBashCommand(absPath string, args []string) (*exec.Cmd, error) {
cmdArgs := append([]string{absPath}, args...)
cmd := exec.Command("bash", cmdArgs...)
cmd.Dir = filepath.Dir(absPath)
return cmd, nil
}
func buildTsCommand(registryRoot, absPath string, args []string) (*exec.Cmd, error) {
tsxBin := filepath.Join(registryRoot, "frontend", "node_modules", ".bin", "tsx")
if _, err := os.Stat(tsxBin); os.IsNotExist(err) {
return nil, fmt.Errorf("tsx not found — run: cd frontend && pnpm add -D tsx")
}
cmdArgs := append([]string{absPath}, args...)
cmd := exec.Command(tsxBin, cmdArgs...)
cmd.Dir = filepath.Dir(absPath)
return cmd, nil
}