Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
649d3cb
chore(lint): green the golangci-lint gate
TresPies-source Jul 16, 2026
c2f27d6
fix(cli): surface gateway errors, two-tier interrupt, pipeline hygiene
TresPies-source Jul 16, 2026
79ec839
fix(commands): complete /help, correct gateway error labels, command …
TresPies-source Jul 16, 2026
0e848be
feat(craft): make /craft stubs real (write/prune/elevate)
TresPies-source Jul 16, 2026
40b9ed8
fix(security): Gemini key leak, artifact path traversal, package-all …
TresPies-source Jul 16, 2026
c22b3bc
fix(cli): un-brick /disposition set, telemetry opt-out, Windows hook …
TresPies-source Jul 16, 2026
a22902f
feat(protocol): inject genius protocol by default + ship kata-harness…
TresPies-source Jul 16, 2026
7534fc9
fix(plugins): parse Claude-Code hook schema so harness hooks load
TresPies-source Jul 16, 2026
86d0f6e
feat(commands): add /doctor for gateway/provider/config visibility
TresPies-source Jul 16, 2026
aae8dbc
feat(hooks): add SessionStart lifecycle event so harness startup hook…
TresPies-source Jul 16, 2026
b89b9c5
fix(config): stop env overrides persisting into settings.json; dispos…
TresPies-source Jul 16, 2026
8728b77
feat(doctor): surface protocol + harness state in /doctor
TresPies-source Jul 16, 2026
3a7cd64
test(commands): isolate HOME so tests stop clobbering ~/.dojo/setting…
TresPies-source Jul 16, 2026
e5f1b54
feat(repl): add glamour markdown renderer (RenderMarkdown)
TresPies-source Jul 16, 2026
432ec97
feat(session): real resume — history, /session ls, resume-by-id, --se…
TresPies-source Jul 16, 2026
233e5d9
feat(repl): complete tab-completion + per-turn token readout
TresPies-source Jul 16, 2026
e717943
feat(code): add /code undo to revert uncommitted working-tree changes
TresPies-source Jul 16, 2026
6601919
feat(protocol): add /protocol command for harness discovery + install
TresPies-source Jul 16, 2026
2859ffa
refactor(render): extract internal/mdrender; render markdown in /skil…
TresPies-source Jul 16, 2026
f67c0a1
docs(cli): document /doctor, /protocol, /session ls, /code undo; shel…
TresPies-source Jul 16, 2026
b39c6dc
feat(protocol): auto verify-loop + JIT tell-triggered config-vs-code …
TresPies-source Jul 16, 2026
103cad1
feat(protocol): escalate JIT nudge to the debugging gate on repeated/…
TresPies-source Jul 16, 2026
2e74d52
feat(protocol): add /protocol show and /protocol edit
TresPies-source Jul 16, 2026
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
62 changes: 58 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ Settings are loaded from `~/.dojo/settings.json`. A missing file is not an error
| `DOJO_PROVIDER` | `defaults.provider` |
| `DOJO_TELEMETRY_URL` | telemetry API base |
| `DOJO_SKILLS_PATH` | default skill dir for `/skill package-all` |
| `DOJO_PROTOCOL_DISABLED` | any non-empty value disables the genius protocol (`protocol.enabled`) |
| `DOJO_PROTOCOL_PATH` | `protocol.path` — explicit override doc, takes precedence over `./DOJO.md` |

## Requirements

Expand All @@ -84,6 +86,7 @@ Settings are loaded from `~/.dojo/settings.json`. A missing file is not an error
| `--disposition <d>` | ADA disposition preset: `focused`, `balanced`, `exploratory`, `deliberate` |
| `--one-shot <msg>` | Execute a single message and exit (non-interactive) |
| `--resume` | Resume the most recent session instead of starting fresh |
| `--session <id>` | Resume a specific session ID instead of the most recent one (implies `--resume`) |
| `--no-color` | Disable color output |
| `--plain` | Plain text output — no ANSI colors (for piped or CI use) |
| `--json` | JSON lines output in one-shot mode (for scripted pipelines) |
Expand All @@ -105,6 +108,12 @@ dojo --one-shot "summarize the last run" --json | jq '.text'
dojo --resume
```

**Resume a specific session:**

```bash
dojo --session dojo-cli-20260409-142301
```

**Shell completions:**

```bash
Expand All @@ -123,6 +132,7 @@ Type a message without `/` to chat with the gateway. Use slash commands for stru
|-----------------------------------|--------------------------------------------------------|
| `/help` | Show available commands |
| `/health` | Gateway health and uptime stats |
| `/doctor` | Full diagnostic: gateway, providers, config, protocol, harnesses |
| `/home` | Workspace state overview (TUI panel) |
| `/home plain` | Workspace state in plain text |
| `/model [ls]` | List available models and providers |
Expand Down Expand Up @@ -163,7 +173,10 @@ Type a message without `/` to chat with the gateway. Use slash commands for stru
|-------------------------|------------------------------------------|
| `/session` | Show active session ID |
| `/session new` | Start a fresh session |
| `/session <id>` | Resume a prior session by ID |
| `/session ls` | List recent sessions from local history, most-recent-first |
| `/session resume` | Resume the most recently active session |
| `/session resume <id>` | Resume one specific session by ID (warns if not found in local history) |
| `/session <id>` | Switch directly to a session ID (not verified against gateway) |

