Skip to content
Draft
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
100 changes: 100 additions & 0 deletions .github/workflows/build-matrix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: build-matrix

on:
push:
branches: ["main", "dev"]
pull_request:
paths:
- "cmd/super-ollama/**"
- "internal/**"
- "go.mod"
- "go.sum"
- ".github/workflows/build-matrix.yml"

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

permissions:
contents: read

env:
CGO_ENABLED: "0"

jobs:
build:
name: build / ${{ matrix.os }} / ${{ matrix.arch }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
goos: linux
arch: amd64
goarch: amd64
- os: ubuntu-latest
goos: linux
arch: arm64
goarch: arm64
- os: macos-latest
goos: darwin
arch: amd64
goarch: amd64
- os: macos-latest
goos: darwin
arch: arm64
goarch: arm64
- os: windows-latest
goos: windows
arch: amd64
goarch: amd64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache-dependency-path: go.sum

- name: Build super-ollama binary
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
go build -o bin/super-ollama${{ matrix.goos == 'windows' && '.exe' || '' }} \
-ldflags="-s -w" \
./cmd/super-ollama

- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: super-ollama-${{ matrix.goos }}-${{ matrix.goarch }}
path: bin/super-ollama${{ matrix.goos == 'windows' && '.exe' || '' }}
if-no-files-found: error

# Unit tests for internal/config and the prompt pipeline
test-prompt-pipeline:
name: test / prompt pipeline / ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache-dependency-path: go.sum

- name: Run internal/config tests
run: go test -v -count=1 ./internal/config/...

- name: Run cmd/config tests
run: go test -v -count=1 ./cmd/config/...
97 changes: 92 additions & 5 deletions cmd/super-ollama/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
)

var modelFlag string
var noProfileFlag bool

// superOllamaLogLevel defaults to WARN so inference scheduler noise stays off stderr.
// Set OLLAMA_DEBUG (same semantics as ollama) to enable INFO/DEBUG/TRACE.
Expand All @@ -43,6 +44,7 @@ func main() {
Short: "Terminal-native local LLM CLI (super-ollama fork)",
}
root.PersistentFlags().StringVar(&modelFlag, "model", "", "model name (overrides config default_model)")
root.PersistentFlags().BoolVar(&noProfileFlag, "no-profile", false, "skip injecting ~/.super-ollama/profile.md into the system prompt")

root.AddCommand(
newHiddenRunnerCmd(),
Expand All @@ -52,7 +54,7 @@ func main() {
newStubCmd("todo", "TODO manager (coming in a later phase)"),
newStubCmd("snap", "Screenshot capture (coming in a later phase)"),
newStubCmd("learn", "Learning-loop re-index (coming in a later phase)"),
newConfigShowCmd(),
newConfigCmd(),
)

cobra.CheckErr(root.ExecuteContext(context.Background()))
Expand Down Expand Up @@ -86,6 +88,35 @@ func resolveModel() (string, error) {
return cfg.DefaultModel, nil
}

// buildSystemPrompt assembles the system prompt from profile.md and an optional
// named command prompt (e.g. "ask", "email"). If noProfileFlag is set, profile.md
// is skipped. promptName may be "" to load only the profile.
func buildSystemPrompt(promptName string) (string, error) {
var parts []string

if !noProfileFlag {
profile, err := config.LoadProfile()
if err != nil {
return "", fmt.Errorf("loading profile: %w", err)
}
if profile != "" {
parts = append(parts, profile)
}
}

if promptName != "" {
named, err := config.LoadPrompt(promptName)
if err != nil {
return "", fmt.Errorf("loading prompt %q: %w", promptName, err)
}
if named != "" {
parts = append(parts, named)
}
}

return strings.Join(parts, "\n\n"), nil
}

func readPrompt(args []string) (string, error) {
if len(args) > 0 {
return strings.Join(args, " "), nil
Expand Down Expand Up @@ -114,6 +145,11 @@ func newAskCmd() *cobra.Command {
return fmt.Errorf("prompt is empty; pass arguments or stdin")
}

system, err := buildSystemPrompt("ask")
if err != nil {
return err
}

ctx, stop := signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM)
defer stop()

Expand All @@ -123,7 +159,7 @@ func newAskCmd() *cobra.Command {
}
defer eng.Close()

out, err := eng.Generate(ctx, model, prompt, nil)
out, err := eng.Generate(ctx, model, prompt, system, nil)
if err != nil {
return err
}
Expand All @@ -143,6 +179,11 @@ func newChatCmd() *cobra.Command {
return err
}

system, err := buildSystemPrompt("")
if err != nil {
return err
}

ctx, stop := signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM)
defer stop()

Expand All @@ -153,6 +194,9 @@ func newChatCmd() *cobra.Command {
defer eng.Close()

var msgs []api.Message
if system != "" {
msgs = append(msgs, api.Message{Role: "system", Content: system})
}
scanner := bufio.NewScanner(os.Stdin)
ui.Printf("Model: %s — type /bye to exit\n", model)
for {
Expand Down Expand Up @@ -205,9 +249,36 @@ func newStubCmd(name, short string) *cobra.Command {
}
}

func newConfigCmd() *cobra.Command {
configCmd := &cobra.Command{
Use: "config",
Short: "Manage super-ollama configuration",
}
configCmd.AddCommand(newConfigShowCmd(), newConfigInitCmd())
return configCmd
}

func newConfigInitCmd() *cobra.Command {
return &cobra.Command{
Use: "init",
Short: "Create ~/.super-ollama/ with default config files",
RunE: func(*cobra.Command, []string) error {
if err := config.Init(); err != nil {
return err
}
dir, err := config.Dir()
if err != nil {
return err
}
ui.Printf("Initialized super-ollama directory: %s\n", dir)
return nil
},
}
}

func newConfigShowCmd() *cobra.Command {
return &cobra.Command{
Use: "config",
Use: "show",
Short: "Show effective config path and default model",
RunE: func(*cobra.Command, []string) error {
path, err := config.ResolvedPath()
Expand All @@ -218,11 +289,27 @@ func newConfigShowCmd() *cobra.Command {
if err != nil {
return err
}
ui.Printf("config file: %s\n", path)
ui.Printf("default_model: %s\n", cfg.DefaultModel)
ui.Printf("config file: %s\n", path)
ui.Printf("default_model: %s\n", cfg.DefaultModel)
ui.Printf("embedding_model: %s\n", cfg.EmbeddingModel)
if strings.TrimSpace(modelFlag) != "" {
ui.Printf("active --model override: %s\n", strings.TrimSpace(modelFlag))
}
dir, err := config.Dir()
if err != nil {
return err
}
ui.Printf("super-ollama dir: %s\n", dir)

profile, err := config.LoadProfile()
if err != nil {
return err
}
if profile != "" {
ui.Printf("profile.md: loaded (%d bytes)\n", len(profile))
} else {
ui.Printf("profile.md: not found (run 'super-ollama config init' to create)\n")
}
return nil
},
}
Expand Down
Loading
Loading