Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

wr

wr is a relationship ledger for tasks, CLI sessions, Git checkouts, pull requests, and workpads. Durable data lives in Cloudflare D1 and is accessed through a Cloudflare Worker.

Architecture

  • The Worker owns every durable read and write.
  • D1 is accessed through Drizzle ORM.
  • worker/schema.ts is the schema source of truth; Drizzle Kit generates and applies migrations.
  • Cloudflare Access authenticates browser and CLI users through the same identity provider.
  • The CLI uses Access Managed OAuth with Authorization Code and PKCE. It does not require cloudflared or a client secret.
  • A locally generated UUID identifies one Device and is bound to the authenticated Access user by the Worker.
  • Tasks and pull requests are shared across Devices.
  • The CLI performs local Git, gh, and iTerm2 inspection, then sends the resulting metadata to the Worker.
  • Before sending local paths, the CLI replaces the home directory prefix with ~.

Development

bun install
bun run db:generate --name=describe_change
bun run db:check
bun run typecheck
bun run lint
bun run test
bun run build
bun run compile

bun run build creates the Worker and Web UI under build/. bun run compile creates a standalone CLI at dist/wr; it does not install or replace a binary on PATH. Install dist/wr into a directory on PATH only when you are ready to switch the local CLI.

D1 migrations

Define schema changes in worker/schema.ts, then generate SQL and Drizzle metadata:

bun run db:generate --name=describe_change

Apply generated migrations to D1 through Drizzle Kit:

CLOUDFLARE_ACCOUNT_ID=... \
CLOUDFLARE_DATABASE_ID=... \
CLOUDFLARE_D1_TOKEN=... \
bun run db:migrate

Before deployment, replace the D1 database ID and Access values in wrangler.jsonc.

Cloudflare Access

Enable the Zero Trust organization and choose its team domain in the Cloudflare dashboard. The Terraform configuration in terraform/ creates the Access application, user policy, and Managed OAuth settings. Its API token needs Access: Apps and Policies edit permission for the target account; a Wrangler OAuth token does not include this permission.

CLOUDFLARE_API_TOKEN=<redacted> terraform -chdir=terraform init
CLOUDFLARE_API_TOKEN=<redacted> terraform -chdir=terraform apply \
  -var account_id=... \
  -var application_domain=wr.example.com \
  -var allowed_email=user@example.com \
  -var access_team_domain=example.cloudflareaccess.com

Copy the wrangler_vars output into the matching values in wrangler.jsonc, then configure the CLI:

wr config server https://wr.example.com
wr config enable .
wr server open

The first remote command opens the Access login page. The CLI dynamically registers as a public OAuth client, completes PKCE through a loopback callback, and stores the access and refresh tokens in ~/.config/wr/oauth.json with owner-only permissions. WR_SERVER_URL overrides the configured server URL.

The Worker validates the Cf-Access-Jwt-Assertion generated by Access and uses its sub claim as the user identity. The Device UUID sent by the CLI is accepted only when it belongs to that authenticated user.

Hooks

Use these commands as SessionStart, UserPromptSubmit, and SessionEnd hooks. Configure the first two as asynchronous hooks where the client supports it so they do not block the agent:

Client SessionStart UserPromptSubmit SessionEnd
Claude Code wr internal session-event --cli claude wr internal session-prompt --cli claude wr internal session-end --cli claude
Codex wr internal session-event --cli codex wr internal session-prompt --cli codex wr internal session-end --cli codex
Devin CLI wr internal session-event --cli devin wr internal session-prompt --cli devin wr internal session-end --cli devin

For Devin CLI, add the commands under the hooks key in ~/.config/devin/config.json (for .devin/hooks.v1.json, omit the outer hooks wrapper):

{
  "hooks": {
    "SessionStart": [{
      "matcher": "",
      "hooks": [{"type": "command", "command": "wr internal session-event --cli devin", "timeout": 5}]
    }],
    "UserPromptSubmit": [{
      "matcher": "",
      "hooks": [{"type": "command", "command": "wr internal session-prompt --cli devin", "timeout": 5}]
    }],
    "SessionEnd": [{
      "matcher": "",
      "hooks": [{"type": "command", "command": "wr internal session-end --cli devin", "timeout": 5}]
    }]
  }
}

Devin sends the stable session_id on stdin and exposes the project root as DEVIN_PROJECT_DIR; the hook adapter uses that directory as the stored checkout path. To address a Devin session explicitly from a shell, pass --session devin:<session-id>.

SessionStart and UserPromptSubmit outside an enabled repository are ignored. The first submitted prompt is stored on the CLI session and is not overwritten by later prompts. Hook requests silently refresh the same Access OAuth session used by other CLI commands.

Commands

The existing resource-oriented command interface remains:

wr task add MOQ-123 --title "Queued task"
wr task start MOQ-123 --worktree .
wr task done MOQ-123
wr task cancel MOQ-123

wr pr add 123 --task MOQ-123
wr pr sync
wr pr sync --all
wr link workpad add ./workpad.md --task MOQ-123

wr show
wr ui
wr doctor
wr task list --global --status active
wr task list --all
wr run sync
wr run focus <session-or-run-id>
wr terminal focus <terminal-id>

wr pr sync obtains current GitHub metadata through the local gh command and sends it to the Worker. wr run sync ends active runs from the current Device when their recorded iTerm2 sessions no longer exist. Runs without a terminal ID are left unchanged. wr ui loads focus targets owned by the authenticated Access user once, filters locally, and focuses the selected iTerm2 pane locally.

Resource lists default to the current Device and Current records: tasks are open or active, pull requests are open, and sessions, runs, terminals, and executions are active. Use --all to include every Device and historical status. An explicit --status selects a status for the current Device. The legacy --global flag remains available; it also removes the cwd repository filter while including every Device and historical status.

These scopes are always limited to the authenticated Access user. They never expose another user's sessions, runs, checkouts, executions, terminals, or workpad links.

The Web UI at the Worker hostname displays the globally shared task list after Cloudflare Access authentication.

About

A Cloudflare-backed relationship ledger for tasks, agent sessions, Git worktrees, pull requests, and workpads.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages