Skip to content
Open
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
20 changes: 11 additions & 9 deletions apps/server/src/services/skills/builtin-skills/bb-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,23 +339,25 @@ environment pull-request show <id>`. Diff commands require an explicit target
- Known ACP agents can appear automatically when their CLI is installed on the
host; for example `opencode`, `omp`, Grok Build's `grok` CLI, or Hermes'
`hermes` CLI on PATH appears as provider `acp-opencode`, `acp-omp`,
`acp-grok`, or `acp-hermes-agent`.
`acp-grok`, or `acp-hermes-agent`. `acp-cursor` and `acp-junie` are listed
even without their CLI, because bb can install those itself.
- Cursor ACP threads discover project skills from `.cursor/skills`. This root
can link to `.agents/skills`. `bb skill list` shows linked Cursor skills under
`cursor-project` and keeps them read-only.
- Custom ACP agents live in the ACP providers plugin's `customAgents` setting,
a JSON array: `bb plugin config provider-acp set customAgents '[{"id":"amp",
"displayName":"Amp","command":"amp","args":["acp"]}]'`. The user supplies a
slug `id`; bb exposes it as provider id `acp-<id>`, which is permanent.
`cursor` is reserved; `opencode`, `omp`, `grok` and `hermes-agent` are not,
so an entry with one of those ids replaces the shipped agent. The plugin
re-registers as soon as the setting changes. The configured command is local code execution and only works with a
co-located daemon. Optional per-agent fields: `args`, `env`, `cwd`,
`modelCli`, `reasoningCli`, `nativeReasoning`, `nativeSkillRoots`
`cursor` is reserved; `opencode`, `omp`, `grok`, `hermes-agent` and `junie`
are not, so an entry with one of those ids replaces the shipped agent. The
plugin re-registers as soon as the setting changes. The configured command
is local code execution and only works with a co-located daemon. Optional
per-agent fields: `args`, `env`, `cwd`, `modelCli`, `reasoningCli`,
`nativeReasoning`, `nativeSkillRoots`
(`{"user": [...], "project": [...]}` relative paths), `permissionCli`,
`supportsManualCompaction`, and `dialect` (`cursor`, `opencode`, `omp`, or
`grok`). The old `customAcpAgents` array in `config.json` is deprecated; bb
reads it and warns until 0.41.
`supportsManualCompaction`, and `dialect` (`cursor`, `opencode`, `omp`,
`grok`, or `junie`). The old `customAcpAgents` array in `config.json` is
deprecated; bb reads it and warns until 0.41.
- Top-level `customModels` in the same `config.json` registers extra picker
models. `providerId` accepts a built-in provider id or any `acp-*` provider
id. The provider must still accept the id: `claude-code` and `codex` accept
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,11 @@ describe("buildPluginProviderRegistration", () => {
logoUrl: "/api/v1/system/providers/acp-hermes-agent/logo",
icon: undefined,
},
{
id: "acp-junie",
logoUrl: "/api/v1/system/providers/acp-junie/logo",
icon: undefined,
},
]);
});
});
Expand Down
19 changes: 16 additions & 3 deletions apps/server/test/public/public-provider-installations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,19 @@ describe("public provider installation routes", () => {

expect(response.status).toBe(200);
const body = (await readJson(response)) as ProviderCliStatusResponse;
expect(Object.keys(body)).toEqual(["codex", "claude-code", "pi", "acp-cursor"]);
expect(Object.keys(body)).toEqual([
"codex",
"claude-code",
"pi",
"acp-cursor",
"acp-junie",
]);
expect(Object.values(body).map((status) => status.displayName)).toEqual([
"Codex",
"Claude Code",
"Pi",
"Cursor",
"Junie",
]);
expect(
responder.requests
Expand All @@ -181,7 +188,7 @@ describe("public provider installation routes", () => {
? request.command.providerId
: null,
),
).toEqual(["codex", "claude-code", "pi", "acp-cursor"]);
).toEqual(["codex", "claude-code", "pi", "acp-cursor", "acp-junie"]);
});
});

