fix(cli): agent-UX + billing-visibility follow-ups (RND-590/592/593/594/595)#166
Open
mpjunior92 wants to merge 67 commits into
Open
fix(cli): agent-UX + billing-visibility follow-ups (RND-590/592/593/594/595)#166mpjunior92 wants to merge 67 commits into
mpjunior92 wants to merge 67 commits into
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrote the top-up command to support both USDC and credit card payment methods. Users can now choose between on-chain USDC payment or credit card checkout. Changes: - Added method flag to select payment method (usdc or card) - Extracted USDC flow into handleUsdc() method - Added handleCard() method for credit card checkout flow - Added pollForCredits() helper to share polling logic - Updated description and examples - Integrated with new SDK methods: getPaymentMethods and purchaseCredits Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
### New CLI Commands **User-facing:** - `ecloud billing redeem-coupon [--code CODE]` — redeem a coupon code for credits (prompts interactively if no `--code` flag) **Admin (requires admin privileges):** - `ecloud admin coupons create --amount <dollars>` — create a new coupon - `ecloud admin coupons list [--active] [--redeemed] [--limit N] [--offset N]` — list coupons with optional filters - `ecloud admin coupons get <id>` — get coupon details - `ecloud admin coupons deactivate <id>` — deactivate a coupon - `ecloud admin coupons redeem <id> --address <wallet>` — redeem a coupon on behalf of a user - `ecloud admin admins add <address>` — grant admin privileges - `ecloud admin admins remove <address>` — revoke admin privileges - `ecloud admin admins list` — list all admins ### SDK Changes - `BillingApiClient` — added methods for all admin and coupon REST endpoints - `AdminModule` — new module wrapping admin API surface (`createAdminModule`) - `BillingModule` — added `redeemCoupon(code)` method - New types: `AdminCoupon`, `AdminUser`, `CreateCouponResponse`, `ListCouponsResponse`, `GetCouponResponse`, `AddAdminResponse`, `ListAdminsResponse`, `RedeemCouponResponse`
The `billing cancel` command was cherry-picking only `private-key` and `verbose` from commonFlags, leaving `--environment`, `--rpc-url`, `--max-fee-per-gas`, `--max-priority-fee`, and `--nonce` undeclared. This made the command unusable in CI/scripted flows because there was no non-interactive way to pick the environment. Spread `...commonFlags` like the other billing subcommands (status, subscribe, top-up, list-cards) so all common flags are accepted; keep `product` and `force` flags on top.
The 'ecloud compute app upgrade' command would hang indefinitely after submitting the on-chain transaction whenever the orchestrator was silent (15+ minutes observed in the wild) — there was no deadline, no progress between status transitions, and no recovery hint. - Bound watchUntilUpgradeComplete with a deadline (default 10 minutes, overridable via ECLOUD_WATCH_TIMEOUT_SECONDS env var or an explicit timeoutSeconds option). - Log every status transition on its own line with elapsed seconds, mirroring watchUntilRunning. - On timeout, throw a typed WatchUpgradeTimeoutError carrying appId, lastStatus, elapsedSeconds, and timeoutSeconds. - CLI catches the timeout, prints a recovery hint pointing the user to 'ecloud compute app info <id>' along with txHash and appId, and exits non-zero. Success path is unchanged.
watchUntilRunning previously only logged on status transitions, so when the orchestrator silently kept the app in Unknown the user saw a single "Status: Unknown (1s)" line forever (visible especially over non-TTY stdout where carriage-return overwrites are invisible). The loop also had no timeout, so the CLI would hang indefinitely. Add a 30s heartbeat that re-emits the current status with elapsed time, plus a configurable timeout (default 10 minutes, override via ECLOUD_WATCH_TIMEOUT_SECONDS) that throws a typed WatchTimeoutError. The CLI deploy command catches it and prints a hint pointing at 'ecloud compute app info <id>' before exiting non-zero. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sepolia-dev AppController was upgraded to v1.5.x (eigenx-contracts KMS-006, PR #15), which added a 4th field `containerPolicy` to the on-chain `Release` struct. This changed the `createApp` selector from 0xa60daa8f to 0x5e92a19f (and likewise createAppWithIsolatedBilling / upgradeApp). The SDK still shipped the 3-field ABI, so every deploy/upgrade encoded the old selector and the upgraded contract reverted with empty revert data — opaque "execution reverted" with no decodable reason. Changes: - Replace the vendored AppController.json with the v1.5.x ABI generated from eigenx-contracts master (createApp/createAppWithIsolatedBilling/upgradeApp now take the 4-field Release; adds createEmptyApp, confirmUpgrade, etc.). All SDK-used functions remain present. - Add ContainerPolicy / EnvVar types + EMPTY_CONTAINER_POLICY default, and an optional `containerPolicy` field on Release (backwards-compatible: callers that omit it get an empty policy that preserves the image's own entrypoint/env). - Encode containerPolicy at both release-encoding sites in caller.ts (prepareDeployBatch / prepareUpgradeBatch) via a shared helper. - Add release-encoding regression test pinning the 0x5e92a19f selector and the 4-field encoding so this drift cannot silently return. Verified E2E on sepolia-dev: deploy now passes the on-chain createApp step (app created on-chain, status STARTED) where it previously bare-reverted. The follow-on "Failed state" during TEE provisioning is unrelated to this ABI fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1.5) The first commit swapped the vendored ABI wholesale to v1.5.x, which would have broken sepolia / mainnet-alpha — both still run AppController v1.4.0 (3-field Release, createApp selector 0xa60daa8f). Only sepolia-dev is on v1.5.x (4-field Release + containerPolicy, selector 0x5e92a19f). Verified on-chain via each controller's version() and createApp selector. Make the SDK support BOTH formats, selected per environment: - Keep the v1.5 ABI as AppController.json and re-add the v1.4 ABI as AppController.v1_4.json (the two also differ on getApps AppConfig shape, so the whole ABI is selected, not just the create/upgrade entries). - Add `releaseAbiVersion?: "v1.4" | "v1.5"` to EnvironmentConfig; set sepolia-dev = v1.5, sepolia + mainnet-alpha = v1.4. Omitted defaults to v1.5. - caller.ts: appControllerAbiFor(env) picks the ABI; releaseForViem(release, env) includes containerPolicy only on v1.5. All read/lifecycle calls also route through the version-aware ABI. - Drop the now-unused `Address` import in environment.ts (pre-existing lint error in a file this change touches). Tests: per-version encoding (0xa60daa8f vs 0x5e92a19f, containerPolicy round-trip, arity guards) + env→ABI selection through getEnvironmentConfig. 31 SDK tests pass; tsc + eslint clean on changed files. E2E verified on both: sepolia-dev (v1.5) and sepolia-prod (v1.4) each created an app on-chain (status STARTED) where the wholesale-swap build would have reverted on one of them. (Provisioning "Failed state" for the bare nginx test image is unrelated to the ABI.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nblocks sepolia-dev without breaking prod (#165) Deploys/upgrades on **sepolia-dev** have been failing since the AppController was upgraded to **v1.5.x** (2026-05-28) with an opaque `execution reverted`. **sepolia / mainnet-alpha remain on v1.4.0**, so the fix must support **both** ABIs, selected per environment. ## Root cause eigenx-contracts **KMS-006** added a 4th field `containerPolicy` to the on-chain `Release` struct on v1.5.x: ``` v1.4.x: Release = (rmsRelease, publicEnv, encryptedEnv) // createApp 0xa60daa8f v1.5.x: Release = (rmsRelease, publicEnv, encryptedEnv, ContainerPolicy) // createApp 0x5e92a19f ``` The SDK shipped only the 3-field ABI. Against v1.5.x (sepolia-dev) it encoded the now-nonexistent `0xa60daa8f` selector → empty-data revert. Verified on-chain per environment: | Env | Controller | `version()` | Release | |---|---|---|---| | sepolia-dev | `0xa86DC1C…` | 1.5.1 | 4-field | | sepolia | `0x0dd810a6…` | 1.4.0 | 3-field | | mainnet-alpha | `0xc38d35Fc…` | 1.4.0 | 3-field | ## Design — support both, select per environment - Vendor **both** ABIs: `AppController.json` (v1.5) and `AppController.v1_4.json` (v1.4). They also diverge on the `getApps` `AppConfig` shape, so the whole ABI is selected, not just create/upgrade. - Add `releaseAbiVersion?: "v1.4" | "v1.5"` to `EnvironmentConfig`: **sepolia-dev = v1.5**, **sepolia + mainnet-alpha = v1.4** (omitted defaults to v1.5). Future prod upgrade = one-line config flip. - `caller.ts`: `appControllerAbiFor(env)` selects the ABI; `releaseForViem(release, env)` adds `containerPolicy` only on v1.5. Add `ContainerPolicy`/`EnvVar` types + `EMPTY_CONTAINER_POLICY` (empty policy preserves the image’s own entrypoint/env; current deploy path supplies none). ## Testing - [x] Unit: per-version encoding asserts `0xa60daa8f` (v1.4) and `0x5e92a19f` (v1.5), containerPolicy round-trip, and arity guards - [x] Unit: env→ABI selection through real `getEnvironmentConfig` (sepolia-dev → v1.5, sepolia/mainnet-alpha → v1.4) - [x] 31 SDK tests pass; `tsc --noEmit` clean; `eslint` clean on changed files - [x] E2E sepolia-dev (v1.5): deploy created app on-chain, status `STARTED` - [x] E2E sepolia-prod (v1.4): deploy created app on-chain (tx `0x8dc3c88e…`), status `STARTED` — the case the wholesale-swap approach would have reverted - [x] Both test apps terminated afterward ### Known follow-on (out of scope) The bare-`nginx` test image enters **Failed state** during TEE provisioning (off-chain watcher, identical on both envs) — a runtime concern unrelated to this ABI fix. ### Branch target Targets `release/v1.0.0` as requested. Same drift exists on `master`; forward-port (or re-sync `master` from eigenx-contracts bindings). The new `createEmptyApp` two-step flow is present in the v1.5 ABI but not adopted — this is the minimal fix to keep the existing one-shot `createApp` deploy path valid across both contract versions. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…into RND-589 branch
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One-shot status via getStatuses; --json emits { appId, status }. --wait blocks
via the bounded watchDeployment machinery (honors --watch-timeout /
ECLOUD_WATCH_TIMEOUT_SECONDS), catches WatchTimeoutError with a recovery hint,
then prints a final status read. Gives agents a supported wait mechanism
instead of tight-looping 'app info'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pp info' (RND-592) Rewrite Gate 3 to use the bounded 'app status --wait' instead of a bare 'app info' poll loop (the tight loop that trips rate limits). Add --json to the supported-flags list and update the upgrade gate. Also brings SKILL.md into prettier compliance (was pre-existing non-conforming; whitespace-only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onfirm behavior The PR #162 merge changed confirmWithDefault to return the default in non-TTY mode instead of throwing, so promptUseVerifiableBuild(false) now resolves to false (regular build) rather than erroring. Update the two tests that asserted the old throw behavior. Mainnet deploy confirm stays safe: it's gated on !flags.force and defaults to false, so non-TTY without --force cancels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…f throwing (RND-589) Optional yes/no confirms must not block a non-interactive run. In non-TTY mode confirmWithDefault now returns its default rather than throwing 'Use --force'. Safe for the mainnet deploy confirm: that path is gated on !flags.force and defaults to false, so a non-interactive mainnet deploy without --force cancels rather than auto-proceeding. Makes the committed tests (which assert this) match the code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-591) Callers keying off exit status can now tell which stage failed: 2 = invalid/missing input (pre-build) 3 = build/push failed (no on-chain tx attempted) 4 = build OK but on-chain tx failed (image already pushed; re-run reuses it) Wrap prepare* (build) and execute* (on-chain) in stage-labeled try/catch in both deploy.ts and upgrade.ts; the non-interactive precheck now exits 2. On-chain failure message states the image was already built+pushed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ND-591) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e-flight check (RND-597) Three gaps that let an arm64 image deploy and crash on first request in the TEE: 1. digest.ts: the 'architecture undetectable' branch assumed linux/amd64 and returned without verifying. Now throws createPlatformErrorMessage (fail closed). 2. prepare.ts: verify the remote --image-ref is linux/amd64 (docker manifest inspect, no pull) BEFORE layerRemoteImageIfNeeded, so an arm64 ref fails in ~seconds with the buildx/--verifiable remediation instead of after a multi-minute pull+layer+push. 3. dockerhub.ts: resolveDockerHubImageDigest (prebuilt verifiable images) now asserts a linux/amd64 manifest — checks the index for an amd64 entry, or the config blob's architecture for single-platform — rejecting otherwise. --verifiable --repo --commit is unchanged (server-side build, no local Docker; never hits these paths). Tests: digest.test.ts (undetectable→throw, single-platform arm64, multi-platform no-amd64) + dockerhub.test.ts (multi/single platform accept+reject). 40 SDK + 99 CLI tests pass. E2E sepolia-dev: arm64 --image-ref rejected pre-push in ~16s (exit 3); amd64 passes pre-flight and proceeds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…RND-596) Compute credits do not pay on-chain gas (paid by the EOA via EIP-7702), so an agent funded only with credits had its deploy tx revert with no machine-readable pre-check. Add a typed pre-flight gate in the SDK so BOTH CLI and SDK/agent paths are protected: - New InsufficientGasError + assertSufficientGas(publicClient, address, gasEstimate) in common/gas/insufficientGas.ts; threshold is gasEstimate.maxCostWei (not zero — dust below cost still fails). Exported from the SDK index. - Call it at all 4 prepare* gas-estimate sites (deploy + upgrade, normal + verifiable), right after estimateBatchGas, before returning. - billing status now shows the wallet's on-chain ETH (best-effort) so the credit-vs-gas gap is visible pre-deploy. Tests: assertSufficientGas (above/equal/dust-below/error-shape). 44 SDK + 99 CLI tests pass. E2E sepolia-dev: a 0.0044-ETH wallet is blocked with 'needs ~0.0198 ETH ... credits do not pay on-chain gas'; billing status shows the ETH line. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Strip RND-* ticket references from code comments and test names across the CLI and SDK packages. Ticket tracking belongs in the PR/commit/branch, not in committed source. Comment-only and describe()-label changes — no behavior change; all affected tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ions
Replace bare `let prepared, gasEstimate;` / `let res;` (implicit any) with
explicit annotations in the deploy and upgrade command flows. None take
undefined — the catch block calls this.error(..., {exit}) (returns never),
so the vars are definitely assigned. Types sourced from the SDK's exported
Prepare{Deploy,Upgrade}Result / GasEstimate and Awaited<ReturnType<...>>.
No behavior change; deploy/upgrade tests + prettier pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The optional-input helpers (env-file, log-visibility, resource-usage- monitoring, dockerfile) called isNonInteractive() internally, re-deriving the decision from process.stdin/CI on every call. That dropped the --non-interactive flag: the bare call only sees CI + !isTTY, so --non-interactive on a real TTY (CI unset) still prompted. Resolve isNonInteractive(flags) once at the command boundary in deploy/ upgrade and thread the boolean into each helper as a parameter — matching the existing getInstanceTypeInteractive(..., nonInteractive) shape. No global state: helpers are now pure and unit-testable by passing the bool (no process.stdin mocking), and --non-interactive is honored everywhere. Tests: added a block asserting each helper honors the injected decision on a TTY (the dropped-flag case), plus a sanity check that nonInteractive=false still reaches the prompt. 104 CLI tests pass; eslint + prettier clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The wallet-ETH balance read in `billing status` was wrapped in an empty catch that discarded the error. It lumped three distinct failures — malformed --private-key, bad environment config, and transient RPC errors — into a silent no-op: the user saw no ETH line and no reason why. Keep it best-effort (must not abort `billing status`) but warn with the reason via this.warn, matching the existing pattern in app list/info. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getDockerfileInteractive / getEnvFileInteractive / getLogSettingsInteractive / getResourceUsageMonitoringInteractive / getInstanceTypeInteractive each take a nonInteractive argument now, so the "Interactive" suffix read contradictorily (getDockerfileInteractive(..., nonInteractive)). These helpers resolve a value from flag/default/prompt in either mode, so rename to getDockerfile / getEnvFile / getLogSettings / getResourceUsageMonitoring / getInstanceType and update their doc comments. The genuinely interactive-only helpers (getImageReferenceInteractive, getEnvironmentInteractive, etc.) keep their suffix. Also clarify exitCodes.ts: exit 1 is oclif's default for unclassified errors, not one we define — the doc comment listed it alongside our 2/3/4. No behavior change; 106 CLI tests pass; eslint + prettier clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…exit code
The old exitCodes test only asserted the EXIT_CODES constants and a trivial
errorMessage helper — it would pass even if the codes were never wired into
the commands. And the six this.error(..., { exit }) blocks (3 stages x deploy
/upgrade) duplicated the message + code mapping inline.
Extract a pure stageFailure(operation, stage, err) -> { message, exit } that
owns the mapping, and call it from both commands. Now the real logic is unit-
tested directly (invalid-input -> 2, build -> 3 "no X was attempted",
onchain -> 4 "image already pushed, re-run reuses it"), with operation-specific
wording, and the duplication is gone.
110 CLI tests pass; eslint + prettier clean; no new tsc errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…; dedup instance-type fetch
Two correctness fixes surfaced by review:
1. Insufficient-gas failures were misclassified as exit 3 ("build failed,
no deployment attempted"). assertSufficientGas runs inside prepare*() AFTER
the image is built+pushed, so an InsufficientGasError surfaces through the
build try/catch — but the image already exists. stageFailure now reclassifies
InsufficientGasError as on-chain (exit 4, "re-run reuses the pushed image"),
matching the documented invariant and giving agents an accurate signal.
2. Non-interactive deploy/upgrade with --dockerfile but no --image-ref slipped
past the all-at-once required-input check, then threw at the interactive
--image-ref prompt as an unclassified exit 1. collectMissingRequiredInputs
now requires --image-ref (the push destination) when building from a local
Dockerfile, so it's reported as invalid-input (exit 2) up front.
Also dedup: fetchAvailableInstanceTypes was byte-identical in deploy.ts and
upgrade.ts — extracted to utils/instanceTypes.ts. (This also removes one
duplicate copy of a pre-existing SkuInfo fallback-shape tsc error: 43 -> 41.)
113 CLI tests pass; eslint + prettier clean; no new tsc errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The platform-rejection message from digest.ts (hit by the prepare.ts pre-flight for a plain --image-ref) still pointed at `docker build` + "use the SDK", while the CLI's prebuilt-verifiable path (dockerhub.ts) already recommended `docker buildx ... --push` OR a server-side verifiable build (--verifiable --repo <repo> --commit <sha>). An arm64 --image-ref is exactly the case where the verifiable-build escape hatch is most useful, so both arm64 entry points now give the same remediation. Updated createPlatformErrorMessage to match dockerhub.ts and asserted both `buildx` and `--verifiable --repo --commit` appear. SDK + CLI unit suites pass; prettier clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…keep --json pure Addresses review feedback on #164: - --wait now does a one-shot read first and only blocks while the app is in a transitional status (created/deploying/upgrading/resuming/stopping/ terminating), matched case-insensitively. Settled statuses (Running, Stopped, Terminated, Suspended, Failed) return immediately instead of polling until the watch timeout. - --wait --json no longer corrupts stdout: the SDK compute module now accepts a logger override, and the command routes SDK progress output ("Waiting for app to start...", "Status: ...") to stderr in JSON mode so stdout stays a single JSON object. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tatuses Align the deploy SKILL.md agent guidance and the --wait flag help text with the shipped behavior: --wait only blocks while the app is transitioning (Deploying/Upgrading/etc.) and returns immediately for already-settled statuses (Running/Stopped/Terminated/Suspended/Failed). Completes the agent-facing docs acceptance criterion; the command, --wait timeout handling, and 502/503/504 retry already landed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enumerate the caller's apps on-chain (getAllAppsByDeveloper), filter out terminated, resolve profile names via the coordinator-DB-backed /info, and error (exit 2) when a live app already uses the requested name — pointing to 'app upgrade <addr>'. --force-new bypasses. The check is fail-open: any read failure warns and proceeds rather than blocking a legitimate deploy. Fixes the duplicate-billable-app leak where the name check only consulted the local YAML registry, so a clean machine/CI (empty registry) never detected an existing same-named app and silently provisioned a second one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After `app upgrade`, poll the release digest until it matches the digest just deployed (or warn "indexer propagation in progress"), so an agent never reads the stale pre-upgrade digest and retries an already-successful upgrade. The expected digest is surfaced out of the SDK (prepareRelease -> PreparedUpgrade -> CLI) so an exact target is known on both the verifiable (sha256:) and non-verifiable (0x bytes32) paths; reconcileReleaseDigest normalizes either form. Adds the reconcileReleaseDigest SDK helper (polls getApp, never throws on timeout) and exposes it on the app module. Verified root cause in compute-tee: GET /apps/:id serves the digest from a Ponder indexer via GraphQL POST with no cache layer (API code has no cache on this path; the GCP HTTP LB has enable_cdn=false). So re-reading -- not cache-busting -- is the fix; the ticket's --fresh/--no-cache criterion is dropped as a no-op against this backend. Closes RND-593. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p info When DOMAIN is unset the app runs but nothing binds ports 80/443, so HTTP(S) requests are refused with no signal — a "Running" web app that is unreachable. - deploy/upgrade now warn pre-flight (after the env file is finalized, so an inline --env DOMAIN=... still counts as on) when isTlsEnabledFromEnvFile is false, pointing to `ecloud compute app configure tls`. - app info shows a static TLS line (gated behind a showTls option so it stays out of the denser app list). DOMAIN is encrypted (private env), so the line is informational rather than a live on/off read. No behavior change when DOMAIN is set. Closes RND-594. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…llet billing status previously printed one ambiguous "Credits" line (the Stripe balance) and a lone wallet-ETH line, so an agent couldn't tell applied promo credit from spendable funds. Now it shows a grouped funds block: - Credits (Stripe): Promotional (+expiry), Paid, Total — pulled from the billing-api 3-way split (new SDK billing.getAccountCredits → the deployed GET /accounts/:eth/credits route), not recomputed in the CLI — with a note that Stripe credits are separate from the on-chain wallet. - Wallet (<env>): ETH (gas) + USDC (via getTopUpInfo, 6 decimals). Each read is best-effort: a failure warns and degrades (credits → single Stripe line; wallet → omit), and core subscription status always prints. Verified live against both deployed billing-API versions: compute-tee (sepolia/mainnet prod, GCP) and ecloud-platform (sepolia-dev, AWS) — both return the promotional/paid/total split on the unprefixed /accounts/:eth/credits route. Closes RND-595. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… ETH Two display fixes in `billing status`, surfaced while testing RND-595: 1. Line items: the Stripe description format is '<qty> × <SKU> (at $price / month)', but the renderer took the last two words as the SKU, yielding garbage like 'Compute (/ month)): ...'. Now parse the SKU with a regex (verbatim fallback if it stops matching) and use the structured quantity/price/subtotal fields. Price is the hourly rate (verified against the published pricing table; the description's '/ month' text is a Stripe-side mislabel), so 'hours × $price/hour' is preserved. Dropped the dead sepolia/mainnet branch. 2. Wallet ETH: render at most 4 decimal places (e.g. 0.0989 ETH) instead of the full 18-decimal wei value — the leading digits are what matter for gas. Both extracted as pure, unit-tested helpers in billingFormat.ts. Live-verified on sepolia (compute-tee); line items are a no-op on sepolia-dev (ecloud-platform returns none).
2365d57 to
63c0273
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up CLI/SDK fixes on top of #164, addressing five UX/DX tickets plus a billing-display bug. Each ticket is its own commit; live-verified against both deployed backends (compute-tee on sepolia/mainnet, ecloud-platform on sepolia-dev).
Changes
RND-590 — deploy guards against duplicate same-named billable apps (
59c69d9)deployenumerated name collisions only against the local YAML registry, so a clean machine/CI (empty registry) silently provisioned a second billable app. Now enumerates the callers apps on-chain (getAllAppsByDeveloper), resolves profile names via/info, and errors (exit 2, pointing toapp upgrade) when a live app already uses the name.--force-new` bypasses. The check is fail-open: a read failure warns and proceeds.RND-592 —
app status --waitdocs (c435144)Align the deploy skill guidance and
--waithelp text with shipped behavior:--waitonly blocks while the app is transitioning and returns immediately for settled statuses.RND-593 — reconcile release digest after upgrade (
b19e4ad)After
app upgrade, the release digest is served by a lagging Ponder indexer, so an agent could read the old digest and retry a successful upgrade. NewreconcileReleaseDigestSDK helper polls until the reported digest matches the just-deployed one (or warns "propagation in progress", exit 0). The digest is surfaced out of the SDK so the exact target is known on both verifiable and non-verifiable paths.--fresh(from the ticket) was dropped after verifying in the server source that the digest comes from a GraphQL POST with no cache layer — re-reading, not cache-busting, is the fix.RND-594 — surface TLS-off (DOMAIN unset) (
3158617)When
DOMAINis unset the app runs but nothing binds ports 80/443, with no signal.deploy/upgradenow warn pre-flight;app infoshows a static TLS line (gated so it stays out of the denserapp list). DOMAIN is encrypted, so the info line is informational.RND-595 — billing status credit split (
6785e88)billing statusprinted one ambiguous "Credits" line. Now shows a grouped funds block: Promotional (+expiry) / Paid / Total from the billing-api 3-way split (new SDKgetAccountCredits), plus Wallet ETH + USDC — with a note that Stripe credits are separate from on-chain funds. Each read is best-effort.Billing display fix (
1ed75f5) + reconcile doc (cfcd923)Pre-existing line-item bug: the renderer took the last two words of the Stripe description as the SKU, yielding garbage like
Compute (/ month)). Now parses the SKU correctly and uses structured price/quantity fields; preserves the (correct) hourly rate, verified against the published pricing table. Also shortens wallet ETH display to 4 dp.New
billing statusoutputThe old format collapsed all of this into a single ambiguous
Credits: $19.98line, with line items rendered asCompute (/ month)).Testing
billing status(both backends),app status --wait(both), and the credit split / line items with a real wallet.🤖 Generated with Claude Code