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 .air.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ tmp_dir = ".air"
[build]
cmd = "go build -o ./.air/antares ./cmd/antares"
bin = "./.air/antares"
full_bin = "./.air/antares serve --foreground"
full_bin = "./.air/antares --foreground"
include_ext = ["go", "yaml", "yml", "md"]
exclude_dir = [".air", "web", "dist", ".git", "node_modules", "internal/server/dist"]
exclude_regex = ["_test\\.go"]
Expand Down
2 changes: 1 addition & 1 deletion HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ below is done and green unless called out under **Next Steps**.
- **Embedded bundle:** `make build-web` builds `web/dist` and copies it to
`internal/server/dist` (which is gitignored except `.gitkeep` — keep that file;
`make build-web` deletes it, so `touch internal/server/dist/.gitkeep` after).
- **Run the binary:** on macOS use `antares serve --foreground`. Cross-compiled
- **Run the binary:** on macOS use `antares --foreground`. Cross-compiled
`CGO_ENABLED=0` darwin binaries get an invalid ad-hoc signature and are
SIGKILLed (exit 137) on other machines — fix with `codesign --force --sign -`.
- Tests: `GOTOOLCHAIN=go1.26.3 go test ./...` → 32 packages, all green.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,10 @@ Production build:

```bash
make build # → bin/antares, dashboard embedded
./bin/antares serve # starts in the background
./bin/antares # starts in the background
```

Use `antares serve --foreground` when running under systemd, Docker, or while
Use `antares --foreground` when running under systemd, Docker, or while
debugging and you want the server attached to the current terminal. Daemon logs
are written to `~/.antares/logs/daemon.log`.

Expand Down Expand Up @@ -344,8 +344,8 @@ gate access behind an allow list or a pairing approval flow.
Their tools are namespaced `mcp__<server>__<tool>` and made available to the
model automatically; a server that fails to start is reported, never fatal.

**Two interfaces.** A full-screen terminal UI (`antares`) and a web dashboard
(`antares serve`) over the same agent, sessions, and memory. The TUI has a
**Two interfaces.** A web dashboard (`antares`) and a full-screen terminal UI
(`antares tui`) over the same agent, sessions, and memory. The TUI has a
multiline composer, slash-command completion, live tool output, history recall,
scrollback, and Ctrl+C interrupt. Run `/help` inside it for the full list.

