Skip to content

v0.0.2 release candidate - #32

Merged
ndenny merged 32 commits into
mainfrom
develop
Oct 20, 2026
Merged

ndenny merged 32 commits into
mainfrom
develop

Conversation

@ndenny

@ndenny ndenny commented Oct 19, 2026

Copy link
Copy Markdown
Member

Merge to main

ndenny and others added 30 commits September 1, 2026 07:31
Docs:
- README/skills no longer claim create commands read JSON from stdin
  *only* — cli.GetBody also accepts CLI Shorthand args. Heredoc stdin is
  still the recommended form, and the "no --body/--data/-d" fact stands.
- README: demote Manual installation / MacOS / Windows / Linux to
  subheadings under Installation so the hierarchy reads correctly.
- setup-api-monitor.md: the scheduled target is the call ID, not the
  "monitor ID", matching the rest of that skill.

Code:
- skills: updateClaudeMD no longer swallows os.ReadFile errors. An
  unreadable-but-present CLAUDE.md was treated as empty and then
  overwritten, destroying its contents.

Tests:
- oauth: cover the reflected-XSS hardening in the auth callback handler.
  Both new escaping tests fail if html.EscapeString is removed.
- skills: table-driven tests for resolveTarget (no flag, each flag, and
  every mutually-exclusive combination).
- cli: TestAuthHookFailure was order-dependent — it needs a non-nil
  configs map and rsh-profile set, or it panics before reaching the auth
  hook. It now runs standalone.
- cli: typo in TestLoadCache comment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restish upstream moved off msgpack/v2 for a decoder safety fix
(security: update msgpack decoder to v3) as part of their v2 rewrite;
we were still on v2.1.1, older than what they had before that switch.
v3 keeps the same Marshal/Unmarshal signatures so this is a drop-in
import swap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TestResolveTarget still called resolveTarget with 3 args and expected
error strings without --claude-skills, left stale after the
claude-skills feature merged a 4th parameter. This broke go vet/go
test on this branch. Adds the claudeSkills field, updates the expected
error text, and covers the --claude-skills path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- TestAuthHookFailure now cleans up the configs/authHandlers entries
  and rsh-profile setting it introduces, so later tests don't observe
  its side effects.
- Close the httptest response body after reading it in the auth
  callback tests, matching normal net/http contract.
- resolveTarget's error-path test cases now also assert the returned
  agent kind is agentCustom, fully specifying its error behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All four .goreleaser configs sort tags with -version:refname, so when
multiple environment tags point at the same commit (e.g. qc/beta/qc-stable
cut from the same develop HEAD), goreleaser's git-based tag detection
picks whichever tag sorts highest rather than the one that actually
triggered the run — silently baking the wrong version into main.version.

Set GORELEASER_CURRENT_TAG to $GITHUB_REF_NAME so goreleaser always uses
the triggering tag instead of inferring it from git state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Live-tested all 8 claude-skills workflows plus the three embedded
onboarding skills against a real apimetrics-qc account and org, and
fixed every confirmed discrepancy:

- list-call-results doesn't exist -> list-results-by-call, whose flags
  are --from/--result-category/--cursor/--limit, not --since/--before
  (also fixed on list-results itself).
- run-call doesn't exist -> run-monitor <id> runs API, browser, and MCP
  monitors alike.
- list-slos/get-slo/create-slo/update-slo/delete-slo don't exist. SLOs
  are one per project: get-project-slo/update-project-slo (creates if
  none exists)/delete-project-slo, scoped by include_tags/exclude_tags
  with no scope/scope_id field. Rewrote apimetrics-slo-review's model
  accordingly.
- list-schedules-for-call doesn't exist -> list-schedules-by-call.
- get-result's "always a summary" claim only holds for below-ANALYST
  callers; ANALYST+ (most project members) get the full
  request/response/timing/DNS/TLS object back by default.
- project-bootstrap's "the CLI cannot create a project" claim was
  wrong reasoning (create-project-in-org exists as a flat command
  outside the `project` noun-group) even though it happened to be
  right in effect for non-admin accounts. Documented the org-admin
  path, and the previously-undocumented gotcha that creating a project
  does not grant the creator access to it (needs a follow-up
  create-project-access call).
