Add a Beeper installer wired to the shipped device-auth flow#15
Add a Beeper installer wired to the shipped device-auth flow#15Miyamura80 wants to merge 21 commits into
Conversation
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
There was a problem hiding this comment.
2 issues found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="scripts/install-beeper.sh">
<violation number="1" location="scripts/install-beeper.sh:84">
P2: `uninstall --dry-run` fails with the non-interactive confirmation error instead of previewing the uninstall. Treating dry-run as an automatic confirmation would preserve the documented “change nothing” preview behavior.</violation>
</file>
<file name="README.md">
<violation number="1" location="README.md:116">
P3: The README now advertises Linux and Windows supervisors, but `edison-stdiod install --help` still describes `install` as a macOS LaunchAgent command. Updating the CLI help with this documentation change would keep the advertised interface and the runnable help output consistent.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| confirm() { | ||
| [ "$ASSUME_YES" -eq 1 ] && return 0 | ||
| [ "$INTERACTIVE" -eq 0 ] && die "refusing to run a confirming action non-interactively: $1" \ |
There was a problem hiding this comment.
P2: uninstall --dry-run fails with the non-interactive confirmation error instead of previewing the uninstall. Treating dry-run as an automatic confirmation would preserve the documented “change nothing” preview behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/install-beeper.sh, line 84:
<comment>`uninstall --dry-run` fails with the non-interactive confirmation error instead of previewing the uninstall. Treating dry-run as an automatic confirmation would preserve the documented “change nothing” preview behavior.</comment>
<file context>
@@ -0,0 +1,452 @@
+
+confirm() {
+ [ "$ASSUME_YES" -eq 1 ] && return 0
+ [ "$INTERACTIVE" -eq 0 ] && die "refusing to run a confirming action non-interactively: $1" \
+ "pass --yes to proceed, or --dry-run to preview"
+ printf '%s [y/N] ' "$1" >&2; read -r ans; [ "$ans" = "y" ] || [ "$ans" = "Y" ]
</file context>
| | --- | --- | | ||
| | `login` | Persist credentials + backend URL to `~/.config/edison-stdiod/config.toml` (mode `0600`). Merges on re-run, so you can rotate the API key without re-supplying the backend URL. | | ||
| | `install` | Register the OS supervisor unit (macOS LaunchAgent) so the daemon starts at login and restarts on crash. Requires `login` first. | | ||
| | `install` | Register the OS supervisor unit (LaunchAgent on macOS, systemd `--user` unit on Linux, Scheduled Task on Windows) so the daemon starts at login and restarts on crash. Requires `login` first. | |
There was a problem hiding this comment.
P3: The README now advertises Linux and Windows supervisors, but edison-stdiod install --help still describes install as a macOS LaunchAgent command. Updating the CLI help with this documentation change would keep the advertised interface and the runnable help output consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 116:
<comment>The README now advertises Linux and Windows supervisors, but `edison-stdiod install --help` still describes `install` as a macOS LaunchAgent command. Updating the CLI help with this documentation change would keep the advertised interface and the runnable help output consistent.</comment>
<file context>
@@ -112,7 +113,7 @@ TLDR: `edison-stdiod --help` (and `edison-stdiod <command> --help` for any subco
| --- | --- |
| `login` | Persist credentials + backend URL to `~/.config/edison-stdiod/config.toml` (mode `0600`). Merges on re-run, so you can rotate the API key without re-supplying the backend URL. |
-| `install` | Register the OS supervisor unit (macOS LaunchAgent) so the daemon starts at login and restarts on crash. Requires `login` first. |
+| `install` | Register the OS supervisor unit (LaunchAgent on macOS, systemd `--user` unit on Linux, Scheduled Task on Windows) so the daemon starts at login and restarts on crash. Requires `login` first. |
| `uninstall` | Stop and remove the supervisor unit. Pass `--purge` to also delete the persisted config and logs. |
| `run` | Run the daemon in the foreground (normally invoked by the service unit). Reads config or accepts `--backend` / `--api-key` / `--device-id` / `--label` flags (also via `EDISON_*` env vars). |
</file context>
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Prototype one-command installer that wires Beeper into the Edison Watch MCP gateway on macOS: installs prerequisites, brings up a headless Beeper Server, supervises the stdiod tunnel daemon, registers Beeper's stdio MCP proxy (npx @beeper/desktop-mcp) as a tunnel child, binds the Beeper access token, and prints the Edison MCP URL. Built to be driven by an agent or a human: every input is a flag or env var, missing inputs fail fast with the exact fix, subcommands follow a resource+verb pattern, --help carries examples, and --dry-run/--yes/--json are supported. Interactive prompts only run behind --interactive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Ran the script on Linux against the real edison-stdiod binary (built from this checkout) plus stubbed beeper, which surfaced several defects: - add_networks leaked IFS=',' into run()'s "$*" logging, so --dry-run previewed 'beeper accounts add' calls with commas instead of spaces. Split on commas via tr without mutating IFS. - Token-bind curl printed 'http 000000' (its own 000 plus a '|| echo 000' fallback) and had no timeout. Use '|| true' with an empty-check and add --connect-timeout/-m so it fails fast. - --dry-run without --beeper-token aborted at the token step instead of previewing. Add a dry-run branch that reports the intent and continues. - edison-stdiod install failures spilled a raw anyhow backtrace and aborted mid-flow via set -e. Export RUST_BACKTRACE/RUST_LIB_BACKTRACE=0 and wrap each edison-stdiod step so failures produce an actionable message. - Skip the live 'server list' idempotency probe under --dry-run. - The real binary carries a Linux (systemd --user) path, so the hard macOS-only guard was stricter than the daemon. Warn on Linux and let the daemon report capability, block only truly unsupported platforms. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Reported: 'install --dry-run' died on a missing 'beeper' CLI instead of previewing. Rework dependency handling around a single ensure_tool helper: - --dry-run previews the install command for each missing dep and never fails, so you can inspect the whole plan before installing anything. - Auto-install now requires consent (--install-deps or --interactive) AND a confirmation (auto-passed by --yes, prompted under --interactive, refused non-interactively without --yes). - After running an installer, validate the command actually landed on PATH; validate the installer itself (brew/cargo) exists before invoking it. - No consent still fails fast with the exact manual command. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
…tics
Traced the real backend route: POST /api/v1/servers/{name}/env
(UpdateServerEnvRequest) stages the value in the device env_store and
respawns the child. The script's guessed path/body were already correct.
- Drop the 'guessed route' hedging; document the confirmed endpoint.
- The endpoint is admin-only, so surface a clear 401/403 message telling
the user --ew-api-key must belong to an org admin.
- Distinguish 000 (unreachable / daemon not yet connected) from other codes,
each with an actionable next step; keep the failure non-fatal.
- Bump the bind timeout to 30s since the call blocks on a child respawn.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Add a TTY-aware color layer with semantic helpers (step/ok/warn/info) so both dry-run and real runs read as grouped phases instead of a wall of white text: - Colors auto-disable when stderr is not a TTY, when NO_COLOR is set, or with the new --no-color flag, so piped and agent output stays a clean, parseable ASCII stream (verified: no escape codes leak when piped). - Phase headers (>>), success (+), info (-), warn (!), and error (x) markers. - Result block on stdout is gated separately on stdout being a TTY, and the copy-paste 'claude mcp add' line stays uncolored so it pastes cleanly. - --json output is unchanged and never colorized. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
From a real macOS run: - brew install dumped its full auto-update 'New Formulae' wall. Export HOMEBREW_NO_AUTO_UPDATE=1 and HOMEBREW_NO_ENV_HINTS=1 and pass --quiet so dependency installs are calm. - The token step optimistically tried a nonexistent mint endpoint and then pointed at a GUI-only path. Beeper exposes no headless token mint, so state that plainly: guide the user to create a token once in the app UI and re-run with --beeper-token, noting the re-run is fast because deps and the Beeper Server are already set up and every step is idempotent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Beeper has no headless token mint (OAuth is authorization_code + PKCE), but the CLI already holds a valid Desktop API bearer after 'beeper setup'. Add format-agnostic discovery that reuses it so no GUI 'Approved connections' step is needed: - discover_beeper_token gathers candidate strings from 'beeper config path' and the macOS Keychain, then keeps the first that authenticates against the local OAuth userinfo endpoint (discovered from the well-known metadata). - ensure_beeper_token tries discovery before failing; on success it reuses the token silently (only a masked fingerprint is shown, never the secret). - New 'token' subcommand runs discovery standalone for quick verification. - All probes are timeout-bounded so a missing server fails fast instead of hanging. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Field findings: 'beeper config path' returns a single file (~/.beeper/ config.json), and the Desktop API did not answer on IPv4, suggesting an IPv6-only bind. Make token discovery robust: - Probe the well-known metadata on 127.0.0.1, [::1], and localhost (and BEEPER_API_URL if set), using the first that answers as the validation base. - Scan the whole config directory (dirname of the config file) plus ~/.beeper, not just the single config file. - Report clearly when the Desktop API is unreachable so the cause is obvious. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
…tatus' Root cause of the empty ~/.beeper and dead 23373: 'beeper status' exits 0 even when no server is configured, so ensure_beeper_server printed 'already running' and SKIPPED 'beeper setup --server --install'. The server was never authorized and the Desktop API never started. - Add beeper_api_base(): probe the well-known metadata across ports 23373-23378 on 127.0.0.1/localhost/[::1] (honor BEEPER_API_URL), echo the first reachable base URL. - ensure_beeper_server now runs setup when the API is unreachable and re-probes after, failing with a clear 'finish the browser authorization' message if it still is not up. - discover_beeper_token reuses beeper_api_base for the same port range. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Field finding: the CLI stores the bearer verbatim as "accessToken" in ~/.beeper/targets/<name>.json. The previous discovery cat'd the entire ~/.beeper tree (including the 100MB server binary and sqlite DBs), so the real token was crowded out past the candidate cutoff. - Extract "accessToken" straight from targets/*.json and try those first, remembering the first as the canonical token. - Limit the broad fallback scan to JSON files under 1M (skip binaries/DBs/logs). - If no candidate passes the live userinfo check, fall back to the explicit accessToken rather than failing (userinfo can be strict about scopes; a truly wrong token surfaces as a real child auth error later). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
… child Real macOS run got all the way through headless token discovery and daemon install, then failed at 'edison-stdiod server add ... --arg -y': clap reads a hyphen-leading value in the space form as an unknown flag. Use the --arg=-y equals form (verified: parses where the space form errors). Also pass the discovered Desktop API base URL to the child as BEEPER_API_URL and BEEPER_DESKTOP_BASE_URL, because the server may bind a non-default port (23374 here) while @beeper/desktop-mcp defaults to 23373. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
A real run reached the daemon and then failed inside 'server add' with a raw 'HTTP 401 Invalid or inactive API key'. Pre-validate the key against GET /api/v1/servers in ensure_ew_api_key so a bad or wrong-environment key fails early with an actionable message (including the demo backend hint), before login/install/server-add run. Non-401 responses proceed; unreachable backend warns and continues. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
A stale 'beeper' registered under an old device_id caused a 409 (names are unique per org, so the this-device 'server list' check missed it). On a conflict, remove the org-level server (admin-only) and re-add it on this device. If it still fails (e.g. non-admin key), fail with guidance to use --server-name or delete it in the dashboard. Also skip the live probe cleanly under --dry-run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
The 409 self-heal never ran: 'out=$(edison-stdiod server add ...)' as a bare assignment fails under set -e when the command returns non-zero, so bash exited silently right after the daemon install (output was captured, nothing printed). Guard both add captures with '&& rc=0 || rc=$?' so the failure is recorded and the conflict handler runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Adding a stdio_tunnel server is gated behind interactive step-up re-auth (backend _require_recent_supabase_login). Detect the STEP_UP_REQUIRED response and explain the real cause and unblock (add once in the dashboard, or set STEP_UP_BYPASS_EMAIL_DOMAINS on a non-release backend) instead of the generic admin-key message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Documents the STEP_UP_REQUIRED blocker on POST /api/v1/servers for stdio_tunnel adds and proposes a device-pairing token flow that keeps the security gate while making one-command install headless after a one-time device enrollment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
…design doc The step-up blocker this script fought is gone: stdiod #16 (browser device authorization) + backend scoped device authorization shipped on main. Rewrite the installer to match, and remove docs/headless-server-add.md (it proposed exactly what shipped). - Auth: `edison-stdiod login --backend <url>` device/browser flow instead of --api-key. Idempotent via the persisted client credential; --no-open for headless, --relogin to force. - Server add: treat as submit-for-approval (client mcp-requests). Drop the step-up handling, the 409 remove+re-add self-heal, and the api-key pre-validation, none of which apply under device auth. - Token: stop POSTing a fabricated /servers/{name}/env route. Under device auth the child's env arrives from the backend, so discover a reusable Beeper token and print it for the operator to set on the server in the dashboard. - Scope to the real capability: Beeper serves MCP only from the Desktop app (headless server has none, per github.com/beeper/cli issue #20). Probe the Desktop API and print the exact human steps (enable MCP, approve server, set token) rather than pretending they are automatable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
c1d2867 to
4b3f1e8
Compare
- parse_flags: guard value-taking flags via needval() so a missing value dies through the error contract instead of set -u's raw "unbound variable", and reject a flag-looking value so a forgotten arg cannot swallow the next flag. - discover_beeper_token: iterate config dirs as an array so macOS "Application Support" (spaced) paths survive; cap the generic scrape and bound live validation to 20 candidates at a 3s timeout (was up to 120x5s); dedupe candidates preserving order; factor out beeper_userinfo_endpoint. - Extract server_registered(): one definition of the idempotency check (jq precise name match, grep fallback), used by install and doctor. - Reject stray positional args instead of silently swallowing them. - Beeper Desktop check is non-fatal now: wire the Edison side and print the action rather than stopping at the prerequisite. - Warn when the saved stdiod backend differs from --ew-backend. - confirm(): local ans. Clarify DEVICE_LABEL is display-only. Convert two `&& || ` chains to if/else. Drop the doubled find redirect. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
When the device is already authorized to a different backend than requested, only fall back to the saved session when --ew-backend was not given. An explicit --ew-backend that disagrees now stops with switch guidance instead of silently targeting the saved backend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Real-world testing showed the device-scoped `server add` declares no env, so
the dashboard shows no field to set BEEPER_ACCESS_TOKEN. The admin route
POST /servers/{name}/env still accepts and verifies it, so re-add that push as
an explicit `bind-token` subcommand (needs --ew-api-key admin), to run after
the request is approved. Also correct the approval guidance (Servers/Overview,
"not verified" pre-token is expected) and point the token step at bind-token.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="scripts/install-beeper.sh">
<violation number="1" location="scripts/install-beeper.sh:606">
P2: Binding exposes a discovered or environment-supplied Beeper credential in curl's process arguments while the request runs. Stream the JSON body through stdin instead of passing it to `--data`.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| local code | ||
| code="$(curl -s -o /dev/null -w '%{http_code}' -m 60 --connect-timeout 5 -X POST "$url" \ | ||
| -H "Authorization: Bearer ${EW_API_KEY}" -H "Content-Type: application/json" \ | ||
| --data "$(printf '{"env":{"BEEPER_ACCESS_TOKEN":"%s"}}' "$tok")" 2>/dev/null || true)" |
There was a problem hiding this comment.
P2: Binding exposes a discovered or environment-supplied Beeper credential in curl's process arguments while the request runs. Stream the JSON body through stdin instead of passing it to --data.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/install-beeper.sh, line 606:
<comment>Binding exposes a discovered or environment-supplied Beeper credential in curl's process arguments while the request runs. Stream the JSON body through stdin instead of passing it to `--data`.</comment>
<file context>
@@ -579,6 +580,44 @@ cmd_token() {
+ local code
+ code="$(curl -s -o /dev/null -w '%{http_code}' -m 60 --connect-timeout 5 -X POST "$url" \
+ -H "Authorization: Bearer ${EW_API_KEY}" -H "Content-Type: application/json" \
+ --data "$(printf '{"env":{"BEEPER_ACCESS_TOKEN":"%s"}}' "$tok")" 2>/dev/null || true)"
+ [ -z "$code" ] && code="000"
+ case "$code" in
</file context>
| --data "$(printf '{"env":{"BEEPER_ACCESS_TOKEN":"%s"}}' "$tok")" 2>/dev/null || true)" | |
| --data-binary @- < <(printf '{"env":{"BEEPER_ACCESS_TOKEN":"%s"}}' "$tok") 2>/dev/null || true)" |
The `token` action line pointed at a dashboard field that does not exist for a device-auth server. Point it at `bind-token` instead, and add --reveal to print the full token to stdout on demand (default stays masked). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
What
Adds
scripts/install-beeper.sh, an agent-friendly helper that wires Beeper into the Edison Watch MCP gateway on macOS. Rebased onto currentmain, so it targets the shipped browser/device-authorization flow (stdiod #16 + backend scoped device authorization) rather than the old API-key + step-up path.Why it changed shape since the first draft
Two things landed / were found after the first version:
edison-stdiod loginis now a browser/device OAuth flow (no API-key paste), andserver addsubmits a device-scoped request for admin approval. The old machinery this script carried (api-key login,POST /servers/{name}/envtoken bind,STEP_UP_REQUIREDhandling, 409 remove+re-add) no longer applies and is removed.beeperServer exposes no MCP endpoint (github.com/beeper/cli issue #20);@beeper/desktop-mcpwraps the Desktop app's endpoint, enabled once under Settings > Developers > MCP. A fully unattended install is therefore not possible today, so the script automates the Edison side and prints the exact human steps for the rest instead of pretending they are automatable.What the script does
Automated:
npx,edison-stdiod).edison-stdiod login --backend <url>device authorization (idempotent via the persisted client credential;--no-openfor headless,--reloginto force).edison-stdiod installto supervise the daemon.edison-stdiod server add beeper --command npx --arg=-y --arg=@beeper/desktop-mcp(submits for approval;server listis the idempotency check).Printed as explicit
action:lines (the human-gated steps):beeperserver once in the dashboard.BEEPER_ACCESS_TOKENon that server in the dashboard. The script discovers a reusable token and prints it masked; under device auth the child's env is delivered by the backend, so the script does not fabricate an env endpoint.Agent-friendly throughout: every input is a flag or
UPPER_SNAKEenv var,--dry-run/--yes/--json/--verbose, TTY-aware colors, resource+verb subcommands (install,doctor,status,token,mcp-url,uninstall), and fail-fast errors that print the exact fix.Also
docs/headless-server-add.md: it proposed a device-pairing token to get past the step-up gate, which is exactly what the shipped device-authorization flow does. Superseded.mainalready covers platform support and the new login flow.Test plan
bash -n+ repo AI-writing hook passinstall --dry-runexercises the full flow and exits 0🤖 Generated with Claude Code