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
78 changes: 50 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
[![CI Status](https://img.shields.io/github/actions/workflow/status/dkmnx/kairo/ci.yml?branch=main&style=flat-square)](https://github.com/dkmnx/kairo/actions)
[![License](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](LICENSE)

**Secure CLI for managing Claude Code API providers** with age (X25519) encryption, multi-provider support, and audit logging.
**Secure CLI for managing Claude Code and Qwen Code API providers** with age (X25519) encryption, multi-provider support, and audit logging.

## Prerequisites

### Required: Claude Code CLI
### Required: Claude Code CLI or Qwen Code CLI

Kairo acts as a wrapper around Claude Code CLI to enable multi-provider support. You need to install Claude Code first.
Kairo acts as a wrapper around Claude Code or Qwen Code CLI to enable multi-provider support. You need to install at least one of them.

**Install Claude Code:**

Expand All @@ -37,10 +37,22 @@ Kairo acts as a wrapper around Claude Code CLI to enable multi-provider support.
npm install -g @anthropic-ai/claude-code
```

**Install Qwen Code:**

- Visit: <https://qwenlm.github.io/qwen-code-docs/>
- Or via package managers:

```bash
# npm
npm install -g qwen-code
```

**Verify installation:**

```bash
claude --version
# or
qwen --version
```

## Quick Start
Expand Down Expand Up @@ -107,13 +119,14 @@ flowchart TB

## Features

| Feature | Description |
| ---------------------- | ---------------------------------------------------------- |
| **Multi-Provider** | Native Anthropic, Z.AI, MiniMax, Kimi, DeepSeek, custom |
| **Secure Encryption** | Age (X25519) encryption for all API keys |
| **Key Rotation** | Regenerate encryption keys periodically |
| **Audit Logging** | Track all configuration changes |
| **Cross-Platform** | Linux, macOS, Windows support |
| Feature | Description |
| ---------------------- | ------------------------------------------------------------------- |
| **Multi-Harness** | Claude Code (default), Qwen Code |
| **Secure Encryption** | Age (X25519) encryption for all API keys |
| **Key Rotation** | Regenerate encryption keys periodically |
| **Audit Logging** | Track all configuration changes |
| **Cross-Platform** | Linux, macOS, Windows support |
| **Model Override** | `--model` flag to override Qwen Code model (passed through to qwen) |

## Metrics

Expand Down Expand Up @@ -175,24 +188,30 @@ kairo metrics reset

### Execution

| Command | Description |
| ---------------------------- | ------------------------------------------ |
| `kairo switch <provider>` | Switch and exec Claude |
| `kairo <provider> [args]` | Shorthand for switch |
| `kairo -- "query"` | Query mode (default provider) |
| Command | Description |
| ----------------------------------------- | ------------------------------------------ |
| `kairo switch <provider>` | Switch and exec CLI (claude or qwen) |
| `kairo switch <provider> --harness qwen` | Switch using Qwen CLI |
| `kairo switch <provider> --model <model>` | Override model (Qwen only) |
| `kairo harness get` | Get current default harness |
| `kairo harness set <harness>` | Set default harness (claude or qwen) |
| `kairo <provider> [args]` | Shorthand for switch |
| `kairo -- "query"` | Query mode (default provider) |

### Maintenance

| Command | Description |
| ------------------------------- | ---------------------------------- |
| `kairo rotate` | Rotate encryption key |
| `kairo audit <list\|export>` | View/export audit logs |
| `kairo backup` | Create backup of configuration |
| `kairo restore <file>` | Restore from backup |
| `kairo recover` | Generate/restore recovery phrase |
| `kairo audit` | View/export audit logs |
| `kairo metrics` | View performance metrics |
| `kairo update` | Check for updates |
| `kairo completion <shell>` | Shell completion |
| `kairo version` | Show version info |

[Full Command Reference](cmd/README.md)

## Configuration

| OS | Location |
Expand Down Expand Up @@ -229,34 +248,37 @@ kairo metrics reset

### Reference

| Resource | Description |
|---------------------------------------------------------------|--------------------------------------|
| [Command Reference](cmd/README.md) | CLI command details |
| [Internal Packages](internal/README.md) | Core modules reference |
| [Troubleshooting](docs/troubleshooting/README.md) | Common issues and solutions |
| [Changelog](CHANGELOG.md) | Version history |
| Resource | Description |
| --------------------------------------------------------------- | -------------------------------------- |
| [Development Guide](docs/guides/development-guide.md) | Setup and contribution |
| [Architecture](docs/architecture/README.md) | System design and diagrams |
| [Wrapper Scripts](docs/architecture/wrapper-scripts.md) | Security design and rationale |
| [Contributing](docs/contributing/README.md) | Contribution workflow |
| [Troubleshooting](docs/troubleshooting/README.md) | Common issues and solutions |
| [Changelog](CHANGELOG.md) | Version history |

## Building

```bash
# Build
task build # or: go build -o dist/kairo .
just build # or: go build -o dist/kairo .

# Test
task test # or: go test -race ./...
just test # or: go test -race ./...

# Lint
task lint # or: gofmt -w . && go vet ./...
just lint # or: gofmt -w . && go vet ./...

# Format
task format # or: gofmt -w .
just format # or: gofmt -w .
```

## Security

- Age (X25519) encryption for all API keys
- 0600 permissions on sensitive files
- Secrets decrypted in-memory only
- Secure wrapper scripts for both Claude and Qwen harnesses
- Key generation on first run
- Use `kairo rotate` for periodic key rotation

Expand Down
75 changes: 54 additions & 21 deletions cmd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@ CLI command implementations using the Cobra framework.

## Structure

| File | Purpose |
| ------------------- | ---------------------------------------------- |
| `root.go` | Root command, banner display, global flags |
| `setup.go` | Interactive configuration wizard |
| `config.go` | Configure individual providers |
| `list.go` | List all configured providers |
| `status.go` | Test connectivity for all providers |
| `test.go` | Test specific provider connectivity |
| `switch.go` | Switch provider and execute Claude |
| `default.go` | Get or set default provider |
| `reset.go` | Remove provider configurations |
| `rotate.go` | Rotate encryption key |
| `version.go` | Display version information |
| `update.go` | Check for and install updates |
| `completion.go` | Shell completion support |
| `audit.go` | View and export audit logs |
| File | Purpose |
| ------------------- | ------------------------------------------------ |
| `root.go` | Root command, banner display, global flags |
| `setup.go` | Interactive configuration wizard |
| `config.go` | Configure individual providers |
| `list.go` | List all configured providers |
| `status.go` | Test connectivity for all providers |
| `test.go` | Test specific provider connectivity |
| `switch.go` | Switch provider and execute CLI (Claude or Qwen) |
| `harness.go` | Manage CLI harness (claude or qwen) |
| `default.go` | Get or set default provider |
| `reset.go` | Remove provider configurations |
| `rotate.go` | Rotate encryption key |
| `version.go` | Display version information |
| `update.go` | Check for and install updates |
| `completion.go` | Shell completion support |
| `audit.go` | View and export audit logs |

## Command Architecture

Expand Down Expand Up @@ -77,11 +78,15 @@ flowchart TB

### Execution

| Command | Description |
| ----------------------------- | -------------------------------------------------- |
| `kairo switch <provider>` | Switch and execute Claude |
| `kairo <provider> [args]` | Shorthand for switch (e.g., `kairo zai`) |
| `kairo -- "query"` | Query mode using default provider |
| Command | Description |
| ----------------------------------------- | -------------------------------------------------- |
| `kairo switch <provider>` | Switch and execute CLI (claude or qwen) |
| `kairo switch <provider> --harness qwen` | Switch using Qwen CLI |
| `kairo switch <provider> --model <model>` | Override model (Qwen harness only) |
| `kairo harness get` | Get current default harness |
| `kairo harness set <harness>` | Set default harness (claude or qwen) |
| `kairo <provider> [args]` | Shorthand for switch (e.g., `kairo zai`) |
| `kairo -- "query"` | Query mode using default provider |

### Maintenance

Expand Down Expand Up @@ -163,6 +168,7 @@ go test -v ./cmd/... -run Integration
| ---------------- | -------------------------------- |
| `-v, --verbose` | Enable verbose output |
| `-h, --help` | Show help for command |
| `--harness` | CLI harness to use (claude/qwen) |

## Banner Display

Expand All @@ -174,6 +180,33 @@ kairo v1.2.0 - Provider: zai

This is rendered from `internal/version/version.go` and `providers` package.

## CLI Harnesses

Kairo supports multiple CLI harnesses:

| Harness | CLI Binary | Description |
| -------- | ---------- | --------------------- |
| `claude` | `claude` | Claude Code (default) |
| `qwen` | `qwen` | Qwen Code |

### Using Harnesses

```bash
# Use Qwen harness for a specific provider
kairo switch zai --harness qwen

# Override model for Qwen
kairo switch zai --harness qwen --model qwen-turbo

# Set default harness globally
kairo harness set qwen

# Get current harness
kairo harness get
```

The `--model` flag is passed through to Qwen CLI. If not specified, uses the provider's configured model.

## Provider Shorthand

Users can use provider name directly instead of `switch`:
Expand Down
99 changes: 99 additions & 0 deletions cmd/harness.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package cmd

import (
"errors"
"fmt"
"os"
"strings"

"github.com/dkmnx/kairo/internal/config"
kairoerrors "github.com/dkmnx/kairo/internal/errors"
"github.com/dkmnx/kairo/internal/ui"
"github.com/spf13/cobra"
)

var harnessGetCmd = &cobra.Command{
Use: "get",
Short: "Get current harness",
Long: "Get the currently configured default harness",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
dir := getConfigDir()
if dir == "" {
ui.PrintError("Config directory not found")
return
}

cfg, err := configCache.Get(dir)
if err != nil {
ui.PrintError(fmt.Sprintf("Error loading config: %v", err))
return
}

if cfg.DefaultHarness == "" {
ui.PrintInfo("No default harness configured (using claude)")
return
}

ui.PrintInfo(fmt.Sprintf("Default harness: %s", cfg.DefaultHarness))
},
}

var harnessSetCmd = &cobra.Command{
Use: "set <harness>",
Short: "Set default harness",
Long: "Set the default CLI harness to use (claude or qwen)",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
harnessName := strings.ToLower(args[0])

if harnessName != "claude" && harnessName != "qwen" {
ui.PrintError(fmt.Sprintf("Invalid harness: '%s'", args[0]))
ui.PrintInfo("Valid harnesses: claude, qwen")
return
}

dir := getConfigDir()
if dir == "" {
ui.PrintError("Config directory not found")
return
}

if err := os.MkdirAll(dir, 0700); err != nil {
ui.PrintError(fmt.Sprintf("Error creating config directory: %v", err))
return
}

cfg, err := configCache.Get(dir)
if err != nil && !errors.Is(err, kairoerrors.ErrConfigNotFound) {
ui.PrintError(fmt.Sprintf("Error loading config: %v", err))
return
}
if err != nil {
cfg = &config.Config{
Providers: make(map[string]config.Provider),
DefaultModels: make(map[string]string),
}
}

cfg.DefaultHarness = harnessName
if err := config.SaveConfig(dir, cfg); err != nil {
ui.PrintError(fmt.Sprintf("Error saving config: %v", err))
return
}

ui.PrintSuccess(fmt.Sprintf("Default harness set to: %s", harnessName))
},
}

var harnessCmd = &cobra.Command{
Use: "harness",
Short: "Manage CLI harness",
Long: "Manage the CLI harness (claude or qwen)",
}

func init() {
harnessCmd.AddCommand(harnessGetCmd)
harnessCmd.AddCommand(harnessSetCmd)
rootCmd.AddCommand(harnessCmd)
}
Loading
Loading