onyx-cli 0.1.0__py3-none-win_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
README.md ADDED
@@ -0,0 +1,158 @@
1
+ # Onyx CLI
2
+
3
+ A terminal interface for chatting with your [Onyx](https://github.com/onyx-dot-app/onyx) agent. Built with Go using [Bubble Tea](https://github.com/charmbracelet/bubbletea) for the TUI framework.
4
+
5
+ ## Installation
6
+
7
+ ```shell
8
+ pip install onyx-cli
9
+ ```
10
+
11
+ Or with uv:
12
+
13
+ ```shell
14
+ uv pip install onyx-cli
15
+ ```
16
+
17
+ ## Setup
18
+
19
+ Run the interactive setup:
20
+
21
+ ```shell
22
+ onyx-cli configure
23
+ ```
24
+
25
+ This prompts for your Onyx server URL and API key, tests the connection, and saves config to `~/.config/onyx-cli/config.json`.
26
+
27
+ Environment variables override config file values:
28
+
29
+ | Variable | Required | Description |
30
+ |----------|----------|-------------|
31
+ | `ONYX_SERVER_URL` | No | Server base URL (default: `http://localhost:3000`) |
32
+ | `ONYX_API_KEY` | Yes | API key for authentication |
33
+ | `ONYX_PERSONA_ID` | No | Default agent/persona ID |
34
+
35
+ ## Usage
36
+
37
+ ### Interactive chat (default)
38
+
39
+ ```shell
40
+ onyx-cli
41
+ ```
42
+
43
+ ### One-shot question
44
+
45
+ ```shell
46
+ onyx-cli ask "What is our company's PTO policy?"
47
+ onyx-cli ask --agent-id 5 "Summarize this topic"
48
+ onyx-cli ask --json "Hello"
49
+ ```
50
+
51
+ | Flag | Description |
52
+ |------|-------------|
53
+ | `--agent-id <int>` | Agent ID to use (overrides default) |
54
+ | `--json` | Output raw NDJSON events instead of plain text |
55
+
56
+ ### List agents
57
+
58
+ ```shell
59
+ onyx-cli agents
60
+ onyx-cli agents --json
61
+ ```
62
+
63
+ ## Commands
64
+
65
+ | Command | Description |
66
+ |---------|-------------|
67
+ | `chat` | Launch the interactive chat TUI (default) |
68
+ | `ask` | Ask a one-shot question (non-interactive) |
69
+ | `agents` | List available agents |
70
+ | `configure` | Configure server URL and API key |
71
+ | `validate-config` | Validate configuration and test connection |
72
+
73
+ ## Slash Commands (in TUI)
74
+
75
+ | Command | Description |
76
+ |---------|-------------|
77
+ | `/help` | Show help message |
78
+ | `/clear` | Clear chat and start a new session |
79
+ | `/agent` | List and switch agents |
80
+ | `/attach <path>` | Attach a file to next message |
81
+ | `/sessions` | List recent chat sessions |
82
+ | `/configure` | Re-run connection setup |
83
+ | `/connectors` | Open connectors in browser |
84
+ | `/settings` | Open settings in browser |
85
+ | `/quit` | Exit Onyx CLI |
86
+
87
+ ## Keyboard Shortcuts
88
+
89
+ | Key | Action |
90
+ |-----|--------|
91
+ | `Enter` | Send message |
92
+ | `Escape` | Cancel current generation |
93
+ | `Ctrl+O` | Toggle source citations |
94
+ | `Ctrl+D` | Quit (press twice) |
95
+ | `Scroll` / `Shift+Up/Down` | Scroll chat history |
96
+ | `Page Up` / `Page Down` | Scroll half page |
97
+
98
+ ## Building from Source
99
+
100
+ Requires [Go 1.24+](https://go.dev/dl/).
101
+
102
+ ```shell
103
+ cd cli
104
+ go build -o onyx-cli .
105
+ ```
106
+
107
+ ## Development
108
+
109
+ ```shell
110
+ # Run tests
111
+ go test ./...
112
+
113
+ # Build
114
+ go build -o onyx-cli .
115
+
116
+ # Lint
117
+ staticcheck ./...
118
+ ```
119
+
120
+ ## Publishing to PyPI
121
+
122
+ The CLI is distributed as a Python package via [PyPI](https://pypi.org/project/onyx-cli/). The build system uses [hatchling](https://hatch.pypa.io/) with [manygo](https://github.com/nicholasgasior/manygo) to cross-compile Go binaries into platform-specific wheels.
123
+
124
+ ### CI release (recommended)
125
+
126
+ Tag a release and push — the `release-cli.yml` workflow builds wheels for all platforms and publishes to PyPI automatically:
127
+
128
+ ```shell
129
+ tag --prefix cli
130
+ ```
131
+
132
+ To do this manually:
133
+
134
+ ```shell
135
+ git tag cli/v0.1.0
136
+ git push origin cli/v0.1.0
137
+ ```
138
+
139
+ The workflow builds wheels for: linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, windows/arm64.
140
+
141
+ ### Manual release
142
+
143
+ Build a wheel locally with `uv`. Set `GOOS` and `GOARCH` to cross-compile for other platforms (Go handles this natively — no cross-compiler needed):
144
+
145
+ ```shell
146
+ # Build for current platform
147
+ uv build --wheel
148
+
149
+ # Cross-compile for a different platform
150
+ GOOS=linux GOARCH=amd64 uv build --wheel
151
+
152
+ # Upload to PyPI
153
+ uv publish
154
+ ```
155
+
156
+ ### Versioning
157
+
158
+ Versions are derived from git tags with the `cli/` prefix (e.g. `cli/v0.1.0`). The tag is parsed by `internal/_version.py` and injected into the Go binary via `-ldflags` at build time.
cmd/agents.go ADDED
@@ -0,0 +1,63 @@
1
+ package cmd
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "text/tabwriter"
7
+
8
+ "github.com/onyx-dot-app/onyx/cli/internal/api"
9
+ "github.com/onyx-dot-app/onyx/cli/internal/config"
10
+ "github.com/spf13/cobra"
11
+ )
12
+
13
+ func newAgentsCmd() *cobra.Command {
14
+ var agentsJSON bool
15
+
16
+ cmd := &cobra.Command{
17
+ Use: "agents",
18
+ Short: "List available agents",
19
+ RunE: func(cmd *cobra.Command, args []string) error {
20
+ cfg := config.Load()
21
+ if !cfg.IsConfigured() {
22
+ return fmt.Errorf("onyx CLI is not configured — run 'onyx-cli configure' first")
23
+ }
24
+
25
+ client := api.NewClient(cfg)
26
+ agents, err := client.ListAgents(cmd.Context())
27
+ if err != nil {
28
+ return fmt.Errorf("failed to list agents: %w", err)
29
+ }
30
+
31
+ if agentsJSON {
32
+ data, err := json.MarshalIndent(agents, "", " ")
33
+ if err != nil {
34
+ return fmt.Errorf("failed to marshal agents: %w", err)
35
+ }
36
+ fmt.Println(string(data))
37
+ return nil
38
+ }
39
+
40
+ if len(agents) == 0 {
41
+ fmt.Println("No agents available.")
42
+ return nil
43
+ }
44
+
45
+ w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 4, 2, ' ', 0)
46
+ _, _ = fmt.Fprintln(w, "ID\tNAME\tDESCRIPTION")
47
+ for _, a := range agents {
48
+ desc := a.Description
49
+ if len(desc) > 60 {
50
+ desc = desc[:57] + "..."
51
+ }
52
+ _, _ = fmt.Fprintf(w, "%d\t%s\t%s\n", a.ID, a.Name, desc)
53
+ }
54
+ _ = w.Flush()
55
+
56
+ return nil
57
+ },
58
+ }
59
+
60
+ cmd.Flags().BoolVar(&agentsJSON, "json", false, "Output agents as JSON")
61
+
62
+ return cmd
63
+ }
cmd/ask.go ADDED
@@ -0,0 +1,124 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "fmt"
7
+ "os"
8
+ "os/signal"
9
+ "syscall"
10
+
11
+ "github.com/onyx-dot-app/onyx/cli/internal/api"
12
+ "github.com/onyx-dot-app/onyx/cli/internal/config"
13
+ "github.com/onyx-dot-app/onyx/cli/internal/models"
14
+ "github.com/spf13/cobra"
15
+ )
16
+
17
+ func newAskCmd() *cobra.Command {
18
+ var (
19
+ askAgentID int
20
+ askJSON bool
21
+ )
22
+
23
+ cmd := &cobra.Command{
24
+ Use: "ask [question]",
25
+ Short: "Ask a one-shot question (non-interactive)",
26
+ Args: cobra.ExactArgs(1),
27
+ RunE: func(cmd *cobra.Command, args []string) error {
28
+ cfg := config.Load()
29
+ if !cfg.IsConfigured() {
30
+ return fmt.Errorf("onyx CLI is not configured — run 'onyx-cli configure' first")
31
+ }
32
+
33
+ question := args[0]
34
+ agentID := cfg.DefaultAgentID
35
+ if cmd.Flags().Changed("agent-id") {
36
+ agentID = askAgentID
37
+ }
38
+
39
+ ctx, stop := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM)
40
+ defer stop()
41
+
42
+ client := api.NewClient(cfg)
43
+ parentID := -1
44
+ ch := client.SendMessageStream(
45
+ ctx,
46
+ question,
47
+ nil,
48
+ agentID,
49
+ &parentID,
50
+ nil,
51
+ )
52
+
53
+ var sessionID string
54
+ var lastErr error
55
+ gotStop := false
56
+ for event := range ch {
57
+ if e, ok := event.(models.SessionCreatedEvent); ok {
58
+ sessionID = e.ChatSessionID
59
+ }
60
+
61
+ if askJSON {
62
+ wrapped := struct {
63
+ Type string `json:"type"`
64
+ Event models.StreamEvent `json:"event"`
65
+ }{
66
+ Type: event.EventType(),
67
+ Event: event,
68
+ }
69
+ data, err := json.Marshal(wrapped)
70
+ if err != nil {
71
+ return fmt.Errorf("error marshaling event: %w", err)
72
+ }
73
+ fmt.Println(string(data))
74
+ if _, ok := event.(models.ErrorEvent); ok {
75
+ lastErr = fmt.Errorf("%s", event.(models.ErrorEvent).Error)
76
+ }
77
+ if _, ok := event.(models.StopEvent); ok {
78
+ gotStop = true
79
+ }
80
+ continue
81
+ }
82
+
83
+ switch e := event.(type) {
84
+ case models.MessageDeltaEvent:
85
+ fmt.Print(e.Content)
86
+ case models.ErrorEvent:
87
+ return fmt.Errorf("%s", e.Error)
88
+ case models.StopEvent:
89
+ fmt.Println()
90
+ return nil
91
+ }
92
+ }
93
+
94
+ if ctx.Err() != nil {
95
+ if sessionID != "" {
96
+ client.StopChatSession(context.Background(), sessionID)
97
+ }
98
+ if !askJSON {
99
+ fmt.Println()
100
+ }
101
+ return nil
102
+ }
103
+
104
+ if lastErr != nil {
105
+ return lastErr
106
+ }
107
+ if !gotStop {
108
+ if !askJSON {
109
+ fmt.Println()
110
+ }
111
+ return fmt.Errorf("stream ended unexpectedly")
112
+ }
113
+ if !askJSON {
114
+ fmt.Println()
115
+ }
116
+ return nil
117
+ },
118
+ }
119
+
120
+ cmd.Flags().IntVar(&askAgentID, "agent-id", 0, "Agent ID to use")
121
+ cmd.Flags().BoolVar(&askJSON, "json", false, "Output raw JSON events")
122
+ // Suppress cobra's default error/usage on RunE errors
123
+ return cmd
124
+ }
cmd/chat.go ADDED
@@ -0,0 +1,33 @@
1
+ package cmd
2
+
3
+ import (
4
+ tea "github.com/charmbracelet/bubbletea"
5
+ "github.com/onyx-dot-app/onyx/cli/internal/config"
6
+ "github.com/onyx-dot-app/onyx/cli/internal/onboarding"
7
+ "github.com/onyx-dot-app/onyx/cli/internal/tui"
8
+ "github.com/spf13/cobra"
9
+ )
10
+
11
+ func newChatCmd() *cobra.Command {
12
+ return &cobra.Command{
13
+ Use: "chat",
14
+ Short: "Launch the interactive chat TUI (default)",
15
+ RunE: func(cmd *cobra.Command, args []string) error {
16
+ cfg := config.Load()
17
+
18
+ // First-run: onboarding
19
+ if !config.ConfigExists() || !cfg.IsConfigured() {
20
+ result := onboarding.Run(&cfg)
21
+ if result == nil {
22
+ return nil
23
+ }
24
+ cfg = *result
25
+ }
26
+
27
+ m := tui.NewModel(cfg)
28
+ p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion())
29
+ _, err := p.Run()
30
+ return err
31
+ },
32
+ }
33
+ }
cmd/configure.go ADDED
@@ -0,0 +1,19 @@
1
+ package cmd
2
+
3
+ import (
4
+ "github.com/onyx-dot-app/onyx/cli/internal/config"
5
+ "github.com/onyx-dot-app/onyx/cli/internal/onboarding"
6
+ "github.com/spf13/cobra"
7
+ )
8
+
9
+ func newConfigureCmd() *cobra.Command {
10
+ return &cobra.Command{
11
+ Use: "configure",
12
+ Short: "Configure server URL and API key",
13
+ RunE: func(cmd *cobra.Command, args []string) error {
14
+ cfg := config.Load()
15
+ onboarding.Run(&cfg)
16
+ return nil
17
+ },
18
+ }
19
+ }
cmd/root.go ADDED
@@ -0,0 +1,40 @@
1
+ // Package cmd implements Cobra CLI commands for the Onyx CLI.
2
+ package cmd
3
+
4
+ import "github.com/spf13/cobra"
5
+
6
+ // Version and Commit are set via ldflags at build time.
7
+ var (
8
+ Version string
9
+ Commit string
10
+ )
11
+
12
+ func fullVersion() string {
13
+ if Commit != "" && Commit != "none" && len(Commit) > 7 {
14
+ return Version + " (" + Commit[:7] + ")"
15
+ }
16
+ return Version
17
+ }
18
+
19
+ // Execute creates and runs the root command.
20
+ func Execute() error {
21
+ rootCmd := &cobra.Command{
22
+ Use: "onyx-cli",
23
+ Short: "Terminal UI for chatting with Onyx",
24
+ Long: "Onyx CLI — a terminal interface for chatting with your Onyx agent.",
25
+ Version: fullVersion(),
26
+ }
27
+
28
+ // Register subcommands
29
+ chatCmd := newChatCmd()
30
+ rootCmd.AddCommand(chatCmd)
31
+ rootCmd.AddCommand(newAskCmd())
32
+ rootCmd.AddCommand(newAgentsCmd())
33
+ rootCmd.AddCommand(newConfigureCmd())
34
+ rootCmd.AddCommand(newValidateConfigCmd())
35
+
36
+ // Default command is chat
37
+ rootCmd.RunE = chatCmd.RunE
38
+
39
+ return rootCmd.Execute()
40
+ }
cmd/validate.go ADDED
@@ -0,0 +1,41 @@
1
+ package cmd
2
+
3
+ import (
4
+ "fmt"
5
+
6
+ "github.com/onyx-dot-app/onyx/cli/internal/api"
7
+ "github.com/onyx-dot-app/onyx/cli/internal/config"
8
+ "github.com/spf13/cobra"
9
+ )
10
+
11
+ func newValidateConfigCmd() *cobra.Command {
12
+ return &cobra.Command{
13
+ Use: "validate-config",
14
+ Short: "Validate configuration and test server connection",
15
+ RunE: func(cmd *cobra.Command, args []string) error {
16
+ // Check config file
17
+ if !config.ConfigExists() {
18
+ return fmt.Errorf("config file not found at %s\n Run 'onyx-cli configure' to set up", config.ConfigFilePath())
19
+ }
20
+
21
+ cfg := config.Load()
22
+
23
+ // Check API key
24
+ if !cfg.IsConfigured() {
25
+ return fmt.Errorf("API key is missing\n Run 'onyx-cli configure' to set up")
26
+ }
27
+
28
+ _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Config: %s\n", config.ConfigFilePath())
29
+ _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Server: %s\n", cfg.ServerURL)
30
+
31
+ // Test connection
32
+ client := api.NewClient(cfg)
33
+ if err := client.TestConnection(cmd.Context()); err != nil {
34
+ return fmt.Errorf("connection failed: %w", err)
35
+ }
36
+
37
+ _, _ = fmt.Fprintln(cmd.OutOrStdout(), "Status: connected and authenticated")
38
+ return nil
39
+ },
40
+ }
41
+ }
go.mod ADDED
@@ -0,0 +1,45 @@
1
+ module github.com/onyx-dot-app/onyx/cli
2
+
3
+ go 1.26.0
4
+
5
+ require (
6
+ github.com/charmbracelet/bubbles v0.20.0
7
+ github.com/charmbracelet/bubbletea v1.3.4
8
+ github.com/charmbracelet/glamour v0.8.0
9
+ github.com/charmbracelet/lipgloss v1.1.0
10
+ github.com/spf13/cobra v1.9.1
11
+ golang.org/x/term v0.30.0
12
+ golang.org/x/text v0.34.0
13
+ )
14
+
15
+ require (
16
+ github.com/alecthomas/chroma/v2 v2.14.0 // indirect
17
+ github.com/atotto/clipboard v0.1.4 // indirect
18
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
19
+ github.com/aymerick/douceur v0.2.0 // indirect
20
+ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
21
+ github.com/charmbracelet/x/ansi v0.8.0 // indirect
22
+ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
23
+ github.com/charmbracelet/x/term v0.2.1 // indirect
24
+ github.com/dlclark/regexp2 v1.11.0 // indirect
25
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
26
+ github.com/gorilla/css v1.0.1 // indirect
27
+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
28
+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
29
+ github.com/mattn/go-isatty v0.0.20 // indirect
30
+ github.com/mattn/go-localereader v0.0.1 // indirect
31
+ github.com/mattn/go-runewidth v0.0.16 // indirect
32
+ github.com/microcosm-cc/bluemonday v1.0.27 // indirect
33
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
34
+ github.com/muesli/cancelreader v0.2.2 // indirect
35
+ github.com/muesli/reflow v0.3.0 // indirect
36
+ github.com/muesli/termenv v0.16.0 // indirect
37
+ github.com/rivo/uniseg v0.4.7 // indirect
38
+ github.com/spf13/pflag v1.0.6 // indirect
39
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
40
+ github.com/yuin/goldmark v1.7.4 // indirect
41
+ github.com/yuin/goldmark-emoji v1.0.3 // indirect
42
+ golang.org/x/net v0.38.0 // indirect
43
+ golang.org/x/sync v0.19.0 // indirect
44
+ golang.org/x/sys v0.31.0 // indirect
45
+ )
go.sum ADDED
@@ -0,0 +1,94 @@
1
+ github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
2
+ github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
3
+ github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
4
+ github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
5
+ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
6
+ github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
7
+ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
8
+ github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
9
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
10
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
11
+ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
12
+ github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
13
+ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
14
+ github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
15
+ github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
16
+ github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
17
+ github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
18
+ github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo=
19
+ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
20
+ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
21
+ github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs=
22
+ github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw=
23
+ github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
24
+ github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
25
+ github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
26
+ github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
27
+ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
28
+ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
29
+ github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q=
30
+ github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
31
+ github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
32
+ github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
33
+ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
34
+ github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
35
+ github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
36
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
37
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
38
+ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
39
+ github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
40
+ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
41
+ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
42
+ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
43
+ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
44
+ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
45
+ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
46
+ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
47
+ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
48
+ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
49
+ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
50
+ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
51
+ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
52
+ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
53
+ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
54
+ github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
55
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
56
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
57
+ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
58
+ github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
59
+ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
60
+ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
61
+ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
62
+ github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
63
+ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
64
+ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
65
+ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
66
+ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
67
+ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
68
+ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
69
+ github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
70
+ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
71
+ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
72
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
73
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
74
+ github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
75
+ github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
76
+ github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
77
+ github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4=
78
+ github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
79
+ golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
80
+ golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
81
+ golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
82
+ golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
83
+ golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
84
+ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
85
+ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
86
+ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
87
+ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
88
+ golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
89
+ golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
90
+ golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
91
+ golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
92
+ golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
93
+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
94
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
hatch_build.py ADDED
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import subprocess
5
+ from typing import Any
6
+
7
+ import manygo
8
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
9
+
10
+
11
+ class CustomBuildHook(BuildHookInterface):
12
+ """Build hook to compile the Go binary and include it in the wheel."""
13
+
14
+ def initialize(self, version: Any, build_data: Any) -> None: # noqa: ARG002
15
+ """Build the Go binary before packaging."""
16
+ build_data["pure_python"] = False
17
+
18
+ # Set platform tag for cross-compilation
19
+ goos = os.getenv("GOOS")
20
+ goarch = os.getenv("GOARCH")
21
+ if manygo.is_goos(goos) and manygo.is_goarch(goarch):
22
+ build_data["tag"] = "py3-none-" + manygo.get_platform_tag(
23
+ goos=goos,
24
+ goarch=goarch,
25
+ )
26
+
27
+ # Get config and environment
28
+ binary_name = self.config["binary_name"]
29
+ tag_prefix = self.config.get("tag_prefix", binary_name)
30
+ tag = os.getenv("GITHUB_REF_NAME", "dev").removeprefix(f"{tag_prefix}/")
31
+ commit = os.getenv("GITHUB_SHA", "none")
32
+
33
+ # Build the Go binary if it doesn't exist
34
+ # Build the Go binary (always rebuild to ensure correct version injection)
35
+ if not os.path.exists(binary_name):
36
+ print(f"Building Go binary '{binary_name}'...")
37
+ pkg = "github.com/onyx-dot-app/onyx/cli/cmd"
38
+ ldflags = f"-X {pkg}.version={tag}" f" -X {pkg}.commit={commit}" " -s -w"
39
+ subprocess.check_call( # noqa: S603
40
+ ["go", "build", f"-ldflags={ldflags}", "-o", binary_name],
41
+ )
42
+
43
+ build_data["shared_scripts"] = {binary_name: binary_name}
internal/_version.py ADDED
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import re
5
+
6
+ # Must match tag_prefix in pyproject.toml [tool.hatch.build.targets.wheel.hooks.custom]
7
+ TAG_PREFIX = "cli"
8
+
9
+ _tag = os.environ.get("GITHUB_REF_NAME", "v0.0.0-dev").removeprefix(f"{TAG_PREFIX}/")
10
+ _match = re.search(r"v?(\d+\.\d+\.\d+)", _tag)
11
+ __version__ = _match.group(1) if _match else "0.0.0"