diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..ac4050d
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,62 @@
+name: CI
+
+# build-artifacts.yml is workflow_dispatch-only and builds without testing, so
+# nothing ran on a pull request. That is how 4d63d75 landed on main having
+# deleted three imported assets: `go vet` stays clean, and only a build or a
+# test run reaches those paths.
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ go:
+ name: Go
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+
+ - uses: actions/setup-go@v7
+ with:
+ go-version-file: go.mod
+ cache-dependency-path: go.sum
+
+ - run: go vet ./...
+
+ # -race because the write coordination lock in internal/app is the kind of
+ # thing a plain run will not exercise.
+ - run: go test -race ./...
+
+ frontend:
+ name: Frontend
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+
+ - uses: pnpm/action-setup@v6
+ with:
+ version: 11.17.0
+
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 26
+ cache: pnpm
+ cache-dependency-path: frontend/pnpm-lock.yaml
+
+ - name: Install dependencies
+ working-directory: frontend
+ run: pnpm install --frozen-lockfile
+
+ - name: Test
+ working-directory: frontend
+ run: pnpm run test
+
+ # Runs tsc --noEmit before vite, so a type error fails here rather than in
+ # a release build. This is the step that would have caught 4d63d75.
+ - name: Build
+ working-directory: frontend
+ run: pnpm run build
diff --git a/agents.lock.json b/agents.lock.json
index 02fc7b3..241ad64 100644
--- a/agents.lock.json
+++ b/agents.lock.json
@@ -132,6 +132,45 @@
"windows"
],
"rank": 5
+ },
+ "cursor": {
+ "name": "Cursor",
+ "group": "platform",
+ "command": "cursor",
+ "config_mode": "guide",
+ "platforms": [
+ "macos",
+ "linux",
+ "windows"
+ ],
+ "guide": "Use Cursor subscription or official account settings; OneAgent does not write a private Cursor configuration.",
+ "rank": 6
+ },
+ "openclaw": {
+ "name": "OpenClaw",
+ "group": "gateway",
+ "command": "openclaw",
+ "config_mode": "guide",
+ "platforms": [
+ "macos",
+ "linux",
+ "windows"
+ ],
+ "guide": "Install from OpenClaw docs, then run: openclaw onboard --install-daemon && openclaw gateway status",
+ "rank": 7
+ },
+ "hermes": {
+ "name": "Hermes",
+ "group": "gateway",
+ "command": "hermes",
+ "config_mode": "guide",
+ "platforms": [
+ "macos",
+ "linux",
+ "windows"
+ ],
+ "guide": "Install Hermes, then configure models with hermes model or ~/.hermes/config.yaml.",
+ "rank": 8
}
}
}
diff --git a/frontend/dist/.keep b/frontend/dist/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/components/icons/assets/cursor.svg b/frontend/src/components/icons/assets/cursor.svg
new file mode 100644
index 0000000..f814fdc
--- /dev/null
+++ b/frontend/src/components/icons/assets/cursor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/components/icons/assets/hermes.png b/frontend/src/components/icons/assets/hermes.png
new file mode 100644
index 0000000..2d629d2
Binary files /dev/null and b/frontend/src/components/icons/assets/hermes.png differ
diff --git a/frontend/src/components/icons/assets/openclaw.svg b/frontend/src/components/icons/assets/openclaw.svg
new file mode 100644
index 0000000..7bfb7fc
--- /dev/null
+++ b/frontend/src/components/icons/assets/openclaw.svg
@@ -0,0 +1,60 @@
+
+
diff --git a/internal/app/settings_test.go b/internal/app/settings_test.go
index 73ad543..3c8b2bd 100644
--- a/internal/app/settings_test.go
+++ b/internal/app/settings_test.go
@@ -342,11 +342,19 @@ type countingRunner struct {
runs int
}
-func (r *countingRunner) Run(ctx context.Context, argv []string, env map[string]string, timeout time.Duration) (process.Result, error) {
+// Answers the probe itself rather than delegating to a real process. Delegating
+// made the result depend on the host: the caller simulates macOS, so the argv is
+// `defaults read -g AppleLocale`, which succeeds on a developer's Mac and is not
+// a command at all on Linux. A probe that cannot run reports "unanswered" and is
+// retried by design, so on Linux the count was 5 rather than 1 -- the assertion
+// failed while the caching it describes was working correctly.
+func (r *countingRunner) Run(context.Context, []string, map[string]string, time.Duration) (process.Result, error) {
r.runs++
- return r.Runner.Run(ctx, argv, env, timeout)
+ return process.Result{ExitCode: 0, Stdout: "en_US\n"}, nil
}
+func (r *countingRunner) LookPath(string) (string, bool) { return "", false }
+
// The same preference has to reach npm, not just the runtime download. The fake
// runner records every environment, so the assertion is the registry npm was
// actually pointed at.
diff --git a/internal/app/testdata/status-empty-linux-arm64.json b/internal/app/testdata/status-empty-linux-arm64.json
index 61fae6e..65a5bf5 100644
--- a/internal/app/testdata/status-empty-linux-arm64.json
+++ b/internal/app/testdata/status-empty-linux-arm64.json
@@ -7,20 +7,21 @@
},
"capabilities": {
"canInstall": {
- "codex": false,
+ "aider": false,
"claude-code": false,
- "opencode": false,
+ "codex": false,
+ "cursor": false,
+ "hermes": false,
"kilo-cli": false,
- "aider": false,
"openclaw": false,
- "hermes": false,
- "cursor": false,
- "kiro": false,
- "gemini-cli": false,
- "cline": false,
- "continue": false,
- "qwen-code": false,
- "kilo-vscode": false
+ "opencode": false
+ },
+ "missingRuntime": {
+ "aider": "uv",
+ "claude-code": "node",
+ "codex": "node",
+ "kilo-cli": "node",
+ "opencode": "node"
},
"supportedAgentIds": [
"codex",
@@ -28,234 +29,131 @@
"opencode",
"kilo-cli",
"aider",
- "openclaw",
- "hermes",
"cursor",
- "kiro",
- "gemini-cli",
- "cline",
- "continue",
- "qwen-code",
- "kilo-vscode"
- ],
- "missingRuntime": {
- "aider": "uv",
- "claude-code": "node",
- "codex": "node",
- "kilo-cli": "node",
- "opencode": "node"
- }
+ "openclaw",
+ "hermes"
+ ]
},
"agents": {
- "codex": {
+ "aider": {
"installed": false,
- "config": "${HOME}/.codex/config.toml",
"configured": false,
"guideOnly": false,
+ "config": "${HOME}/.oneagent/aider.env",
"version": null,
"lockedVersion": null,
"canInstall": false,
"provider": null,
+ "profileId": null,
"model": null,
"baseUrl": null,
"updatedAt": null,
- "detected": null,
- "profileId": null
+ "detected": null
},
"claude-code": {
"installed": false,
- "config": "${HOME}/.claude/settings.json",
- "configured": false,
- "guideOnly": false,
- "version": null,
- "lockedVersion": null,
- "canInstall": false,
- "provider": null,
- "model": null,
- "baseUrl": null,
- "updatedAt": null,
- "detected": null,
- "profileId": null
- },
- "opencode": {
- "installed": false,
- "config": "${HOME}/.config/opencode/opencode.json",
- "configured": false,
- "guideOnly": false,
- "version": null,
- "lockedVersion": null,
- "canInstall": false,
- "provider": null,
- "model": null,
- "baseUrl": null,
- "updatedAt": null,
- "detected": null,
- "profileId": null
- },
- "kilo-cli": {
- "installed": false,
- "config": "${HOME}/.config/kilo/kilo.jsonc",
"configured": false,
"guideOnly": false,
+ "config": "${HOME}/.claude/settings.json",
"version": null,
"lockedVersion": null,
"canInstall": false,
"provider": null,
+ "profileId": null,
"model": null,
"baseUrl": null,
"updatedAt": null,
- "detected": null,
- "profileId": null
+ "detected": null
},
- "aider": {
+ "codex": {
"installed": false,
- "config": "${HOME}/.oneagent/aider.env",
"configured": false,
"guideOnly": false,
+ "config": "${HOME}/.codex/config.toml",
"version": null,
"lockedVersion": null,
"canInstall": false,
"provider": null,
+ "profileId": null,
"model": null,
"baseUrl": null,
"updatedAt": null,
- "detected": null,
- "profileId": null
- },
- "openclaw": {
- "installed": false,
- "config": "",
- "configured": false,
- "guideOnly": true,
- "version": null,
- "lockedVersion": null,
- "canInstall": false,
- "provider": null,
- "model": null,
- "baseUrl": null,
- "updatedAt": null,
- "detected": null,
- "profileId": null
- },
- "hermes": {
- "installed": false,
- "config": "",
- "configured": false,
- "guideOnly": true,
- "version": null,
- "lockedVersion": null,
- "canInstall": false,
- "provider": null,
- "model": null,
- "baseUrl": null,
- "updatedAt": null,
- "detected": null,
- "profileId": null
+ "detected": null
},
"cursor": {
"installed": false,
- "config": "",
"configured": false,
"guideOnly": true,
- "version": null,
- "lockedVersion": null,
- "canInstall": false,
- "provider": null,
- "model": null,
- "baseUrl": null,
- "updatedAt": null,
- "detected": null,
- "profileId": null
- },
- "kiro": {
- "installed": false,
"config": "",
- "configured": false,
- "guideOnly": true,
"version": null,
"lockedVersion": null,
"canInstall": false,
"provider": null,
+ "profileId": null,
"model": null,
"baseUrl": null,
"updatedAt": null,
- "detected": null,
- "profileId": null
+ "detected": null
},
- "gemini-cli": {
+ "hermes": {
"installed": false,
- "config": "",
"configured": false,
"guideOnly": true,
- "version": null,
- "lockedVersion": null,
- "canInstall": false,
- "provider": null,
- "model": null,
- "baseUrl": null,
- "updatedAt": null,
- "detected": null,
- "profileId": null
- },
- "cline": {
- "installed": false,
"config": "",
- "configured": false,
- "guideOnly": true,
"version": null,
"lockedVersion": null,
"canInstall": false,
"provider": null,
+ "profileId": null,
"model": null,
"baseUrl": null,
"updatedAt": null,
- "detected": null,
- "profileId": null
+ "detected": null
},
- "continue": {
+ "kilo-cli": {
"installed": false,
- "config": "",
"configured": false,
- "guideOnly": true,
+ "guideOnly": false,
+ "config": "${HOME}/.config/kilo/kilo.jsonc",
"version": null,
"lockedVersion": null,
"canInstall": false,
"provider": null,
+ "profileId": null,
"model": null,
"baseUrl": null,
"updatedAt": null,
- "detected": null,
- "profileId": null
+ "detected": null
},
- "qwen-code": {
+ "openclaw": {
"installed": false,
- "config": "",
"configured": false,
"guideOnly": true,
+ "config": "",
"version": null,
"lockedVersion": null,
"canInstall": false,
"provider": null,
+ "profileId": null,
"model": null,
"baseUrl": null,
"updatedAt": null,
- "detected": null,
- "profileId": null
+ "detected": null
},
- "kilo-vscode": {
+ "opencode": {
"installed": false,
- "config": "",
"configured": false,
- "guideOnly": true,
+ "guideOnly": false,
+ "config": "${HOME}/.config/opencode/opencode.json",
"version": null,
"lockedVersion": null,
"canInstall": false,
"provider": null,
+ "profileId": null,
"model": null,
"baseUrl": null,
"updatedAt": null,
- "detected": null,
- "profileId": null
+ "detected": null
}
},
"catalog": [
@@ -291,22 +189,6 @@
"platformNote": "",
"rank": 2
},
- {
- "id": "cursor",
- "name": "Cursor",
- "group": "platform",
- "configMode": "guide",
- "guideOnly": true,
- "lockedVersion": null,
- "protocol": null,
- "platforms": [
- "macos",
- "linux",
- "windows"
- ],
- "platformNote": "",
- "rank": 3
- },
{
"id": "opencode",
"name": "OpenCode",
@@ -321,55 +203,7 @@
"windows"
],
"platformNote": "",
- "rank": 4
- },
- {
- "id": "openclaw",
- "name": "OpenClaw",
- "group": "gateway",
- "configMode": "guide",
- "guideOnly": true,
- "lockedVersion": null,
- "protocol": null,
- "platforms": [
- "macos",
- "linux",
- "windows"
- ],
- "platformNote": "",
- "rank": 5
- },
- {
- "id": "hermes",
- "name": "Hermes",
- "group": "gateway",
- "configMode": "guide",
- "guideOnly": true,
- "lockedVersion": null,
- "protocol": null,
- "platforms": [
- "macos",
- "linux",
- "windows"
- ],
- "platformNote": "",
- "rank": 6
- },
- {
- "id": "gemini-cli",
- "name": "Gemini CLI",
- "group": "platform",
- "configMode": "guide",
- "guideOnly": true,
- "lockedVersion": null,
- "protocol": null,
- "platforms": [
- "macos",
- "linux",
- "windows"
- ],
- "platformNote": "",
- "rank": 7
+ "rank": 3
},
{
"id": "kilo-cli",
@@ -385,7 +219,7 @@
"windows"
],
"platformNote": "",
- "rank": 8
+ "rank": 4
},
{
"id": "aider",
@@ -401,11 +235,11 @@
"windows"
],
"platformNote": "",
- "rank": 9
+ "rank": 5
},
{
- "id": "kiro",
- "name": "Kiro",
+ "id": "cursor",
+ "name": "Cursor",
"group": "platform",
"configMode": "guide",
"guideOnly": true,
@@ -417,44 +251,12 @@
"windows"
],
"platformNote": "",
- "rank": 10
- },
- {
- "id": "cline",
- "name": "Cline",
- "group": "ide",
- "configMode": "guide",
- "guideOnly": true,
- "lockedVersion": null,
- "protocol": null,
- "platforms": [
- "macos",
- "linux",
- "windows"
- ],
- "platformNote": "",
- "rank": 11
- },
- {
- "id": "continue",
- "name": "Continue",
- "group": "ide",
- "configMode": "guide",
- "guideOnly": true,
- "lockedVersion": null,
- "protocol": null,
- "platforms": [
- "macos",
- "linux",
- "windows"
- ],
- "platformNote": "",
- "rank": 12
+ "rank": 6
},
{
- "id": "qwen-code",
- "name": "Qwen Code",
- "group": "ide",
+ "id": "openclaw",
+ "name": "OpenClaw",
+ "group": "gateway",
"configMode": "guide",
"guideOnly": true,
"lockedVersion": null,
@@ -465,12 +267,12 @@
"windows"
],
"platformNote": "",
- "rank": 13
+ "rank": 7
},
{
- "id": "kilo-vscode",
- "name": "Kilo VS Code",
- "group": "ide",
+ "id": "hermes",
+ "name": "Hermes",
+ "group": "gateway",
"configMode": "guide",
"guideOnly": true,
"lockedVersion": null,
@@ -481,7 +283,7 @@
"windows"
],
"platformNote": "",
- "rank": 14
+ "rank": 8
}
],
"groups": [
@@ -503,17 +305,17 @@
}
],
"providers": {
- "ppio": {
- "name": "PPIO",
- "home": "https://ppio.com/",
- "base_url": "https://api.ppio.com/openai",
- "anthropic_base_url": "https://api.ppio.com/anthropic"
- },
"novita": {
"name": "Novita",
"home": "https://novita.ai/",
"base_url": "https://api.novita.ai/openai",
"anthropic_base_url": "https://api.novita.ai/anthropic"
+ },
+ "ppio": {
+ "name": "PPIO",
+ "home": "https://ppio.com/",
+ "base_url": "https://api.ppio.com/openai",
+ "anthropic_base_url": "https://api.ppio.com/anthropic"
}
},
"mirrors": [
@@ -533,26 +335,25 @@
}
],
"paths": {
- "logs": "${HOME}/.oneagent/logs",
- "profile": "${HOME}/.oneagent/profile.json",
- "codex_config": "${HOME}/.codex/config.toml",
+ "aider_config": "${HOME}/.oneagent/aider.env",
"claude-code_config": "${HOME}/.claude/settings.json",
- "opencode_config": "${HOME}/.config/opencode/opencode.json",
+ "codex_config": "${HOME}/.codex/config.toml",
"kilo-cli_config": "${HOME}/.config/kilo/kilo.jsonc",
- "aider_config": "${HOME}/.oneagent/aider.env"
+ "logs": "${HOME}/.oneagent/logs",
+ "opencode_config": "${HOME}/.config/opencode/opencode.json",
+ "profile": "${HOME}/.oneagent/profile.json"
},
"backups": {
- "codex": false,
+ "aider": false,
"claude-code": false,
- "opencode": false,
+ "codex": false,
"kilo-cli": false,
- "aider": false,
+ "opencode": false,
"profile": false
},
"profiles": [],
"activeProfile": null,
- "environment": null,
- "environmentError": null,
+ "firstRun": true,
"runtimes": [
{
"id": "node",
@@ -587,6 +388,8 @@
"requiredByHint": "Aider"
}
],
+ "environment": null,
+ "environmentError": null,
"desktopAgent": {
"id": "desktop-agent",
"name": "ChatGPT Desktop",
@@ -596,6 +399,5 @@
"source": "unknown",
"configPath": "${HOME}/.codex/config.toml",
"configSharedWith": "Codex"
- },
- "firstRun": true
+ }
}