feat(cli): migrate usage collection to agentsview (CLI 1.0)#120
Conversation
Introduces a new collector adapter for agentsview >= 0.26.1 that mirrors the ccusage v18 contract. The adapter probes the binary on PATH without spawning a subprocess, version-gates by parsing `agentsview version` (tolerates pre-release/build suffixes), and invokes `agentsview usage daily --json --breakdown --agent claude --offline --since/--until/--timezone` for the requested window. Reuses the shared parseDailyUsageOutput so the agentsview output path benefits from the same token-normalization and bounds checks as ccusage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds STRAUDE_COLLECTOR=auto|agentsview|legacy in straude push. - auto: prefer agentsview >= 0.26.1 for Claude when available; keep Straude's native Codex collector for Codex; fall back to legacy when agentsview is missing/outdated, or while the one-time native-Codex repair is pending. - agentsview: require agentsview >= 0.26.1; still keep native Codex. - legacy: ccusage + native Codex. A 3s probe gates the agentsview path so a stuck binary cannot make the command feel hung. Bumps packages/cli to 1.0.0. Mirrors the legacy "no Claude data" carve-out for the agentsview branch: agentsview errors that match a conservative pattern downgrade to Codex-only sync; genuine failures still exit fatally. Adds collector_mode/collector_preference/agentsview_available/ codex_repair_pending/agentsview_version to the usage_pushed PostHog event so we can dashboard rollout shape and detect parity drift. Telemetry is wrapped in try/catch. `straude push --debug` now also prints agentsview availability+version, the codex-native repair flag and timestamp, the IANA timezone passed to agentsview, and the offline-pricing mode. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
/api/usage/submit now allow-lists collector metadata keys (claude/codex/ unified), enforces a 512-byte cap, and accepts the new "agentsview-v1" literal for claude and unified provenance. Unknown keys/values return 400 instead of being silently persisted. Tightens the trusted-Codex-correction rule so that straude-codex-native-v1 can only lower an existing higher-cost device row when the request authenticates through the CLI token path. A Supabase-web session presenting the same collector tag is now treated as untrusted and the mayOverwriteDevice guard preserves the existing total. Adds two regression tests: - rejects unknown collector metadata keys with 400 - rejects web-auth Codex repair attempts even with the trusted collector tag (asserts the device upsert + delete chains are not invoked and the existing higher cost is preserved) - accepts agentsview-v1 collector metadata via CLI auth and persists it on the device_usage row. UsageCollectorMeta gains an optional `unified: "agentsview-v1"` field for forward compatibility with a future fully-consolidated collector. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updates the user-facing copy that anchored exclusively on ccusage so
existing users aren't surprised by the CLI 1.0 collector model:
- PrivacyPledge component links both ccusage and agentsview as
legitimate local collectors.
- /privacy "What Straude cannot access" mentions both.
- /cli Data Sources section honestly describes auto's preference order
(agentsview >= 0.26.1 for Claude when installed; ccusage fallback;
Codex always on Straude's native collector).
- README narrative aligns with the new collector model.
The privacy pledge itself is unchanged: only aggregated daily totals
ever leave the user's machine.
Manual web import now accepts both schema shapes: the legacy
{ "type": "daily", "data": [...] } and the modern { daily, totals }
that current ccusage v18 and agentsview emit. Adds a toCanonicalEntry
helper that maps either shape to the canonical entry the API expects,
deriving totalTokens from input+output+cacheCreation+cacheRead when
absent. Updates instructions to point users at either
`ccusage daily --json --since YYYYMMDD --until YYYYMMDD` or
`agentsview usage daily --json --since YYYY-MM-DD --until YYYY-MM-DD`.
Does not change the verified-vs-unverified semantics or the
hardcoded web-import device_id constant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds docs/agentsview-cli-1.0-migration-plan.md with the implementation plan that landed: hybrid collector strategy (agentsview for Claude, native for Codex), collector-selection table, accuracy rules, server metadata contract, test coverage requirements, and the explicit gate list for a future codex-native retirement PR. Adds a 2026-05-01 entry in DECISIONS.md recording why CLI 1.0 keeps codex-native rather than full-cutover, alternatives considered, and the server rule (only CLI-authenticated straude-codex-native-v1 can lower totals; agentsview-v1 is provenance only). Updates docs/agentsview-migration-exploration.md with a 2026-05-01 header pointing at the implementation plan, so the older exploration isn't read as the active plan. Updates CHANGELOG (Added/Changed/Fixed) for the agentsview adapter, codex-only fallback, collector telemetry, debug provenance, dual-shape import parser, version-regex widening, the server tightening regression test, and the privacy/landing copy refresh. Updates docs/CLI.md for STRAUDE_COLLECTOR modes, the new agentsview invocation, and a troubleshooting note for the version gate. Updates docs/API.md to reflect the collector metadata field, the 30-day backfill window, and the device_id requirement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughReplaces legacy local collectors with an agentsview-first CLI: adds agentsview subprocess support and PATH detection, generalizes daily-usage parsing for multiple sources, validates collector metadata server-side, normalizes web imports, refactors push to submit unified collector payloads, updates tests, and refreshes docs and package metadata. ChangesCLI 1.0 Agentsview Collector Integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/app/(app)/settings/import/page.tsx (1)
128-151:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard the submit request with
try/catch/finallyto avoid sticky loading state.If
fetchrejects (orres.json()throws),setLoading(false)is skipped and the form can remain stuck in “Importing...”. Wrap this block intry/catch/finallyand set loading infinally.Suggested patch
- const res = await fetch("/api/usage/submit", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - entries, - source: "web", - device_id: "00000000-0000-0000-0000-000000000001", - device_name: "web-import", - }), - }); - - if (!res.ok) { - const body = await res.json(); - setError(body.error ?? "Import failed"); - } else { - const body = await res.json(); - setResults(body.results); - posthog.capture("usage_imported", { - days_imported: (body.results as ImportResult[]).length, - source: "web", - }); - } - setLoading(false); + try { + const res = await fetch("/api/usage/submit", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + entries, + source: "web", + device_id: "00000000-0000-0000-0000-000000000001", + device_name: "web-import", + }), + }); + + const body = await res.json(); + if (!res.ok) { + setError(body.error ?? "Import failed"); + } else { + setResults(body.results); + posthog.capture("usage_imported", { + days_imported: (body.results as ImportResult[]).length, + source: "web", + }); + } + } catch { + setError("Import failed. Please try again."); + } finally { + setLoading(false); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(app)/settings/import/page.tsx around lines 128 - 151, The submit flow currently calls fetch and res.json() without error handling so setLoading(false) can be skipped; wrap the network/response handling in a try/catch/finally inside the same async function (the block surrounding fetch("/api/usage/submit") that uses setError, setResults and posthog.capture) so any thrown error is caught and setError(...) is called in catch, and setLoading(false) is always executed in finally; keep the existing logic for handling res.ok and posthog.capture("usage_imported") but move res.json() calls into the try block and ensure you only await res.json() once per branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/__tests__/api/usage-submit.test.ts`:
- Around line 979-987: The negative assertions are unreachable because
deviceUpsertChain and deviceDeleteChain are never returned by the fromFn mock;
update the fromFn mock (the mock that returns query chains) to explicitly return
deviceUpsertChain and deviceDeleteChain for the calls under test (e.g., use
fromFn.mockImplementation or mockReturnValueOnce to return deviceUpsertChain for
the upsert flow and deviceDeleteChain for the delete flow), so that
not.toHaveBeenCalled() checks are meaningful; reference the deviceUpsertChain,
deviceDeleteChain and fromFn mocks when making the change.
In `@docs/agentsview-migration-exploration.md`:
- Around line 11-23: Update the document's top-level metadata to reflect this
concrete CLI 1.0 implementation decision: change the Status field from an
"Exploration" or draft value to something like "Implementation" (or
"Adopted/Committed") and set Last updated to 2026-05-01 (the date in the section
heading); also update any Summary/Title lines that still read as exploratory to
reference the CLI 1.0 implementation plan and link to
docs/agentsview-cli-1.0-migration-plan.md for details so readers won't get mixed
signals about whether this is exploratory or finalized.
In `@packages/cli/README.md`:
- Around line 29-31: Update the README.md smart-sync description to consistently
state the 30-day cap: change the bullet that currently reads “Subsequent runs:
pushes all days since the last sync (up to 7 days)” to mention the 30-day
maximum instead (e.g., “up to 30 days”), so the three bullets (“First run: ...”,
“Subsequent runs: ...”, “Already synced today: ...”) and the later documentation
lines (around the existing 51-53 text) all reflect the same 30-day cap.
In `@packages/cli/src/commands/push.ts`:
- Around line 379-385: The metadata-only agentsView version probe is using the
full options.timeoutMs (via getAgentsViewVersion) which can block if users pass
a large timeout; change the call to getAgentsViewVersion so it uses the same
capped/short probe timeout that isSupportedAgentsViewInstalled uses (i.e., pass
the capped probe timeout variable/value used earlier rather than
options.timeoutMs) so the eager, non-gating version fetch remains best-effort
and bounded.
- Around line 78-81: The isMissingAgentsViewClaudeDataError function's regex is
too strict and misses messages like "No valid Claude data directories found";
update isMissingAgentsViewClaudeDataError to broaden its matching (either reuse
the same pattern as isMissingClaudeDataError or expand the current regex to
allow optional words like "valid" and arbitrary tokens between "no" and "claude"
and to match "data directories found"/"directories" variants,
case-insensitively) so that missing-Claude-data errors trigger the graceful
Codex-only fallback instead of a fatal exit.
In `@packages/cli/src/lib/binary.ts`:
- Around line 6-10: The PATH probing currently uses dirs = (process.env.PATH ||
"").split(delimiter) and will treat empty segments as the current directory,
causing false positives; update the dirs construction to filter out empty
entries (e.g., .filter(Boolean) or .filter(dir => dir !== "")) before running
suffixes.some and existsSync(join(dir, binary + ext)). Keep references to dirs,
suffixes, existsSync, join, binary and delimiter so the change is localized to
the existing probe logic.
---
Outside diff comments:
In `@apps/web/app/`(app)/settings/import/page.tsx:
- Around line 128-151: The submit flow currently calls fetch and res.json()
without error handling so setLoading(false) can be skipped; wrap the
network/response handling in a try/catch/finally inside the same async function
(the block surrounding fetch("/api/usage/submit") that uses setError, setResults
and posthog.capture) so any thrown error is caught and setError(...) is called
in catch, and setLoading(false) is always executed in finally; keep the existing
logic for handling res.ok and posthog.capture("usage_imported") but move
res.json() calls into the try block and ensure you only await res.json() once
per branch.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b5381d9a-bc8d-4097-a5c6-50f42aa37813
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (24)
README.mdapps/web/__tests__/api/usage-submit.test.tsapps/web/app/(app)/post/new/page.tsxapps/web/app/(app)/settings/import/page.tsxapps/web/app/(landing)/cli/page.tsxapps/web/app/(landing)/privacy/page.tsxapps/web/app/api/usage/submit/route.tsapps/web/components/landing/PrivacyPledge.tsxapps/web/types/index.tsdocs/API.mddocs/CHANGELOG.mddocs/CLI.mddocs/DECISIONS.mddocs/agentsview-cli-1.0-migration-plan.mddocs/agentsview-migration-exploration.mdpackages/cli/README.mdpackages/cli/__tests__/agentsview.test.tspackages/cli/__tests__/commands/push.test.tspackages/cli/package.jsonpackages/cli/src/commands/push.tspackages/cli/src/index.tspackages/cli/src/lib/agentsview.tspackages/cli/src/lib/binary.tspackages/cli/src/lib/ccusage.ts
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
docs/agentsview-cli-1.0-migration-plan.md (1)
132-139: 💤 Low valueConsider restructuring the non-goals list for better readability.
The explicit non-goals section is valuable, but four consecutive sentences beginning with "No" (lines 135-138) slightly reduce readability. Consider restructuring to improve flow while maintaining clarity.
♻️ Proposed refactor for improved readability
## Explicit Non-Goals -- No Straude-maintained pricing table for agent usage. -- No fallback to ccusage. -- No fallback to native Codex parsing. -- No per-agent collection logic in Straude. -- No server-side recomputation of local collector costs. +This migration explicitly excludes: + +- Straude-maintained pricing tables for agent usage +- Fallback to ccusage or native Codex parsing +- Per-agent collection logic in Straude +- Server-side recomputation of local collector costs🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/agentsview-cli-1.0-migration-plan.md` around lines 132 - 139, Reword the "Explicit Non-Goals" section to avoid four consecutive list items starting with "No" by converting each item into positive noun-phrase bullets or short clauses (e.g., "Straude-maintained pricing table for agent usage is out of scope" → "Straude-maintained pricing table for agent usage (out of scope)"), or group related items into a sublist, keeping the header "Explicit Non-Goals" and the existing items' intents intact; update the four lines that currently begin with "No" so each reads as a concise out-of-scope statement that improves flow and readability.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/agentsview-cli-1.0-migration-plan.md`:
- Line 62: Update the compound modifier "post creation behavior" in the sentence
that currently reads Keep the 30-day backfill window, `device_id` requirement,
device aggregation, and post creation behavior unchanged. to use a hyphenated
form: replace "post creation behavior" with "post-creation behavior" so the
modifier is grammatically correct.
In `@docs/agentsview-migration-exploration.md`:
- Around line 6-7: Update the phrasing that presents "use agentsview as Straude
CLI's single local collector for all supported coding agents" to clearly mark it
as the target end-state/next phase rather than current CLI 1.0 behavior;
specifically, in the sentence referencing agentsview and any mentions of
removing `STRAUDE_COLLECTOR` or legacy/native paths (also the nearby block
around lines 91-94), add a short qualifier like "target state" or "next phase"
and a brief note that the PR ships transitional behavior and retains the
legacy/native paths for CLI 1.0 to avoid implying features have already been
removed.
In `@packages/cli/README.md`:
- Line 29: Update the README line that currently says "First run: opens a
browser tab to authenticate, then pushes recent usage." to explicitly state the
backfill window (e.g., "pushes the last 3 days of usage" or "pushes recent usage
(typically 3 days)") so readers know the exact time range; edit the sentence
text in packages/cli/README.md where the "First run" description appears to
replace "recent usage" with the chosen explicit phrasing.
---
Nitpick comments:
In `@docs/agentsview-cli-1.0-migration-plan.md`:
- Around line 132-139: Reword the "Explicit Non-Goals" section to avoid four
consecutive list items starting with "No" by converting each item into positive
noun-phrase bullets or short clauses (e.g., "Straude-maintained pricing table
for agent usage is out of scope" → "Straude-maintained pricing table for agent
usage (out of scope)"), or group related items into a sublist, keeping the
header "Explicit Non-Goals" and the existing items' intents intact; update the
four lines that currently begin with "No" so each reads as a concise
out-of-scope statement that improves flow and readability.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0599533a-a098-41c6-b1f1-78ce14fda087
📒 Files selected for processing (8)
docs/CHANGELOG.mddocs/CLI.mddocs/DECISIONS.mddocs/agentsview-cli-1.0-migration-plan.mddocs/agentsview-migration-exploration.mdpackages/cli/README.mdpackages/cli/__tests__/agentsview.test.tspackages/cli/src/lib/agentsview.ts
✅ Files skipped from review due to trivial changes (2)
- docs/CHANGELOG.md
- docs/DECISIONS.md
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/cli/tests/agentsview.test.ts
- packages/cli/src/lib/agentsview.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/commands/push.ts`:
- Around line 358-384: Filter response.results for successful actions (e.g.,
action === "created" || action === "updated"), map those successful result
indices back to the corresponding entries (response.results is ordered to match
entries), and then compute latestDate, totalCost, totalTokens, days_pushed,
datesCreated, and datesUpdated only from that subset; call
updateLastPushDate(latestDate) only when there is at least one successful entry
and send telemetry (posthog.capture) using the filtered totals and counts (also
compute anomalyCounts only over the successful entries) instead of using totals
derived from every attempted entry.
In `@packages/cli/src/lib/agentsview.ts`:
- Around line 47-67: The callback for execFileCb is currently treating stderr as
a property on the ExecException (error.stderr) which loses subprocess error
output; update the callback signature in the Promise resolver from (err, stdout)
to (err, stdout, stderr) and use the stderr value (e.g. const detail =
stderr?.trim() || error.message || "unknown error") when building the rejection
message, while keeping existing timeout/killed handling for error.killed or
error.signal and retaining execFileCb, cmd, cmdArgs and
DEFAULT_SUBPROCESS_TIMEOUT_MS as-is.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3a1a467a-90b2-415c-afc5-4cf47daf4da7
📒 Files selected for processing (24)
README.mdapps/web/__tests__/api/usage-submit.test.tsapps/web/app/(app)/settings/import/page.tsxapps/web/app/(landing)/cli/page.tsxapps/web/app/(landing)/privacy/page.tsxapps/web/app/api/usage/submit/route.tsapps/web/components/landing/PrivacyPledge.tsxapps/web/types/index.tsdocs/API.mddocs/CHANGELOG.mddocs/CLI.mddocs/DECISIONS.mddocs/ROADMAP.mddocs/agentsview-cli-1.0-migration-plan.mddocs/agentsview-migration-exploration.mdpackages/cli/README.mdpackages/cli/__tests__/agentsview.test.tspackages/cli/__tests__/commands/push.test.tspackages/cli/__tests__/flows/cli-sync-flow.test.tspackages/cli/__tests__/resolve-push-date-range.test.tspackages/cli/src/commands/push.tspackages/cli/src/index.tspackages/cli/src/lib/agentsview.tspackages/cli/src/lib/binary.ts
💤 Files with no reviewable changes (1)
- packages/cli/tests/resolve-push-date-range.test.ts
✅ Files skipped from review due to trivial changes (8)
- packages/cli/src/index.ts
- apps/web/app/(landing)/privacy/page.tsx
- apps/web/app/(landing)/cli/page.tsx
- README.md
- docs/DECISIONS.md
- docs/ROADMAP.md
- docs/API.md
- packages/cli/README.md
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/cli/src/lib/binary.ts
- apps/web/types/index.ts
- packages/cli/tests/agentsview.test.ts
- apps/web/app/(app)/settings/import/page.tsx
| const latestDate = entries.reduce( | ||
| (latest, e) => (e.date > latest ? e.date : latest), | ||
| entries[0]!.date, | ||
| ); | ||
| if (shouldRunCodexRepair && !codexCollectFailed && blockedDates.size === 0) { | ||
| config.codex_native_repair_completed_at = new Date().toISOString(); | ||
| config.last_push_date = latestDate; | ||
| saveConfig(config); | ||
| } else { | ||
| updateLastPushDate(latestDate); | ||
| } | ||
| updateLastPushDate(latestDate); | ||
|
|
||
| const totalCost = entries.reduce((sum, e) => sum + e.costUSD, 0); | ||
| const totalTokens = entries.reduce((sum, e) => sum + e.totalTokens, 0); | ||
| const datesCreated = response.results.filter((r) => r.action === "created").length; | ||
| const datesUpdated = response.results.filter((r) => r.action === "updated").length; | ||
| posthog.capture({ | ||
| distinctId: getDistinctId(config), | ||
| event: "usage_pushed", | ||
| properties: { | ||
| days_pushed: entries.length, | ||
| dates_created: datesCreated, | ||
| dates_updated: datesUpdated, | ||
| total_cost_usd: Math.round(totalCost * 100) / 100, | ||
| total_tokens: totalTokens, | ||
| dry_run: false, | ||
| anomalies_medium_low: anomalyCounts.mediumLow, | ||
| anomalies_low_confidence: anomalyCounts.low, | ||
| anomalies_unresolved: anomalyCounts.unresolved, | ||
| }, | ||
| }); | ||
| try { | ||
| posthog.capture({ | ||
| distinctId: getDistinctId(config), | ||
| event: "usage_pushed", | ||
| properties: { | ||
| days_pushed: entries.length, | ||
| dates_created: datesCreated, | ||
| dates_updated: datesUpdated, | ||
| total_cost_usd: Math.round(totalCost * 100) / 100, | ||
| total_tokens: totalTokens, | ||
| dry_run: false, | ||
| anomalies_medium_low: anomalyCounts.mediumLow, | ||
| anomalies_low_confidence: anomalyCounts.low, | ||
| anomalies_unresolved: anomalyCounts.unresolved, | ||
| collector_mode: "agentsview-unified", | ||
| agentsview_version: agentsViewVersion, | ||
| }, |
There was a problem hiding this comment.
Only advance sync state from successful rows.
/api/usage/submit can now return 207, but this block still derives last_push_date, days_pushed, total_cost_usd, and total_tokens from every attempted entry. If the newest date fails while an older one succeeds, the next smart sync skips the failed day entirely. The telemetry below is overstated for the same reason.
Suggested fix
- const latestDate = entries.reduce(
+ const successfulDates = new Set(response.results.map((result) => result.date));
+ const successfulEntries = entries.filter((entry) => successfulDates.has(entry.date));
+
+ const latestDate = successfulEntries.reduce(
(latest, e) => (e.date > latest ? e.date : latest),
- entries[0]!.date,
+ successfulEntries[0]!.date,
);
updateLastPushDate(latestDate);
- const totalCost = entries.reduce((sum, e) => sum + e.costUSD, 0);
- const totalTokens = entries.reduce((sum, e) => sum + e.totalTokens, 0);
+ const totalCost = successfulEntries.reduce((sum, e) => sum + e.costUSD, 0);
+ const totalTokens = successfulEntries.reduce((sum, e) => sum + e.totalTokens, 0);
const datesCreated = response.results.filter((r) => r.action === "created").length;
const datesUpdated = response.results.filter((r) => r.action === "updated").length;
try {
posthog.capture({
distinctId: getDistinctId(config),
event: "usage_pushed",
properties: {
- days_pushed: entries.length,
+ days_pushed: successfulEntries.length,
dates_created: datesCreated,
dates_updated: datesUpdated,
total_cost_usd: Math.round(totalCost * 100) / 100,
total_tokens: totalTokens,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/commands/push.ts` around lines 358 - 384, Filter
response.results for successful actions (e.g., action === "created" || action
=== "updated"), map those successful result indices back to the corresponding
entries (response.results is ordered to match entries), and then compute
latestDate, totalCost, totalTokens, days_pushed, datesCreated, and datesUpdated
only from that subset; call updateLastPushDate(latestDate) only when there is at
least one successful entry and send telemetry (posthog.capture) using the
filtered totals and counts (also compute anomalyCounts only over the successful
entries) instead of using totals derived from every attempted entry.
| return new Promise((resolve, reject) => { | ||
| execFileCb(cmd, cmdArgs, { | ||
| encoding: "utf-8", | ||
| timeout: timeoutMs ?? DEFAULT_SUBPROCESS_TIMEOUT_MS, | ||
| maxBuffer: 10 * 1024 * 1024, | ||
| shell: process.platform === "win32", | ||
| }, (err, stdout) => { | ||
| if (!err) { | ||
| resolve(stdout); | ||
| return; | ||
| } | ||
| const error = err as ExecError; | ||
| if (error.killed || error.signal === "SIGTERM") { | ||
| reject(new Error( | ||
| "agentsview timed out. Try running `agentsview usage daily --json --offline` directly to verify it works.", | ||
| )); | ||
| return; | ||
| } | ||
| const detail = error.stderr?.trim() || error.message || "unknown error"; | ||
| reject(new Error(`agentsview failed: ${detail}`)); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the callback signature of Node.js child_process.execFile, and is stderrexposed on theExecException object or as the callback's third argument?
💡 Result:
The callback signature of Node.js child_process.execFile is: callback(error, stdout, stderr) Where on success error is null, and stdout/stderr are the child process outputs. [1] stderr is exposed as the callback’s third argument (stderr), not on the ExecException/“error” object itself. The docs describe that stdout and stderr output are provided to the callback as stdout (2nd argument) and stderr (3rd argument). [1][2]
Citations:
- 1: https://nodejs.org/docs/latest/api/child_process.html
- 2: https://nodejs.org/docs/v22.11.0/api/child_process.html
🏁 Script executed:
fd -t f agentsview.tsRepository: ohong/straude
Length of output: 92
🏁 Script executed:
sed -n '47,67p' packages/cli/src/lib/agentsview.tsRepository: ohong/straude
Length of output: 805
🏁 Script executed:
cat -n packages/cli/src/lib/agentsview.ts | head -80Repository: ohong/straude
Length of output: 3204
Capture stderr from execFile's third callback argument.
Node.js execFile passes stderr as the callback's third argument, not on the ExecException object. The current code attempts to access error.stderr?.trim(), which is unreliable and causes most non-timeout failures to fall back to error.message, losing the actual subprocess error output. Update the callback signature to accept the stderr parameter and use it instead.
Suggested fix
- }, (err, stdout) => {
+ }, (err, stdout, stderr) => {
if (!err) {
resolve(stdout);
return;
}
const error = err as ExecError;
if (error.killed || error.signal === "SIGTERM") {
reject(new Error(
"agentsview timed out. Try running `agentsview usage daily --json --offline` directly to verify it works.",
));
return;
}
- const detail = error.stderr?.trim() || error.message || "unknown error";
+ const detail = stderr?.trim() || error.message || "unknown error";
reject(new Error(`agentsview failed: ${detail}`));
});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/lib/agentsview.ts` around lines 47 - 67, The callback for
execFileCb is currently treating stderr as a property on the ExecException
(error.stderr) which loses subprocess error output; update the callback
signature in the Promise resolver from (err, stdout) to (err, stdout, stderr)
and use the stderr value (e.g. const detail = stderr?.trim() || error.message ||
"unknown error") when building the rejection message, while keeping existing
timeout/killed handling for error.killed or error.signal and retaining
execFileCb, cmd, cmdArgs and DEFAULT_SUBPROCESS_TIMEOUT_MS as-is.
|
Pausing this migration work for now, will reopen if it's needed. |
Summary
straude push.v0.28.0+, probes the version with a bounded metadata timeout, and runs one unfilteredagentsview usage daily --json --breakdown --offlinecall for the requested date range.collector.unified = "agentsview-v1"; the CLI no longer routes to ccusage, native Codex collection, collector selection env vars, Codex repair flags, or collector merge logic./api/usage/submittrust semantics so CLI-authenticated unified agentsview rows can replace same-device totals, while web-auth spoofing remains blocked and older collector metadata stays valid for already-published clients.Why
The goal of this migration is separation of concerns: agentsview owns local session discovery, token accounting, model mapping, and estimated spend for all coding agents it supports. Straude owns auth, upload, feed/profile behavior, and display of already-normalized daily totals.
The previous hybrid direction kept too much accounting logic in Straude. This branch now treats agentsview as the collector boundary and leaves the old collectors only as temporary revert code.
Test plan
bun run testinpackages/clibun run buildinpackages/clibun run test __tests__/agentsview.test.ts __tests__/commands/push.test.ts __tests__/flows/cli-sync-flow.test.tsinpackages/clibun run test __tests__/api/usage-submit.test.tsinapps/webbun run typecheckinapps/webbun run lintinapps/webgit diff --checkSummary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests