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
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- uses: oven-sh/setup-bun@v2

- run: bun install
- run: npm test
- run: bun test
- run: bun run build
- run: npm publish --provenance --access public
env:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ coverage/
*.temp
context.md
.omc/

# Local agent state
.swarm/
memory/
206 changes: 0 additions & 206 deletions ARCHITECTURE.md

This file was deleted.

17 changes: 0 additions & 17 deletions DEFERRED.md

This file was deleted.

26 changes: 23 additions & 3 deletions bin/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node

import { startProxyServer } from "../src/proxy/server"
import { env } from "../src/env"
import { exec as execCallback } from "child_process"
import { promisify } from "util"

Expand All @@ -14,9 +15,28 @@ process.on("unhandledRejection", (reason) => {
console.error(`[PROXY] Unhandled rejection (recovered): ${reason instanceof Error ? reason.message : reason}`)
})

const port = parseInt(process.env.CLAUDE_PROXY_PORT || "3456", 10)
const host = process.env.CLAUDE_PROXY_HOST || "127.0.0.1"
const idleTimeoutSeconds = parseInt(process.env.CLAUDE_PROXY_IDLE_TIMEOUT_SECONDS || "120", 10)
// Port/host resolution priority:
// 1. CLAUDE_PROXY_PORT / CLAUDE_PROXY_HOST (explicit, highest priority)
// 2. ANTHROPIC_BASE_URL (parsed from URL)
// 3. Defaults (127.0.0.1:3456)
let host = "127.0.0.1"
let port = 3456
const baseUrl = process.env.ANTHROPIC_BASE_URL
if (baseUrl) {
try {
const url = new URL(baseUrl)
host = url.hostname
if (url.port) port = parseInt(url.port, 10)
} catch {
console.error(`\x1b[33m⚠ Invalid ANTHROPIC_BASE_URL: ${baseUrl}, using defaults\x1b[0m`)
}
}
// Explicit env vars override ANTHROPIC_BASE_URL
const envPort = env("PORT")
const envHost = env("HOST")
if (envPort) port = parseInt(envPort, 10)
if (envHost) host = envHost
const idleTimeoutSeconds = parseInt(env("IDLE_TIMEOUT_SECONDS") || "120", 10)

export async function runCli(
start = startProxyServer,
Expand Down
7 changes: 0 additions & 7 deletions memory/user_code.md

This file was deleted.

96 changes: 96 additions & 0 deletions src/__tests__/admin-routes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { describe, it, expect, beforeEach, afterEach } from "bun:test"
import { Hono } from "hono"
import { createAdminRoutes } from "../keys/routes"
import { initAdmin, generateJwt } from "../keys/auth"
import { mkdtempSync, rmSync } from "fs"
import { join } from "path"
import { tmpdir } from "os"

describe("Admin routes auth", () => {
const MASTER_KEY = "test-master-key-123"
let app: Hono
let jwt: string
let tmpDir: string

beforeEach(() => {
tmpDir = mkdtempSync(join(tmpdir(), "admin-routes-test-"))
process.env.MERIDIAN_ADMIN_FILE = join(tmpDir, "admin.json")
process.env.MERIDIAN_MASTER_KEY = MASTER_KEY
initAdmin()
jwt = generateJwt()
app = new Hono()
app.route("/admin", createAdminRoutes())
})

afterEach(() => {
delete process.env.MERIDIAN_ADMIN_FILE
delete process.env.MERIDIAN_MASTER_KEY
rmSync(tmpDir, { recursive: true, force: true })
})

it("serves dashboard HTML without auth", async () => {
const res = await app.fetch(new Request("http://localhost/admin"))
expect(res.status).toBe(200)
const text = await res.text()
expect(text).toContain("Admin Dashboard")
})

it("rejects API calls without JWT", async () => {
const res = await app.fetch(new Request("http://localhost/admin/keys"))
expect(res.status).toBe(401)
})

it("accepts API calls with valid JWT", async () => {
const res = await app.fetch(new Request("http://localhost/admin/keys", {
headers: { "Authorization": `Bearer ${jwt}` }
}))
expect(res.status).toBe(200)
})

it("rejects API calls with invalid JWT", async () => {
const res = await app.fetch(new Request("http://localhost/admin/keys", {
headers: { "Authorization": "Bearer invalid.jwt.token" }
}))
expect(res.status).toBe(401)
})

it("login returns JWT with valid master key", async () => {
const res = await app.fetch(new Request("http://localhost/admin/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: MASTER_KEY }),
}))
expect(res.status).toBe(200)
const data = await res.json() as any
expect(data.token).toBeTruthy()
expect(data.token.split(".")).toHaveLength(3)
})

it("login rejects wrong master key", async () => {
const res = await app.fetch(new Request("http://localhost/admin/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: "wrong-key" }),
}))
expect(res.status).toBe(401)
})

it("GET /admin/models returns model catalog", async () => {
const res = await app.fetch(new Request("http://localhost/admin/models", {
headers: { "Authorization": `Bearer ${jwt}` }
}))
expect(res.status).toBe(200)
const models = await res.json() as any
expect(Array.isArray(models)).toBe(true)
expect(models.length).toBeGreaterThan(0)
})

it("GET /admin/settings returns settings", async () => {
const res = await app.fetch(new Request("http://localhost/admin/settings", {
headers: { "Authorization": `Bearer ${jwt}` }
}))
expect(res.status).toBe(200)
const settings = await res.json() as any
expect(settings.maxConcurrent).toBeGreaterThan(0)
})
})
Loading