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
23 changes: 23 additions & 0 deletions .surface
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,7 @@ CMD basecamp setup
CMD basecamp setup agents
CMD basecamp setup claude
CMD basecamp setup codex
CMD basecamp setup grok
CMD basecamp show
CMD basecamp skill
CMD basecamp skill install
Expand Down Expand Up @@ -13205,6 +13206,27 @@ FLAG basecamp setup codex --stats type=bool
FLAG basecamp setup codex --styled type=bool
FLAG basecamp setup codex --todolist type=string
FLAG basecamp setup codex --verbose type=count
FLAG basecamp setup grok --account type=string
FLAG basecamp setup grok --agent type=bool
FLAG basecamp setup grok --cache-dir type=string
FLAG basecamp setup grok --count type=bool
FLAG basecamp setup grok --help type=bool
FLAG basecamp setup grok --hints type=bool
FLAG basecamp setup grok --ids-only type=bool
FLAG basecamp setup grok --in type=string
FLAG basecamp setup grok --jq type=string
FLAG basecamp setup grok --json type=bool
FLAG basecamp setup grok --markdown type=bool
FLAG basecamp setup grok --md type=bool
FLAG basecamp setup grok --no-hints type=bool
FLAG basecamp setup grok --no-stats type=bool
FLAG basecamp setup grok --profile type=string
FLAG basecamp setup grok --project type=string
FLAG basecamp setup grok --quiet type=bool
FLAG basecamp setup grok --stats type=bool
FLAG basecamp setup grok --styled type=bool
FLAG basecamp setup grok --todolist type=string
FLAG basecamp setup grok --verbose type=count
FLAG basecamp show --account type=string
FLAG basecamp show --agent type=bool
FLAG basecamp show --all-comments type=bool
Expand Down Expand Up @@ -18658,6 +18680,7 @@ SUB basecamp setup
SUB basecamp setup agents
SUB basecamp setup claude
SUB basecamp setup codex
SUB basecamp setup grok
SUB basecamp show
SUB basecamp skill
SUB basecamp skill install
Expand Down
17 changes: 16 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,26 @@ basecamp-cli/
│ └── version/ # Version info
├── e2e/ # BATS integration tests
├── skills/ # Agent skills
├── hooks/ # Agent lifecycle hooks (both agents)
├── hooks/ # Agent lifecycle hooks (both plugin agents)
├── .claude-plugin/ # Claude Code integration
└── .codex-plugin/ # Codex plugin manifest
```

## Coding-agent integrations

Coding-agent integration lives in `internal/harness` (agent registry, detection, plugin and
skill health checks) and `internal/commands/wizard_agents.go` (`basecamp setup
claude|codex|grok|agents`). Claude Code and Codex each get a native plugin from the
`basecamp/claude-plugins` marketplace and have registrations of their own (`claude.go`,
`codex.go`). Grok Build has no plugin: it reads the shared `~/.agents/skills/basecamp` skill
directly, so it is a row of `harness.SkillAgent` (name, id, home env var, home directory,
binary) in `skill_agent.go`, and everything in `internal/commands` that touches a shared-skill
agent — the setup handler, the `BASECAMP_SETUP_AGENT` values, doctor's remediation — loops over
`harness.SkillAgents()` rather than naming it. A new shared-skill agent is a new row; the skill
picker's `(Global)` row, the prose lists in the installers and the docs are the places to update
by hand. `setup <id>` never fabricates an agent's home directory: a skill-only agent that is not
detected is reported missing, not created.

## Basecamp API Reference

API documentation: https://github.com/basecamp/bc3-api
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

`basecamp` is the official command-line interface for Basecamp. Manage projects, todos, messages, and more from your terminal or through AI agents.

- Works standalone or with any AI agent (Claude, Codex, Copilot, Gemini)
- Works standalone or with any AI agent (Claude, Codex, Grok, Copilot, Gemini)
- JSON output with breadcrumbs for easy navigation
- OAuth authentication with automatic token refresh
- Includes agent skills plus native Claude Code and Codex plugins
- Includes agent skills plus native Claude Code and Codex plugins; Grok Build reads the shared skill directly

## Quick Start

Expand Down Expand Up @@ -107,7 +107,7 @@ The first interactive `basecamp` run applies the recommended setup automatically

- Account granted by OAuth, otherwise the existing configured account or first available account, saved globally
- No global default project; directory-specific and environment project settings continue to apply
- Every detected Claude Code or Codex integration
- Every detected Claude Code, Codex or Grok integration

Run the same setup directly with `basecamp setup`. To choose the account, default project, config scope, and agent integrations, run:

Expand Down Expand Up @@ -278,6 +278,8 @@ codex plugin add basecamp@37signals
To pick up a newer plugin version later, refresh the marketplace with
`codex plugin marketplace upgrade 37signals` (or re-run `basecamp setup codex`).

**Grok Build:** `basecamp setup grok` — installs the shared skill and confirms it is healthy. There is no Grok plugin: Grok reads user skills from `~/.grok/skills/` and from the cross-agent `~/.agents/skills/`, so the shared `~/.agents/skills/basecamp` skill is the whole integration. Grok is detected by `$GROK_HOME` (default `~/.grok`) or a `grok` binary on `PATH`, in `~/.local/bin`, or in `$GROK_HOME/bin` where its installers put it. Start a new Grok session after setup to load the skill.

**Other agents:** Point your agent at [`skills/basecamp/SKILL.md`](skills/basecamp/SKILL.md) for Basecamp workflow coverage.

**Agent discovery:** Every command supports `--help --agent` for structured JSON output (flags, gotchas, subcommands). Use `basecamp commands --json` for the full catalog.
Expand Down Expand Up @@ -335,7 +337,7 @@ client-registration flow) is obsolete and safe to delete.
```bash
basecamp doctor # Check CLI health and diagnose issues
basecamp doctor --verbose # Verbose output with details
basecamp doctor --json # Structured checks, including Claude and Codex
basecamp doctor --json # Structured checks, including Claude, Codex and Grok
```

### Windows: Smart App Control and SmartScreen
Expand Down
22 changes: 21 additions & 1 deletion e2e/installer.bats
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ EOF
[[ "$status" -eq 0 ]]
[[ "$output" == *"setup claude"* ]]
[[ "$output" != *"setup codex"* ]] # codex unadvertised → never invoked
[[ "$output" != *"setup grok"* ]] # grok likewise
}

# Explicit `codex` on an old binary that lacks `setup codex` must NOT run the
Expand All @@ -212,6 +213,25 @@ EOF
[[ "$output" != *"setup codex"* ]]
}

# Grok is the same explicit-selector shape as codex: an old binary that does
# not advertise `setup grok` degrades to the shared skill.
@test "old binary + BASECAMP_SETUP_AGENT=grok degrades to 'skill install', never 'setup grok'" {
write_stub old
run_post_install_setup "export BASECAMP_SETUP_AGENT=grok"
[[ "$status" -eq 0 ]]
[[ "$output" == *"skill install"* ]]
[[ "$output" != *"setup grok"* ]]
}

# A new binary owns the selector: the installer hands every value, grok
# included, to `setup agents` rather than dispatching per agent itself.
@test "new binary + BASECAMP_SETUP_AGENT=grok dispatches to 'setup agents'" {
run_post_install_setup "export BASECAMP_SETUP_AGENT=grok"
[[ "$status" -eq 0 ]]
[[ "$output" == *"setup agents"* ]]
[[ "$output" != *"setup grok"* ]]
}

@test "install.sh has no residual 'setup claude' dispatch" {
# `setup claude` may appear only inside the explicit-selector fallback case.
run grep -n 'setup claude' "$INSTALL_SH"
Expand All @@ -235,7 +255,7 @@ EOF
grep -q 'setup agents' "$INSTALL_PS1"
grep -q 'skill install' "$INSTALL_PS1"
grep -q 'catch {' "$INSTALL_PS1"
# Explicit claude|codex selectors must be capability-checked before dispatch,
# Explicit claude|codex|grok selectors must be capability-checked before dispatch,
# so an old binary never gets an unadvertised subcommand as a stray arg.
grep -qF 'match "(?m)^\s+$selector\s"' "$INSTALL_PS1"
# The keyring escape hatch belt (see the BASECAMP_NO_KEYRING tests below).
Expand Down
12 changes: 11 additions & 1 deletion e2e/setup.bats
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ run_in_pty() {
# non-interactive paths and have to keep working — a persistent hook would have
# taken all of them out, which is the easiest thing to get wrong here.

# hide_agent_binaries drops the developer's real claude/codex from PATH. Without
# hide_agent_binaries drops the developer's real claude/codex/grok from PATH. Without
# it these tests shell out to whichever agent CLI happens to be installed, and
# those have prompts of their own — a hang in somebody else's tool, unrelated to
# the gate under test. What we are asserting is that the parent's gate does not
Expand Down Expand Up @@ -227,3 +227,13 @@ hide_agent_binaries() {
assert_not_timed_out
assert_success
}

@test "setup grok still runs without a terminal" {
create_credentials
create_global_config '{"account_id": 99999}'
hide_agent_binaries

run_guarded "basecamp setup grok --json < /dev/null"
assert_not_timed_out
assert_success
}
14 changes: 11 additions & 3 deletions install.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ irm https://raw.githubusercontent.com/basecamp/basecamp-cli/main/scripts/install

> **Note:** The install scripts run `basecamp setup` whenever they can attach it to a usable interactive terminal, including the standard `curl | bash` command. When no usable controlling terminal is available, output is redirected, or `BASECAMP_NONINTERACTIVE=1`/`true` is set, they skip authentication and run `basecamp setup agents`. That command installs the baseline agent skill and **attempts to connect** a single detected coding agent (best effort). If several agents are detected, or none is, only the baseline skill is installed and the per-agent commands are surfaced. Explicitly skipping first-time setup with `BASECAMP_SKIP_SETUP=1` still runs `setup agents`. If optional first-time setup is cancelled or does not finish, the installed CLI remains ready and the installer prints the command to resume setup.
>
> Choose which agent to connect with `BASECAMP_SETUP_AGENT` (`claude`, `codex`, `all`, or `none`). Set it for the interpreter, not the fetch:
> Choose which agent to connect with `BASECAMP_SETUP_AGENT` (`claude`, `codex`, `grok`, `all`, or `none`). Set it for the interpreter, not the fetch:
> - Bash: `curl -fsSL https://basecamp.com/install-cli | BASECAMP_SETUP_AGENT=codex bash`
> - PowerShell: `$env:BASECAMP_SETUP_AGENT='codex'; irm https://raw.githubusercontent.com/basecamp/basecamp-cli/main/scripts/install.ps1 | iex`
>
Expand Down Expand Up @@ -131,7 +131,7 @@ EXECUTE NOW: Start with Step 1. Mark TODO items complete as you go. Stop when `b

**Do not execute this section unless explicitly requested.** The core installation is complete when DONE WHEN passes.

Interactive setup in Step 1 connects every detected agent. Without a controlling terminal, the installer still installs the baseline skill and attempts to connect one detected agent. Run the commands here to force a specific agent, repair a connection, or connect agents that were not available during installation. Both agent plugins require the `basecamp` CLI installed above — the plugin invokes it for every Basecamp operation.
Interactive setup in Step 1 connects every detected agent. Without a controlling terminal, the installer still installs the baseline skill and attempts to connect one detected agent. Run the commands here to force a specific agent, repair a connection, or connect agents that were not available during installation. Every integration requires the `basecamp` CLI installed above — the plugins and the skill invoke it for every Basecamp operation.

### Claude Code

Expand Down Expand Up @@ -163,7 +163,15 @@ codex plugin add basecamp@37signals

To pick up a newer plugin version later, refresh with `codex plugin marketplace upgrade 37signals` (or re-run `basecamp setup codex`).

Verify either agent integration with structured diagnostics:
### Grok Build

```bash
basecamp setup grok
```

This installs the shared Basecamp skill and confirms it is healthy. There is no Grok plugin: Grok reads user skills from `~/.grok/skills/` and from the cross-agent `~/.agents/skills/`, so the shared skill at `~/.agents/skills/basecamp/SKILL.md` is the whole integration. Setup requires Grok to be present — `$GROK_HOME` (default `~/.grok`) or a `grok` binary on `PATH`, in `~/.local/bin`, or in `$GROK_HOME/bin` — and never creates the Grok home itself. Start a new Grok session afterwards to load the skill.

Verify any agent integration with structured diagnostics:

```bash
basecamp doctor --json
Expand Down
19 changes: 19 additions & 0 deletions internal/commands/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,14 @@ func buildDoctorBreadcrumbs(checks []Check) []output.Breadcrumb {
Cmd: "basecamp setup codex",
Description: "Install or update the Codex plugin",
})
default:
if agent, ok := skillAgentForCheck(c.Name); ok {
breadcrumbs = append(breadcrumbs, output.Breadcrumb{
Action: "setup_" + agent.ID,
Cmd: "basecamp setup " + agent.ID,
Description: "Install the shared Basecamp skill for " + agent.Name,
})
}
}
}