- bulk init's apimetrics:/<collection> scheme doesn't resolve -- it's
  a cosmetic example string in the CLI's own --help (bulk/commands.go)
  with no actual scheme-resolution logic behind it, and fails with a
  literal DNS lookup on host "apimetrics". Fixed the example in
  commands.go to a resolvable placeholder host, and pointed the skill
  at a real host+path instead.
- Three embedded onboarding skills (skills/embed/*.md) referenced a
  nonexistent --api-key flag in their error-recovery sections, and
  setup-api-monitor.md additionally had the run-call/list-call-results
  bugs above plus a result.success/failure_reason shape that doesn't
  exist (real field is result_category, PASS/FAIL/WARN/ERROR/TIMEOUT/
  QUEUED).

performance-analytics and the shared operating-rules boilerplate were
otherwise verified correct and left alone where no bug was found.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKRzfBhwWbRrARvJsbFnaY
…lk init example

- Standardize on result_category as the PASS/FAIL/WARN/ERROR/TIMEOUT/QUEUED
  field everywhere (AGENTS.md, failure-investigation, incident-triage,
  project-bootstrap), matching the embedded skills and the rest of this PR.
  A separate result field carries a transport-completion value (e.g.
  COMPLETE), which is now called out explicitly where the two could be
  confused.
- config-as-code: drop the redundant/inconsistent first bulk init example
  (it set url to a bare id without --url-template, contradicting the
  documented url/uri/self/link-or-url-template guidance) and keep the
  single --url-template form, which was confirmed working live.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKRzfBhwWbRrARvJsbFnaY
Address Copilot review feedback from PR #16
Several fixes from the previous commits were written as a diff against
the prior (incorrect) text -- "there is no list-call-results (use
list-results-by-call)", "the apimetrics:/<collection> scheme does not
work", "there is no run-call" -- which only makes sense to someone who
saw the old version. A fresh reader (a new agent invoking the skill,
someone who never saw the prior draft) has no context for what's being
negated. Rewrote all of these to state the current CLI surface
directly and positively, and swapped the placeholder `<call-or-monitor-id>`
positional argument name for `<monitor-id>`, matching this codebase's
convention of "monitor" as the umbrella term for an API call/browser
test/MCP test.

Left alone the handful of pre-existing "there is no --api-key flag" /
"there is no --body/--data/-d flag" warnings, which predate this PR,
don't reference anything removed from these docs, and read fine
standalone as guardrails against a very plausible wrong guess.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKRzfBhwWbRrARvJsbFnaY
--apimetrics-project-id does exist (confirmed live in get-project-slo
and update-project-slo's own generated option schema), but it isn't
the documented, general-purpose mechanism for targeting a different
project. That's the global --project-id flag (registered in
cli/cli.go, and what cli/config.go's "no active project" error tells
users to pass), so point at that instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKRzfBhwWbRrARvJsbFnaY
commands.go:317's example used /monitors while the bulk init subcommand's
own examples (line 346, pre-existing) use /users -- inconsistent for no
reason. Match them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKRzfBhwWbRrARvJsbFnaY
Fix goreleaser version mislabeling when multiple tags share a commit
- State facts directly instead of contrasting with what earlier skill
  text claimed (one SLO per project, list-results flags, run-monitor,
  project creation).
- Remove the --api-key references from AGENTS.md and the embedded
  setup skills.
- Drop get-project-slo from the list-envelope summaries.
- slo-review: derive availability attainment from get-call-passfail-range/
  get-call-passfail-total and latency from query-*-performance, keeping
  list-results for drilling into violations; add the same guidance to
  AGENTS.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VcnM8iRzyY2ZEyNi6iyp28
- Drop the "not noun/verb groups" and "Do not invent --body" lines from
  the shared skill preamble, and the matching "There is no --body" line
  from the embedded setup skills.
- skills.go: generated agent guide states the flat-command and stdin
  facts directly, matching AGENTS.md.
- weekly-health-review: take pass/warning/failure counts from
  get-call-passfail-range instead of paging list-results and counting;
  keep list-results for failure detail. SLO attainment points at the
  slo-review method. AGENTS.md table row updated to match.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VcnM8iRzyY2ZEyNi6iyp28
…e-cli-review

# Conflicts:
#	skills/skills.go
… API-call only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Describe it in the general case with <monitor-id> rather than as API-call only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lows

Verified every skill, AGENTS.md, and README example against apimetrics-qc
and fixed what the CLI and server actually do:

- result_category is PASS/SLOW/WARNING/FAIL (empty while queued; poll on
  meta.result); --result-category silently ignores other values. Result
  rows are {id, meta, timing}; there is no response_time/test/created.
- -f paths are rooted at the response envelope (body.), and must be quoted
  for zsh; shorthand needs URLs quoted and </dev/null when non-interactive.
- MCP monitors need a Streamable HTTP endpoint and steps need wait_ms.
- SLO period is DAY/WEEK/MONTH; get-project-slo returns defaults when none
  is stored; get-call-passfail-total's top-level stat.total is not a count.
- bulk: checkout does not record its project, push needs partial bodies,
  and push reports success on failed uploads.
- Note deprecated v2 call commands and their v3 replacements while keeping
  the commands production still lists.
- onboard now prints the eight workflow skills as well as the setup
  guides, with a test; fix the root --help example's nonexistent command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
account_id alone selects the grantee (defaulting to the caller) and email
is an informational label; the top-level stat.total counts only
uncategorised results, matching the legacy aggregation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CLI already caches refresh tokens and tries them before falling back
to a browser login, but it never asked Auth0 for one: the scopes were
"openid profile email", so every expired access token meant logging in
again. Add offline_access to the scopes.

Also log at debug level why a refresh was rejected before falling back,
and correct the cache comment, which assumed the server never returns a
new refresh token (it does under refresh token rotation).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`apimetrics login --device` uses the OAuth 2.0 device authorization grant
(RFC 8628): the CLI prints a URL and a short code, the user approves it in
a browser on any device, and the CLI polls the token endpoint until they
do. Unlike the browser login it needs no redirect to localhost:8484, so it
works over SSH and in containers.

The new oauth-device-code handler takes the browser login's parameters and
cache key, so the token it gets is used by every later command, and it
tries a cached refresh token first like the browser login does. The device
endpoint is derived from the token URL (Auth0's /oauth/device/code) and the
audience from the authorize URL's query string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Address Copilot review: requestDeviceToken duplicated requestToken's
HTTP call and its response parsing. Both now use postForm and a new
parseToken helper in request.go. requestToken now also returns a body
read error rather than ignoring it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test set a 1ms timeout against a 2ms mock delay. A context is only
marked expired when its timer callback runs, so on a busy machine gock's
2ms timer could fire first, the mock replied 200 and MakeRequest
returned no error. Reproduced locally under CPU load (13 failures in
6,000 runs). Use a 10ms timeout and a 10s delay: gock stops waiting as
soon as the context is done, so the test is no slower.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ills

Verified against apimetrics-qc: run-monitor returns {call_id, context,
location_id, result_id, user}, and --from/--time reject a bare date
with a 4xx "Invalid request".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
doRequestWithRetry derived each attempt's rsh-timeout context from
req.Context(), but req had already been replaced with the previous
attempt's request, so a retry after a timeout started with an expired
context and failed immediately. Timeouts were never really retried.

It also deferred every cancel until the function returned, which is
before the caller reads the body. With -t set, any body larger than the
transport's buffer was cut off with "context canceled" (4MB came back
as 3,963 bytes).

And with --rsh-retry 0 it returned early and skipped the timeout
altogether.

Each attempt now derives its timeout from the request's original
context. A discarded attempt is released immediately, and the returned
response's timeout is released when its body is closed. The retry loop
also runs with no retries, so the timeout always applies. As a side
effect, -v now logs the request and response with --rsh-retry 0 too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add device code login (apimetrics login --device)
Fix stale/incorrect CLI claims in skills and AGENTS.md
ndenny and others added 2 commits October 19, 2026 07:54
Resolve conflict in cli/serviceaccount_test.go by keeping both the
offline_access scope test and develop's device-code login tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Request offline_access so browser logins get a refresh token
@ndenny
ndenny enabled auto-merge October 19, 2026 21:46
@ndenny
ndenny merged commit 2d79ebf into main Oct 20, 2026
16 of 18 checks passed
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.

2 participants