feat: tabla apps en registry — modelo, parser, indexer y CLI

Agrega soporte completo para indexar aplicaciones del directorio apps/.
Cada app tiene un descriptor app.md con frontmatter YAML que el indexer
recoge automaticamente. Incluye migracion 004, modelo App, ParseAppMD,
ValidateApp, store CRUD con FTS5, y soporte en fn list/search/show.
Crea descriptores app.md para docker_tui, pipeline_launcher y metabase_registry.
This commit is contained in:
2026-03-29 00:13:57 +01:00
parent 95959f713c
commit f570e783fe
11 changed files with 465 additions and 6 deletions
+74 -4
View File
@@ -102,7 +102,10 @@ func cmdIndex() {
os.Exit(1)
}
fmt.Printf("Indexed %d functions, %d types\n", result.Functions, result.Types)
// Flush WAL to main db file so external readers (e.g. Metabase) see changes.
db.WalCheckpoint()
fmt.Printf("Indexed %d functions, %d types, %d apps\n", result.Functions, result.Types, result.Apps)
for _, e := range result.ValidationErrors {
fmt.Fprintf(os.Stderr, " INVALID: %s\n", e)
}
@@ -151,7 +154,13 @@ func cmdSearch(args []string) {
os.Exit(1)
}
if len(fns) == 0 && len(types) == 0 {
apps, err := db.SearchApps(query, lang, domain)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
if len(fns) == 0 && len(types) == 0 && len(apps) == 0 {
fmt.Println("No results.")
return
}
@@ -174,6 +183,16 @@ func cmdSearch(args []string) {
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)
}
}
w.Flush()
}
@@ -212,6 +231,12 @@ func cmdList(args []string) {
os.Exit(1)
}
apps, err := db.SearchApps("", 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")
@@ -228,7 +253,16 @@ func cmdList(args []string) {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", t.Algebraic, t.ID, t.Version, t.Domain)
}
}
if len(fns) == 0 && len(types) == 0 {
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(fns) == 0 && len(types) == 0 && len(apps) == 0 {
fmt.Println("Registry is empty. Run 'fn index' first.")
}
w.Flush()
@@ -258,6 +292,12 @@ func cmdShow(args []string) {
return
}
a, errA := db.GetApp(id)
if errA == nil {
printApp(a)
return
}
fmt.Fprintf(os.Stderr, "not found: %s\n", id)
os.Exit(1)
}
@@ -342,6 +382,34 @@ func printType(t *registry.Type) {
}
}
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.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)
}
}
// --- add ---
func cmdAdd(args []string) {
@@ -367,8 +435,10 @@ func cmdAdd(args []string) {
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")
default:
fmt.Fprintf(os.Stderr, "unknown kind: %s (use function, pipeline, or component)\n", kind)
fmt.Fprintf(os.Stderr, "unknown kind: %s (use function, pipeline, component, or app)\n", kind)
os.Exit(1)
}