Skip to content

feat: Implement Uplink remote management system#6

Open
MiniCodeMonkey wants to merge 55 commits intomainfrom
feat/uplink
Open

feat: Implement Uplink remote management system#6
MiniCodeMonkey wants to merge 55 commits intomainfrom
feat/uplink

Conversation

@MiniCodeMonkey
Copy link
Owner

Summary

Implements the Uplink feature — a complete remote management and control system for Chief. This enables a central server to manage Chief instances running on remote machines via WebSocket, supporting remote PRD sessions, run control, file watching, project management, and automated deployment.

Changes

CLI Foundation

  • Migrated CLI to Cobra framework for extensible command structure (US-001)
  • Extracted shared engine from TUI for reuse across command modes (US-005)

Authentication

  • Credential storage with secure keyring integration (US-002)
  • chief login / chief logout with automatic token refresh (US-003, US-004)
  • One-time setup token for automated provisioning (US-025)

WebSocket Communication Layer

  • WebSocket client with automatic reconnection and exponential backoff (US-006)
  • Protocol handshake with version negotiation (US-007)
  • Typed message serialization with 30+ message types (US-008)
  • Rate limiting to prevent message flooding (US-026)

Server Mode (chief serve)

  • Headless server command for remote-managed operation (US-009)
  • Workspace scanner for project discovery (US-010)
  • Selective file watcher with gitignore-aware filtering (US-011)
  • State snapshots and project handlers (US-012)
  • Graceful shutdown with connection draining (US-027)
  • Configurable WebSocket URL for local development (US-028)

Remote Operations

  • Interactive PRD sessions over WebSocket (US-013)
  • Session timeout with idle warnings (US-014)
  • Run control — start, pause, resume, cancel (US-015)
  • Quota detection and automatic pause (US-016)
  • Real-time run progress streaming (US-017)
  • Project settings management (US-018)
  • Per-story logging and retrieval (US-019)
  • Per-story diff generation (US-020)
  • Git clone and project creation (US-021)
  • Remote chief update triggered via WebSocket (US-022, US-023)

Deployment

  • Systemd service unit file (US-024)
  • Cloud-init script for automated VM provisioning (US-024)

Stats

  • 58 files changed
  • ~19,550 lines added
  • 28 user stories implemented (US-001 through US-028)
  • Comprehensive test coverage across all new packages

MiniCodeMonkey and others added 30 commits February 15, 2026 21:34
Replace manual flag parsing with Cobra command tree. All existing
commands (new, edit, status, list, wiggum) and flags (--max-iterations,
--no-sound, --no-retry, --verbose, --merge, --force, --help, --version)
are preserved. TUI remains the default when no subcommand is given.
Clean, organized help output with command grouping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create internal/engine package that wraps loop.Manager with fan-out
event subscription, enabling multiple concurrent consumers (TUI and
future WebSocket handler). Refactor TUI to consume events from engine
instead of directly from loop.Manager.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…elease

Dev builds from git describe (e.g. v0.4.0-61-gd06835b) were compared
as plain strings against the latest release tag, causing a false
"update available" prompt. Extract the base semver from git-describe
suffixes so builds ahead of a tag are recognized as current.
The CLI was directing users to /device but the web app serves the
device code entry page at /oauth/device.
Running `chief serve` without --workspace produced a confusing error
with an empty path. Now defaults to the current directory and resolves
to an absolute path before validation.
…efresh

The Go client previously hardcoded the WS URL, but on Laravel Cloud the
Reverb WebSocket server runs at a separate hostname. Include ws_url in
OAuth token responses so the client discovers it automatically during
login and refresh, storing it in credentials.yaml.

WS URL precedence: flag > env > user config > credentials > default.
Add internal/uplink package with HTTP client for the device API.
Replaces WebSocket-based connect/disconnect with HTTP POST endpoints.

- Client struct with functional options pattern (matching ws.Client style)
- Connect(ctx) calls POST /api/device/connect, returns WelcomeResponse with Reverb config
- Disconnect(ctx) calls POST /api/device/disconnect
- HTTPS enforcement with localhost/127.0.0.1 exception
- Thread-safe SetAccessToken() for token refresh
- ConnectWithRetry() with exponential backoff + jitter (same pattern as ws.Client)
- Auth error classification (401/403 not retried)
- 20 unit tests with httptest mock server
…oding

Set explicit Origin header on WebSocket dial to satisfy Reverb's origin
check. Double-unmarshal the connection_established data field to match
the real Pusher wire format (JSON string containing JSON). Update test
helper to match the real protocol.
Strip the -dirty suffix before parsing so dev builds like
"0.4.0-81-g1d1ebf3-dirty" correctly resolve to base version "0.4.0"
instead of being treated as a different version.
The server requires grant_type=refresh_token in the request body and
returns HTML error pages without an Accept: application/json header,
causing JSON parse failures on refresh.
…ages

The server's CommandRelayController sends commands wrapped in
{"type": ..., "payload": {...}} but handlers expected fields at the
top level. Extract the payload before dispatching to handlers.
Add shared JSON fixtures and contract tests that verify both sides
agree on the wire format. Catches serialization mismatches (like
port-as-string, name-vs-project_slug, payload wrapper) with zero
infrastructure — just unit tests against canonical fixture files.

Also fixes mock Pusher server to double-encode connection_established
data, matching the real Pusher protocol.

- contract/fixtures/ synced from chief-uplink (source of truth)
- internal/contract/contract_test.go: 9 contract tests
- Makefile: sync-fixtures and test-contract targets
- .gitignore: exclude synced fixtures
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant