npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nsicsm/cfed

v0.4.5

Published

CLI for the cfed federal construction proposal AI workspace dogfood loop.

Readme

@nsicsm/cfed

CLI for the cfed federal construction proposal AI workspace. Built for Ahama (BD Lead operator) and Adam (NSI exec demo target) to drive the platform without per-tick curl boilerplate.

Install

npm i -g @nsicsm/cfed
cfed auth login --email [email protected]
# Check your email for the 6-digit code, paste it into the prompt.

That's it — zero-config against the production cfed deployment. No env vars required.

Optional env (dev/staging overrides)

The following default to production values and only need to be set when pointing the CLI at a non-prod cfed:

CFED_APP_URL=http://localhost:3000                       # default: https://cfed.nsicsm.com
CFED_SUPABASE_URL=https://<project_ref>.supabase.co      # default: https://zatknvlpeatcxjewpwlc.supabase.co
CFED_SUPABASE_PUBLISHABLE_KEY=<publishable-key>          # default: prod publishable key

These are PUBLIC values (URLs aren't secrets; Supabase publishable keys are designed to be embedded in client apps and are wire-visible to any anonymous browser hitting the prod web app). The defaults make npm i -g @nsicsm/cfed && cfed auth login zero-config.

Agent/dogfood-mode flags (also optional):

CFED_AGENT=true
CFED_AGENT_NAME=ahama
[email protected]

Optional automation override (Ahama agent only)

CFED_SUPABASE_SERVICE_ROLE_KEY=<service-role-jwt>

End users NEVER set this. It exists only for non-interactive automation that cannot read an inbox (e.g. the BD-Lead Ahama agent). When set, cfed auth login falls back to the legacy admin/generate_link flow: it mints an OTP server-side using the service-role JWT and verifies it inline, with no interactive prompt and no email round-trip. When unset (the default), cfed auth login POSTs to the cfed server route /api/auth/cli-login, which (server-side) mints an OTP via Supabase admin and delivers it via Resend — the user reads the 6-digit code from their inbox and pastes it back into the CLI prompt.

Changelog

0.4.4

  • End-user OTP flow now uses NSI Platform's Resend-backed email delivery (was Supabase built-in SMTP in 0.4.3, which was never wired up — end users never received codes).
  • CLI POSTs to ${CFED_APP_URL}/api/auth/cli-login which server-side mints an OTP via Supabase admin and sends it via Resend.
  • Verify type unified to 'magiclink' for both end-user and agent paths (since both OTPs now come from admin.generateLink).
  • Legacy service-role admin/generate_link path still works when CFED_SUPABASE_SERVICE_ROLE_KEY is set (Ahama agent workflow); byte-identical request semantics.

0.4.3

  • End-user zero-config: cfed auth login --email <addr> no longer requires CFED_SUPABASE_SERVICE_ROLE_KEY.
  • New OTP-code flow: enter the 6-digit code from your email when prompted.
  • Agent/automation users with CFED_SUPABASE_SERVICE_ROLE_KEY set keep the existing admin/generate_link path (no behavior change).

0.4.2

  • Fix: cfed --version now reports the package version correctly (0.4.1 shipped with a stale hardcoded version string in cli.ts).

0.4.1

  • Zero-config: CFED_APP_URL / CFED_SUPABASE_URL / CFED_SUPABASE_PUBLISHABLE_KEY now default to production values; env vars remain as optional dev/staging overrides.
  • CFED_SUPABASE_SERVICE_ROLE_KEY: retained as required. cfed auth login calls Supabase Auth admin/generate_link directly to mint a magic-link OTP — there is no /api/* route substitute because the operator has no user session yet at login time. The service-role key is a secret and is intentionally NOT defaulted.

Commands

cfed auth login [--email <addr>] [--json]

Mints a Supabase session for the given email and persists it to ~/.config/cfed/session.json (mode 600). Required for all subsequent commands.

cfed auth login --email [email protected]
# Check your email for a 6-digit code; paste it into the prompt.

By default (end-user flow, zero-config) the command calls Supabase Auth POST /auth/v1/otp with the publishable key, which triggers an email containing a 6-digit OTP code; the CLI then prompts you to paste the code and verifies it via POST /auth/v1/verify. No secrets required.

If CFED_SUPABASE_SERVICE_ROLE_KEY is set, the command instead calls POST /auth/v1/admin/generate_link (which returns the OTP in the response body) and verifies inline — the non-interactive automation path used by the Ahama BD-Lead agent. End users never need this.

Email template note: the Supabase project's "Magic Link" email template must include the {{ .Token }} variable for the 6-digit code to appear in the user's inbox. Default Supabase templates include both {{ .ConfirmationURL }} and {{ .Token }}. If your email contains only a clickable link with no code, ask your admin to edit the template at Authentication → Email Templates → Magic Link to include {{ .Token }}.

cfed exec <opp-id> [--repair-buckets] [--json]

Runs the executive_summary skill for an opportunity. Refreshes the session in-band if expired (emits a one-line stderr hint on actual refresh).

cfed exec 11111111-1111-1111-1111-111111111111
cfed exec 11111111-1111-1111-1111-111111111111 --repair-buckets

With --repair-buckets, the CLI first verifies the project for that opportunity has all 4 canonical bucket kinds (solicitation, competitive-advantage, deliverables, vendor-bids); any missing kinds trigger the create_project_buckets RPC before the exec call. Orphan buckets are ignored.

cfed worker drain [--max-ticks N] [--until-empty] [--json]

Fires POST /api/work/queue-poll to drain the background worker queue. Reuses the same session-cookie auth as cfed exec — no separate token dance.

cfed worker drain                       # one tick
cfed worker drain --max-ticks 5         # five consecutive ticks, 2s between
cfed worker drain --until-empty         # tick until processed=0 (capped at 200)

Each tick prints processed, succeeded, failed, and runtime_ms. After the run, a one-line cumulative summary is printed. With --json, a single JSON object is emitted to stdout containing all ticks plus totals and a stop_reason (max_ticks, empty_queue, or until_empty_cap).

cfed ingest sam-gov [--since YYYY-MM-DD] [--json]

Fires POST /api/ingest/sam-gov to trigger the Layer 1 SAM.gov ingest pull. Reuses the same session-cookie auth as cfed exec / cfed worker drain — no separate base64 cookie dance.

cfed ingest sam-gov                          # use the route's default window
cfed ingest sam-gov --since 2026-05-01       # override the window start date

Human output is a single compact line: inserted_count, updated_count, amendments_count, parse_queue_count, attachment_count, errors (count). With --json, a single JSON object with the same fields (errors_count) is emitted to stdout. Non-2xx responses surface the upstream error body and exit non-zero.

cfed audit <opp-id> [--json]

Multi-source client-side join over an opportunity: opp basics + recent skill runs + approvals. Three fetches fire in parallel (Promise.all) against existing surfaces — no new server routes were added.

cfed audit 11111111-1111-1111-1111-111111111111
cfed --json audit 11111111-1111-1111-1111-111111111111

Sources:

  • GET /api/opportunities/{id} — opp basics (title, agency, estimated_value, pipeline_stage, deadline_response). 404 → exit 2 with audit <opp-id> not_found.
  • GET /rest/v1/agent_runs?opportunity_id=eq.{id} — recent skill runs (id, agent_type, status, started_at, completed_at, error, result_summary). Direct PostgREST — no dedicated /api/* route exists at v1.
  • GET /rest/v1/approvals?opportunity_id=eq.{id} — approvals (stage, final_status, egm/ogm decisions). Direct PostgREST — no dedicated /api/* route exists at v1.

Human output is a 3-section layout (opportunity / agent_runs / approvals) with bullet rows per entry. With --json, a single object {opportunity, agent_runs, approvals} is emitted to stdout — easy to pipe through jq.

executive_summaries and audit_log joins are deferred to cfed audit v2.1.

cfed pipeline [snapshot] [--near-deadline-days N] [--source X] [--json]

BD-Lead morning snapshot: "what's the state of the world?" One PostgREST round-trip pulls active+terminal-stage opportunities (auto_eliminated rows excluded server-side) and aggregates client-side into per-stage counts, total estimated value, and near-deadline counts.

cfed pipeline                                # 14-day near-deadline window (default)
cfed pipeline snapshot                       # explicit form, same behavior as `cfed pipeline`
cfed pipeline --near-deadline-days 30        # widen the deadline window
cfed pipeline --source sam.gov               # filter to a single ingest source
cfed --json pipeline                         # structured output

Human output:

== pipeline (199 active opps, $127.1M) ==
  forecast              12     $78.8M     5 near deadline
  pending-triage       123     $12.0M    87 near deadline
  pre_rfp                6      $0.7M     4 near deadline
  rfp_released         55      $9.8M    27 near deadline
  proposal_in_progress   2      $1.4M     2 near deadline
  matoc                  1     $25.0M     0 near deadline
== terminal ==
  submitted              1
  awarded                1
  lost                   1

The 6 active stages render in lifecycle order: forecast, pending-triage, pre_rfp, rfp_released, proposal_in_progress, matoc. The 3 terminal stages render in submitted, awarded, lost. Money is formatted as $X.XM / $X.XK / $N for sub-thousand. With --json, the payload is {active: [...], terminal: [...], totals: {active_count, active_value}}. auto_eliminated=true rows are excluded from all counts (matches the active-pipeline kanban filter from 2026-05-12).

cfed pipeline move <opp-id> --stage <target> --reason "..." [--allow-backwards] [--json]

Move an opportunity to a target pipeline_stage. Direct PostgREST writes: PATCH /rest/v1/opportunities (sets pipeline_stage only) followed by POST /rest/v1/audit_log (entry with action='pipeline_move', before_state, after_state). Pipeline move is the BD-Lead's stage-edit primitive — it does NOT touch auto_eliminated (that's cfed triage eliminate's territory) and does NOT trigger any agent runs.

cfed pipeline move 11111111-1111-1111-1111-111111111111 --stage rfp_released --reason "RFP dropped on SAM."
cfed pipeline move 11111111-1111-1111-1111-111111111111 --stage awarded --reason "Award notice received."
cfed pipeline move 11111111-1111-1111-1111-111111111111 --stage forecast --reason "Operator override." --allow-backwards
cfed --json pipeline move 11111111-1111-1111-1111-111111111111 --stage submitted --reason "Proposal submitted."

--stage accepts one of forecast, pending-triage, pre_rfp, rfp_released, proposal_in_progress, matoc, submitted, awarded, lost, cancelled. Any other value exits 2 before any network call. --reason is REQUIRED (missing or empty exits 2). Bad <opp-id> (non-UUID) also exits 2 before any network call.

Backwards-transition guard. Lifecycle order is forecast → pending-triage → pre_rfp → rfp_released → proposal_in_progress → matoc → submitted → awarded. Terminal stages (awarded, lost, cancelled) are end-states. If the requested transition is backwards in this order, OR moves OUT of a terminal stage, the command exits 2 with backwards transition <prev> → <target> requires --allow-backwards. Pass --allow-backwards to override (operator action recorded in audit_log.reason). Same-stage no-op moves are not gated and emit an idempotent audit row.

Opp-not-found exits 2 with pipeline move <id> not_found. PATCH or audit_log non-2xx surface the upstream error body and exit Server (3). No rollback on partial failure — the audit gap is intentional (matches cfed triage accept/eliminate semantics) and surfaces in the next dogfood pass.

Human output: pipeline move <opp-id> → pipeline_stage=<prev>→<target> (reason: <tagged reason>). With --json, the payload is {opportunity_id, action: 'pipeline_move', prev_state, new_state, audit_log_id, reason}. When CFED_AGENT=true, --reason is auto-tagged with [dogfood-<CFED_AGENT_NAME>] (idempotent — preserves an existing prefix).

cfed opportunities list [--stage X] [--source X] [--naics 123456] [--deadline-within 14d] [--value-min 5M] [--limit 20] [--sort deadline|value|created] [--json]

BD-Lead workhorse list view: "show me what's on the board." One PostgREST round-trip pulls non-auto-eliminated opportunities and renders a column-aligned table.

cfed opportunities list                                  # default: deadline asc, limit 20
cfed opportunities list --stage rfp_released --limit 50
cfed opportunities list --source sam.gov --deadline-within 14d
cfed opportunities list --naics 236220 --value-min 5M
cfed opportunities list --sort value                     # highest estimated_value first
cfed --json opportunities list --stage pending-triage    # structured output

Default sort is deadline (asc, nulls last). value and created are desc. Limit defaults to 20 and caps at 200. --value-min accepts 5M, 500k, or a raw integer. --deadline-within Nd restricts to opportunities whose deadline_response falls between now and now+N days (future-only — past deadlines are excluded). --stage is single-value only; multi-stage filtering is intentionally out of scope.

Human output is a column-aligned table:

== opportunities (3) ==
  id        value   agency  title                                               naics   source
  11111111  $25.0M  AFGSC   Hangar reroof, Barksdale AFB                        236220  sam.gov
  22222222  $5.0M   USACE   Levee repair, Tulsa District                        237990  sam.gov
  33333333  $700.0K -       Small project missing agency                        -       -

Columns: id (8-char prefix of UUID) | value ($X.XM / $X.XK / $N) | agency (leaf segment of the hierarchical SAM path — splits on . or /, falls back to full string) | title (truncated to 50 chars with ) | naics | source. With --json, the payload is {opportunities: [...], count, applied_filters}applied_filters echoes the parsed inputs so downstream tooling can verify it parsed 5M5000000 etc.

Subsequent list commands (approvals list, triage queue, eliminated list) will copy this command's shape — idiomatic copy-then-refactor is fine.

cfed approvals list [--pending] [--decided] [--as egm|ogm] [--stage pre_rfp|post_rfp|matoc] [--limit 20] [--json]

Pending-approvals view per BD-Lead E2E findings 2026-05-18, Gap R2. One PostgREST round-trip JOINs approvals with opportunities via embedded-resource syntax, then sorts client-side by parent-opp deadline_response ASC nulls-last and truncates to --limit.

cfed approvals list                          # default: pending, sorted by parent-opp deadline asc, limit 20
cfed approvals list --decided --limit 50     # show decided rows (approved | disapproved)
cfed approvals list --as egm                 # rows EGM uniquely owns (decision still null)
cfed approvals list --as ogm                 # rows OGM uniquely owns (parallel + decision null)
cfed approvals list --stage post_rfp         # filter on approval.stage (NOT opp.pipeline_stage)
cfed --json approvals list --as ogm          # structured output

Default is --pending (matches final_status IS NULL OR final_status='pending' — both forms exist in current DB rows). --decided overrides --pending and matches final_status IN ('approved','disapproved'). --as <role> scopes to rows that role uniquely owns:

  • egm: egm_decision IS NULL AND approval_type IN ('single','parallel')auto_go rows are already pre-approved.
  • ogm: ogm_decision IS NULL AND approval_type='parallel' — single approvals are EGM-only by convention; auto_go is already done.

--stage filters on the approval row's own stage column (pre_rfp | post_rfp | matoc), not the parent opportunity's pipeline_stage. Limit defaults to 20 and caps at 200. The fetch over-pulls 2x the limit so the client-side deadline sort honors --limit even though PostgREST embedded-resource ORDER syntax has inconsistent support across Supabase versions.

Human output is a column-aligned table:

== approvals (2) ==
  approval_id  stage     type    egm  ogm  opp_id    value   title                          deadline
  aaaaaaaa     pre_rfp   single  -    -    bbbbbbbb  $25.0M  Hangar reroof, Barksdale AFB   2026-06-01
  cccccccc     post_rfp  dual    -    -    dddddddd  $5.0M   Levee repair, Tulsa District   2026-06-14

Columns: approval_id (8-char) | stage | type (single/dual/auto — DB approval_type single/parallel/auto_go mapped to display labels) | egm decision | ogm decision | opp_id (8-char) | value ($X.XM) | title (truncated 40 chars) | deadline (YYYY-MM-DD). With --json, the payload is {approvals: [...], count, applied_filters} — the embedded opportunities object is preserved on each approval row so downstream tooling can read title/value/deadline/pipeline_stage without a second fetch.

cfed triage queue [--state recommended|untriaged|all] [--decision keep|reject|review] [--confidence-min 0..1] [--rule H1] [--flag <name>] [--limit 20] [--sort value|confidence] [--json]

"Review Required" surface per BD-Lead E2E findings 2026-05-18, Gap R4. Two PostgREST round-trips — one for pending-triage opportunities, one for completed opportunity_triage agent_runs — then a client-side anti-join + filter + sort. Mirrors the cfed pipeline / cfed approvals list fetch-then-aggregate pattern.

cfed triage queue                                    # default: recommended, value DESC, limit 20
cfed triage queue --state untriaged --limit 50       # the 123 pre-trigger backlog
cfed triage queue --state all                        # both buckets, value DESC, limit 20
cfed triage queue --decision keep --confidence-min 0.8
cfed triage queue --rule H1                          # opps the skill flagged on rule H1
cfed triage queue --flag set_aside_8a                # opps with the 8(a) set-aside flag
cfed triage queue --sort confidence                  # highest-confidence first
cfed --json triage queue --state all                 # structured output

Three states:

  • recommended (default): pending-triage opps that have a completed opportunity_triage agent_run. The "ready for human review" cohort.
  • untriaged: pending-triage opps that have NO completed opportunity_triage agent_run — the pre-trigger backlog (123 opps at 2026-05-18).
  • all: union of the two buckets.

Filters (all apply on the parsed result_summary jsonb on the latest completed triage run):

  • --decision keep|reject|review matches result_summary.decision = 'accept' | 'reject' | 'human-review'. Combining --decision with --state untriaged is a Validation error (untriaged rows have no triage run).
  • --confidence-min N keeps rows with result_summary.confidence >= N.
  • --rule <code> keeps rows whose result_summary.rules_applied array contains <code> (e.g. H1).
  • --flag <name> keeps rows whose result_summary.flags array contains <name>.

Default sort is value (DESC, nulls last). confidence sorts by result_summary.confidence DESC nulls-last. Limit defaults to 20 and caps at 200.

Human output is a column-aligned table:

== triage queue (2) [recommended=2 untriaged=123] ==
  id        decision  confidence   value  agency  title                              rules     flags
  11111111  KEEP            0.95  $25.0M  AFGSC   Hangar reroof, Barksdale AFB       H1,H2,H4  -
  22222222  REJECT          0.62   $5.0M  USACE   Levee repair, Tulsa District       H3        set_aside_8a

Columns: id (8-char opp UUID prefix) | decision (KEEP for accept, REJECT for reject, REVIEW for human-review) | confidence (two decimals, e.g. 0.95; - if null) | value ($X.XM / $X.XK / $N) | agency (leaf segment of SAM path) | title (truncated 40 chars) | rules (comma-joined, max 3 with +N remainder) | flags (comma-joined, first match with +N remainder).

With --json, the payload is {opportunities: [...], count, applied_filters, state_summary: {recommended, untriaged}}. state_summary reflects total counts in BOTH buckets regardless of which --state is filtered, so the operator always sees how many rows live in the other bucket. Each opportunities[] row exposes both opportunity (id, title, agency, estimated_value, pipeline_stage, source) and agent_run (id, status, started_at, completed_at, result_summary) — agent_run is null for untriaged rows.

cfed triage accept <opp-id> --reason "..." [--target-stage forecast|pre_rfp] [--json]

Accept a pending-triage opportunity into the active pipeline. Direct PostgREST writes: PATCH /rest/v1/opportunities (sets pipeline_stage + auto_eliminated=false) followed by POST /rest/v1/audit_log (entry with action='triage_accept', before_state, after_state). Both refuse to act unless the opportunity's current pipeline_stage is pending-triage — this is the human-in-the-loop guard on the Review Required surface.

cfed triage accept 11111111-1111-1111-1111-111111111111 --reason "Strong fit; advance to forecast."
cfed triage accept 11111111-1111-1111-1111-111111111111 --reason "Skip forecast; push to pre_rfp." --target-stage pre_rfp
cfed --json triage accept 11111111-1111-1111-1111-111111111111 --reason "..."   # structured output

--reason is REQUIRED (missing or empty exits 2). --target-stage accepts forecast (default) or pre_rfp; any other value exits 2 before any network call. Bad <opp-id> (non-UUID) also exits 2 before any network call.

Stage guard: if the opportunity is not in pending-triage, exits 2 with opp <id> stage=<stage>, not pending-triage — triage accept only valid on Review Required queue. Opp-not-found exits 2 with triage accept <id> not_found.

Human output: triage accept <opp-id> → pipeline_stage=<target> (reason: <reason>). With --json, the payload is {opportunity_id, action: 'triage_accept', prev_state, new_state, audit_log_id, reason}. audit_log_id is null if the audit insert returns no body row (defensive — PostgREST Prefer: return=representation normally returns the inserted row).

cfed triage eliminate <opp-id> --reason "..." [--json]

Manually eliminate a pending-triage opportunity. Sets opportunities.auto_eliminated=true and writes an audit_log entry with action='triage_eliminate'. Does NOT change pipeline_stage — the opp stays in pending-triage but falls off the active pipeline via the auto_eliminated=true filter (matches the existing Fort Worth seed pattern). Same stage guard as accept.

cfed triage eliminate 11111111-1111-1111-1111-111111111111 --reason "8(a) set-aside; NSI not 8(a)-certified."
cfed --json triage eliminate 11111111-1111-1111-1111-111111111111 --reason "..."

--reason is REQUIRED. Validation errors mirror triage accept. Human output: triage eliminate <opp-id> → auto_eliminated=true (reason: <reason>). With --json, the payload is {opportunity_id, action: 'triage_eliminate', prev_state, new_state, audit_log_id, reason}; new_state.pipeline_stage is always pending-triage.

When CFED_AGENT=true, --reason is auto-tagged with [dogfood-<CFED_AGENT_NAME>] for both accept and eliminate (idempotent — preserves an existing prefix). Mirrors cfed decide.

PR #4 ships cfed triage accept and cfed triage eliminate — the first action commands under cfed triage. The CLI is bidirectional at 0.4.0. Bulk-action and non-triage-stage variants (cfed eliminate, cfed pipeline move) ship in follow-up PRs.

cfed eliminated list [--since 30d] [--method auto|manual|terminal] [--naics 236220] [--limit 20] [--json]

"Show me what fell off the board" view (v2.1.4). One PostgREST round-trip pulls opportunities that match the eliminated criteria within the --since window, then derives method + eliminated_at client-side.

cfed eliminated list                          # default: last 30d, sort updated_at DESC, limit 20
cfed eliminated list --since 7d               # last 7 days only
cfed eliminated list --method auto            # only auto_eliminated=true rows
cfed eliminated list --method terminal        # only lost / cancelled rows
cfed eliminated list --naics 236220 --limit 50
cfed --json eliminated list --since 90d       # structured output

Eliminated criteria (server-side OR-filter):

  • auto_eliminated = true, OR
  • pipeline_stage IN ('eliminated', 'lost', 'cancelled')

Time window filters on updated_at (best available signal for "when did this opp flip to eliminated" without joining audit_log). eliminated_at is rendered as YYYY-MM-DD from updated_at, falling back to created_at if updated_at is null.

method is derived client-side (auto_eliminated takes precedence over stage):

  • autoauto_eliminated=true
  • terminalpipeline_stage IN (lost, cancelled) AND not auto-eliminated
  • manual — anything else (e.g. pipeline_stage='eliminated' from an operator action)

Validation: --since must match ^\d+d$ (e.g. 7d, 30d, 90d); --method must be one of auto|manual|terminal; --limit defaults to 20, caps at 200.

Human output is a column-aligned table:

== eliminated (2) ==
  id        eliminated_at  method    reason   value  agency  title
  11111111  2026-05-10     auto      -       $25.0M  AFGSC   Hangar reroof, Barksdale AFB
  22222222  2026-05-12     terminal  -        $5.0M  USACE   Levee repair, Tulsa District

Columns: id (8-char opp UUID prefix) | eliminated_at (YYYY-MM-DD) | method (auto/manual/terminal) | reason | value ($X.XM / $X.XK / $N) | agency (leaf segment of SAM path) | title (truncated 40 chars).

reason is hard-coded to - in v2.1.4. v2.2.x action commands (cfed eliminated restore, cfed triage eliminate, etc.) will populate audit_log and a follow-up will JOIN that for richer reason text. The cfed eliminated namespace is reserved for these future siblings.

With --json, the payload is {opportunities: [...], count, applied_filters}. Each opportunities[] row exposes opportunity (full DB row), eliminated_at, method, and reason.

cfed eliminate <opp-id> --reason "..." [--json]

Non-triage eliminate path (v2.2.2). Kills an opportunity at ANY pipeline_stage — post-triage, in-progress, awarded, lost, anything. Distinct from cfed triage eliminate, which is the human-in-the-loop guard on the pending-triage Review Required queue. Same DB effect (auto_eliminated=true, pipeline_stage preserved), different audit_log.action value (eliminate vs triage_eliminate) so dashboards can distinguish the two cohorts.

cfed eliminate 11111111-1111-1111-1111-111111111111 --reason "Customer cancelled the project."
cfed --json eliminate 11111111-1111-1111-1111-111111111111 --reason "..."

--reason is REQUIRED (missing or empty exits 2). Bad <opp-id> (non-UUID) exits 2 before any network call. Opp-not-found exits 2 with eliminate <id> not_found. No stage validation — works at every stage by design. auto_eliminated=true is the falls-off-active-pipeline mechanism; pipeline_stage is left untouched so historical context survives.

Human output: eliminate <opp-id> → auto_eliminated=true (reason: <tagged reason>). With --json, the payload is {opportunity_id, action: 'eliminate', prev_state, new_state, audit_log_id, reason}; new_state.pipeline_stage always equals prev_state.pipeline_stage. When CFED_AGENT=true, --reason is auto-tagged with [dogfood-<CFED_AGENT_NAME>] (idempotent — preserves an existing prefix). Restore-from-eliminated is a separate ticket (Gap #5) — this command is one-way at v2.2.2.

cfed decide <approval-id> --as <egm|ogm> --decision <approved|disapproved> [--reason "..."] [--json]

Records a go/no-go decision on an approval row. After the decide call succeeds, the CLI does a best-effort read-back against the opportunity to surface the updated pipeline_stage and auto_eliminated flag — this exposes stage-transition / zombie-state side-effects inline. Read-back failures emit a stderr warning but do not gate exit 0 (the decide call already succeeded).

cfed decide 5dc14d7d-570e-457c-8f2a-caf151ec1f7c --as egm --decision approved --reason "Strong fit, approve to advance."
cfed decide 5dc14d7d-570e-457c-8f2a-caf151ec1f7c --as egm --decision disapproved --reason "8(a) set-aside, NSI not 8(a)-certified."

When CFED_AGENT=true, --reason is auto-tagged with [dogfood-<CFED_AGENT_NAME>] (idempotent — preserves an existing prefix).

PR #3 ships decide. The CLI is feature-complete at 0.3.0.

Exit codes

  • 0 success
  • 1 env / session error
  • 2 validation error (bad arg)
  • 3 server error (API / RPC failure)
  • 4 unexpected

Spec

See ../.k2so/specs/cli-cfed-v1.md for the full v1 spec and acceptance criteria. Working HTTP reference at ../.k2so/specs/cli-cfed-v1-curl-reference.md.