Skip to content

Token efficient deploy and retrieves via --quiet and --no-progress flags#1585

Open
jprichter wants to merge 1 commit into
salesforcecli:mainfrom
jprichter:feat/token-efficient-deploy
Open

Token efficient deploy and retrieves via --quiet and --no-progress flags#1585
jprichter wants to merge 1 commit into
salesforcecli:mainfrom
jprichter:feat/token-efficient-deploy

Conversation

@jprichter

Copy link
Copy Markdown

Why

sf project deploy start is verbose even with --concise. Output comes in two phases:

  1. During execution: the live MultiStageOutput streaming block (stages + per-stage counters + footer). In a TTY this redraws in place, but when a harness captures piped stdout, each redraw can land as a fresh line which is a large, repetitive token sink.
  2. After completion: the DeployResultFormatter report (the "Deployed Source" table listing every component, plus failures/deletes/test results).

--concise only narrows phase 2 (drops the success table; keeps failures), and has no effect on phase 1 — which is most of the residual verbosity. There was no single switch to get genuinely minimal output, and nothing surfaced the --json --concise recipe.

Output matrix

Switch Phase 1: streaming progress Phase 2: final report
(default / normal) full full success table
--concise full failures only
--no-progress OFF full success table (unchanged)
--quiet OFF one-line summary / minimal

--quiet--no-progress on the streaming axis (both turn streaming off) but differs on the report axis. So --quiet is implemented as "imply --no-progress behavior + collapse the report," reusing one shared suppression primitive rather than two parallel code paths.

Measured impact

Against a live scratch org (sample-project-multiple-packages), a representative deploy:

Mode stdout bytes
baseline (default) ~19,423
--no-progress ~700
--quiet ~90

~99.5% reduction at --quiet. Example --quiet success line:
Deployed 2/2 components to test-…@example.com (Deploy ID …).

What

Adds opt-in "minimal output" modes to the deploy/retrieve commands so that AI coding harnesses and other token or log-conscious consumers don't pay an out-sized cost every time an agent deploys or retrieves metadata. Three coordinated pieces:

  1. --quiet — a true minimal-output verbosity that both suppresses the live streaming progress and collapses the final result report to a single summary line.
  2. --no-progress flag + SF_DEPLOY_PROGRESS env var — suppress only the live streaming MultiStageOutput, leaving the full final report intact. This is the reusable primitive that --quiet builds on (flag overrides env var; both default to showing progress).
  3. Documentation of the already-working --json --concise recipe, and clarification of what --concise actually does.

All behavior is opt-in. No command's default output changes.

Commands affected

--quiet / --no-progress added to:

  • project deploy start
  • project deploy validate
  • project deploy quick (no live progress stream; --quiet mainly trims the final report —
    --no-progress is accepted for parity, documented as such)
  • project deploy resume
  • project retrieve start

Deliberately excluded: deploy preview / retrieve preview — their entire output is the component list, so a "quiet preview" would print almost nothing.

How

  • Shared suppression primitive. shouldShowDeployProgress({ jsonEnabled, quiet, noProgress }) in src/utils/deployStages.ts centralizes the precedence: --json → off; --quiet/--no-progress→ off; otherwise SF_DEPLOY_PROGRESS (showDeployProgress()); otherwise default on. Every deploy command wires its DeployStages through this one helper.
  • Verbosity ladder. DeployResultFormatter.determineVerbosity() resolves
    verbose > quiet > concise > normal; --quiet collapses the report to the one-line summary.
  • Resume cache precedence. resolveResumeVerbose() ensures a current explicit verbosity flag (--quiet/--concise) overrides a cached verbose: true from the original deploy.
  • Retrieve. Retrieve has its own inline MultiStageOutput and no concise/verbose ladder, so it gets --quiet (one-line "Retrieved N files from …" summary on success) + --no-progress only — the smallest change that solves the token problem. (See "Pre-existing asymmetry" below.)

Reviewer call-out: the jsonEnabled reuse in deployStages.ts

The MultiStageOutput is suppressed via jsonEnabled: jsonEnabled || !showProgress. This is intentional and commented. MultiStageOutput's jsonEnabled option is effectively a non-interactive-mode toggle; when true it skips painting the live stage UI. It does not change serialization. OR-ing in !showProgress reuses that existing "don't render to the terminal" behavior for a non-JSON reason (--quiet/--no-progress). The only mismatch is nominal (the option is named for its most common trigger). A follow-up is noted to add a dedicated suppressOutput/enabled option upstream in @oclif/multi-stage-output and retire the overload.

Pre-existing asymmetry

Deploy has a verbose | concise | normal ladder and retrieve does not. This change narrows the gap that matters for token cost (deploy and retrieve now share --quiet + --no-progress). The remaining gap (no --concise/--verbose on retrieve) is left in place.

Testing

  • Unit: 147 passing, including new command-level coverage:
    • test/commands/deploy/quick.test.ts — quiet suppresses command-owned success logs, failure detail still surfaces under --quiet.
    • test/commands/deploy/resume.test.tsresolveResumeVerbose precedence, incl. a current --concise overriding cached verbose.
    • test/utils/deployStages.test.tsshouldShowDeployProgress precedence matrix.
    • Updated test/utils/output.test.ts (ANSI-aware assertions) and test/commands/retrieve/start.test.ts.
  • NUT: test/nuts/deploy/quiet.nut.ts — 9 cases run green against a live Dev Hub
    (TESTKIT_AUTH_URL / AUTO strategy): baseline, --no-progress, SF_DEPLOY_PROGRESS=false, --quiet, --quiet --json (files: []), flag exclusivity (oclif exit 2), validate --quiet ("Validated" wording), resume --quiet, retrieve --quiet, and the preview guard.
  • Snapshot: command-snapshot.json regenerated showing all five in-scope commands carry quiet + no-progress, both preview commands excluded.
  • Docs: message files updated (incl. the output matrix and the --json --concise recipe). README.md is intentionally excluded as it's regenerated and committed by the release bot (svc-cli-bot, chore(release)`), which will pick up the new flag help from the message files at the next release. (Regenerating it locally produced unrelated tooling-version churn so it was reverted.)

Potential Follow-ups

  • Add a dedicated suppressOutput/enabled option to @oclif/multi-stage-output and retire the jsonEnabled || !showProgress overload here.
  • project deploy report could similarly gain from --quiet.

Backwards compatibility

No default output changes; every new behavior is opt-in via flag or env var. --quiet is mutually exclusive with --verbose/--concise (oclif exits 2 on conflict). The --json payload shape is unchanged.

@jprichter jprichter requested a review from a team as a code owner June 12, 2026 03:59
@salesforce-cla

Copy link
Copy Markdown

Thanks for the contribution! Before we can merge this, we need @jprichter to sign the Salesforce Inc. Contributor License Agreement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant