feat: 0.2.0 — full platform CLI (32 commands, per-agent keychain, metered calls) - #3
Conversation
…ered calls Grow the CLI from 5 onboarding verbs to the full Floe platform surface (~75 subcommands across 32 commands): - Command registry: each noun is a self-contained module with its own flags and help; `floe help <cmd>` works everywhere, flags cannot collide across nouns. - Per-agent keychain slots + `floe use`: switching agents reuses each agent's stored key instead of burning one of its 5 key slots; v0.1 configs and the legacy keychain slot migrate automatically. - Metered calls on the agent plane: chat, embed, speak, transcribe, and pay (the x402 proxy, with --check pre-flight) — every call prints its X-Floe-Cost-USDC cost and budget remaining. - Management: agents (incl. pause kill-switch), keys/devkeys, budget (incl. reserve), policy (incl. chain + dry-run test), allowlist, credit, activity, usage (incl. coverage), ledger, billing (incl. the CSV export), account, team, funds, cashout, webhooks, models, estimate, providers, phone, actions, orchestrators, vendors. - Conventions: --json everywhere, --yes for destructive/money verbs (type-to-confirm interactively, refuse non-interactively), exit codes 0/1/2/4/5, 429 retry on reads, sanitized error output, atomic config writes, corrupt-config fail-loud. 420 tests across 33 files (all fetch-stubbed); typecheck clean. Includes a regression suite with production-representative numeric ids.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughFloe CLI 0.2.0 expands the terminal platform with registered commands, multi-agent credentials, developer management, governance, observability, billing, money movement, telephony, and metered gateway operations. ChangesFloe CLI platform expansion
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 53
🤖 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 `@README.md`:
- Around line 24-29: Make the README quickstart use one consistent CLI
installation flow: add the global npm installation command before the examples,
then invoke the installed binary with floe init, floe status, floe chat, and
floe test --voice so no command depends on an unavailable global executable.
In `@src/commands/account.ts`:
- Around line 34-42: Sanitize every API-sourced value before terminal rendering:
in src/commands/account.ts lines 34-42, wrap dev.walletAddress with
sanitizeText; in src/commands/team.ts lines 88-99, wrap m.memberWallet and
result.role with sanitizeText; and in src/commands/team.ts lines 163-165,
sanitize result.role before passing it to bold.
In `@src/commands/actions.ts`:
- Around line 58-65: In targetAgent, normalize agent.id to a string once after
resolveAgentRef returns, then use that normalized value for both the
ctx.config.agents lookup and the returned id field.
In `@src/commands/activity.ts`:
- Around line 114-131: Update renderExpanded and the corresponding compact
rendering path to pass event.timestamp through sanitizeText before formatting
and output. Preserve the existing timestamp display format while ensuring both
timestamp render locations handle network-sourced values consistently with the
other fields.
In `@src/commands/billing.ts`:
- Around line 92-99: Wrap both user-supplied output file writes in the billing
command, including the write in the `flags.out` branch and the other write site,
with handling that converts filesystem failures into a `UsageError` naming the
resolved path. Preserve the existing successful output and JSON-return behavior.
- Around line 88-100: Update billingInvoiceCommand’s --out handling to match
billingExportCommand: treat "-" as the stdout sentinel instead of passing it to
resolve/writeFileSync, or explicitly reject it with UsageError. Preserve the
existing file-writing and JSON summary behavior for normal output paths.
In `@src/commands/budget.ts`:
- Around line 91-94: Update the missing-key error in the per-day budget handling
of the budget command to name the targeted agent and direct the user to the
key-listing command instead of suggesting `floe init`. Use the agent identifier
available in this flow, such as the value passed through `--agent`, while
preserving the existing UsageError behavior.
In `@src/commands/cashout.ts`:
- Around line 78-86: Move the duplicated openInBrowser helper into src/lib/ and
make it parse the URL, accept only valid https: URLs, and use cmd.exe with
['/c', 'start', '', url] on Windows without shell mode. Replace the local helper
in src/commands/cashout.ts lines 78-86, where the caller supplies res.payUrl,
and src/commands/funds.ts lines 114-122, where the caller supplies checkoutUrl,
with the shared validated helper.
In `@src/commands/chat.ts`:
- Around line 22-78: Move meterOf, meterRows, resolveGatewayModel, and readStdin
out of the chat command module into a new shared src/lib/gateway.ts module,
preserving their implementations and exports. Update chat.ts, embed.ts,
speak.ts, transcribe.ts, and pay.ts to import these helpers from the new module,
removing the sibling-command dependency without changing behavior.
- Around line 40-47: Update meterRows to pass budgetRemainingUsd through
sanitizeText before interpolating it into the “budget left” row, while
preserving the existing conditional and formatting behavior.
In `@src/commands/credit.ts`:
- Around line 171-180: Sanitize agent.name before interpolating it into the
remapped ApiError message in the catch block, using the file’s existing
sanitizeText helper consistently with the other renderings of this value.
Preserve the existing status code, error code, and message behavior apart from
sanitizing the displayed name.
In `@src/commands/devkeys.ts`:
- Line 119: Update the JSON response in the devkeys revoke flow around the
flags.json branch to convert the raw keyId argv value to a number before calling
printJson. Update the corresponding test assertion in devkeys.test.ts to expect
numeric keyId 3 while preserving the existing response shape.
- Around line 72-81: Update the limit-error handling in
src/commands/devkeys.ts:72-81 to match the stable machine code confirmed with
the API team rather than the prose “Limit exceeded”, preserving the existing
replacement error message and rethrow behavior. In
src/commands/webhooks.ts:231-240, apply the same stable-code matching for
webhook limit errors and use the server-provided webhook limit instead of
hardcoding 10.
In `@src/commands/estimate.ts`:
- Around line 82-84: Guard est.usage before the Object.entries call in the
human-readable formatting path, preserving the existing output when usage is
present and avoiding a TypeError when the gateway omits it. Leave the --json
path unchanged.
In `@src/commands/funds.ts`:
- Around line 184-188: Update the execute flow in the withdraw command to catch
failures from the api.dev call, including uncertain or lost responses, and
report the prepared.transferId together with the command users can run to
inspect that transfer’s state. Preserve the successful return of { prepared,
executed }, but avoid propagating only a raw error that encourages preparing a
second transfer.
In `@src/commands/keys.ts`:
- Around line 51-61: The key ID comparisons use mismatched string and number
types, preventing the configured key from being identified. In
src/commands/keys.ts lines 51-61, update the marker comparison to guard against
undefined and compare String(k.id) with String(agent.keyId); in
src/commands/status.ts lines 56-58, normalize both values with String() in the
keys.find predicate. Also store String(minted.id) in src/commands/init.ts line
181 and String(rotated.id) in src/commands/keys.ts line 186 to preserve the
AgentEntry.keyId string invariant.
- Around line 48-58: Sanitize all API-sourced text rendered by the keys listing
flow, including agent.name in the no-keys message and heading, plus k.keyPrefix
and k.label in the row output. Reuse the existing sanitizeText helper already
used elsewhere in this file, while preserving the current fallback values and
formatting.
In `@src/commands/models.ts`:
- Around line 84-89: Update fmtRate so rates below the cent threshold use
fixed-decimal formatting rather than toPrecision(2), preventing exponential
notation for values below 1e-6. Preserve the existing trimming and "$…/M" output
format.
In `@src/commands/pay.ts`:
- Around line 120-146: Move the response-to-ApiError mapping from proxyRefusal
into the existing mapping function in src/lib/api.ts, export that function, and
reuse it from proxyRefusal. Preserve parsing of error envelopes and the 429
Retry-After hint while removing the duplicated implementation from pay.ts.
- Around line 167-179: Update agentContext to return the already-resolved agent
key, then destructure and reuse that value in the pay command instead of calling
resolveAgentKey again. Preserve the existing “No agent key found” behavior by
guarding the key before constructing the Authorization header; do not allow an
undefined value to produce “Bearer undefined”.
In `@src/commands/phone.ts`:
- Around line 218-223: Encode agent.id with encodeURIComponent at every phone
route interpolation in this file, including the request containing the numbers
search and the other agent-specific routes. Preserve the existing route
structure and use the encoded value consistently wherever agent.id is inserted
into a path.
- Around line 333-362: Wrap the per-agent numbers request in the existing
withTelephony helper, matching the pattern used by the other phone commands,
while preserving the current GET endpoint and response handling. Also apply the
wrapper to the fleet request in the flags.all branch so both phone list paths
provide the standard telephony-unavailable guidance.
- Around line 309-318: Validate the purchase response immediately after parsing
JSON before dereferencing number, and convert missing or malformed number data
into a clear command error while preserving the successful debit context. Also
validate costRaw before calling rawToUsd, handling an absent or invalid
X-Floe-Cost-USDC header without allowing a raw TypeError or conversion failure
in either the JSON or human-readable output paths.
- Around line 421-423: Update the confirmAction call in the phone command to use
the sanitized e164 value in the confirmation prompt text, while retaining the
raw e164 argument only as the comparison token if required by confirmAction’s
input contract.
In `@src/commands/policy.ts`:
- Around line 95-134: Extract the duplicated targetAgent, formatSeconds, and
describeWindow implementations from src/commands/policy.ts (lines 95-134) and
src/commands/allowlist.ts (lines 52-84) into one shared module, preserving their
current behavior. Update both files to import the shared helpers; remove the
local copies from src/commands/allowlist.ts (lines 52-84) and replace the
implementations in src/commands/policy.ts (lines 95-134) with imports.
- Around line 263-279: Update matchKey normalization in the policy command
around the kind/match handling: preserve flags.match exactly for task policies,
and lowercase it only for API and vendor kinds. Keep API matchKind derivation
and vendor address validation operating on the normalized value, while ensuring
task IDs remain case-sensitive and consistent with the unchanged value sent by
budget handling.
In `@src/commands/speak.ts`:
- Around line 44-49: Update the file-output path in the speak command around
writeFileSync to catch write failures and report that the gateway call
succeeded, including the metered cost, while preserving the existing stdout
path. Also apply the CLI’s --yes confirmation convention before overwriting an
existing output file, or explicitly document overwrite behavior in the command
usage text.
In `@src/commands/usage.ts`:
- Around line 230-232: Update the usage output near the hardcoded dark-spend
note to render the server-provided CoverageResponse.dark value through
sanitizeText(coverage.dark), preserving the existing output formatting; do not
leave the hardcoded wording as the displayed value.
- Around line 82-89: The duplicated parseDays implementations must be
centralized to prevent the API day bound from drifting. In src/commands/usage.ts
lines 82-89, remove the local parseDays and import the shared helper; do the
same in src/commands/ledger.ts lines 44-51. Define parseDays and the MAX_DAYS
bound once in a shared module such as src/lib/duration.ts, preserving the
existing 1–90 validation and UsageError message.
- Around line 112-114: Apply sanitizeText to all specified network-sourced
terminal fields: wrap p.date in src/commands/usage.ts lines 112-114,
ledger.groupBy in src/commands/ledger.ts line 76, and the checkedAt fallback in
src/commands/vendors.ts lines 38-47. Preserve the existing table output and
sanitization of the other fields.
- Around line 261-284: Validate flags within the `run` branches in
`src/commands/usage.ts` so each subcommand rejects any inapplicable `agent`,
`days`, or `window` option instead of silently ignoring it. Keep only the flags
supported by `usageSummaryCommand`, `usageCoverageCommand`, and
`usageSeriesCommand` respectively, and raise the existing usage error before
invoking the command when an unsupported flag is provided.
- Around line 214-216: Update the usage output construction around
process.stdout.write to provide a fallback before sanitization: pass agent.name
?? String(agent.id) to sanitizeText, matching usageSeriesCommand while
preserving the existing heading format.
In `@src/commands/use.ts`:
- Around line 32-34: Add regression assertions around the use flow to verify
that migrate() persists both keyId and keyPrefix under agents['7'] after use
completes. Keep the existing use behavior unchanged and assert the stored values
match the migrated metadata.
In `@src/commands/webhooks.ts`:
- Around line 199-204: Update webhooksCreateCommand’s URL validation to parse
the URL and accept only http or https protocols, throwing the existing
UsageError before any API call for other schemes such as file. Preserve the
current invalid-URL handling and message format.
- Around line 348-355: Update the --retry branch in the webhooks command to run
parseLimit before performing any I/O, preserving the existing out-of-range
--limit validation. Wrap the retry API call and result handling with withWebhook
using the same webhook lookup/error behavior as the list path, so missing
webhook IDs produce the standardized “Webhook <id> not found.” response.
In `@src/lib/api.ts`:
- Around line 126-133: Update the 429 retry branch in the request retry flow to
clamp a finite Retry-After delay to a positive lower bound while retaining the
existing 10-second upper bound and fallback backoff. Before sleeping and
continuing, cancel or otherwise consume the discarded response body via the
existing Response object res.
In `@src/lib/config.ts`:
- Around line 69-85: Update readConfig to validate that the parsed JSON root is
a non-null, non-array object before passing it to migrate; otherwise throw the
same UsageError used for invalid JSON. Preserve the existing missing-file
behavior and migration flow for valid object roots.
In `@src/lib/confirm.ts`:
- Around line 16-21: Update confirmAction to sanitize both summary and expected
with the existing sanitizeText utility before constructing either terminal
prompt or error message. Use the sanitized expected value for comparison with
the user’s answer, ensuring the accepted input matches exactly what the prompt
displays.
In `@src/lib/types.ts`:
- Around line 4-5: Update the created-agent lookup in init.ts to compare
String(created agent ID) with String(SerializedAgent.id), ensuring numeric and
string representations match. Keep the existing lookup behavior unchanged apart
from normalizing both compared values.
In `@src/main.ts`:
- Around line 127-129: Update the catch handler in main so non-Error rejection
values are normalized to a safe string before calling sanitizeText. Preserve the
existing error prefix and exitCode assignment, while ensuring sanitizeText never
receives undefined or another non-string value.
In `@test/agents.test.ts`:
- Around line 257-275: Add a test alongside the existing agent creation
validation cases that invokes main with an agent name containing an invalid
character, such as “bad/name”. Assert process.exitCode is 2 and the stubbed
fetch calls remain empty, covering the AGENT_NAME validation in agents.ts.
In `@test/cashout.test.ts`:
- Around line 141-191: Add a `cashout start` test in the existing suite that
stubs `POST /v1/offramp/start` to return the `withdrawal_exceeds_user_balance`
error, then assert the command output includes the `spend-only` welcome-credit
hint. Reuse the established API stubbing and invocation pattern without changing
the existing success-path tests.
In `@test/chat.test.ts`:
- Around line 124-144: Extend the streaming test for main to return the SSE
response body through a ReadableStream, splitting a data frame mid-JSON across
chunks and asserting the reconstructed output contains “Hello”. Add a subsequent
frame after data: [DONE] and assert its content is absent from stdout, covering
the done guard while preserving the existing stream:true request assertions.
In `@test/embed.test.ts`:
- Around line 79-83: Update the dimension assertion in the embedding output test
around the stdout expectations so it verifies the rendered row contains the
dimension value 5, rather than matching the digit anywhere in stdout. Keep the
existing cost and vector-elision assertions unchanged.
In `@test/keys.test.ts`:
- Around line 7-17: Hoist the mocked keychain store used by the vi.mock factory
in the keys test and clear it in a beforeEach hook. Preserve the existing
getSecret and setSecret behavior while ensuring each test starts with an empty
store, following the established secretStore pattern from the regression tests.
In `@test/models.test.ts`:
- Around line 118-128: Extend the “falls back to the agent key when no dev key
is available” test to assert the rendered models output, not just the
Authorization header. Reuse the expected data from LIST_BODY and verify the
output produced by main on the agent-plane anyPlaneJson response path.
In `@test/pay.test.ts`:
- Around line 238-248: Mock the keychain module in test/pay.test.ts, matching
the existing ../src/lib/keychain.js mock pattern used by test/estimate.test.ts,
so resolveAgentKey cannot access the OS keychain during the missing-credential
test. Preserve the test’s FLOE_AGENT_KEY setup, exit-code and stderr assertions,
and fetch non-call assertion.
- Around line 95-249: Extend the pay command tests around main to cover --data
`@file` and --data - input, including resolveData’s ENOENT error message; add a
replay response with X-Floe-Idempotent-Replay: true and assert replayed output
plus the human replay note; add a large response body exceeding MAX_HUMAN_BODY
and assert the truncation footer; and add --check coverage for a 402 response
with unreadable payment terms, asserting the !result.payment behavior and
expected exit/output.
In `@test/phone.test.ts`:
- Around line 112-118: Update the afterEach cleanup hook to restore mocks,
unstub environments and globals, and reset process.exitCode before attempting
rmSync(configDir). Ensure cleanup still completes if rmSync throws by preserving
the existing try/finally cleanup pattern used by the related test files.
In `@test/regressions.test.ts`:
- Around line 90-102: Move the URL assertions out of the fetch stubs in the
affected regression tests, including the cases around the key rotation requests.
Record each requested URL in the stub, then assert the expected endpoints after
main resolves, following the existing request-recording pattern used later in
the same file. Keep the stubs focused on returning responses so URL mismatches
report directly from the assertions.
In `@test/speak.test.ts`:
- Around line 111-139: Add coverage in the speak command tests for the --out -
path: verify TTY stdout rejects binary output before fetch, --out - with --json
is rejected before fetch, and a successful request writes the raw audio bytes to
stdout while reporting byte count and cost to stderr. Use the existing main,
fetch stubs, and stdout/stderr spies, and assert no output file or network call
is produced for refusal cases.
In `@test/transcribe.test.ts`:
- Around line 115-136: Extend the transcribe command tests with a zero-byte file
case verifying the empty-file error, exit code 2, and no network call, and an
unknown-extension case verifying the upload request uses audio/wav. Reuse the
existing test setup and fetch mock patterns around main, and cover the
validation branch in transcribe.ts without changing existing behavior.
In `@test/usage.test.ts`:
- Around line 92-103: Update the beforeEach test setup to stub FLOE_API_URL to
an empty string, matching test/models.test.ts and test/vendors.test.ts, so
environment configuration cannot override the test config and URL assertions
remain isolated.
🪄 Autofix
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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 73a7a746-2aa8-41db-b0b5-4a7b7a5f9a40
📒 Files selected for processing (75)
README.mdSECURITY.mdpackage.jsonsrc/commands/account.tssrc/commands/actions.tssrc/commands/activity.tssrc/commands/agents.tssrc/commands/allowlist.tssrc/commands/billing.tssrc/commands/budget.tssrc/commands/cashout.tssrc/commands/chat.tssrc/commands/credit.tssrc/commands/devkeys.tssrc/commands/embed.tssrc/commands/estimate.tssrc/commands/funds.tssrc/commands/index.tssrc/commands/init.tssrc/commands/keys.tssrc/commands/ledger.tssrc/commands/models.tssrc/commands/orchestrators.tssrc/commands/pay.tssrc/commands/phone.tssrc/commands/policy.tssrc/commands/providers.tssrc/commands/speak.tssrc/commands/status.tssrc/commands/team.tssrc/commands/test.tssrc/commands/transcribe.tssrc/commands/usage.tssrc/commands/use.tssrc/commands/vendors.tssrc/commands/webhooks.tssrc/lib/api.tssrc/lib/command.tssrc/lib/config.tssrc/lib/confirm.tssrc/lib/context.tssrc/lib/duration.tssrc/lib/keychain.tssrc/lib/table.tssrc/lib/types.tssrc/main.tstest/account.test.tstest/actions.test.tstest/activity.test.tstest/agents.test.tstest/allowlist.test.tstest/billing.test.tstest/budget.test.tstest/cashout.test.tstest/chat.test.tstest/credit.test.tstest/devkeys.test.tstest/embed.test.tstest/estimate.test.tstest/funds.test.tstest/keys.test.tstest/ledger.test.tstest/models.test.tstest/orchestrators.test.tstest/pay.test.tstest/phone.test.tstest/policy.test.tstest/providers.test.tstest/regressions.test.tstest/speak.test.tstest/team.test.tstest/transcribe.test.tstest/usage.test.tstest/vendors.test.tstest/webhooks.test.ts
- harden openInBrowser for network-sourced URLs (https-only, no cmd.exe shell parsing on Windows) - guard undefined agent key in pay, missing usage field in estimate, post-charge write failures in speak, execute failures in funds (with transferId + no-blind-retry hint) - sanitize remaining network-sourced strings (wallets, roles, timestamps, key metadata, phone numbers, confirm prompts, non-Error throws) - clamp Retry-After floor + cancel discarded 429 bodies; reject non-object config roots; String()-normalize remaining id compares - pre-network validation: webhook URL scheme, --limit/--retry conflicts, inapplicable --days/--window, billing invoice --out - - README quickstart install step; devkeys --json keyId type parity - test hardening: SSE chunk-split stream case, keychain-store isolation, cleanup in finally, output assertions, new edge-case coverage (429 tests)
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/commands/status.ts (2)
44-46: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMark a missing configured agent as stale.
When
configuredexists but no matching agent or active fallback exists,agentisundefined. The current expression then setsconfigStaletofalse, so status hides the stale configuration.Proposed fix
- const configStale = Boolean(configured && agent && String(agent.id) !== String(configured.id)); + const configStale = Boolean( + configured && (!agent || String(agent.id) !== String(configured.id)), + );🤖 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 `@src/commands/status.ts` around lines 44 - 46, Update the configStale calculation near the agent selection so a present configured agent is marked stale when no agent was resolved, as well as when the resolved agent ID differs from the configured ID. Preserve the existing non-stale behavior when no configuration exists or the matching agent is found.
75-82: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winApply one terminal-output sanitization rule to both command paths.
Both paths interpolate API-derived strings without
sanitizeText. Sanitize values before formatting or applying color wrappers.
src/commands/status.ts#L75-L82: sanitize developer identity, agent name, status, and suspension reason before buildingrows.src/commands/init.ts#L105-L106: sanitizea.namebefore writing the interactive selection list.🤖 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 `@src/commands/status.ts` around lines 75 - 82, Apply terminal-output sanitization in both command paths: in src/commands/status.ts lines 75-82, sanitize developer identity, agent name, status, and suspension reason before interpolation or color wrapping; in src/commands/init.ts lines 105-106, sanitize a.name before adding it to the interactive selection list. Use the existing sanitizeText helper and preserve the current formatting and status behavior.src/commands/init.ts (1)
3-9: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAvoid shell execution in
openInBrowserOn Windows,
shell: process.platform === 'win32'makesspawninvokecmd.exe. Use the non-shell Windows opener already used insrc/commands/cashout.tsandsrc/commands/funds.ts.🤖 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 `@src/commands/init.ts` around lines 3 - 9, Update openInBrowser in init.ts to avoid shell-based spawn on Windows by reusing the existing non-shell Windows opener pattern from the cashout and funds command implementations, while preserving the current behavior on other platforms.Source: Linters/SAST tools
♻️ Duplicate comments (1)
src/commands/budget.ts (1)
93-95: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winSanitize all resolved and API-derived strings before terminal output.
Both paths sanitize some fields but leave one displayed value unprotected.
src/commands/budget.ts#L93-L95: wrapagent.name ?? agent.idwithsanitizeTextbefore constructingUsageError.src/commands/keys.ts#L59-L63: wrapusedwithsanitizeTextbefore passing it todim.🤖 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 `@src/commands/budget.ts` around lines 93 - 95, Sanitize both untrusted display values before terminal output: in src/commands/budget.ts lines 93-95, wrap agent.name ?? agent.id with sanitizeText when constructing UsageError; in src/commands/keys.ts lines 59-63, wrap used with sanitizeText before passing it to dim.
🤖 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 `@src/commands/keys.ts`:
- Around line 48-50: Update the no-key message in the keys command to keep the
remediation scoped to the selected agent: replace the unqualified floe init
--new-key hint with floe keys create --agent using the selected agent reference,
or otherwise include that reference in the initialization command.
In `@src/commands/speak.ts`:
- Around line 49-57: Update the writeFileSync call in the speak command to
propagate the native filesystem error directly instead of wrapping it in a new
Error. Remove the surrounding catch-and-rethrow logic while preserving the
existing successful gateway and metering flow.
In `@test/chat.test.ts`:
- Around line 171-194: Remove the duplicate streaming test covering split data
frames and ignoring content after [DONE], keeping the other equivalent test
intact. Limit the change to the redundant test block and preserve the remaining
streaming coverage.
---
Outside diff comments:
In `@src/commands/init.ts`:
- Around line 3-9: Update openInBrowser in init.ts to avoid shell-based spawn on
Windows by reusing the existing non-shell Windows opener pattern from the
cashout and funds command implementations, while preserving the current behavior
on other platforms.
In `@src/commands/status.ts`:
- Around line 44-46: Update the configStale calculation near the agent selection
so a present configured agent is marked stale when no agent was resolved, as
well as when the resolved agent ID differs from the configured ID. Preserve the
existing non-stale behavior when no configuration exists or the matching agent
is found.
- Around line 75-82: Apply terminal-output sanitization in both command paths:
in src/commands/status.ts lines 75-82, sanitize developer identity, agent name,
status, and suspension reason before interpolation or color wrapping; in
src/commands/init.ts lines 105-106, sanitize a.name before adding it to the
interactive selection list. Use the existing sanitizeText helper and preserve
the current formatting and status behavior.
---
Duplicate comments:
In `@src/commands/budget.ts`:
- Around line 93-95: Sanitize both untrusted display values before terminal
output: in src/commands/budget.ts lines 93-95, wrap agent.name ?? agent.id with
sanitizeText when constructing UsageError; in src/commands/keys.ts lines 59-63,
wrap used with sanitizeText before passing it to dim.
🪄 Autofix
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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 45146d20-2dbd-470c-a106-b3fac814211d
📒 Files selected for processing (39)
README.mdsrc/commands/account.tssrc/commands/actions.tssrc/commands/activity.tssrc/commands/billing.tssrc/commands/budget.tssrc/commands/cashout.tssrc/commands/chat.tssrc/commands/credit.tssrc/commands/devkeys.tssrc/commands/estimate.tssrc/commands/funds.tssrc/commands/init.tssrc/commands/keys.tssrc/commands/ledger.tssrc/commands/pay.tssrc/commands/phone.tssrc/commands/speak.tssrc/commands/status.tssrc/commands/team.tssrc/commands/usage.tssrc/commands/vendors.tssrc/commands/webhooks.tssrc/lib/api.tssrc/lib/config.tssrc/lib/confirm.tssrc/main.tstest/agents.test.tstest/cashout.test.tstest/chat.test.tstest/devkeys.test.tstest/embed.test.tstest/keys.test.tstest/models.test.tstest/phone.test.tstest/regressions.test.tstest/speak.test.tstest/transcribe.test.tstest/usage.test.ts
- empty keys-list hint now mints for the selected agent, not the active one - speak write failures rethrow the native fs error (code/path/syscall kept), with the charged-cost note moved to stderr - drop the accidentally duplicated stream test
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 `@src/commands/keys.ts`:
- Around line 48-50: Make the copy-paste command in the no-keys output of the
keys command shell-safe by quoting the agent reference or using a validated
unambiguous agent.id instead of the API-sourced name. Preserve the display name
separately, and add regression coverage for names containing whitespace and
shell metacharacters.
In `@src/commands/speak.ts`:
- Around line 52-58: Update the warning construction in the error path that
writes to process.stderr so the --out path is converted to a path-specific
single-line escaped representation before interpolation. Keep sanitizeText for
the existing output text, preserve the success-and-charged message, and continue
rethrowing the original err.
🪄 Autofix
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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 26314782-50b0-4beb-8c38-7b3628b4a204
📒 Files selected for processing (3)
src/commands/keys.tssrc/commands/speak.tstest/chat.test.ts
💤 Files with no reviewable changes (1)
- test/chat.test.ts
There was a problem hiding this comment.
Pull request overview
Expands Floe CLI 0.2.0 into a full platform interface with modular commands, per-agent credentials, metered calls, management operations, and machine-readable output.
Changes:
- Introduces a command registry and broad platform command surface.
- Adds per-agent key storage, migration, confirmations, retries, and atomic configuration writes.
- Adds extensive fetch-stubbed coverage and updated documentation.
Reviewed changes
Copilot reviewed 75 out of 75 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/main.ts |
Registry-based command dispatch and error handling |
src/lib/api.ts |
Public routes, raw responses, and read retries |
src/lib/command.ts |
Shared command contract and options |
src/lib/config.ts |
Version-two config migration and atomic writes |
src/lib/confirm.ts |
Destructive-action confirmation gate |
src/lib/context.ts |
Shared credential and agent resolution |
src/lib/duration.ts |
Human-duration parsing |
src/lib/keychain.ts |
Per-agent credential slots |
src/lib/table.ts |
ANSI-aware table rendering |
src/lib/types.ts |
Numeric/string agent ID support |
src/commands/account.ts |
Account inspection and rename |
src/commands/activity.ts |
Filtered activity feed |
src/commands/billing.ts |
Billing summaries and exports |
src/commands/chat.ts |
Metered chat and SSE streaming |
src/commands/credit.ts |
Credit-line inspection and opening |
src/commands/devkeys.ts |
Developer-key management |
src/commands/embed.ts |
Metered embeddings |
src/commands/estimate.ts |
Cost estimation |
src/commands/index.ts |
Command registry and sections |
src/commands/init.ts |
Multi-agent initialization and migration |
src/commands/ledger.ts |
Grouped spend ledger |
src/commands/models.ts |
Model catalog and pricing |
src/commands/providers.ts |
BYOK provider-key management |
src/commands/speak.ts |
Metered text-to-speech |
src/commands/status.ts |
Multi-agent status reporting |
src/commands/team.ts |
Team roster management |
src/commands/test.ts |
Registry integration for gateway testing |
src/commands/transcribe.ts |
Metered transcription |
src/commands/use.ts |
Active-agent switching |
src/commands/vendors.ts |
Vendor probe status |
test/account.test.ts |
Account command coverage |
test/activity.test.ts |
Activity filtering coverage |
test/billing.test.ts |
Billing and export coverage |
test/budget.test.ts |
Budget reservation coverage |
test/chat.test.ts |
Chat and streaming coverage |
test/embed.test.ts |
Embedding coverage |
test/estimate.test.ts |
Estimate coverage |
test/ledger.test.ts |
Ledger coverage |
test/models.test.ts |
Model catalog coverage |
test/pay.test.ts |
x402 proxy coverage |
test/providers.test.ts |
Provider-key coverage |
test/speak.test.ts |
Speech output coverage |
test/transcribe.test.ts |
Transcription coverage |
test/vendors.test.ts |
Vendor status coverage |
README.md |
Platform command and agent guidance |
SECURITY.md |
Supported version updated to 0.2.x |
package.json |
Package version and description update |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
@CodeRabbit review |
|
- duration: reject a huge-but-valid value (would overflow to a non-safe integer, JSON.stringify → null, API applies the wrong window). Add parseDuration tests. (Copilot) - config: readConfig only treats ENOENT as empty; EACCES/EIO and other read failures now surface, so a later write can't rename a new config over the unreadable one and lose saved agents. (Copilot, High) - init/use: if local secret storage fails after the server minted the one-time key, print it (shown once) before rethrowing so it's recoverable. (Copilot, High) - chat: cancel the reader and break as soon as [DONE] arrives, so a gateway that holds the SSE socket open past the terminator can't hang the CLI. (Copilot) - README: describe --json as stable machine-readable CLI JSON (metered commands wrap the body), not 'exactly what the API returned'. (Copilot)
|
Addressed all six — pushed. High
Medium
Low
Verify: |
|
@CodeRabbit review |
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 76 out of 76 changed files in this pull request and generated 1 comment.
Suppressed comments (8)
src/main.ts:74
- The unknown command name is unsanitized argv data, allowing terminal escape sequences to reach stderr. Apply
sanitizeTextbefore printing it.
src/commands/activity.ts:92 - As with
--since,Date.parseaccepts non-ISO date strings here even though the API contract requires ISO 8601. Validate the timestamp syntax explicitly so invalid CLI input consistently exits as usage error 2.
if (Number.isNaN(Date.parse(flags.until))) {
src/commands/budget.ts:47
- The API can serialize key IDs as numbers while config IDs may be strings. This strict comparison then misses the machine's key and can display the first key's budget instead. Compare canonical string forms, as the keys and status commands already do.
? keys.find((k) => k.id === agent.keyId)
src/commands/status.ts:46
- When the configured agent has been deleted and there is no other active agent,
agentis undefined and this expression incorrectly leavesconfigStalefalse. Track the configured-agent lookup separately so a missing configured agent is always reported as stale.
const agent =
profile.agents.find((a) => configured && String(a.id) === String(configured.id)) ??
profile.agents.find((a) => a.status === 'active');
const configStale = Boolean(configured && agent && String(agent.id) !== String(configured.id));
src/main.ts:57
- The unknown help target comes directly from argv and is written without sanitization, so an argument containing OSC/CSI bytes can control the terminal. Sanitize it before interpolation, consistent with the command error paths below.
This issue also appears on line 74 of the same file.
src/main.ts:87
parseArgserror messages include the user-provided option text. Writing the message verbatim leaves this error path vulnerable to terminal escape injection, unlike the sanitized runtime error paths.
src/commands/activity.ts:86Date.parseaccepts many non-ISO inputs such as locale-style dates, despite this flag promising and forwarding an ISO 8601 timestamp. Those values pass client validation and are then rejected or interpreted differently by the API; require an explicit ISO timestamp shape before parsing.
This issue also appears on line 92 of the same file.
if (Number.isNaN(Date.parse(flags.since))) {
src/commands/funds.ts:414
- The polling loop treats every failure as transient, including 401/403 and other permanent 4xx responses. If the credential expires, the command waits the full two minutes and misleadingly reports that no deposit arrived; rethrow non-rate-limit client errors immediately.
} catch {
// Transient API error mid-watch — keep polling until the deadline.
}
…eset Number(presetRaw) on a huge amount loses precision / becomes Infinity → JSON serializes the preset as null, sending the wrong (or no) preset to the card checkout. Guard with Number.isSafeInteger before dividing. (Copilot, High)
|
Fixed — |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@src/commands/funds.ts`:
- Around line 445-449: Update the amount validation around presetFiat and the
usdToRaw conversion so that after deriving the fiat number from the safe raw
integer, converting it back to raw must equal the original raw value. Throw the
existing UsageError for values that fail this round-trip check, while preserving
acceptance of values that round-trip exactly.
🪄 Autofix
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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 78e2ed15-9157-4589-951e-26391a5a8fe1
📒 Files selected for processing (1)
src/commands/funds.ts
…iat float raw/1e6 can lose precision even for a safe-integer raw (e.g. 9007199254740991 -> 9007199254.740992), billing a different amount than validated. Reject when Math.round(fiat*1e6) !== raw. (CodeRabbit)
|
Good catch — added a float round-trip guard alongside the overflow one. The onramp preset now rejects any |
What
Grows
@floelabs/clifrom 5 onboarding verbs to the full Floe platform surface: 32 commands / ~75 subcommands, so everything the dev-dashboard does can be done from the terminal — built for both humans and AI coding agents (Claude Code, Codex, Cursor).Highlights
floe help <cmd>everywhere; no cross-command flag collisions.floe use— switching agents reuses each agent's stored key (no more burning one of the 5 key slots per switch). v0.1 configs and the legacy keychain slot migrate automatically.chat,embed,speak,transcribe,pay(x402 proxy,--checkpre-flight) — cost + budget remaining printed on every call.pausekill-switch), keys/devkeys, budget (+reserve), policy (+chain, dry-runtest), allowlist, credit, activity, usage (+coverage), ledger, billing (incl. CSV export), account, team, funds, cashout, webhooks, models, estimate, providers, phone, actions, orchestrators, vendors.--jsonon everything,--yesfor destructive/money verbs (type-to-confirm interactively; refuse non-interactively), exit codes 0/1/2/4/5, 429 retry on reads, sanitized error output, atomic config writes.Quality
keys rotateanduse,--agentsilently ignored onbudget/keys,initunderFLOE_AGENT_KEY) + regression suite with numeric-id fixtures.mainauto-publishes 0.2.0 to npm.floe status,floe agents list --rollup,floe chat "hi",floe activity,floe billing,floe policy chain,floe pay <url> --check.Summary by CodeRabbit