fix(cli): frictionless relay init; teammates join with relay init --key

Merged
KJ
8d ago
+488-94

What

1. relay init asks nothing by default

Plain relay init asks no questions, in a terminal too. It picks:

  • Server: this machine. --hosted uses the hosted service, and --url/--key use your own server.
  • Tools: every installed agent tool.
  • Scope: global.

It ends with a summary of what it chose and the flag that changes each choice:

Chosen without asking:
  Server:  hosted workspace "khaliqgant-rq8a-project" (--name to name it)
  Tools:   claude, codex, opencode, grok, devin (the ones installed here; --tools to choose)
  Scope:   global (--scope project for this project only)
To choose each step yourself, run: relay init --interactive
  • The old prompts are behind -i, --interactive. I chose this name over --choose because -i/--interactive is the usual Unix spelling (rm -i, git add -i), it reads as "ask me", and it pairs with --yes.
  • --interactive without a terminal is an error, not a silent fallback.
  • --yes is still accepted and does nothing, so existing scripts and docs keep working.
  • A pipe or CI run behaves exactly like the default.

2. Joining a hosted workspace takes one command

  • relay init --key <key> joins the hosted workspace that key belongs to (HOSTED_ORIGIN). --url still selects your own server.
  • Naming. --name is optional: the workspace takes the name GET /v1/workspace gives it, with a numeric suffix if another saved workspace already has that name. If the server gives no name, it falls back to the person and the folder.
  • Origin safety. A key already bound to another server is refused before anything is sent. guardCredentialOrigin still binds the key after the check, and an explicit --url still moves the binding.
  • Join output. Joining prints Joined hosted workspace "<name>" (https://cast.agentrelay.com). and Your key is only ever sent to the hosted service. It is recorded as an own setup, so a name alone never confirms which workspace the core serves (review fix).
  • relay init --hosted without --name names the workspace after the person and the folder, unique among saved workspaces, and says how to change it. For teammates it prints:
    On another machine, run this in the project to join it and connect its agent tools (treat this key like a password):
      npx -y agent-relay@next init --key '<key>'
    
    The package spec is agent-relay@next for a -next prerelease (or an unpublished build), and the exact version for a stable release. It needs no install on the teammate's machine.
  • relay workspace join still only saves the workspace. It now says so and points at relay init --key <key> --name <name>, adding --url for a configured or bound self-hosted server. It never echoes the key, and never points a self-hosted key at the hosted service (review fix).
  • shellWord moves to lib/agent-registration.ts and also quotes words that start with =, because zsh would expand them (review fix).
  • Docs. docs/quickstart.md is updated: the default, --interactive, flags, and "Across machines". The CHANGELOG is updated under [Unreleased - Major].

Tests

  • init.test.ts:
    • no prompts by default, even in a TTY, plus the chosen-without-asking summary
    • --interactive prompts (every guided test now passes it) and fails without a terminal
    • --yes is still accepted
    • the default hosted name, with a -2 suffix for a second one
    • the npx spec per CLI version
    • --key alone joins hosted under the server's name
    • an explicit --name and --url still work
    • a key bound to a self-hosted server is refused without any request
    • a joined hosted workspace isn't confirmed by name alone
  • workspace.test.ts: the join hint, a bound key, and quoting of =.
  • vitest run init.test.ts workspace.test.ts: 137 passed. tsc --noEmit: clean. ESLint: 0 errors. Prettier: clean.

Live two-user e2e (macOS, Codex 0.163, isolated HOME/AGENT_RELAY_HOME/CODEX_HOME/XDG, CLI built from this branch, probe from #2029)

User A, in ~/rq8a-project:

$ relay init --hosted
Created hosted workspace "khaliqgant-rq8a-project".
Named after you and this folder; pass --name to choose another.
On another machine, run this in the project to join it and connect its agent tools (treat this key like a password):
  npx -y agent-relay@14.0.0 init --key 'rk_…'
…

The source build's version is 14.0.0, so it prints that exact version. A published 15.0.0-next.* build prints agent-relay@next; this is covered by tests.

User B, in ~/rq8b-project, ran that line with the published npx -y agent-relay@… swapped for this branch's build:

$ relay init --key 'rk_…'
Joined hosted workspace "khaliqgant-rq8a-project" (https://cast.agentrelay.com).
Your key is only ever sent to the hosted service.
…
Chosen without asking:
  Server:  workspace "khaliqgant-rq8a-project" on https://cast.agentrelay.com
  Tools:   claude, codex, opencode, grok, devin (the ones installed here; --tools to choose)
  Scope:   global (--scope project for this project only)

Then Codex on both sides (with #2029's SessionStart preamble):

  • A fresh A session (0 relayed messages before) was asked to DM B. It used the local agent-relay-probe relay socket-request … --path '/send?to=codex-rq8b-project-session' with no approval prompt and no ChatGPT connector. Result: message 242068751546728448.
  • B received it injected. B's reply, message 242069012398882816, was injected into A.
  • relay init --uninstall in both removed hooks.json, rules/ and every trust entry. Codex's own [tui] keys are the only config.toml change.

The hosted test workspace khaliqgant-rq8a-project remains on cast.agentrelay.com.

🤖 Generated with Claude Code


Agent Relay sessions

  • claude session 8ab900cc · opened via gh pr create · last active 2026-10-30