Repository navigation
feat(cli): sign in by approving on your iPhone with auth --email - #326
Conversation
`shelltime auth --email you@example.com` (also on `init`) asks the server to push a sign-in request to the ShellTime iOS app of that account, prints the 2-digit code the iPhone shows, and polls until the request is approved, denied or expired. On approval it saves the new token like the browser flow does. It needs no browser, so an agent can run it and relay the code to the user. Output is plain lines (the spinner only shows on a terminal). Network and server errors are retried until the request expires; a request the API refuses, a denial or an expiry print why in red and exit 1 (ErrAuthNotApproved). `--token` and `--email` together are an error. Plain `shelltime auth` keeps the browser handshake untouched, which the Mac app's setup relies on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013cY2Zv1kE3eQV5b9c5sQJA
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
⏱️
|
| Scenario | base | head | Δ | p | |
|---|---|---|---|---|---|
Startup/floor |
256 µs ±28% | 268 µs ±17% | ~ | 0.436 | A/A |
Startup/version |
3.98 ms ±13% | 4.19 ms ±54% | ~ | 0.529 | ✅ |
Track/daemon/pre |
4.31 ms ±15% | 4.78 ms ±12% | ~ | 0.123 | ✅ |
Track/daemon/post |
4.32 ms ±19% | 4.52 ms ±20% | ~ | 0.436 | ✅ |
Track/direct/pre |
4.80 ms ±47% | 4.28 ms ±18% | ~ | 0.218 | ✅ |
Track/direct/post |
8.85 ms ±20% | 8.99 ms ±18% | ~ | 0.739 | ✅ |
Track/direct/post-sync |
10.14 ms ±14% | 10.99 ms ±13% | ~ | 0.280 | ✅ |
Binary size: 19.1 MiB → 19.1 MiB (+0.1%).
CPU, tail latency and memory
Per exec, base → head (Δ when significant).
| Scenario | p95 wall | user CPU | sys CPU | peak RSS |
|---|---|---|---|---|
Startup/floor |
330 µs → 340 µs | 207 µs → 218 µs | 12 µs → 12 µs | 9.1 MiB → 9.1 MiB |
Startup/version |
5.09 ms → 5.16 ms | 2.32 ms → 2.37 ms | 1.65 ms → 1.84 ms | 15.0 MiB → 15.3 MiB (+2.34%) |
Track/daemon/pre |
5.51 ms → 5.95 ms | 2.52 ms → 2.53 ms | 1.88 ms → 2.14 ms (+14.01%) | 15.8 MiB → 16.2 MiB (+2.44%) |
Track/daemon/post |
5.26 ms → 5.80 ms | 2.17 ms → 2.56 ms (+18.17%) | 2.17 ms → 1.89 ms | 15.7 MiB → 16.1 MiB (+2.75%) |
Track/direct/pre |
6.30 ms → 5.27 ms | 2.47 ms → 2.15 ms | 2.40 ms → 2.02 ms | 15.7 MiB → 16.1 MiB (+2.26%) |
Track/direct/post |
12.16 ms → 11.12 ms | 5.32 ms → 5.59 ms | 3.15 ms → 3.22 ms | 16.9 MiB → 17.3 MiB (+2.51%) |
Track/direct/post-sync |
11.86 ms → 13.38 ms | 6.98 ms → 7.06 ms | 3.71 ms → 3.93 ms | 18.7 MiB → 19.1 MiB (+1.84%) |
benchstat
goos: linux
goarch: amd64
pkg: github.com/malamtime/cli/perf
cpu: AMD EPYC
│ base │ head │
│ sec/op │ sec/op vs base │
Startup/floor-2 255.9µ ± 28% 268.3µ ± 17% ~ (p=0.436 n=10)
Startup/version-2 3.984m ± 13% 4.193m ± 54% ~ (p=0.529 n=10)
Track/daemon/pre-2 4.308m ± 15% 4.780m ± 12% ~ (p=0.123 n=10)
Track/daemon/post-2 4.320m ± 19% 4.522m ± 20% ~ (p=0.436 n=10)
Track/direct/pre-2 4.802m ± 47% 4.283m ± 18% ~ (p=0.218 n=10)
Track/direct/post-2 8.849m ± 20% 8.989m ± 18% ~ (p=0.739 n=10)
Track/direct/post-sync-2 10.14m ± 14% 10.99m ± 13% ~ (p=0.280 n=10)
geomean 3.622m 3.743m +3.34%
│ base │ head │
│ p50-sec/op │ p50-sec/op vs base │
Startup/floor-2 235.1µ ± 25% 248.5µ ± 12% ~ (p=0.529 n=10)
Startup/version-2 3.850m ± 16% 4.067m ± 30% ~ (p=0.436 n=10)
Track/daemon/pre-2 4.172m ± 16% 4.714m ± 11% ~ (p=0.123 n=10)
Track/daemon/post-2 4.144m ± 10% 4.356m ± 11% ~ (p=0.143 n=10)
Track/direct/pre-2 4.397m ± 17% 4.020m ± 22% ~ (p=0.579 n=10)
Track/direct/post-2 7.972m ± 7% 8.510m ± 12% ~ (p=0.190 n=10)
Track/direct/post-sync-2 10.03m ± 13% 10.66m ± 8% ~ (p=0.436 n=10)
geomean 3.423m 3.582m +4.64%
│ base │ head │
│ p95-sec/op │ p95-sec/op vs base │
Startup/floor-2 329.8µ ± 52% 340.3µ ± 50% ~ (p=0.971 n=10)
Startup/version-2 5.089m ± 13% 5.155m ± 151% ~ (p=0.971 n=10)
Track/daemon/pre-2 5.511m ± 16% 5.949m ± 16% ~ (p=0.315 n=10)
Track/daemon/post-2 5.265m ± 117% 5.801m ± 19% ~ (p=0.739 n=10)
Track/direct/pre-2 6.303m ± 178% 5.266m ± 35% ~ (p=0.247 n=10)
Track/direct/post-2 12.16m ± 97% 11.12m ± 100% ~ (p=0.853 n=10)
Track/direct/post-sync-2 11.86m ± 59% 13.38m ± 73% ~ (p=0.353 n=10)
geomean 4.610m 4.657m +1.00%
│ base │ head │
│ peak-rss-B │ peak-rss-B vs base │
Startup/floor-2 9.135Mi ± 2% 9.130Mi ± 1% ~ (p=0.565 n=10)
Startup/version-2 14.96Mi ± 0% 15.31Mi ± 0% +2.34% (p=0.000 n=10)
Track/daemon/pre-2 15.79Mi ± 0% 16.17Mi ± 0% +2.44% (p=0.000 n=10)
Track/daemon/post-2 15.69Mi ± 0% 16.12Mi ± 0% +2.75% (p=0.000 n=10)
Track/direct/pre-2 15.70Mi ± 0% 16.05Mi ± 0% +2.26% (p=0.000 n=10)
Track/direct/post-2 16.86Mi ± 0% 17.28Mi ± 0% +2.51% (p=0.000 n=10)
Track/direct/post-sync-2 18.74Mi ± 0% 19.09Mi ± 0% +1.84% (p=0.000 n=10)
geomean 14.96Mi 15.26Mi +2.01%
│ base │ head │
│ sys-sec/op │ sys-sec/op vs base │
Startup/floor-2 12.36µ ± 52% 11.78µ ± 69% ~ (p=0.796 n=10)
Startup/version-2 1.651m ± 16% 1.840m ± 40% ~ (p=0.579 n=10)
Track/daemon/pre-2 1.880m ± 17% 2.143m ± 15% +14.01% (p=0.015 n=10)
Track/daemon/post-2 2.167m ± 19% 1.893m ± 29% ~ (p=0.436 n=10)
Track/direct/pre-2 2.404m ± 26% 2.024m ± 29% ~ (p=0.280 n=10)
Track/direct/post-2 3.148m ± 18% 3.216m ± 35% ~ (p=0.529 n=10)
Track/direct/post-sync-2 3.706m ± 23% 3.930m ± 27% ~ (p=0.190 n=10)
geomean 1.128m 1.123m -0.51%
│ base │ head │
│ user-sec/op │ user-sec/op vs base │
Startup/floor-2 207.0µ ± 19% 217.5µ ± 9% ~ (p=0.353 n=10)
Startup/version-2 2.322m ± 12% 2.368m ± 19% ~ (p=0.796 n=10)
Track/daemon/pre-2 2.519m ± 19% 2.534m ± 18% ~ (p=0.631 n=10)
Track/daemon/post-2 2.169m ± 16% 2.563m ± 19% +18.17% (p=0.007 n=10)
Track/direct/pre-2 2.467m ± 24% 2.149m ± 22% ~ (p=0.684 n=10)
Track/direct/post-2 5.319m ± 9% 5.586m ± 16% ~ (p=0.247 n=10)
Track/direct/post-sync-2 6.982m ± 13% 7.061m ± 7% ~ (p=0.529 n=10)
geomean 2.189m 2.241m +2.38%
Δ compares medians; ~ means no significant difference (Mann-Whitney U, p ≥ 0.05). A scenario is flagged when it is more than 10% and more than 250µs slower with p < 0.05. Startup/floor runs true, not shelltime: it is an A/A check of runner noise. Runner: linux/amd64, AMD EPYC. go1.27.1, 10 rounds × 100 execs per scenario and binary.
Codecov Report❌ Patch coverage is
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
|
/claude review Generated by Claude Code |
Codecov's patch check failed at 65.8% against its 80% target: the command path itself (ApplyTokenByAppApproval, the config write after an approval, the sleep and the indicator) had no tests. Make the poll sleep swappable like the service and cover the approved, denied and unreachable paths, `shelltime auth --email` writing the token to config.yaml in a temporary HOME, sleepContext and the piped indicator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013cY2Zv1kE3eQV5b9c5sQJA
|
@claude review Generated by Claude Code |
|
Claude finished @AnnatarHe's task in 22s —— View job Code review:
|
From the review: a 408 or 425 from a proxy in front of the API is transient like a 5xx or 429, so keep polling instead of ending the sign-in; and clamp the server's poll interval to 1-30 s so a bad value can't stall the first poll for an hour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013cY2Zv1kE3eQV5b9c5sQJA
|
Thanks for the review.
Generated by Claude Code |
Summary
shelltime auth --email you@example.comsigns in without a browser. The user approves the request in the ShellTime iOS app, which makes this flow usable from an agent. The same flag works onshelltime init.model.AppApprovalService. It makes unauthenticated POSTs to/api/v1/auth/app-approval/{request,poll}.NewHTTPClient, so the proxy setting applies.*AppApprovalError, which carries the HTTP status and the server's message.waitForAppApprovalpolls at the server'sintervaland stops when the context is cancelled or the request expires.ErrAuthNotApproved.mainmaps that to exit code 1, as it does for doctor, so agents can tell the sign-in didn't complete.shelltime authkeeps the browser handshake. The Mac app's setup scrapes that output.--tokentogether with--emailis rejected.Needs shelltime/server#540. On a server without it, or for an account with no eligible iOS app, the CLI prints the server's message, suggests plain
shelltime auth, and exits 1.Test plan
model/app_approval_test.go(httptest): request path, payload, user agent, no Authorization header; poll path and payload; the error envelope, a non-JSON error and an unreachable hostcommands/auth_app_approval_test.go(fake service, injected sleep):--tokentogether with--emailgo vet ./...andgo test ./...passconfig.yamland accepted asAuthorization: CLI <token>🤖 Generated with Claude Code
https://claude.ai/code/session_013cY2Zv1kE3eQV5b9c5sQJA
Generated by Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.