Session IDs follow the format `dojo-cli-YYYYMMDD-HHmmss` when created via `/session new`.

Expand Down Expand Up @@ -218,6 +231,16 @@ Track statuses: `pending`, `in-progress`, `completed`, `blocked`.
| `/hooks ls` | List loaded hook rules from all plugins |
| `/hooks fire <event>` | Manually fire a hook event (for testing) |

### Protocol & Harnesses

| Command | Description |
|--------------------------------------|-------------------------------------------------------------|
| `/protocol` or `/protocol status` | Genius-protocol enabled/source + kata-harness install state |
| `/protocol harnesses` | List the KE harness catalog: status, ratified, installed |
| `/protocol install <name> [--yes]` | Install a ratified, locally-available harness into `plugins.path` |

See [Genius Protocol](#genius-protocol) below for how the protocol doc is resolved and overridden.

### Dispositions

| Command | Description |
Expand Down Expand Up @@ -269,6 +292,7 @@ Track statuses: `pending`, `in-progress`, `completed`, `blocked`.
| `/code build` | Run `go build ./...` |
| `/code vet` | Run `go vet ./...` |
| `/code gate` | Run the full build gate: build + test + vet |
| `/code undo` | Preview unstaged changes to tracked files, then revert on confirmation |

## Directory Structure

Expand Down Expand Up @@ -333,6 +357,33 @@ Create and save custom presets with `/disposition create`:
/disposition create sprint fast shallow assertive high
```

## Genius Protocol

A workspace "genius protocol" — a compact operating doctrine — is injected into every session **by default**. The CLI prepends it to the first turn of a chat or `--one-shot` run (and sets it as the system prompt for gateways that support one); later turns rely on the gateway's own session context instead of re-sending it.

Resolution order (first match wins):

1. an explicit `protocol.path` in `~/.dojo/settings.json`
2. `./DOJO.md` in the current project directory
3. `~/.dojo/DOJO.md`
4. the embedded default doc

**Override or disable it:**

```bash
# Disable for one run
DOJO_PROTOCOL_DISABLED=1 dojo
```

```json
// ~/.dojo/settings.json — disable persistently
{ "protocol": { "enabled": false } }
```

Or drop a `DOJO.md` file in the project root (or `~/.dojo/DOJO.md` for a machine-wide override) to replace the content instead of turning it off — either is picked up automatically ahead of the embedded default.

`/init` writes an editable `~/.dojo/DOJO.md` starter copy (never overwriting one that already exists) and installs the ratified **kata-harness** plugin alongside the rest of the first-party plugin set. Check current state anytime with `/doctor` (PROTOCOL + HARNESSES sections) or `/protocol status`; browse and install other KE harnesses with `/protocol harnesses` and `/protocol install <name>` — see [Protocol & Harnesses](#protocol--harnesses) above.

## Plugin System

Plugins extend the CLI with hook rules and skills. Place plugin directories under `~/.dojo/plugins/` (or the path configured in `plugins.path`), or use `/plugin install` to clone from a git URL.
Expand Down Expand Up @@ -386,15 +437,18 @@ XP is earned through guided tutorials (`/guide`), daily practice sessions, and r

## Session Management

Sessions scope conversation history on the gateway. Each `dojo` invocation generates a session ID automatically. You can rotate or resume sessions mid-session.
Sessions scope conversation history on the gateway. Each `dojo` invocation generates a session ID automatically. You can rotate, list, or resume sessions mid-session.

```
/session # show current session ID
/session new # rotate to a fresh session
/session dojo-cli-20260409 # resume a specific session
/session ls # list recent sessions from local history
/session resume # resume the most recently active session
/session resume dojo-cli-20260409-142301 # resume one specific session by ID
/session dojo-cli-20260409-142301 # switch directly (not verified against gateway)
```

Use `--resume` at startup to continue the most recent session automatically.
Use `--resume` at startup to continue the most recent session automatically, or `--session <id>` to resume one specific session by ID (implies `--resume`).

## Design

Expand Down
105 changes: 95 additions & 10 deletions cmd/dojo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (

"github.com/DojoGenesis/cli/internal/client"
"github.com/DojoGenesis/cli/internal/config"
"github.com/DojoGenesis/cli/internal/protocol"
"github.com/DojoGenesis/cli/internal/repl"
"github.com/DojoGenesis/cli/internal/state"
gcolor "github.com/gookit/color"
)

Expand All @@ -28,6 +30,7 @@ func main() {
flagOneShot = flag.String("one-shot", "", "Execute a single message and exit (non-interactive)")
flagCompletion = flag.String("completion", "", "Generate shell completions (bash|zsh|fish)")
flagResume = flag.Bool("resume", false, "Resume the most recent session instead of starting fresh")
flagSession = flag.String("session", "", "Resume a specific session ID instead of the most recent one (implies --resume; see /session ls)")
flagJSON = flag.Bool("json", false, "Output JSON lines in one-shot mode (for scripted pipelines)")
flagPlain = flag.Bool("plain", false, "Plain text output (no ANSI colors, for piped/CI usage)")
)
Expand All @@ -48,6 +51,20 @@ func main() {
os.Exit(0)
}

// Bare positional subcommands: `dojo version` / `dojo help` behave like the
// --version / -h flags. Neither needs config or a gateway, so handle them
// before anything else rather than launching the REPL.
if args := flag.Args(); len(args) > 0 {
switch args[0] {
case "version":
fmt.Printf("dojo %s\n", version)
os.Exit(0)
case "help":
flag.Usage()
os.Exit(0)
}
}

// Load config
cfg, err := config.Load()
if err != nil {
Expand All @@ -73,12 +90,21 @@ func main() {
// Build gateway client
gw := client.New(cfg.Gateway.URL, cfg.Gateway.Token, cfg.Gateway.Timeout)

// Cancellable context — catches Ctrl+C
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

// One-shot mode: send a single message and exit
// One-shot mode: send a single message and exit. Ctrl+C cancels the single
// turn and exits.
//
// NOTE (SessionStart/SessionEnd, W4-LIFECYCLE): deliberately NOT fired
// here. It would need its own plugin scan + hooks.Runner (this path never
// builds a repl.REPL, so there's nothing to reuse), but the actual
// blocker is that "prompt"/"agent" type hooks write straight to stdout
// via fmt.Printf regardless of --json (see runHook in
// internal/hooks/runner.go) — that would corrupt the JSON-lines contract
// --json promises to scripted/CI consumers of --one-shot. Revisit if/when
// hook stdout output is made --json-aware.
if *flagOneShot != "" {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

workspaceRoot, _ := os.Getwd()
req := client.ChatRequest{
Message: *flagOneShot,
Expand All @@ -87,8 +113,23 @@ func main() {
Stream: true,
WorkspaceRoot: workspaceRoot,
}
err = gw.ChatStream(ctx, req, func(chunk client.SSEChunk) {
// Carry the genius protocol on this single turn (prepends req.Message and
// sets req.SystemPrompt when enabled; inert under DOJO_PROTOCOL_DISABLED).
protocol.NewInjector(cfg).Apply(&req)

// Record the first gateway error event. Without this a failed stream
// (429 quota, 404 dead model, …) renders blank and the process exits 0,
// so every failure looks like a silent success. See repl.EventError.
var (
errSeen bool
errMsg string
)
streamErr := gw.ChatStream(ctx, req, func(chunk client.SSEChunk) {
ev := repl.ClassifyChunk(chunk)
if ev.Type == repl.EventError && !errSeen {
errSeen = true
errMsg = ev.Content
}
if *flagJSON {
if out := ev.RenderJSON(); out != "" {
fmt.Println(out)
Expand All @@ -102,14 +143,54 @@ func main() {
if !*flagJSON {
fmt.Println()
}
if err != nil {
fatalf("one-shot error: %s", err)

// Transport-level failure (couldn't connect, stalled, non-200 status).
if streamErr != nil {
if ctx.Err() != nil {
fmt.Fprintln(os.Stderr, "dojo: cancelled")
os.Exit(130)
}
fmt.Fprintf(os.Stderr, "dojo: %s\n", gw.FriendlyError(streamErr))
os.Exit(1)
}
// Transport succeeded but the stream carried a gateway error event —
// exit non-zero so scripts and CI see the failure.
if errSeen {
fmt.Fprintf(os.Stderr, "dojo: gateway error: %s\n", errMsg)
os.Exit(1)
}
return
}

// --json is a one-shot-only pipeline flag; in interactive mode it is a silent
// no-op today, so say so on stderr rather than ignoring it quietly.
if *flagJSON {
fmt.Fprintln(os.Stderr, "dojo: --json only applies to --one-shot mode; ignoring")
}

// Interactive REPL. The base context is deliberately NOT bound to SIGINT: a
// single Ctrl+C during a streaming turn must cancel only that turn (handled
// inside the REPL), not end the whole session. SIGTERM still shuts down.
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM)
defer stop()

// --session <id> resumes one SPECIFIC session (Claude-Code-style
// resume-by-id), vs. --resume's "whatever was last active". repl.New's
// signature is unchanged (owned by a sibling change — see
// internal/repl/repl.go): its resume bool contract is "load
// state.LastSessionID and restore it verbatim". --session piggybacks on
// that exact contract instead of widening it: persist the requested id
// as the last session BEFORE constructing the REPL, then force
// resume=true so repl.New picks it up. Bare --resume (no --session) is
// untouched — same bool passthrough as before.
resume := *flagResume
if *flagSession != "" {
state.SaveSession(*flagSession)
resume = true
}

// Run REPL (plugin scan happens inside repl.New)
r := repl.New(cfg, gw, *flagResume, *flagPlain || *flagNoColor)
r := repl.New(cfg, gw, resume, *flagPlain || *flagNoColor)
if err := r.Run(ctx); err != nil {
fatalf("repl error: %s", err)
}
Expand Down Expand Up @@ -157,14 +238,16 @@ _dojo() {
'/card:dojo profile card'
'/warroom:scout vs challenger debate'
'/craft:DojoCraft practitioner workbench'
'/doctor:read-only diagnostic'
'/protocol:KE harness discovery + install'
)
_describe 'command' commands
}
compdef _dojo dojo
`)
case "bash":
fmt.Print(`_dojo_completions() {
COMPREPLY=($(compgen -W "/help /health /home /model /tools /agent /skill /session /run /garden /trail /snapshot /trace /pilot /practice /projects /project /hooks /settings /guide /code /bloom /apps /workflow /doc /init /activity /plugin /disposition /telemetry /sensei /card /warroom /craft exit" -- "${COMP_WORDS[COMP_CWORD]}"))
COMPREPLY=($(compgen -W "/help /health /home /model /tools /agent /skill /session /run /garden /trail /snapshot /trace /pilot /practice /projects /project /hooks /settings /guide /code /bloom /apps /workflow /doc /init /activity /plugin /disposition /telemetry /sensei /card /warroom /craft /doctor /protocol exit" -- "${COMP_WORDS[COMP_CWORD]}"))
}
complete -F _dojo_completions dojo
`)
Expand Down Expand Up @@ -203,6 +286,8 @@ complete -c dojo -f -a "/sensei" -d "koan from the sensei"
complete -c dojo -f -a "/card" -d "dojo profile card"
complete -c dojo -f -a "/warroom" -d "scout vs challenger debate"
complete -c dojo -f -a "/craft" -d "DojoCraft practitioner workbench"
complete -c dojo -f -a "/doctor" -d "read-only diagnostic"
complete -c dojo -f -a "/protocol" -d "KE harness discovery + install"
`)
default:
fmt.Fprintf(os.Stderr, "dojo: unknown shell %q (supported: bash, zsh, fish)\n", shell)
Expand Down
29 changes: 20 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,31 @@ go 1.24.0

require (
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/glamour v1.0.0
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
github.com/chzyer/readline v1.5.1
github.com/gookit/color v1.6.0
github.com/wailsapp/wails/v2 v2.12.0
golang.org/x/term v0.36.0
)

require (
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/ansi v0.10.2 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/labstack/echo/v4 v4.13.3 // indirect
Expand All @@ -30,13 +37,15 @@ require (
github.com/leaanthony/gosod v1.0.4 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/leaanthony/u v1.1.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.17 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
Expand All @@ -48,8 +57,10 @@ require (
github.com/wailsapp/go-webview2 v1.0.22 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.22.0 // indirect
github.com/yuin/goldmark v1.7.13 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
)
Loading
Loading