Expand All @@ -1152,6 +1160,17 @@ func buildDoctorBreadcrumbs(checks []Check) []output.Breadcrumb {
return unique
}

// skillAgentForCheck returns the shared-skill agent whose check is named, so
// its remediation reads the harness table rather than a case per agent.
func skillAgentForCheck(name string) (harness.SkillAgent, bool) {
for _, agent := range harness.SkillAgents() {
if name == agent.Name+" Skill" {
return agent, true
}
}
return harness.SkillAgent{}, false
}

// pluralize returns singular or plural form based on count.
func pluralize(n int, singular, plural string) string {
if n == 1 {
Expand Down
15 changes: 15 additions & 0 deletions internal/commands/doctor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,21 @@ func TestBuildDoctorBreadcrumbs_Codex(t *testing.T) {
assert.Equal(t, "basecamp setup codex", breadcrumbs[0].Cmd)
}

// A shared-skill agent's failing skill check remediates with its own
// `setup <id>`, read from the harness table rather than a case per agent.
func TestBuildDoctorBreadcrumbs_SkillAgents(t *testing.T) {
for _, agent := range harness.SkillAgents() {
t.Run(agent.ID, func(t *testing.T) {
breadcrumbs := buildDoctorBreadcrumbs([]Check{{Name: agent.Name + " Skill", Status: "fail"}})

require.Len(t, breadcrumbs, 1)
assert.Equal(t, "setup_"+agent.ID, breadcrumbs[0].Action)
assert.Equal(t, "basecamp setup "+agent.ID, breadcrumbs[0].Cmd)
})
}
assert.Empty(t, buildDoctorBreadcrumbs([]Check{{Name: "Grok Skill", Status: "pass"}}), "a passing check needs no remediation")
}

func TestCheckLegacyInstall_SkipsKeyringWhenNoKeyring(t *testing.T) {
t.Setenv("BASECAMP_NO_KEYRING", "1")
t.Setenv("XDG_CACHE_HOME", t.TempDir())
Expand Down
Loading
Loading