Expand Down
2 changes: 1 addition & 1 deletion cmd/antares/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func acquireDaemonStartLock(timeout time.Duration) (func(), error) {
continue
}
if time.Now().After(deadline) {
return nil, errors.New("another antares serve command is still starting the daemon")
return nil, errors.New("another antares command is still starting the daemon")
}
time.Sleep(50 * time.Millisecond)
}
Expand Down
1 change: 1 addition & 0 deletions cmd/antares/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func run() error {

switch command {
case "serve":
// `serve` stays as a script alias; the documented command is bare `antares`.
return cmdServe(args)
case "stop":
return cmdStop(args)
Expand Down
4 changes: 2 additions & 2 deletions cmd/smokefixture/main.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// smokefixture seeds an isolated ANTARES_HOME so `bin/antares serve
// --foreground` can boot for smoke testing without touching the developer's
// smokefixture seeds an isolated ANTARES_HOME so `bin/antares --foreground`
// can boot for smoke testing without touching the developer's
// real ~/.antares, without dialling any provider, and with the exact session
// row the dashboard route-walk expects at /c/<id>.
//
Expand Down
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# HTTP API

`antares serve` exposes a JSON API on `:8787` and serves the dashboard from the
`antares` exposes a JSON API on `:8787` and serves the dashboard from the
same port. Everything the dashboard does is available here.

## Authentication
Expand Down
2 changes: 1 addition & 1 deletion docs/backups.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ antares backup
# on the new one
export ANTARES_HOME=/var/lib/antares
antares backup restore /path/to/antares-….tar.gz
antares serve
antares
```

Sessions, memory, skills, schedules, and settings all come across. Check the
Expand Down
2 changes: 1 addition & 1 deletion docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ ENV ANTARES_HOME=/data \
TOOLS_BROWSER_EXECUTABLE=/usr/bin/chromium
VOLUME /data
EXPOSE 8787
CMD ["antares", "serve"]
CMD ["antares", "--foreground"]
```

Chromium is only needed for the browser tool. Leave it out and everything else
Expand Down
19 changes: 9 additions & 10 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,20 @@ both. This only sets the default; either is available at any time.
./bin/antares
```

That is the terminal interface. Type a message and press Enter. `Alt+Enter`
makes a new line, `Ctrl+C` interrupts a turn and quits on a second press.
That starts the API and dashboard in the background. Open
<http://localhost:8787>. The chat there is the same agent over the same
sessions and the same memory as the terminal UI.

Type `/` for the command palette — `/help` lists everything, `/status` shows what
is configured, `/model` changes the model without leaving the conversation.

For the dashboard instead:
For the terminal interface:

```bash
./bin/antares serve
./bin/antares tui
```

Then open <http://localhost:8787>. The chat there is the same agent over the
same sessions and the same memory; you can start a conversation in one and
continue it in the other.
Type a message and press Enter. `Alt+Enter` makes a new line, `Ctrl+C`
interrupts a turn and quits on a second press. Type `/` for the command
palette — `/help` lists everything, `/status` shows what is configured,
`/model` changes the model without leaving the conversation.

## Reaching it from another machine

Expand Down
2 changes: 1 addition & 1 deletion docs/verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ OPENAI_API_KEY=sk-… go test ./internal/llm -run TestLiveSpeakRoundTrip -v
## Chat gateways

Gateways need a running bot and, for the webhook ones, a reachable URL. Verify
each by configuring it, starting `antares serve`, and sending the bot a message —
each by configuring it, starting `antares`, and sending the bot a message —
it should reply.

| Gateway | What to provide | How it connects |
Expand Down
10 changes: 5 additions & 5 deletions scripts/smoke-run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
// 1. Allocate a fresh temp ANTARES_HOME + workspace so nothing under the
// developer's real ~/.antares is read or written.
// 2. Bind three loopback ports:
// - antares serve (dashboard + API)
// - antares (dashboard + API)
// - openai-compatible provider stub (deterministic /v1/models catalogue
// plus an empty non-billing /v1/chat/completions in case something
// the walk touches actually issues a request)
// 3. Run bin/smokefixture (cmd/smokefixture) to hash the synthetic
// password, write config.yaml, and seed the SMOKE_FIXTURE_SESSION_ID
// row through the real store.Open (schema stays honest).
// 4. Launch bin/antares serve --foreground with a scrubbed env — only
// 4. Launch bin/antares --foreground with a scrubbed env — only
// PATH / HOME / TMPDIR / SHELL / LANG / the ANTARES_* pointers survive;
// inherited provider keys (OPENAI_API_KEY, ANTHROPIC_API_KEY, …) never
// reach the child, so a stale key on the developer's box cannot silently
Expand Down Expand Up @@ -237,15 +237,15 @@ async function main() {
'--session-id', 'smoke-session',
])

const server = spawn(ANTARES_BIN, ['serve', '--foreground'], {
const server = spawn(ANTARES_BIN, ['--foreground'], {
env: baseEnv,
cwd: REPO,
stdio: ['ignore', 'inherit', 'inherit'],
})
onExit(terminate(server, 'antares serve'))
onExit(terminate(server, 'antares'))
server.on('exit', (code, signal) => {
if (!cleaning) {
console.error(`smoke-run: antares serve exited early code=${code} signal=${signal}`)
console.error(`smoke-run: antares exited early code=${code} signal=${signal}`)
}
})

Expand Down
9 changes: 9 additions & 0 deletions web/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 26 additions & 2 deletions web/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" class="dark">
<html lang="en" class="dark" data-palette="facebook">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
Expand All @@ -9,7 +9,31 @@
<link rel="apple-touch-icon" sizes="180x180" href="/antares-180.png" />
<link rel="manifest" href="/manifest.webmanifest" />

<meta name="theme-color" content="#0f0a0b" />
<meta name="theme-color" content="#1c1c1c" />
<script>
(function () {
try {
var theme = JSON.parse(localStorage.getItem('antares.theme') || '"dark"')
if (theme !== 'light' && theme !== 'dark') theme = 'dark'
var palette = JSON.parse(localStorage.getItem('antares.palette') || '"facebook"')
var palettes = ['claude', 'facebook', 'pinterest', 'supabase']
var legacy = { zinc: 'facebook', violet: 'facebook', ocean: 'supabase', forest: 'pinterest' }
if (palettes.indexOf(palette) === -1) palette = legacy[palette] || 'facebook'
var root = document.documentElement
root.classList.toggle('dark', theme === 'dark')
root.setAttribute('data-palette', palette)
root.style.colorScheme = theme
var colors = {
claude: { light: '#ffffff', dark: '#1c1c1c' },
facebook: { light: '#ffffff', dark: '#1c1c1c' },
pinterest: { light: '#ffffff', dark: '#1c1c1c' },
supabase: { light: '#ffffff', dark: '#1c1c1c' },
}
var meta = document.querySelector('meta[name="theme-color"]')
if (meta) meta.setAttribute('content', colors[palette][theme])
} catch (e) {}
})()
</script>
<meta name="description" content="Antares — a self-hosted AI agent." />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
Expand Down
3 changes: 3 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
"typecheck": "tsc -b --noEmit"
},
"dependencies": {
"@fontsource-variable/cormorant-garamond": "^5.3.0",
"@fontsource-variable/ibm-plex-sans": "^5.3.0",
"@fontsource-variable/inter": "^5.3.0",
"@fontsource-variable/montserrat": "^5.3.0",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
Expand Down
87 changes: 45 additions & 42 deletions web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LoginGate } from '@/components/layout/LoginGate'
import { ROUTES } from '@/lib/routes'
import { allRedirects } from '@/lib/routeManifest'
import { I18nProvider } from '@/lib/i18n'
import { ThemeProvider } from '@/lib/theme-provider'

// Onboarding renders standalone: there is no sidebar to wander off into before
// the agent can answer anything.
Expand Down Expand Up @@ -41,59 +42,61 @@ const REDIRECTS = allRedirects()

export function App() {
return (
<I18nProvider>
<BrowserRouter>
<Routes>
<Route
path="/setup"
element={
<Suspense fallback={null}>
<SetupPage />
</Suspense>
}
/>
<Route
path="/login"
element={
<Suspense fallback={null}>
<LoginPage />
</Suspense>
}
/>
{/* Dev-only prototype routes, standalone (no chrome, no gates). */}
{sampleRoutes.map(({ name, Comp }) => (
<ThemeProvider>
<I18nProvider>
<BrowserRouter>
<Routes>
<Route
key={name}
path={`/sample/${name}`}
path="/setup"
element={
<Suspense fallback={null}>
<Comp />
<SetupPage />
</Suspense>
}
/>
))}
<Route
path="/login"
element={
<Suspense fallback={null}>
<LoginPage />
</Suspense>
}
/>
{/* Dev-only prototype routes, standalone (no chrome, no gates). */}
{sampleRoutes.map(({ name, Comp }) => (
<Route
key={name}
path={`/sample/${name}`}
element={
<Suspense fallback={null}>
<Comp />
</Suspense>
}
/>
))}

{/* LoginGate holds everything behind the dashboard password (when
{/* LoginGate holds everything behind the dashboard password (when
set); SetupGate redirects a fresh install to onboarding; AppShell
owns the page container, header, and Suspense boundary, so every
route inside renders with identical chrome. */}
<Route element={<LoginGate />}>
<Route element={<SetupGate />}>
<Route element={<AppShell />}>
{ROUTES.flatMap(({ path, aliases, component: Component }) =>
[path, ...(aliases ?? [])].map((p) => (
<Route key={p} path={p} element={<Component />} />
)),
)}
{REDIRECTS.map(({ from, to }) => (
<Route key={from} path={from} element={<LegacyRedirect to={to} />} />
))}
<Route path="*" element={<Navigate to="/" replace />} />
<Route element={<LoginGate />}>
<Route element={<SetupGate />}>
<Route element={<AppShell />}>
{ROUTES.flatMap(({ path, aliases, component: Component }) =>
[path, ...(aliases ?? [])].map((p) => (
<Route key={p} path={p} element={<Component />} />
)),
)}
{REDIRECTS.map(({ from, to }) => (
<Route key={from} path={from} element={<LegacyRedirect to={to} />} />
))}
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Route>
</Route>
</Route>
</Routes>
</BrowserRouter>
</I18nProvider>
</Routes>
</BrowserRouter>
</I18nProvider>
</ThemeProvider>
)
}
Loading
Loading