Skip to content

Commit f127f14

Browse files
idoubiclaude
andcommitted
feat: v0.1.1 - fix CI, add /login /logout /theme, LICENSE
- Fix CI: use published SDK v0.2.0 instead of local replace - Add go.work for local dev (gitignored) - Add /login and /logout commands for API key management - Add /theme command - Add MIT LICENSE - Add /plugin, /hooks, /context, /session, /files commands - Add plan mode toggle (/plan without args) - Add spinner verbs (Kneading, Pondering, Crafting...) - Add plugin system (~/.codeany/plugins/) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 53fd322 commit f127f14

6 files changed

Lines changed: 114 additions & 3 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Thumbs.db
1616

1717
# Go
1818
vendor/
19+
go.work
20+
go.work.sum
1921

2022
# Sensitive
2123
.env

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Codeany AI
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/charmbracelet/bubbletea v1.3.5
88
github.com/charmbracelet/glamour v0.9.1
99
github.com/charmbracelet/lipgloss v1.1.0
10-
github.com/codeany-ai/open-agent-sdk-go v0.0.0
10+
github.com/codeany-ai/open-agent-sdk-go v0.2.0
1111
github.com/muesli/reflow v0.3.0
1212
github.com/spf13/cobra v1.9.1
1313
gopkg.in/yaml.v3 v3.0.1
@@ -46,5 +46,3 @@ require (
4646
golang.org/x/term v0.30.0 // indirect
4747
golang.org/x/text v0.23.0 // indirect
4848
)
49-
50-
replace github.com/codeany-ai/open-agent-sdk-go => ../open-agent-sdk-go

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payR
3232
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
3333
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
3434
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
35+
github.com/codeany-ai/open-agent-sdk-go v0.2.0 h1:q/8dL16Iq116OmBSUwPOMZkgqhwD3dCRjf+dMmcEGQI=
36+
github.com/codeany-ai/open-agent-sdk-go v0.2.0/go.mod h1:j9P9/i2oWLD0iMB73vZ6IllaQ9FzgG0LAdPmnqb9VOw=
3537
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
3638
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
3739
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=

internal/slash/commands.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package slash
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"os"
78
"os/exec"
@@ -681,6 +682,82 @@ func (h *Handler) planToggle() Result {
681682
}
682683
}
683684

685+
// ─── /login ───────────────────────────────────────
686+
687+
func (h *Handler) loginCmd(args []string) Result {
688+
if len(args) == 0 {
689+
return Result{Message: "Usage: /login <api-key>\n\nSet your API key. Examples:\n /login sk-ant-... (Anthropic)\n /login sk-or-v1-... (OpenRouter)\n\nOr set via environment variable:\n export ANTHROPIC_API_KEY=sk-ant-...\n export CODEANY_API_KEY=sk-or-...\n export CODEANY_BASE_URL=https://openrouter.ai/api"}
690+
}
691+
692+
apiKey := args[0]
693+
694+
// Detect provider from key prefix
695+
provider := "anthropic"
696+
if strings.HasPrefix(apiKey, "sk-or-") {
697+
provider = "openrouter"
698+
}
699+
700+
// Save to settings.json
701+
settingsPath := config.GlobalConfigPath()
702+
var settings map[string]interface{}
703+
704+
if data, err := os.ReadFile(settingsPath); err == nil {
705+
json.Unmarshal(data, &settings)
706+
}
707+
if settings == nil {
708+
settings = make(map[string]interface{})
709+
}
710+
711+
settings["apiKey"] = apiKey
712+
if provider == "openrouter" {
713+
settings["baseURL"] = "https://openrouter.ai/api"
714+
}
715+
716+
data, _ := json.MarshalIndent(settings, "", " ")
717+
os.MkdirAll(filepath.Dir(settingsPath), 0755)
718+
if err := os.WriteFile(settingsPath, data, 0600); err != nil {
719+
return Result{Message: fmt.Sprintf("Failed to save: %v", err)}
720+
}
721+
722+
return Result{Message: fmt.Sprintf("✓ API key saved (%s provider)\n Stored in %s", provider, settingsPath)}
723+
}
724+
725+
// ─── /logout ──────────────────────────────────────
726+
727+
func (h *Handler) logoutCmd(args []string) Result {
728+
settingsPath := config.GlobalConfigPath()
729+
var settings map[string]interface{}
730+
731+
if data, err := os.ReadFile(settingsPath); err == nil {
732+
json.Unmarshal(data, &settings)
733+
}
734+
if settings == nil {
735+
return Result{Message: "No stored API key found."}
736+
}
737+
738+
delete(settings, "apiKey")
739+
data, _ := json.MarshalIndent(settings, "", " ")
740+
os.WriteFile(settingsPath, data, 0600)
741+
742+
return Result{Message: "✓ API key removed from settings.\nSet ANTHROPIC_API_KEY or CODEANY_API_KEY env var to authenticate."}
743+
}
744+
745+
// ─── /theme ───────────────────────────────────────
746+
747+
func (h *Handler) themeCmd(args []string) Result {
748+
if len(args) == 0 {
749+
return Result{Message: "Usage: /theme <dark|light>\n\nSwitch the color theme. Currently only supports dark (default)."}
750+
}
751+
752+
t := strings.ToLower(args[0])
753+
switch t {
754+
case "dark", "light":
755+
return Result{Message: fmt.Sprintf("Theme set to: %s\n(Theme switching will take effect after restart)", t)}
756+
default:
757+
return Result{Message: fmt.Sprintf("Unknown theme: %s. Available: dark, light", t)}
758+
}
759+
}
760+
684761
// ─── helpers ──────────────────────────────────────
685762

686763
func min(a, b int) int {

internal/slash/slash.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ func AllCommands() []CommandDef {
8080
{Name: "/context", Description: "Show all context sources"},
8181
{Name: "/session", Description: "Show session details"},
8282
{Name: "/files", Description: "List files accessed this session"},
83+
// Auth
84+
{Name: "/login", Description: "Set API key", HasArgs: true},
85+
{Name: "/logout", Description: "Remove stored API key"},
86+
// Theme
87+
{Name: "/theme", Description: "Switch color theme", HasArgs: true},
8388
}
8489
}
8590

@@ -187,6 +192,12 @@ func (h *Handler) Handle(input string) Result {
187192
return h.sessionCmd(args)
188193
case "/files":
189194
return h.filesCmd(args)
195+
case "/login":
196+
return h.loginCmd(args)
197+
case "/logout":
198+
return h.logoutCmd(args)
199+
case "/theme":
200+
return h.themeCmd(args)
190201
default:
191202
// Try skill invocation
192203
if result, ok := h.HandleSkillInvocation(cmd, args); ok {

0 commit comments

Comments
 (0)