diff --git a/CLAUDE.md b/CLAUDE.md
index 18e1c7d75e..6d680490e5 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -257,13 +257,28 @@ By default, the canonical location is `~/.local/src/agent-core`, but the project
### Binary Location
-**Installed binary:** `~/bin/agent-core` (also `$AGENT_CORE_BIN`)
+Two installation methods are supported:
+
+1. **Bun global install (recommended):** `~/.bun/bin/agent-core`
+ - Installed via `bun install -g agent-core`
+ - Wrapper script resolves the actual binary automatically
+
+2. **Manual install:** `~/bin/agent-core` (also `$AGENT_CORE_BIN`)
+ - Direct binary copy from build output
+ - Used by `reload.sh` script
**Run from anywhere:** The binary can be launched from any directory. Just `cd` to your project folder and run `agent-core`.
### Rebuilding
-**Recommended:** Use the reload script (handles kill, build, copy, restart):
+**For bun global install:**
+
+```bash
+# Build and the global install auto-updates
+cd packages/agent-core && bun run build
+```
+
+**For manual install (reload script):**
```bash
# Full reload - kill all, rebuild, restart daemon
diff --git a/packages/agent-core/src/cli/cmd/tui/context/sync.tsx b/packages/agent-core/src/cli/cmd/tui/context/sync.tsx
index 006f550179..df132c7392 100644
--- a/packages/agent-core/src/cli/cmd/tui/context/sync.tsx
+++ b/packages/agent-core/src/cli/cmd/tui/context/sync.tsx
@@ -74,6 +74,10 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
formatter: FormatterStatus[]
vcs: VcsInfo | undefined
path: Path
+ health: {
+ internet: "ok" | "fail" | "checking"
+ providers: { id: string; name: string; status: "ok" | "fail" | "skip" }[]
+ }
}>({
provider_next: {
all: [],
@@ -101,6 +105,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
formatter: [],
vcs: undefined,
path: { state: "", config: "", worktree: "", directory: "" },
+ health: { internet: "checking", providers: [] },
})
const sdk = useSDK()
@@ -389,6 +394,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
sdk.client.provider.auth().then((x) => setStore("provider_auth", reconcile(x.data ?? {}))),
sdk.client.vcs.get().then((x) => setStore("vcs", reconcile(x.data))),
sdk.client.path.get().then((x) => setStore("path", reconcile(x.data!))),
+ // Fetch health status (internet + providers)
+ fetch(`${sdk.url}/global/health/status`)
+ .then((res) => res.json())
+ .then((data: { internet: "ok" | "fail" | "checking"; providers: { id: string; name: string; status: "ok" | "fail" | "skip" }[] }) =>
+ setStore("health", reconcile(data)),
+ )
+ .catch(() => setStore("health", "internet", "fail")),
]).then(() => {
setStore("status", "complete")
})
diff --git a/packages/agent-core/src/cli/cmd/tui/routes/session/footer.tsx b/packages/agent-core/src/cli/cmd/tui/routes/session/footer.tsx
index 4883ea5220..51d8c76526 100644
--- a/packages/agent-core/src/cli/cmd/tui/routes/session/footer.tsx
+++ b/packages/agent-core/src/cli/cmd/tui/routes/session/footer.tsx
@@ -15,6 +15,8 @@ export function Footer() {
const mcp = createMemo(() => Object.values(sync.data.mcp).filter((x) => x.status === "connected").length)
const mcpError = createMemo(() => Object.values(sync.data.mcp).some((x) => x.status === "failed"))
const lsp = createMemo(() => Object.keys(sync.data.lsp))
+ const internet = createMemo(() => sync.data.health.internet)
+ const connectedProviders = createMemo(() => sync.data.health.providers.filter((p) => p.status === "ok").length)
const permissions = createMemo(() => {
if (route.data.type !== "session") return []
return sync.data.permission[route.data.sessionID] ?? []
@@ -71,6 +73,25 @@ export function Footer() {
{permissions().length > 1 ? "s" : ""}
+
+
+
+ ◉
+
+
+ ◉
+
+
+ ◉
+
+
+ {" "}NET
+
+ 0}>
+
+ ◈ {connectedProviders()} LLM
+
+
0 ? theme.success : theme.textMuted }}>• {lsp().length} LSP
diff --git a/packages/agent-core/src/server/route/global.ts b/packages/agent-core/src/server/route/global.ts
index cc30993864..b4a6de8819 100644
--- a/packages/agent-core/src/server/route/global.ts
+++ b/packages/agent-core/src/server/route/global.ts
@@ -4,6 +4,19 @@ import { z } from "zod"
import { streamSSE } from "hono/streaming"
import { GlobalBus } from "@/bus/global"
import { Instance } from "../../project/instance"
+import { Provider } from "@/provider/provider"
+
+// Health status schema for system monitoring
+const HealthStatus = z.object({
+ internet: z.enum(["ok", "fail", "checking"]),
+ providers: z.array(
+ z.object({
+ id: z.string(),
+ name: z.string(),
+ status: z.enum(["ok", "fail", "skip"]),
+ }),
+ ),
+})
export const GlobalRoute = new Hono()
.get(
@@ -27,6 +40,52 @@ export const GlobalRoute = new Hono()
return c.json(true)
},
)
+ .get(
+ "/health/status",
+ describeRoute({
+ summary: "System health status",
+ description: "Get internet connectivity and LLM provider status for system monitoring.",
+ operationId: "health.status",
+ responses: {
+ 200: {
+ description: "Health status",
+ content: {
+ "application/json": {
+ schema: resolver(HealthStatus),
+ },
+ },
+ },
+ },
+ }),
+ async (c) => {
+ // Check internet connectivity
+ let internet: "ok" | "fail" | "checking" = "checking"
+ try {
+ const controller = new AbortController()
+ const timeout = setTimeout(() => controller.abort(), 3000)
+ const response = await fetch("https://cloudflare.com/cdn-cgi/trace", {
+ method: "HEAD",
+ signal: controller.signal,
+ })
+ clearTimeout(timeout)
+ internet = response.ok || response.status < 500 ? "ok" : "fail"
+ } catch {
+ internet = "fail"
+ }
+
+ // Get configured providers and their connection status
+ // Provider.list() returns Record of loaded providers
+ const loadedProviders = await Provider.list()
+
+ const providers = Object.values(loadedProviders).map((p) => ({
+ id: p.id,
+ name: p.name,
+ status: "ok" as "ok" | "fail" | "skip",
+ }))
+
+ return c.json({ internet, providers })
+ },
+ )
.get(
"/event",
describeRoute({