Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,33 @@ plan/
.env*
*.log
*.tmp

# asdf
.tool-versions

### Go ###
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace files
go.work

### Custom ###
# Build output
bin/

### SuperPowers ###
docs/superpowers/
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ help: ## - print the help and usage
include mk/dev.mk
include mk/mkdocs.mk
include mk/adr.mk
include mk/go.mk
100 changes: 100 additions & 0 deletions cmd/mod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/LobsterTrap/lola/internal/modules"
)

var modCmd = &cobra.Command{
Use: "mod",
Short: "Manage lola modules",
}

var modAddCmd = &cobra.Command{
Use: "add <source>",
Short: "Add a module from a source",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
if err := modules.Add(args[0]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
},
}

var modRmCmd = &cobra.Command{
Use: "rm <name>",
Short: "Remove a registered module",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
if err := modules.Remove(args[0]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
},
}

var modLsCmd = &cobra.Command{
Use: "ls",
Short: "List registered modules",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
if err := modules.List(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
},
}

var modInfoCmd = &cobra.Command{
Use: "info [name]",
Short: "Show detailed info for a module",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := ""
if len(args) > 0 {
name = args[0]
}
if err := modules.Info(name); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
},
}

var modUpdateCmd = &cobra.Command{
Use: "update [name]",
Short: "Update a module from its source",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := ""
if len(args) > 0 {
name = args[0]
}
if err := modules.Update(name); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
},
}

var modInitCmd = &cobra.Command{
Use: "init <name>",
Short: "Scaffold a new module",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
if err := modules.Init(args[0]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
},
}

func init() {
modCmd.AddCommand(modAddCmd, modRmCmd, modLsCmd, modInfoCmd, modUpdateCmd, modInitCmd)
rootCmd.AddCommand(modCmd)
}
26 changes: 26 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cmd

import "github.com/spf13/cobra"

var rootCmd = &cobra.Command{
Use: "lola",
Short: "AI Context Package Manager",
Long: `lola - Universal AI Context Package Manager

Lola is a universal AI Context Package Manager, for skills, plugins,
profiles or Context Modules. Lola strives for AI sovereignty without
vendor lock-in. Write your AI Skills once, run anywhere.

Quick start:
lola mod add [git-url|folder|zip|tar] Add a module
lola mod ls List modules
lola install [module] -a [assistant] Install skills`,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

func Execute() error {
return rootCmd.Execute()
}

func init() {
rootCmd.Version = currentVersion()
}
70 changes: 70 additions & 0 deletions cmd/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cmd

import (
"fmt"
"runtime"
"runtime/debug"

"github.com/spf13/cobra"
)

// version is injected at build time:
// -ldflags "-X github.com/LobsterTrap/lola/cmd.version=v1.2.0"
var version string

// resolveVersion determines the version string based on available version sources
// in order of priority: build-time injection, go install module version, git commit hash, or default dev version.
//
// Parameters:
// - version: version injected at build time via ldflags
// - moduleVersion: version from debug.ReadBuildInfo().Main.Version
// - vcsRevision: git commit hash from debug.ReadBuildInfo().Settings
//
// Returns the resolved version string following the fallback chain:
// 1. If version is set (build-time injection) → return it
// 2. If moduleVersion is set and not "(devel)" → return it
// 3. If vcsRevision is set → return "0.0.0-dev+" with first 7 chars of commit hash
// 4. Default → return "0.0.0-dev"
func resolveVersion(version, moduleVersion, vcsRevision string) string {
switch {
case version != "":
return version
case moduleVersion != "" && moduleVersion != "(devel)":
return moduleVersion
case vcsRevision != "":
return "0.0.0-dev+" + vcsRevision[:min(7, len(vcsRevision))]
default:
return "0.0.0-dev"
}
}

// currentVersion queries runtime build info and returns the resolved version.
// It extracts moduleVersion and vcsRevision from debug.ReadBuildInfo() and passes
// them to resolveVersion along with the build-time injected version variable.
func currentVersion() string {
var moduleVersion, vcsRevision string
if bi, ok := debug.ReadBuildInfo(); ok {
moduleVersion = bi.Main.Version
for _, s := range bi.Settings {
if s.Key == "vcs.revision" {
vcsRevision = s.Value
break
}
}
}
return resolveVersion(version, moduleVersion, vcsRevision)
}

// versionCmd represents the version command.
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print lola version and platform info",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("lola version %s %s/%s\n", cmd.Root().Version, runtime.GOOS, runtime.GOARCH)
},
}