Expand Down Expand Up @@ -216,11 +223,17 @@ describe("public provider installation routes", () => {

expect(response.status).toBe(200);
const body = (await readJson(response)) as ProviderCliStatusResponse;
expect(Object.keys(body)).toEqual(["codex", "pi", "acp-cursor"]);
expect(Object.keys(body)).toEqual([
"codex",
"pi",
"acp-cursor",
"acp-junie",
]);
expect(Object.values(body).map((status) => status.displayName)).toEqual([
"Codex",
"Pi",
"Cursor",
"Junie",
]);
expect(warn).toHaveBeenCalledWith(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ const FIRST_PARTY_PROVIDER_DECLARATIONS = [
visibility: "installed",
hasLogo: true,
},
{
builtinName: "provider-acp",
pluginId: "provider-acp",
providerId: "acp-junie",
displayName: "Junie",
supportsThreadArchive: false,
supportsThreadRename: false,
fork: "none",
supportsManualCompaction: false,
supportsUsage: false,
visibility: "always",
hasLogo: true,
},
] as const;

const PROVIDER_IDS = FIRST_PARTY_PROVIDER_DECLARATIONS.map(
Expand Down Expand Up @@ -404,6 +417,7 @@ describe("first-party provider plugins", () => {
"acp-omp",
"acp-grok",
"acp-hermes-agent",
"acp-junie",
]);
const infos = await listSystemProviderInfos(harness.deps, {});
expect(infos.find((info) => info.id === "pi")).toBeUndefined();
Expand Down
2 changes: 1 addition & 1 deletion apps/server/test/system/execution-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1158,7 +1158,7 @@ describe("resolveSystemExecutionOptions", () => {
registry.markRegistrationsSettled();

expect((await providersPromise).map((provider) => provider.id)).toEqual(
["codex", "claude-code", "pi", "acp-cursor"],
["codex", "claude-code", "pi", "acp-cursor", "acp-junie"],
);
},
);
Expand Down
3 changes: 2 additions & 1 deletion apps/server/test/system/provider-states.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ describe("getProviderStates", () => {
"claude-code",
"pi",
"acp-cursor",
"acp-junie",
"acp-opencode",
]);
expect(result.providers[0]).toMatchObject({
Expand Down Expand Up @@ -216,7 +217,7 @@ describe("getProviderStates", () => {
// workspace-scoped and receives the environment path.
expect(healthCwds.filter((cwd) => cwd === undefined)).toHaveLength(4);
expect(healthCwds.filter((cwd) => cwd !== undefined)).toEqual(
Array(4).fill(environment.path),
Array(5).fill(environment.path),
);
});
});
Expand Down
1 change: 1 addition & 0 deletions apps/server/test/threads/thread-default-policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ describe("resolveCreateThreadExecutionDefaults", () => {
"acp-omp",
"acp-grok",
"acp-hermes-agent",
"acp-junie",
]);
expect(
resolveCreateThreadExecutionDefaults(userRegistry, {
Expand Down
18 changes: 18 additions & 0 deletions apps/web/src/landing/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,24 @@ export function OmpIcon({ className }: { className?: string }) {
);
}

// Junie brand mark (JetBrains). Rendered in currentColor for light/dark
// theming.
export function JunieIcon({ className }: { className?: string }) {
return (
<svg
fill="currentColor"
viewBox="0 0 53.12 53"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<title>Junie</title>
<path d="M35.4473 17.668H53.1134V20.614C53.1134 41.2261 44.2752 53.0001 20.7273 53.0001H17.7812V35.334H20.7273C31.0282 35.334 35.4473 30.9149 35.4473 20.614V17.668Z" />
<path d="M17.6661 17.668H0V35.334H17.6661V17.668Z" />
<path d="M35.334 0H17.668V17.6661H35.334V0Z" />
</svg>
);
}

// Hermes Agent brand mark. Rendered in currentColor for light/dark theming.
export function HermesAgentIcon({ className }: { className?: string }) {
return (
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/landing/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ declare const __SITE_ORIGIN__: string;
const SITE_URL = __SITE_ORIGIN__;
export const SITE_TITLE = "bb: the IDE that builds itself";
export const SITE_DESCRIPTION =
"bb can control, customize, and automate itself, laying the groundwork for your own software factory. Fully open source and local-first, with Claude Code, Codex, Cursor, Pi, OpenCode, Grok, omp, and Hermes.";
"bb can control, customize, and automate itself, laying the groundwork for your own software factory. Fully open source and local-first, with Claude Code, Codex, Cursor, Pi, OpenCode, Grok, omp, Hermes, and Junie.";
/** Unfurl copy: the hero sub verbatim, without SITE_DESCRIPTION's provider
* list because link previews truncate around 200 characters. */
export const OG_DESCRIPTION =
Expand Down
8 changes: 5 additions & 3 deletions apps/web/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
CursorIcon,
GrokIcon,
HermesAgentIcon,
JunieIcon,
OmpIcon,
OpenAiIcon,
OpencodeIcon,
Expand Down Expand Up @@ -372,6 +373,7 @@ const PROVIDER_ICONS = [
GrokIcon,
OmpIcon,
HermesAgentIcon,
JunieIcon,
] as const;

/** How many provider logos stay visible on narrow screens before "+N more". */
Expand Down Expand Up @@ -1860,9 +1862,9 @@ function LandingPage() {

<Band title="The gang's all here" flip visual={<SpawnSidebar />}>
<p>
Claude Code, Codex, Cursor, Pi, OpenCode, Grok, omp, and Hermes all
live in bb. Give a task to whichever fits, and have one agent spawn
and manage another, each in its own thread.
Claude Code, Codex, Cursor, Pi, OpenCode, Grok, omp, Hermes, and Junie
all live in bb. Give a task to whichever fits, and have one agent
spawn and manage another, each in its own thread.
</p>
<p>
Each runs on your own subscription: the provider plan you already pay
Expand Down
18 changes: 10 additions & 8 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,9 @@ Known ACP agents appear when their CLI is installed on the host. bb exposes
`acp-opencode` when `opencode` is on PATH and can be launched as `opencode acp`,
`acp-omp` when `omp` (oh-my-pi) is on PATH, `acp-grok` when Grok Build's `grok`
CLI is on PATH and can be launched as `grok agent stdio`, and
`acp-hermes-agent` when Hermes' `hermes` CLI is on PATH. `acp-cursor` is always
listed.
`acp-hermes-agent` when Hermes' `hermes` CLI is on PATH. `acp-cursor` and
`acp-junie` are always listed, because bb can install those CLIs itself from
the provider's page; JetBrains' `junie` is launched as `junie --acp true`.

Add your own agent through the ACP providers plugin's `customAgents` setting,
which holds a JSON array. In the app it is the multi-line editor on the
Expand All @@ -342,11 +343,11 @@ bb plugin config provider-acp set customAgents '[
Each entry needs `id` (lowercase letters, digits and dashes), `displayName`,
and `command`. bb derives the provider id `acp-<id>`; it never changes once a
thread has used it. An id bb always lists (`cursor`) is reserved; an id bb
lists only where the agent is installed (`opencode`, `omp`, `grok`,
`hermes-agent`) is not, so an entry with that id REPLACES the shipped agent.
A replacing entry keeps the shipped agent's `nativeSkillRoots` unless it sets
its own, and bb still lists the roots that agent's host config names (its
config directory, compat trees, configured paths, plugins) either way.
lists only where the agent is installed (`opencode`, `omp`, `grok`, `hermes-agent`, `junie`) is not, so an entry with
that id REPLACES the shipped agent. A replacing entry keeps the shipped
agent's `nativeSkillRoots` unless it sets its own, and bb still lists the
roots that agent's host config names (its config directory, compat trees,
configured paths, plugins) either way.
Optional fields: `args`, `env`, `cwd`, `modelCli` (CLI model listing and
selection), `reasoningCli` (launch-time reasoning flags), `nativeReasoning`
(ACP `session/set_config_option` reasoning), `nativeSkillRoots` (native skills
Expand All @@ -356,7 +357,7 @@ an agent that nests skills or reads them from every ancestor directory),
`permissionCli` (permission-mode launch flags), `supportsManualCompaction`
(only if the agent accepts an explicit compaction request — bb hides
`/compact` otherwise), and `dialect` (the vendor side channels bb reads for
the agent: `cursor`, `opencode`, `omp`, or `grok`).
the agent: `cursor`, `opencode`, `omp`, `grok`, or `junie`).

The change applies immediately: the plugin re-registers its providers when the
setting changes, with no restart and no `config refresh`.
Expand Down Expand Up @@ -475,6 +476,7 @@ table lists what the shipped plugins declare and resolve.
| omp | The active `~/.omp/.../agent` roots and supported Pi, Agents, Claude, Codex, and OpenCode roots | `.omp/skills` and the supported compatibility roots from the repository root to the current directory |
| Grok Build | `$GROK_HOME/skills` or `~/.grok/skills`, plus `~/.agents/skills`, `~/.claude/skills`, `~/.cursor/skills` | The same four roots from the repository root to the current directory |
| Hermes Agent | `$HERMES_HOME/skills` or `~/.hermes/skills` | None |
| Junie | `~/.junie/skills`, `~/.agents/skills` | `.junie/skills` and `.agents/skills` from the repository root to the current directory |

OpenCode also uses `$OPENCODE_CONFIG_DIR/skills` when that variable exists.
Pi and omp use `$PI_CODING_AGENT_DIR` when that variable exists. omp also uses
Expand Down
7 changes: 4 additions & 3 deletions packages/bb-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,12 @@ bb uses whichever providers you have configured. Common providers:
| `opencode` | Install [opencode](https://opencode.ai/) and authenticate per its docs. |
| `grok` | Install [Grok Build](https://docs.x.ai/build/overview) and authenticate with `grok login` or `XAI_API_KEY`. |
| `hermes-agent` | Install [Hermes Agent](https://hermes-agent.nousresearch.com/docs/getting-started/installation), configure credentials with `hermes model`, then verify ACP with `hermes acp --check`. |
| `junie` | Install [Junie CLI](https://junie.jetbrains.com/docs/junie-cli.html) (`curl -fsSL https://junie.jetbrains.com/install.sh \| bash`; BB can run the install from Settings), then run `junie` once to sign in on the welcome screen. |

BB indexes the documented native skill roots for Codex, Claude Code, Pi,
Cursor, OpenCode, omp, Grok Build, and Hermes Agent. It includes user roots,
project roots, and compatibility roots such as `.agents/skills`. These skills
appear in the selected provider's `/` command menu. The Skills page and
Cursor, OpenCode, omp, Grok Build, Hermes Agent, and Junie. It includes user
roots, project roots, and compatibility roots such as `.agents/skills`. These
skills appear in the selected provider's `/` command menu. The Skills page and
`bb skill list` show native skills for Claude Code, Codex, and Cursor. BB also
reads configured Pi, omp, Grok, and Hermes skill directories, plus enabled
provider plugin skills.
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-build/src/svg-asset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const FIRST_PARTY_BRANDING_SVGS = [
"plugins/provider-acp/icons/cursor.svg",
"plugins/provider-acp/icons/grok.svg",
"plugins/provider-acp/icons/hermes-agent.svg",
"plugins/provider-acp/icons/junie.svg",
"plugins/provider-acp/icons/omp.svg",
"plugins/provider-acp/icons/opencode.svg",
"plugins/provider-claude-code/icons/claude-code.svg",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { describe, expect, it } from "vitest";
import {
CURSOR_ACP_MAINTENANCE,
getAcpProviderUsage,
JUNIE_ACP_MAINTENANCE,
__testing,
} from "./provider-maintenance.js";

Expand Down Expand Up @@ -99,4 +101,42 @@ describe("ACP provider maintenance", () => {
message: "opencode install is not available on this host.",
});
});

// Junie's dialect carries an installer and nothing else: bb installs the
// CLI with JetBrains' own script, while sign-in and quota stay inside the
// agent's session. An install-only surface must still produce a runnable
// command, and must not claim a usage surface it cannot answer.
it("installs Junie with JetBrains' installer and reports no usage", async () => {
const run = __testing.buildProviderInstallationRun(
{
...cursorMissingInstallationStatus(),
executableName: "junie",
installAction: {
kind: "install" as const,
label: "Install" as const,
command: "install Junie",
},
},
{
maintenance: JUNIE_ACP_MAINTENANCE,
command: "junie",
action: "install",
},
);
expect(run).toMatchObject({
available: true,
command: { command: "sh" },
verification: { kind: "installed" },
});
expect(run.available ? run.command.displayCommand : "").toContain(
"https://junie.jetbrains.com/install.sh",
);

expect(
await getAcpProviderUsage({
maintenance: JUNIE_ACP_MAINTENANCE,
command: "junie",
}),
).toEqual({ supported: false });
});
});
Loading