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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Botloft

Workspace desktop Windows-first, com código público sob a FSL (vira Apache 2.0 após dois anos), para rodar bots de agentes de código sempre ligados que colaboram entre si: Claude Code, Antigravity (experimental) e, em seguida, Codex. A seção 30 da spec e o ADR 0003 dizem como cada agente entra. Daemon Rust (`botloftd`) + app Tauri v2 + React.
Workspace desktop Windows-first, com código público sob a FSL (vira Apache 2.0 após dois anos), para rodar bots de agentes de código sempre ligados que colaboram entre si: Claude Code, Antigravity e Codex (os dois últimos experimentais). A seção 30 da spec e o ADR 0003 dizem como cada agente entra. Daemon Rust (`botloftd`) + app Tauri v2 + React.

## Fonte de verdade

Expand Down
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
</picture>

Botloft keeps a crew of bots running on your computer, each on the AI coding agent you choose:
[Claude Code](https://code.claude.com), or [Antigravity](https://antigravity.google) as an experimental
option, with Codex next. Each bot has its own folder, memory and conversation. They pass work to each other, keep going after you close the
[Claude Code](https://code.claude.com), or, as experimental options,
[Antigravity](https://antigravity.google) and [Codex](https://developers.openai.com/codex). Each bot has its own folder, memory and conversation. They pass work to each other, keep going after you close the
window and pick up where they left off after a restart. You talk to each one like in a chat app, and
you decide what they may do.

Expand Down Expand Up @@ -88,15 +88,18 @@ itself.
| Usage meter and "Compact" | yes | an estimate of the conversation size; no compaction |
| Isolation between crews | yes | folders are fenced for its file tools, not for commands you allow |

Claude Code is the full experience. Antigravity is off until you add `experimental_agents = ["agy"]`
to Botloft's `config.toml`; then a new bot can be made on it, and Settings has "Agent for new
bots". Codex is the next agent. See [spec section 30](docs/spec.md) for what was checked.
Claude Code is the full experience. Antigravity and Codex are off until you list them in Botloft's
`config.toml` (`experimental_agents = ["agy"]`, `["codex"]` or both); then a new bot can be made on
them, and Settings has "Agent for new bots". A Codex bot reads files, uses the crew's tools and
answers, with the exact size of its conversation; it cannot change files or run commands yet, and
asking you before it acts is the next step. See [spec section 30](docs/spec.md) for what was
checked.

## Get started

You need at least one agent installed and signed in once, with your own account:
[Claude Code](https://code.claude.com/docs/en/setup), or Antigravity (experimental, see
[Agents](#agents)). Bots run on your own plan with that agent.
[Claude Code](https://code.claude.com/docs/en/setup), Antigravity or Codex
(experimental, see [Agents](#agents)). Bots run on your own plan with that agent.

1. Download Botloft for your computer from the
[latest release](https://github.com/httpsphl/botloft/releases/latest):
Expand Down Expand Up @@ -137,7 +140,7 @@ flowchart LR
scheduled task of your Windows user, a systemd user service on Linux or a launch agent on macOS:
it starts when you sign in and comes back soon if it dies.
- **Your agent is the runtime.** Each bot is a real session of the agent you chose (`claude -p` for
Claude Code, `agy -p` for Antigravity) with its own workspace, memory and conversation. There is
Claude Code, `agy -p` for Antigravity, `codex app-server` for Codex) with its own workspace, memory and conversation. There is
no custom agent SDK and no API key to manage.
- **One conversation per bot.** Your messages, messages from other bots and notices from the daemon
reach the bot in order. What it does comes back as a chat: replies, tool use and requests you
Expand Down Expand Up @@ -230,8 +233,8 @@ Windows came first and has had the most use; if something does not work elsewher
<details>
<summary><b>Is Botloft made by Anthropic?</b></summary>

No. Botloft is an independent project. It uses Claude Code and Antigravity as installed on your
computer and is not affiliated with or endorsed by Anthropic or Google.
No. Botloft is an independent project. It uses Claude Code, Antigravity and Codex as installed on your
computer and is not affiliated with or endorsed by Anthropic, Google or OpenAI.
</details>

## Status
Expand All @@ -247,7 +250,7 @@ and a code-signed installer. `main` can be ahead of the latest release.
Requirements: Rust stable (the MSVC toolchain on Windows), Node.js 24 and pnpm. On Linux, the app
also needs the desktop libraries Tauri uses (on Ubuntu: `libwebkit2gtk-4.1-dev`,
`libayatana-appindicator3-dev`, `librsvg2-dev`, `libxdo-dev`). Running real bots also needs an agent
installed and signed in: Claude Code (the native `claude` executable) or Antigravity (`agy`).
installed and signed in: Claude Code (the native `claude` executable), Antigravity (`agy`) or Codex.

```powershell
# Daemon and libraries
Expand Down
6 changes: 4 additions & 2 deletions app/src/features/bots/BotDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,14 @@ export function BotDialog(props: Props) {
/>
{agent !== "claude" && (
<p role="note" className="rounded-lg bg-sunken p-2.5 text-ink-soft text-xs">
{t.bots.dialog.agent.experimental}
{agent === "codex"
? t.bots.dialog.agent.experimentalCodex
: t.bots.dialog.agent.experimental}
</p>
)}
</div>
)}
{editing && editing.agent !== "claude" && (
{editing && editing.agent === "agy" && (
<>
<Toggle
label={t.bots.dialog.fullAccess.title}
Expand Down
16 changes: 16 additions & 0 deletions app/src/features/bots/agentPicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,19 @@ describe("choosing the agent of a new bot", () => {
expect(dialog.queryByLabelText("Model")).toBeNull();
});
});

describe("Codex in the new-bot dialog", () => {
test("says what it cannot do yet", async () => {
const fake = new FakeBotloft();
fake.system = { ...fake.system, enabledAgents: ["claude", "codex"] };
fake.addCrew("Ops");
renderApp(fake);
await crewOpened("Ops");
const [button] = await screen.findAllByRole("button", { name: "New bot" });
fireEvent.click(button as HTMLElement);
const dialog = within(await screen.findByRole("dialog", { name: "New bot" }));
fireEvent.change(dialog.getByLabelText("Agent"), { target: { value: "codex" } });
expect(dialog.getByRole("note").textContent).toMatch(/cannot change files or run commands yet/);
expect(dialog.queryByLabelText("Model")).toBeNull();
});
});
2 changes: 1 addition & 1 deletion app/src/features/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export const ChatComposer = memo(function ChatComposer({
<ModelPicker bot={bot} onLater={setLater} />
</>
) : (
<AgentModelPicker bot={bot} onLater={setLater} />
bot.agent === "agy" && <AgentModelPicker bot={bot} onLater={setLater} />
)}
<button
type="submit"
Expand Down
2 changes: 2 additions & 0 deletions app/src/i18n/en/bots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ export const bots = {
},
experimental:
"Experimental. This bot cannot ask you before it acts. It can work with files in its own folders and use the crew's tools, but it cannot run commands, and it has no usage meter or model choice.",
experimentalCodex:
"Experimental. This bot can read files, use the crew's tools and answer, but it cannot change files or run commands yet. Asking you before it acts is coming next.",
},
color: "Color",
swatch: (color: string) => `Color ${color}`,
Expand Down
2 changes: 2 additions & 0 deletions app/src/i18n/es/bots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ export const bots: Messages["bots"] = {
},
experimental:
"Experimental. Este bot no puede preguntarte antes de actuar. Trabaja con archivos de sus propias carpetas y usa las herramientas del equipo, pero no ejecuta comandos, y no tiene medidor de uso ni elección de modelo.",
experimentalCodex:
"Experimental. Este bot lee archivos, usa las herramientas del equipo y responde, pero todavía no cambia archivos ni ejecuta comandos. Preguntarte antes de actuar viene a continuación.",
},
color: "Color",
swatch: (color: string) => `Color ${color}`,
Expand Down
2 changes: 2 additions & 0 deletions app/src/i18n/pt-BR/bots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export const bots: Messages["bots"] = {
},
experimental:
"Experimental. Este bot não consegue perguntar a você antes de agir. Ele trabalha com arquivos das próprias pastas e usa as ferramentas da equipe, mas não roda comandos, e não tem medidor de uso nem escolha de modelo.",
experimentalCodex:
"Experimental. Este bot lê arquivos, usa as ferramentas da equipe e responde, mas ainda não muda arquivos nem roda comandos. Perguntar a você antes de agir vem a seguir.",
},
color: "Cor",
swatch: (color: string) => `Cor ${color}`,
Expand Down
12 changes: 1 addition & 11 deletions crates/botloftd/src/agent/agy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,7 @@ impl Agent for AgyAgent {
cli.join("settings.json"),
pretty(&settings(files.fenced, files.allowed_commands)),
)?;
// The rules `prepare_bot` wrote for Claude Code are the bot's rules;
// `agy` reads `AGENTS.md` from its folder.
let rules = files
.workspace
.join(".claude")
.join("rules")
.join("botloft.md");
if let Ok(text) = std::fs::read_to_string(rules) {
std::fs::write(files.workspace.join("AGENTS.md"), text)?;
}
Ok(())
super::copy_rules_to_agents_md(files.workspace)
}

fn speaks_control(&self) -> bool {
Expand Down
Loading
Loading