func init() {
rootCmd.AddCommand(versionCmd)
rootCmd.SetVersionTemplate("{{.Version}}\n")
}
28 changes: 28 additions & 0 deletions cmd/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import "testing"

func TestResolveVersion(t *testing.T) {
tt := []struct {
name string
version string
moduleVersion string
vcsRevision string
want string
}{
{"release build", "v1.2.0", "(devel)", "a3f9b12abc", "v1.2.0"},
{"go install", "", "v1.2.0", "", "v1.2.0"},
{"dev with commit", "", "(devel)", "a3f9b12abc", "0.0.0-dev+a3f9b12"},
{"dev short hash", "", "(devel)", "abc", "0.0.0-dev+abc"},
{"dev exact 7 hash", "", "(devel)", "a3f9b12", "0.0.0-dev+a3f9b12"},
{"dev no vcs", "", "(devel)", "", "0.0.0-dev"},
{"no context at all", "", "", "", "0.0.0-dev"},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
if got := resolveVersion(tc.version, tc.moduleVersion, tc.vcsRevision); got != tc.want {
t.Errorf("got %q, want %q", got, tc.want)
}
})
}
}
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/LobsterTrap/lola

go 1.26.3
Comment thread
mrbrandao marked this conversation as resolved.

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.9 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
6 changes: 6 additions & 0 deletions internal/modules/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package modules

// Add registers a new module from the given source (git URL, local path, zip, tar, or OCI reference).
func Add(source string) error {
return ErrNotImplemented
}
9 changes: 9 additions & 0 deletions internal/modules/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Package modules implements Lola's module management domain logic.
package modules

import "errors"

// ErrNotImplemented is returned by stub functions that have not yet been implemented.
// It serves as the template for domain-specific errors (e.g. ErrModuleNotFound) that
// will be added to this file as each function is implemented.
var ErrNotImplemented = errors.New("not yet implemented")
7 changes: 7 additions & 0 deletions internal/modules/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package modules

// Info returns detailed information about the named module.
// TODO: return (*Module, error) once the Module type is defined in pkg/models.
func Info(name string) error {
return ErrNotImplemented
}
6 changes: 6 additions & 0 deletions internal/modules/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package modules

// Init scaffolds a new module directory structure with the given name.
func Init(name string) error {
return ErrNotImplemented
}
7 changes: 7 additions & 0 deletions internal/modules/ls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package modules

// List returns all modules registered in LOLA_HOME.
// TODO: return ([]Module, error) once the Module type is defined in pkg/models.
func List() error {
return ErrNotImplemented
}
6 changes: 6 additions & 0 deletions internal/modules/rm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package modules

// Remove unregisters the module with the given name from LOLA_HOME.
func Remove(name string) error {
return ErrNotImplemented
}
7 changes: 7 additions & 0 deletions internal/modules/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package modules

// Update re-fetches the module with the given name from its original source.
// If name is empty, all registered modules are updated.
func Update(name string) error {
return ErrNotImplemented
}
16 changes: 16 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright © 2026 LobsterTrap Contributors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"os"

"github.com/LobsterTrap/lola/cmd"
)

func main() {
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}
16 changes: 16 additions & 0 deletions mk/go.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Go build targets
# VERSION is set only when HEAD is exactly on a tag; empty string otherwise,
# which causes resolveVersion to fall through to VCS stamping.
VERSION := $(shell git describe --tags --exact-match 2>/dev/null || echo "")
LDFLAGS := -X github.com/LobsterTrap/lola/cmd.version=$(VERSION)

.PHONY: go-build go-test go-vet

go-build: ## - build lola binary (version from tag if on one, else from VCS)
go build -ldflags "$(LDFLAGS)" -o bin/lola .

go-test: ## - run Go test suite
go test ./...

go-vet: ## - run go vet static analysis
go vet ./...
Loading