4e8b5af6c4
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
714 lines
18 KiB
Go
714 lines
18 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"fn-registry/registry"
|
|
)
|
|
|
|
const dbName = "registry.db"
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
printUsage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
switch os.Args[1] {
|
|
case "index":
|
|
cmdIndex()
|
|
case "search":
|
|
cmdSearch(os.Args[2:])
|
|
case "list":
|
|
cmdList(os.Args[2:])
|
|
case "show":
|
|
cmdShow(os.Args[2:])
|
|
case "add":
|
|
cmdAdd(os.Args[2:])
|
|
case "ops":
|
|
cmdOps(os.Args[2:])
|
|
case "proposal":
|
|
cmdProposal(os.Args[2:])
|
|
case "run":
|
|
cmdRun(os.Args[2:])
|
|
case "check":
|
|
cmdCheck(os.Args[2:])
|
|
case "project":
|
|
cmdProject(os.Args[2:])
|
|
case "app":
|
|
cmdApp(os.Args[2:])
|
|
case "analysis":
|
|
cmdAnalysis(os.Args[2:])
|
|
case "sync":
|
|
cmdSync(os.Args[2:])
|
|
case "vault":
|
|
cmdVault(os.Args[2:])
|
|
case "doctor":
|
|
cmdDoctor(os.Args[2:])
|
|
case "match":
|
|
cmdMatch(os.Args[2:])
|
|
case "help", "-h", "--help":
|
|
printUsage()
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
|
|
printUsage()
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func printUsage() {
|
|
fmt.Println(`fn — registry CLI
|
|
|
|
Usage:
|
|
fn index Regenera registry.db desde los .md
|
|
fn search [-k kind] [-p purity] [-l lang] [-d domain] <query>
|
|
fn list [-k kind] [-d domain] [-l lang]
|
|
fn show <id> Muestra entrada completa
|
|
fn add [-k kind] Abre $EDITOR con template
|
|
fn run <id_or_name> [args...] Ejecuta funcion/pipeline (go/py/bash)
|
|
fn check params Lista funciones sin params_schema
|
|
fn ops <subcommand> Gestiona operations.db (fn ops help)
|
|
fn proposal <add|list|show|update> Gestiona proposals
|
|
fn project <init|list|show|status> Gestiona proyectos
|
|
fn app <list|clone|pull> Gestiona apps externas (Gitea)
|
|
fn analysis <list|clone|pull> Gestiona analyses externas (Gitea)
|
|
fn sync [status|locations] Sincroniza con servidor central
|
|
fn vault <list|search|index|info> Gestiona y busca en data vaults
|
|
fn doctor [artefacts|services|sync|uses-functions|unused] [--json]
|
|
Diagnostico read-only del registry
|
|
fn match [--top N] [--format json|text] [--min-score F] "<cmd>"
|
|
Fuzzy match entre comando shell y funciones del registry`)
|
|
}
|
|
|
|
func root() string {
|
|
// Walk up from the binary or cwd to find go.mod
|
|
dir, _ := os.Getwd()
|
|
for {
|
|
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
|
return dir
|
|
}
|
|
parent := filepath.Dir(dir)
|
|
if parent == dir {
|
|
break
|
|
}
|
|
dir = parent
|
|
}
|
|
// Fallback to cwd
|
|
cwd, _ := os.Getwd()
|
|
return cwd
|
|
}
|
|
|
|
func openDB() *registry.DB {
|
|
db, err := registry.Open(filepath.Join(root(), dbName))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
return db
|
|
}
|
|
|
|
// --- index ---
|
|
|
|
func cmdIndex() {
|
|
r := root()
|
|
db, err := registry.Open(filepath.Join(r, dbName))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
defer db.Close()
|
|
|
|
result, err := registry.Index(db, r)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Flush WAL to main db file so external readers (e.g. Metabase) see changes.
|
|
db.WalCheckpoint()
|
|
|
|
// Sync registry.db to Metabase mount directory if it exists.
|
|
metabaseCopy := filepath.Join(r, ".metabase-registry", "registry.db")
|
|
if _, err := os.Stat(filepath.Dir(metabaseCopy)); err == nil {
|
|
src := filepath.Join(r, dbName)
|
|
data, err := os.ReadFile(src)
|
|
if err == nil {
|
|
if err := os.WriteFile(metabaseCopy, data, 0666); err != nil {
|
|
fmt.Fprintf(os.Stderr, "warning: could not sync to metabase: %v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Printf("Indexed %d functions, %d types, %d apps, %d analysis, %d projects, %d vaults, %d unit_tests\n",
|
|
result.Functions, result.Types, result.Apps, result.Analysis, result.Projects, result.Vaults, result.UnitTests)
|
|
for _, e := range result.ValidationErrors {
|
|
fmt.Fprintf(os.Stderr, " INVALID: %s\n", e)
|
|
}
|
|
for _, w := range result.Warnings {
|
|
fmt.Fprintf(os.Stderr, " WARN: %s\n", w)
|
|
}
|
|
for _, e := range result.Errors {
|
|
fmt.Fprintf(os.Stderr, " ERROR: %s\n", e)
|
|
}
|
|
}
|
|
|
|
// --- search ---
|
|
|
|
func cmdSearch(args []string) {
|
|
var kind, purity, lang, domain, query string
|
|
i := 0
|
|
for i < len(args) {
|
|
switch args[i] {
|
|
case "-k":
|
|
i++
|
|
kind = args[i]
|
|
case "-p":
|
|
i++
|
|
purity = args[i]
|
|
case "-l":
|
|
i++
|
|
lang = args[i]
|
|
case "-d":
|
|
i++
|
|
domain = args[i]
|
|
default:
|
|
query = args[i]
|
|
}
|
|
i++
|
|
}
|
|
|
|
db := openDB()
|
|
defer db.Close()
|
|
|
|
fns, err := db.SearchFunctions(query, registry.Kind(kind), registry.Purity(purity), lang, domain)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
types, err := db.SearchTypes(query, lang, domain)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
apps, err := db.SearchApps(query, lang, domain)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
analyses, err := db.SearchAnalysis(query, lang, domain)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
projects, err := db.SearchProjects(query)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(fns) == 0 && len(types) == 0 && len(apps) == 0 && len(analyses) == 0 && len(projects) == 0 {
|
|
fmt.Println("No results.")
|
|
return
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
if len(fns) > 0 {
|
|
fmt.Fprintln(w, "KIND\tID\tPURITY\tDESCRIPTION")
|
|
for _, f := range fns {
|
|
desc := truncate(f.Description, 60)
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", f.Kind, f.ID, f.Purity, desc)
|
|
}
|
|
}
|
|
if len(types) > 0 {
|
|
if len(fns) > 0 {
|
|
fmt.Fprintln(w)
|
|
}
|
|
fmt.Fprintln(w, "ALGEBRAIC\tID\tDESCRIPTION")
|
|
for _, t := range types {
|
|
desc := truncate(t.Description, 60)
|
|
fmt.Fprintf(w, "%s\t%s\t%s\n", t.Algebraic, t.ID, desc)
|
|
}
|
|
}
|
|
if len(apps) > 0 {
|
|
if len(fns) > 0 || len(types) > 0 {
|
|
fmt.Fprintln(w)
|
|
}
|
|
fmt.Fprintln(w, "APP\tID\tLANG\tDESCRIPTION")
|
|
for _, a := range apps {
|
|
desc := truncate(a.Description, 60)
|
|
fmt.Fprintf(w, "app\t%s\t%s\t%s\n", a.ID, a.Lang, desc)
|
|
}
|
|
}
|
|
if len(analyses) > 0 {
|
|
if len(fns) > 0 || len(types) > 0 || len(apps) > 0 {
|
|
fmt.Fprintln(w)
|
|
}
|
|
fmt.Fprintln(w, "ANALYSIS\tID\tLANG\tDESCRIPTION")
|
|
for _, a := range analyses {
|
|
desc := truncate(a.Description, 60)
|
|
fmt.Fprintf(w, "analysis\t%s\t%s\t%s\n", a.ID, a.Lang, desc)
|
|
}
|
|
}
|
|
if len(projects) > 0 {
|
|
if len(fns) > 0 || len(types) > 0 || len(apps) > 0 || len(analyses) > 0 {
|
|
fmt.Fprintln(w)
|
|
}
|
|
fmt.Fprintln(w, "PROJECT\tID\tDESCRIPTION")
|
|
for _, p := range projects {
|
|
desc := truncate(p.Description, 60)
|
|
fmt.Fprintf(w, "project\t%s\t%s\n", p.ID, desc)
|
|
}
|
|
}
|
|
w.Flush()
|
|
}
|
|
|
|
// --- list ---
|
|
|
|
func cmdList(args []string) {
|
|
var kind, domain, lang string
|
|
i := 0
|
|
for i < len(args) {
|
|
switch args[i] {
|
|
case "-k":
|
|
i++
|
|
kind = args[i]
|
|
case "-d":
|
|
i++
|
|
domain = args[i]
|
|
case "-l":
|
|
i++
|
|
lang = args[i]
|
|
}
|
|
i++
|
|
}
|
|
|
|
db := openDB()
|
|
defer db.Close()
|
|
|
|
fns, err := db.SearchFunctions("", registry.Kind(kind), "", lang, domain)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
types, err := db.SearchTypes("", lang, domain)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
apps, err := db.SearchApps("", lang, domain)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
analyses, err := db.SearchAnalysis("", lang, domain)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
if len(fns) > 0 {
|
|
fmt.Fprintln(w, "KIND\tID\tPURITY\tVERSION\tDOMAIN")
|
|
for _, f := range fns {
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", f.Kind, f.ID, f.Purity, f.Version, f.Domain)
|
|
}
|
|
}
|
|
if len(types) > 0 {
|
|
if len(fns) > 0 {
|
|
fmt.Fprintln(w)
|
|
}
|
|
fmt.Fprintln(w, "ALGEBRAIC\tID\tVERSION\tDOMAIN")
|
|
for _, t := range types {
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", t.Algebraic, t.ID, t.Version, t.Domain)
|
|
}
|
|
}
|
|
if len(apps) > 0 {
|
|
if len(fns) > 0 || len(types) > 0 {
|
|
fmt.Fprintln(w)
|
|
}
|
|
fmt.Fprintln(w, "APP\tID\tLANG\tDOMAIN")
|
|
for _, a := range apps {
|
|
fmt.Fprintf(w, "app\t%s\t%s\t%s\n", a.ID, a.Lang, a.Domain)
|
|
}
|
|
}
|
|
if len(analyses) > 0 {
|
|
if len(fns) > 0 || len(types) > 0 || len(apps) > 0 {
|
|
fmt.Fprintln(w)
|
|
}
|
|
fmt.Fprintln(w, "ANALYSIS\tID\tLANG\tDOMAIN")
|
|
for _, a := range analyses {
|
|
fmt.Fprintf(w, "analysis\t%s\t%s\t%s\n", a.ID, a.Lang, a.Domain)
|
|
}
|
|
}
|
|
projects, err := db.ListAllProjects()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(projects) > 0 {
|
|
if len(fns) > 0 || len(types) > 0 || len(apps) > 0 || len(analyses) > 0 {
|
|
fmt.Fprintln(w)
|
|
}
|
|
fmt.Fprintln(w, "PROJECT\tID\tDESCRIPTION")
|
|
for _, p := range projects {
|
|
fmt.Fprintf(w, "project\t%s\t%s\n", p.ID, truncate(p.Description, 60))
|
|
}
|
|
}
|
|
if len(fns) == 0 && len(types) == 0 && len(apps) == 0 && len(analyses) == 0 && len(projects) == 0 {
|
|
fmt.Println("Registry is empty. Run 'fn index' first.")
|
|
}
|
|
w.Flush()
|
|
}
|
|
|
|
// --- show ---
|
|
|
|
func cmdShow(args []string) {
|
|
if len(args) < 1 {
|
|
fmt.Fprintln(os.Stderr, "usage: fn show <id>")
|
|
os.Exit(1)
|
|
}
|
|
id := args[0]
|
|
|
|
db := openDB()
|
|
defer db.Close()
|
|
|
|
f, errF := db.GetFunction(id)
|
|
if errF == nil {
|
|
printFunction(f)
|
|
return
|
|
}
|
|
|
|
t, errT := db.GetType(id)
|
|
if errT == nil {
|
|
printType(t)
|
|
return
|
|
}
|
|
|
|
a, errA := db.GetApp(id)
|
|
if errA == nil {
|
|
printApp(a)
|
|
return
|
|
}
|
|
|
|
an, errAn := db.GetAnalysis(id)
|
|
if errAn == nil {
|
|
printAnalysisEntry(an)
|
|
return
|
|
}
|
|
|
|
p, errP := db.GetProject(id)
|
|
if errP == nil {
|
|
printProjectEntry(p)
|
|
return
|
|
}
|
|
|
|
v, errV := db.GetVault(id)
|
|
if errV == nil {
|
|
printVaultEntry(v)
|
|
return
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "not found: %s\n", id)
|
|
os.Exit(1)
|
|
}
|
|
|
|
func printFunction(f *registry.Function) {
|
|
fmt.Printf("ID: %s\n", f.ID)
|
|
fmt.Printf("Name: %s\n", f.Name)
|
|
fmt.Printf("Kind: %s\n", f.Kind)
|
|
fmt.Printf("Lang: %s\n", f.Lang)
|
|
fmt.Printf("Domain: %s\n", f.Domain)
|
|
fmt.Printf("Version: %s\n", f.Version)
|
|
fmt.Printf("Purity: %s\n", f.Purity)
|
|
fmt.Printf("Signature: %s\n", f.Signature)
|
|
fmt.Printf("Description: %s\n", f.Description)
|
|
fmt.Printf("Tags: %s\n", strings.Join(f.Tags, ", "))
|
|
fmt.Printf("File: %s\n", f.FilePath)
|
|
if len(f.UsesFunctions) > 0 {
|
|
fmt.Printf("Uses fns: %s\n", strings.Join(f.UsesFunctions, ", "))
|
|
}
|
|
if len(f.UsesTypes) > 0 {
|
|
fmt.Printf("Uses types: %s\n", strings.Join(f.UsesTypes, ", "))
|
|
}
|
|
if len(f.Returns) > 0 {
|
|
fmt.Printf("Returns: %s\n", strings.Join(f.Returns, ", "))
|
|
}
|
|
if f.ErrorType != "" {
|
|
fmt.Printf("Error type: %s\n", f.ErrorType)
|
|
}
|
|
if len(f.Imports) > 0 {
|
|
fmt.Printf("Imports: %s\n", strings.Join(f.Imports, ", "))
|
|
}
|
|
if f.ParamsSchema != "" {
|
|
fmt.Printf("Params: %s\n", f.ParamsSchema)
|
|
}
|
|
if f.Example != "" {
|
|
fmt.Printf("\nExample:\n%s\n", f.Example)
|
|
}
|
|
if f.Notes != "" {
|
|
fmt.Printf("\nNotes:\n%s\n", f.Notes)
|
|
}
|
|
if f.Documentation != "" {
|
|
fmt.Printf("\nDocumentation:\n%s\n", f.Documentation)
|
|
}
|
|
if f.Code != "" {
|
|
fmt.Printf("\nCode:\n%s\n", f.Code)
|
|
}
|
|
if f.SourceRepo != "" {
|
|
fmt.Printf("Source repo: %s\n", f.SourceRepo)
|
|
fmt.Printf("Source license: %s\n", f.SourceLicense)
|
|
fmt.Printf("Source file: %s\n", f.SourceFile)
|
|
}
|
|
if f.Kind == registry.KindComponent {
|
|
fmt.Printf("Framework: %s\n", f.Framework)
|
|
if f.HasState != nil {
|
|
fmt.Printf("Has state: %v\n", *f.HasState)
|
|
}
|
|
if len(f.Emits) > 0 {
|
|
fmt.Printf("Emits: %s\n", strings.Join(f.Emits, ", "))
|
|
}
|
|
}
|
|
}
|
|
|
|
func printType(t *registry.Type) {
|
|
fmt.Printf("ID: %s\n", t.ID)
|
|
fmt.Printf("Name: %s\n", t.Name)
|
|
fmt.Printf("Lang: %s\n", t.Lang)
|
|
fmt.Printf("Domain: %s\n", t.Domain)
|
|
fmt.Printf("Version: %s\n", t.Version)
|
|
fmt.Printf("Algebraic: %s\n", t.Algebraic)
|
|
fmt.Printf("Description: %s\n", t.Description)
|
|
fmt.Printf("Tags: %s\n", strings.Join(t.Tags, ", "))
|
|
fmt.Printf("File: %s\n", t.FilePath)
|
|
if len(t.UsesTypes) > 0 {
|
|
fmt.Printf("Uses types: %s\n", strings.Join(t.UsesTypes, ", "))
|
|
}
|
|
if t.SourceRepo != "" {
|
|
fmt.Printf("Source repo: %s\n", t.SourceRepo)
|
|
fmt.Printf("Source license: %s\n", t.SourceLicense)
|
|
fmt.Printf("Source file: %s\n", t.SourceFile)
|
|
}
|
|
if t.Definition != "" {
|
|
fmt.Printf("\nDefinition:\n%s\n", t.Definition)
|
|
}
|
|
if t.Examples != "" {
|
|
fmt.Printf("\nExamples:\n%s\n", t.Examples)
|
|
}
|
|
if t.Notes != "" {
|
|
fmt.Printf("\nNotes:\n%s\n", t.Notes)
|
|
}
|
|
if t.Documentation != "" {
|
|
fmt.Printf("\nDocumentation:\n%s\n", t.Documentation)
|
|
}
|
|
if t.Code != "" {
|
|
fmt.Printf("\nCode:\n%s\n", t.Code)
|
|
}
|
|
}
|
|
|
|
func printApp(a *registry.App) {
|
|
fmt.Printf("ID: %s\n", a.ID)
|
|
fmt.Printf("Name: %s\n", a.Name)
|
|
fmt.Printf("Lang: %s\n", a.Lang)
|
|
fmt.Printf("Domain: %s\n", a.Domain)
|
|
fmt.Printf("Description: %s\n", a.Description)
|
|
fmt.Printf("Tags: %s\n", strings.Join(a.Tags, ", "))
|
|
fmt.Printf("Dir: %s\n", a.DirPath)
|
|
if a.RepoURL != "" {
|
|
fmt.Printf("Repo URL: %s\n", a.RepoURL)
|
|
}
|
|
if a.Framework != "" {
|
|
fmt.Printf("Framework: %s\n", a.Framework)
|
|
}
|
|
if a.EntryPoint != "" {
|
|
fmt.Printf("Entry point: %s\n", a.EntryPoint)
|
|
}
|
|
if len(a.UsesFunctions) > 0 {
|
|
fmt.Printf("Uses fns: %s\n", strings.Join(a.UsesFunctions, ", "))
|
|
}
|
|
if len(a.UsesTypes) > 0 {
|
|
fmt.Printf("Uses types: %s\n", strings.Join(a.UsesTypes, ", "))
|
|
}
|
|
if a.Notes != "" {
|
|
fmt.Printf("\nNotes:\n%s\n", a.Notes)
|
|
}
|
|
if a.Documentation != "" {
|
|
fmt.Printf("\nDocumentation:\n%s\n", a.Documentation)
|
|
}
|
|
}
|
|
|
|
func printAnalysisEntry(a *registry.Analysis) {
|
|
fmt.Printf("ID: %s\n", a.ID)
|
|
fmt.Printf("Name: %s\n", a.Name)
|
|
fmt.Printf("Lang: %s\n", a.Lang)
|
|
fmt.Printf("Domain: %s\n", a.Domain)
|
|
fmt.Printf("Description: %s\n", a.Description)
|
|
fmt.Printf("Tags: %s\n", strings.Join(a.Tags, ", "))
|
|
fmt.Printf("Dir: %s\n", a.DirPath)
|
|
if a.RepoURL != "" {
|
|
fmt.Printf("Repo URL: %s\n", a.RepoURL)
|
|
}
|
|
if a.Framework != "" {
|
|
fmt.Printf("Framework: %s\n", a.Framework)
|
|
}
|
|
if a.EntryPoint != "" {
|
|
fmt.Printf("Entry point: %s\n", a.EntryPoint)
|
|
}
|
|
if len(a.UsesFunctions) > 0 {
|
|
fmt.Printf("Uses fns: %s\n", strings.Join(a.UsesFunctions, ", "))
|
|
}
|
|
if len(a.UsesTypes) > 0 {
|
|
fmt.Printf("Uses types: %s\n", strings.Join(a.UsesTypes, ", "))
|
|
}
|
|
if a.Notes != "" {
|
|
fmt.Printf("\nNotes:\n%s\n", a.Notes)
|
|
}
|
|
if a.Documentation != "" {
|
|
fmt.Printf("\nDocumentation:\n%s\n", a.Documentation)
|
|
}
|
|
}
|
|
|
|
func printProjectEntry(p *registry.Project) {
|
|
fmt.Printf("ID: %s\n", p.ID)
|
|
fmt.Printf("Name: %s\n", p.Name)
|
|
fmt.Printf("Description: %s\n", p.Description)
|
|
fmt.Printf("Tags: %s\n", strings.Join(p.Tags, ", "))
|
|
fmt.Printf("Dir: %s\n", p.DirPath)
|
|
if p.RepoURL != "" {
|
|
fmt.Printf("Repo URL: %s\n", p.RepoURL)
|
|
}
|
|
if p.Notes != "" {
|
|
fmt.Printf("\nNotes:\n%s\n", p.Notes)
|
|
}
|
|
if p.Documentation != "" {
|
|
fmt.Printf("\nDocumentation:\n%s\n", p.Documentation)
|
|
}
|
|
}
|
|
|
|
func printVaultEntry(v *registry.Vault) {
|
|
fmt.Printf("ID: %s\n", v.ID)
|
|
fmt.Printf("Name: %s\n", v.Name)
|
|
if v.ProjectID != "" {
|
|
fmt.Printf("Project: %s\n", v.ProjectID)
|
|
}
|
|
fmt.Printf("Description: %s\n", v.Description)
|
|
if v.Path != "" {
|
|
fmt.Printf("Path: %s\n", v.Path)
|
|
}
|
|
fmt.Printf("Symlink: %v\n", v.Symlink)
|
|
fmt.Printf("Tags: %s\n", strings.Join(v.Tags, ", "))
|
|
}
|
|
|
|
// --- check ---
|
|
|
|
func cmdCheck(args []string) {
|
|
if len(args) < 1 {
|
|
fmt.Fprintln(os.Stderr, "usage: fn check <subcommand>\n\nSubcommands:\n params Lista funciones sin params_schema documentado")
|
|
os.Exit(1)
|
|
}
|
|
switch args[0] {
|
|
case "params":
|
|
cmdCheckParams()
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "unknown check subcommand: %s\n", args[0])
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func cmdCheckParams() {
|
|
db := openDB()
|
|
defer db.Close()
|
|
|
|
fns, err := db.SearchFunctions("", "", "", "", "")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
var missing []registry.Function
|
|
for _, f := range fns {
|
|
if f.ParamsSchema == "" {
|
|
missing = append(missing, f)
|
|
}
|
|
}
|
|
|
|
if len(missing) == 0 {
|
|
fmt.Printf("All %d functions have params_schema documented.\n", len(fns))
|
|
return
|
|
}
|
|
|
|
fmt.Printf("%d/%d functions missing params_schema:\n\n", len(missing), len(fns))
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
fmt.Fprintln(w, "ID\tLANG\tDOMAIN\tKIND")
|
|
for _, f := range missing {
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", f.ID, f.Lang, f.Domain, f.Kind)
|
|
}
|
|
w.Flush()
|
|
}
|
|
|
|
// --- add ---
|
|
|
|
func cmdAdd(args []string) {
|
|
kind := "function"
|
|
for i := 0; i < len(args); i++ {
|
|
if args[i] == "-k" {
|
|
i++
|
|
kind = args[i]
|
|
}
|
|
}
|
|
|
|
editor := os.Getenv("EDITOR")
|
|
if editor == "" {
|
|
editor = "vi"
|
|
}
|
|
|
|
r := root()
|
|
var templatePath string
|
|
switch kind {
|
|
case "function":
|
|
templatePath = filepath.Join(r, "docs", "templates", "function.md")
|
|
case "pipeline":
|
|
templatePath = filepath.Join(r, "docs", "templates", "pipeline.md")
|
|
case "component":
|
|
templatePath = filepath.Join(r, "docs", "templates", "component.md")
|
|
case "app":
|
|
templatePath = filepath.Join(r, "docs", "templates", "app.md")
|
|
case "analysis":
|
|
templatePath = filepath.Join(r, "docs", "templates", "analysis.md")
|
|
case "project":
|
|
templatePath = filepath.Join(r, "docs", "templates", "project.md")
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "unknown kind: %s (use function, pipeline, component, app, analysis, or project)\n", kind)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if _, err := os.Stat(templatePath); os.IsNotExist(err) {
|
|
fmt.Fprintf(os.Stderr, "template not found: %s\n", templatePath)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("Template: %s\n", templatePath)
|
|
fmt.Printf("Copy and edit the template, then run 'fn index' to register it.\n")
|
|
fmt.Printf("\n cp %s functions/<domain>/<name>.md\n $EDITOR functions/<domain>/<name>.md\n fn index\n", templatePath)
|
|
}
|
|
|
|
// --- add type ---
|
|
|
|
func truncate(s string, max int) string {
|
|
if len(s) <= max {
|
|
return s
|
|
}
|
|
return s[:max-3] + "..."
|
|
}
|