Repository navigation
feat(calls): add calls stats and calls export - #26
Conversation
calls stats summarizes calls from GET /platform/calls/stats: totals, answer rate, connected time, groups by day, hour, session or outcome, and an optional day-by-hour heatmap. calls export walks GET /platform/calls/export through the Polymorfa-Next-Cursor header, writes one CSV header (or NDJSON), and keeps progress and resume cursors on standard error. Both accept an organization API key or an exact-project token. The pinned SDK has no typed methods for these operations yet, so they go through the SDK's raw transport with a fetch wrapper that reads the cursor header.
|
Warning Review limit reached
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Next included review available in 58 minutes. View limit detailsLimit details: You’ve used the included review currently available. Your 73 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. The included review limit has been reached and this organization has disabled usage-based review continuation. Wait for reviews to reset or ask a billing admin to change After included review limits. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (12)
Comment |
|
@coderabbitai review |
|
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c2109df53d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const format = flags.format as CallExportFormat | ||
| const environment = await this.developerEnvironment(flags, { | ||
| inferOwner: true, | ||
| }) |
There was a problem hiding this comment.
Check output conflicts before resolving project slugs
When --project is a slug, developerEnvironment() resolves it by making a project-list API request before the existing output path is checked. Consequently, calls export --project <slug> --output <existing-file> can contact the service—or fail with a network/authentication error—instead of immediately reporting the promised local overwrite conflict. Move the output-path preflight ahead of environment resolution.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 723f815. calls export refuses an existing --output file (without --force) before developerEnvironment() resolves a project slug. The test 'refuses an existing output file before resolving a project slug' asserts that no request is made.
| : async (chunk: string) => { | ||
| file ??= await openOutput(outputPath, flags.force) | ||
| await file.write(chunk) | ||
| } |
There was a problem hiding this comment.
When FileHandle.write() completes with bytesWritten smaller than the encoded chunk, which is permitted for low-level writes, the unwritten suffix is silently discarded while the export advances to the next page and reports all records as written. This can leave a corrupted CSV or NDJSON file under disk pressure or on filesystems that return short writes; use an operation that writes the full chunk or loop until all bytes are persisted.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 723f815. File output goes through writeFully, which loops on bytesWritten until the whole encoded chunk is persisted and fails if a write makes no progress.
| outcome: undefined, | ||
| session: undefined, | ||
| } | ||
| const TIME_ZONE = /^(?:UTC|[A-Za-z]+(?:\/[A-Za-z0-9_+-]+){1,2})$/ |
There was a problem hiding this comment.
Accept valid single-component IANA time zones
For users selecting valid IANA identifiers such as GMT, CET, NZ-CHAT, or W-SU, this regex rejects the value locally solely because it lacks a slash, even though these identifiers are recognized time zones. Validate the identifier with the runtime time-zone database (or otherwise include valid aliases) rather than requiring an Area/Location shape.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 723f815. isTimeZoneName no longer requires Area/Location. It validates the name with Intl.DateTimeFormat (the runtime time zone database), so GMT, CET, NZ-CHAT and W-SU are accepted. Numeric offsets are still refused.
…ccept single-name time zones - A short FileHandle.write() no longer drops the rest of a chunk: writeFully keeps writing the remainder and fails if nothing is written. - calls export refuses an existing --output file before resolving a project slug, so the conflict is reported without contacting the API. - --timezone accepts any IANA name the runtime knows (CET, GMT, NZ-CHAT) and still refuses offsets such as +03:00.
|
Codex review follow-up (723f815):
|
# Conflicts: # src/context/environment.ts
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8d3b56fe51
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (chunk.length > 0) { | ||
| try { | ||
| await input.write(chunk) |
There was a problem hiding this comment.
When an NDJSON export successfully returns zero matching records, page.body is empty, so this guard never invokes the lazy file writer. For --output <existing-file> --force, the old file remains untouched while the command reports a successful zero-record export, leaving stale records that can be mistaken for current results; for a new path, no output file is created at all. Ensure a successful empty NDJSON page still opens and truncates or creates the requested destination.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 0b6250d. The first page is now always handed to the writer, even when empty, so a zero-record export creates a new --output file or truncates a --force target. The new test covers both cases.
An NDJSON export with no matching records has an empty body, so the lazy file writer never ran: --force left the old file's records in place and a new path was never created. The first page is now always written.
# Conflicts: # docs/command-research/calls.md
# Conflicts: # docs/command-research/calls.md # src/base-command.ts
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb7365bcd5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| async function writeStandardOutput(chunk: string): Promise<void> { | ||
| // Wait for the pipe to drain so a slow reader bounds memory use. | ||
| if (!process.stdout.write(chunk)) await once(process.stdout, 'drain') |
There was a problem hiding this comment.
Stop cleanly when the stdout consumer closes
When output is piped to a consumer that exits early, such as calls export | head -n 1, stdout emits EPIPE rather than drain; this wait therefore rejects and the export reports a failed page and exits non-zero, or the error can be unhandled if the initial write returned true. This is especially problematic because the command explicitly supports pipelines. Handle closed-pipe errors and stop pagination normally, as event stream already does in its writeStdout path.
Useful? React with 👍 / 👎.
| if (outputPath !== undefined && !flags.force) | ||
| await rejectExisting(outputPath) |
There was a problem hiding this comment.
Preflight forced output paths before exporting
When --force is present, this condition skips all output-path preflight. For example, calls export --force --output <existing-directory> resolves credentials and performs the export request before openOutput() inevitably fails with EISDIR; a path under a nonexistent parent behaves similarly. Check detectable path errors such as directories and missing parents before environment resolution while still deferring file truncation until the first page arrives.
Useful? React with 👍 / 👎.
|
@codex review |
|
Current-head review follow-up ( |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d54c03b3f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ( | ||
| maxDays !== undefined && | ||
| input.since !== undefined && | ||
| input.until !== undefined && | ||
| Date.parse(input.until) - Date.parse(input.since) > maxDays * DAY_MS |
There was a problem hiding this comment.
Validate statistics limits when only --since is supplied
When --since is supplied without --until, the effective upper bound is the API's default of the current time, but this check is skipped entirely. Thus calls stats --group-by hour --since 2026-01-01T00:00:00Z can exceed the documented 31-day limit and still resolve credentials and contact the service before being rejected there. Compute the effective default upper bound when enforcing the hour and day limits so this invalid range fails during local validation as promised.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e5a29f5342
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const mode = this.outputMode(flags) | ||
| const report = (value: object, human: string): void => { | ||
| if (mode === 'json') process.stderr.write(`${JSON.stringify(value)}\n`) | ||
| else if (mode === 'human') process.stderr.write(`${human}\n`) |
There was a problem hiding this comment.
Preserve the resume cursor in quiet mode
When --quiet is used and a later page fails, this branch suppresses the onStop report along with routine progress. The subsequently rendered API error does not include the failed page's cursor, so a partially written export cannot be safely resumed even though --quiet only promises to suppress successful output and the command promises to report recovery cursors on failure. Emit the stopped/resume information for failures even in quiet mode.
Useful? React with 👍 / 👎.
|
Merge gate for exact head e5a29f5: Node 22 current-head CI passed; GitHub Codex completed its current-head review with no findings; local codex review --base origin/dev also found no actionable regression. Full local verification passed with 723 tests in 78 files, format/lint/typecheck/build/pack and zero production audit vulnerabilities. CodeRabbit reported Review rate limited; its SUCCESS check is not a clean review. The owner explicitly authorized merging #26 with CI + Codex and recording this missing CodeRabbit review. Calls remains unreleased; this CLI merge does not deploy or enable it. |
polymorfa calls statsreads call statistics, including grouped rows and an optional day/hour heatmap.polymorfa calls exportstreams CSV or NDJSON pages, writes the CSV header once, supports a safe output file and resume cursor, rejects cursor cycles before duplicate output, withholds a resume cursor after partial write failure, and keeps progress/errors on stderr so stdout remains usable in a pipeline. Invalid filters, time ranges, time zones, and output paths fail before a network request.Both commands use the published typed
Client.callsmethods from exact@polymorfa/sdk@0.1.0-dev.20260923124417(SDK #281, merged atad5e5620). The package-lock integrity is the npm registry tarballsha512-mwcZVuYgFmLJeTSMT12I4YhFFUVKrdXzO+N4LEPAVNIstIxXiC8K8AB4eEmMIzOsShVndcINyj/BZfoTahdPlw==. No raw SDK transport or fetch wrapper remains. Organization keys read team-wide calls by default even when a saved profile has a project owner, or an explicit project when requested; project tokens stay bound to their exact project. Both requiresessions:readat the API.The branch normally includes current CLI
devb782436aand this existing PR targetsdev. It is non-draft.GET /platform/calls/statsand/platform/calls/export; no route or wire shape changes. The accepted monorepo Calls analytics contract is ondev.Full
npm run verifypassed under Node 22 against the exact published SDK: format, lint, typecheck, 723 tests in 78 files, build, pack dry-run, and production audit with zero vulnerabilities. Command tests exercise a local HTTP server, including success, pagination, cursor and output errors, credential scope, and failure exits. A deployed API and real credential were not exercised.