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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ GITEA_ALLOWED_USERS=
# If not set, falls back to BOT_DISPLAY_NAME then config.botName
# GITEA_BOT_MENTION=archon

# Authentication (JWT)
# Set this to enable JWT-based authentication. Must be at least 32 characters.
# Without this, all API access bypasses auth (full admin access for any caller — dev/test only).
# Generate a secure value:
# openssl rand -base64 32
# JWT_SECRET=your-secret-key-min-32-chars-change-this

# Server
PORT=3000
# HOST=0.0.0.0 # Bind address (default: 0.0.0.0). Set to 127.0.0.1 to restrict to localhost only.
Expand Down
10 changes: 8 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
## Core Principles

**Single-Developer Tool**
- No multi-tenant complexity
- Designed for a single operator; multi-user authentication is opt-in via `JWT_SECRET`

**Platform Agnostic**
- Unified conversation interface across Slack/Telegram/GitHub/cli/web
Expand Down Expand Up @@ -249,6 +249,10 @@ bun run cli serve
bun run cli serve --port 4000
bun run cli serve --download-only # Download without starting

# Log in to an Archon server (stores credentials to ~/.archon/auth.json)
bun run cli login
bun run cli login --server-url http://my-server:3090

# Show version
bun run cli version
```
Expand Down Expand Up @@ -375,7 +379,7 @@ import type { DagNode, WorkflowDefinition } from '@/lib/api';

### Database Schema

**8 Tables (all prefixed with `remote_agent_`):**
**10 Tables (all prefixed with `remote_agent_`):**
1. **`codebases`** - Repository metadata and commands (JSONB)
2. **`conversations`** - Track platform conversations with titles and soft-delete support
3. **`sessions`** - Track AI SDK sessions with resume capability
Expand All @@ -384,6 +388,8 @@ import type { DagNode, WorkflowDefinition } from '@/lib/api';
6. **`workflow_events`** - Step-level workflow event log (step transitions, artifacts, errors)
7. **`messages`** - Conversation message history with tool call metadata (JSONB)
8. **`codebase_env_vars`** - Per-project env vars injected into Claude SDK subprocess env (managed via Web UI or `env:` in config)
9. **`users`** - User accounts for JWT authentication (username, password hash, role: admin|user)
10. **`project_members`** - Junction table: user-codebase access grants (roles: owner, member)

**Key Patterns:**
- Conversation ID format: Platform-specific (`thread_ts`, `chat_id`, `user/repo#123`)
Expand Down
25 changes: 14 additions & 11 deletions bun.lock

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

75 changes: 59 additions & 16 deletions migrations/000_combined.sql
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
-- Remote Coding Agent - Combined Schema
-- Version: Combined (final state after migrations 001-020)
-- Version: Combined (final state after migrations 001-022)
-- Description: Complete database schema (idempotent - safe to run multiple times)
--
-- 8 Tables:
-- 10 Tables:
-- 1. remote_agent_codebases
-- 1b. remote_agent_codebase_env_vars
-- 2. remote_agent_conversations
-- 3. remote_agent_sessions
-- 4. remote_agent_isolation_environments
-- 5. remote_agent_workflow_runs
-- 6. remote_agent_workflow_events
-- 7. remote_agent_messages
-- 2. remote_agent_codebase_env_vars
-- 3. remote_agent_users
-- 4. remote_agent_project_members
-- 5. remote_agent_conversations
-- 6. remote_agent_sessions
-- 7. remote_agent_isolation_environments
-- 8. remote_agent_workflow_runs
-- 9. remote_agent_workflow_events
-- 10. remote_agent_messages
--
-- Dropped tables (via migrations):
-- - remote_agent_command_templates (017)
Expand Down Expand Up @@ -40,7 +42,7 @@ COMMENT ON TABLE remote_agent_codebases IS
'Repository metadata: name, URL, working directory, AI assistant type, and command paths (JSONB)';

-- ============================================================================
-- Table 1b: Codebase Env Vars
-- Table 2: Codebase Env Vars
-- ============================================================================

CREATE TABLE IF NOT EXISTS remote_agent_codebase_env_vars (
Expand All @@ -60,7 +62,41 @@ COMMENT ON TABLE remote_agent_codebase_env_vars IS
'Per-project env vars merged into Options.env on Claude SDK calls. Managed via Web UI or config.';

-- ============================================================================
-- Table 2: Conversations
-- Table 3: Users (must precede conversations — FK dependency)
-- ============================================================================

CREATE TABLE IF NOT EXISTS remote_agent_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username VARCHAR(50) UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
display_name VARCHAR(100),
role VARCHAR(20) NOT NULL DEFAULT 'user',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

COMMENT ON TABLE remote_agent_users IS
'User accounts for authentication. First registered user gets admin role.';

-- ============================================================================
-- Table 4: Project Members (must precede conversations — referenced in future queries)
-- ============================================================================

CREATE TABLE IF NOT EXISTS remote_agent_project_members (
user_id UUID NOT NULL REFERENCES remote_agent_users(id) ON DELETE CASCADE,
codebase_id UUID NOT NULL REFERENCES remote_agent_codebases(id) ON DELETE CASCADE,
role VARCHAR(20) NOT NULL DEFAULT 'member',
PRIMARY KEY (user_id, codebase_id)
);

CREATE INDEX IF NOT EXISTS idx_project_members_user_id
ON remote_agent_project_members(user_id);

COMMENT ON TABLE remote_agent_project_members IS
'Junction table: which users have access to which codebases. Roles: owner, member.';

-- ============================================================================
-- Table 5: Conversations (now safe — remote_agent_users exists)
-- ============================================================================

CREATE TABLE IF NOT EXISTS remote_agent_conversations (
Expand All @@ -74,6 +110,7 @@ CREATE TABLE IF NOT EXISTS remote_agent_conversations (
title VARCHAR(255),
deleted_at TIMESTAMP WITH TIME ZONE,
hidden BOOLEAN DEFAULT FALSE,
user_id UUID REFERENCES remote_agent_users(id) ON DELETE SET NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
last_activity_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
Expand All @@ -86,12 +123,14 @@ CREATE INDEX IF NOT EXISTS idx_conversations_hidden
ON remote_agent_conversations(hidden);
CREATE INDEX IF NOT EXISTS idx_conversations_codebase
ON remote_agent_conversations(codebase_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_conversations_user_id
ON remote_agent_conversations(user_id) WHERE deleted_at IS NULL;

COMMENT ON COLUMN remote_agent_conversations.isolation_env_id IS
'UUID reference to isolation_environments table (the only isolation reference)';

-- ============================================================================
-- Table 3: Sessions
-- Table 6: Sessions
-- ============================================================================

CREATE TABLE IF NOT EXISTS remote_agent_sessions (
Expand Down Expand Up @@ -126,7 +165,7 @@ COMMENT ON COLUMN remote_agent_sessions.ended_reason IS
'Why this session was deactivated: reset-requested, cwd-changed, conversation-closed, etc.';

-- ============================================================================
-- Table 4: Isolation Environments
-- Table 7: Isolation Environments
-- ============================================================================

CREATE TABLE IF NOT EXISTS remote_agent_isolation_environments (
Expand Down Expand Up @@ -180,7 +219,7 @@ COMMENT ON COLUMN remote_agent_isolation_environments.workflow_id IS
'Identifier for the work (issue number, PR number, thread hash, etc.)';

-- ============================================================================
-- Table 5: Workflow Runs
-- Table 8: Workflow Runs
-- ============================================================================

CREATE TABLE IF NOT EXISTS remote_agent_workflow_runs (
Expand Down Expand Up @@ -215,7 +254,7 @@ COMMENT ON TABLE remote_agent_workflow_runs IS
'Tracks workflow execution state for resumption and observability';

-- ============================================================================
-- Table 6: Workflow Events
-- Table 9: Workflow Events
-- ============================================================================

CREATE TABLE IF NOT EXISTS remote_agent_workflow_events (
Expand All @@ -237,7 +276,7 @@ COMMENT ON TABLE remote_agent_workflow_events IS
'Lean UI-relevant workflow events for observability (step transitions, artifacts, errors)';

-- ============================================================================
-- Table 7: Messages
-- Table 10: Messages
-- ============================================================================

CREATE TABLE IF NOT EXISTS remote_agent_messages (
Expand Down Expand Up @@ -312,3 +351,7 @@ ALTER TABLE remote_agent_sessions
-- From migration 021: allow_env_keys on codebases
ALTER TABLE remote_agent_codebases
ADD COLUMN IF NOT EXISTS allow_env_keys BOOLEAN NOT NULL DEFAULT FALSE;

-- From migration 022: multi-user auth (user_id on conversations)
ALTER TABLE remote_agent_conversations
ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES remote_agent_users(id) ON DELETE SET NULL;
32 changes: 32 additions & 0 deletions migrations/022_multi_user_auth.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-- Multi-user authentication: users, project memberships, conversation ownership
-- Tables: remote_agent_users, remote_agent_project_members
-- Column: remote_agent_conversations.user_id

-- Users table
CREATE TABLE IF NOT EXISTS remote_agent_users (
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
display_name TEXT,
role TEXT NOT NULL DEFAULT 'user',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);

-- Project members junction
CREATE TABLE IF NOT EXISTS remote_agent_project_members (
user_id TEXT NOT NULL REFERENCES remote_agent_users(id) ON DELETE CASCADE,
codebase_id TEXT NOT NULL REFERENCES remote_agent_codebases(id) ON DELETE CASCADE,
role TEXT NOT NULL DEFAULT 'member',
PRIMARY KEY (user_id, codebase_id)
);

-- Add user_id to conversations (nullable — existing rows keep NULL)
ALTER TABLE remote_agent_conversations
ADD COLUMN IF NOT EXISTS user_id TEXT REFERENCES remote_agent_users(id) ON DELETE SET NULL;

CREATE INDEX IF NOT EXISTS idx_conversations_user_id
ON remote_agent_conversations(user_id) WHERE deleted_at IS NULL;

CREATE INDEX IF NOT EXISTS idx_project_members_user_id
ON remote_agent_project_members(user_id);
10 changes: 9 additions & 1 deletion packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import { chatCommand } from './commands/chat';
import { setupCommand } from './commands/setup';
import { validateWorkflowsCommand, validateCommandsCommand } from './commands/validate';
import { serveCommand } from './commands/serve';
import { loginCommand } from './commands/login';
import { closeDatabase } from '@archon/core';
import {
setLogLevel,
Expand Down Expand Up @@ -104,6 +105,7 @@ Commands:
isolation cleanup --merged Remove environments with branches merged into main
continue <branch> [msg] Continue work on an existing worktree with prior context
complete <branch> [...] Complete branch lifecycle (remove worktree + branches)
login Log in to an Archon server
serve Start the web UI server (downloads web UI on first run)
validate workflows [name] Validate workflow definitions and their references
validate commands [name] Validate command files
Expand Down Expand Up @@ -207,6 +209,7 @@ async function main(): Promise<number> {
'allow-env-keys': { type: 'boolean' },
port: { type: 'string' },
'download-only': { type: 'boolean' },
'server-url': { type: 'string' },
},
allowPositionals: true,
strict: false, // Allow unknown flags to pass through
Expand Down Expand Up @@ -241,7 +244,7 @@ async function main(): Promise<number> {
const subcommand = positionals[1];

// Commands that don't require git repo validation
const noGitCommands = ['version', 'help', 'setup', 'chat', 'continue', 'serve'];
const noGitCommands = ['version', 'help', 'setup', 'chat', 'continue', 'serve', 'login'];
const requiresGitRepo = !noGitCommands.includes(command ?? '');

try {
Expand Down Expand Up @@ -548,6 +551,11 @@ async function main(): Promise<number> {
break;
}

case 'login': {
const serverUrl = values['server-url'] as string | undefined;
return await loginCommand({ serverUrl });
}

case 'serve': {
const servePort = values.port !== undefined ? Number(values.port) : undefined;
const downloadOnly = Boolean(values['download-only']);
Expand Down
Loading
Loading