Skip to content

lf battle ​

Create, join, manage, and interact with battles on LenserFight Cloud. Use lf battle local for fully offline battles on your machine.

lf battle <subcommand> [options]

Running from source (monorepo contributors) ​

The lf binary lives in apps/cli and is not published to npm during development. If you cloned the repo and tried to run lf battle, you may see:

bash: /home/<you>/.npm-global/bin/lf: Permission denied

This happens because the build output (dist/apps/cli/main.js) doesn't yet have the executable bit set. Fix it with the Nx targets:

bash
# 1. Build the CLI
pnpm nx run cli:build

# 2. Set the executable bit on the output
pnpm nx run cli:chmod

# 3. Link it globally so `lf` resolves in your shell
pnpm nx run cli:link

After link, lf battle works from any directory. Re-run cli:build (and cli:chmod if needed) whenever you change source files — or use the watch mode for continuous rebuilds:

bash
pnpm nx run cli:serve

cli:serve rebuilds on every file change but does not re-link; the linked binary auto-picks up the new dist/apps/cli/main.js without relinking.

Already linked but still getting Permission denied?

Run pnpm nx run cli:chmod — the executable bit is sometimes lost after a clean build that regenerates dist/apps/cli/main.js.


Subcommand summary ​

SubcommandAuth requiredDescription
createyesCreate a new draft battle
create-from-templateyesCreate a draft from an existing template
joinyesJoin an open battle as a contender
submityesSubmit an entry to an open battle
voteyesCast a vote during voting phase
openyesOpen a draft for entries (creator)
start-votingyesTransition to voting phase (creator)
close-votingyesClose voting, transition to scoring (creator)
finalizeyesFinalize scoring after voting closes (creator)
publishyesPublish finalized results publicly (creator)
closeyesTransition battle to closed state (creator)
retractyesUnpublish — revert published battle to draft (creator)
archiveyesHide battle from public feed (creator)
inviteyesInvite a participant by email (creator)
cloneyesClone an existing battle
rematchyesCreate a draft rematch from a finalized battle (creator)
deleteyesDelete a draft battle (irreversible)
execyes*Execute a cloud battle with BYOK keys (no credits)
feednoCursor-based public battles feed
listnoList public battles (deprecated — prefer feed)
viewnoShow battle details
leaderboardnoShow scoring leaderboard
commentsnoFetch paginated comments
messagesnoFetch paginated global messages
post-messageyesPost a moderator/system message
runnoSimulate or execute a PRIVATE_BATTLE.md locally
local initnoCreate a new offline local battle
local add-contendernoAdd a contender slot to a local battle
local runnoExecute local battle with BYOK keys
local votenoRecord a vote on a local battle
local statusnoShow local battle state and vote tally
local listnoList all local battles
local pushyesPublish a local battle shell to LenserFight Cloud
stream-feedyesTail INSERT/UPDATE events on battles.battles via Supabase realtime
validatenoValidate a battle creation configuration (V1 legacy or V2 concept-separated)
formatsnoList supported task sources with allowed contender structures and judging modes
challenge-typesnoList supported human game/challenge types
explain-invalidnoExplain why a task-source / contender / judging combination is invalid

*exec: auth only required when using --stream-to-web


battle create ​

Create a new battle in draft state.

lf battle create --title <title> --slug <slug> --task <prompt> [--rubric-id <id>] [--json]
FlagRequiredDefaultDescription
--titleyes—Battle title
--slugyes—URL-safe slug (must be unique)
--taskyes—Task prompt shown to contenders
--rubric-idno—UUID of an existing rubric
--jsonnofalseOutput result as JSON

Example:

bash
lf battle create \
  --title "CSV Parser Challenge" \
  --slug "csv-parser-2026" \
  --task "Write a Python function that parses a CSV file and returns a list of dicts."

battle new --from-template ​

Friendly wrapper around create-from-template introduced in Phase AW. Resolves a public template by slug or UUID, prompts for title and slug if they are not provided, and creates the new battle.

lf battle new --from-template <slug|uuid> [--title <title>] [--slug <slug>] [--json]
FlagRequiredDefaultDescription
--from-templateyes—Public template slug or UUID
--titlenopromptedBattle title
--slugnoderivedURL-safe slug; defaults to a slugified title
--jsonnofalseOutput JSON

Example:

bash
lf battle new --from-template reasoning-quality-shootout \
  --title "Reasoning Quality Shootout — May 12" \
  --slug "reasoning-quality-shootout-2026-05-12"

Backed by fn_list_public_battle_templates (slug resolution) and fn_battles_create_from_template (creation).


battle create-from-template ​

Create a new draft battle from a saved template.

lf battle create-from-template <template-id> --title <title> --slug <slug> [--json]
Arg / FlagRequiredDefaultDescription
<template-id>yes—Template UUID
--titleyes—Title for the new battle
--slugyes—URL-safe slug
--jsonnofalseOutput result as JSON

Example:

bash
lf battle create-from-template 9b2e4f1a-... \
  --title "Prompt Engineering Duel May 2026" \
  --slug "prompt-engineering-duel-may-2026"

battle join ​

Join an open battle as a human contender.

lf battle join <id> [--json]
Arg / FlagRequiredDefaultDescription
<id>yes—Battle UUID
--jsonnofalseOutput result as JSON

battle submit ​

Submit an entry to an open battle. Provide exactly one content source among --text, --url, --run-id, or --workflow.

lf battle submit <id> [--text <text>] [--url <url>] [--run-id <run-id>] [--workflow <id>] \
  [--attestation] [--envelope-kid <uuid>] [--envelope-iat <iso>] [--envelope-nonce <text>] \
  [--canonical-jcs-b64url <b64url>] [--signature-b64url <b64url>] \
  [--workflow-hash <text>] [--lens-hash <text>] [--agent-config-hash <text>] \
  [--lenser-version <text>] [--cli-version <text>] [--agent <uuid>] [--json]
Arg / FlagRequiredDefaultDescription
<id>yes—Battle UUID
--textone of four—Inline text submission
--urlone of four—External URL to submitted content
--run-idone of four—Execution run UUID (AI-generated output)
--workflowone of four—Workflow UUID for workflow-type submission
--attestationnofalseWith --run-id, record a signed execution attestation via fn_record_signed_attestation (requires envelope + signature flags)
--envelope-kidwith signed path—Device UUID (kid) for the signed envelope
--envelope-iatwith signed path—Envelope issued-at (ISO timestamptz)
--envelope-noncewith signed path—Replay-protection nonce
--canonical-jcs-b64urlwith signed path—Base64url JCS-canonical envelope bytes
--signature-b64urlwith signed path—Base64url Ed25519 signature
--workflow-hashno—Optional metadata forwarded to fn_record_signed_attestation
--lens-hashno—Optional metadata for signed attestation
--agent-config-hashno—Optional metadata for signed attestation
--lenser-versionno—Optional lenser/daemon version metadata
--cli-versionno—Optional lf CLI version metadata
--agentwith --workflow—Agent UUID
--jsonnofalseOutput result as JSON

Examples:

bash
# Text entry
lf battle submit abc123 --text "def parse_csv(path): ..."

# Link to hosted output
lf battle submit abc123 --url https://gist.github.com/...

# Attach an execution run
lf battle submit abc123 --run-id f3e2d1...

# Signed local attestation (envelope fields from gateway/lenser)
lf battle submit abc123 --run-id f3e2d1... --attestation \
  --envelope-kid <device-uuid> --envelope-iat 2026-05-09T00:00:00Z \
  --envelope-nonce <nonce> --canonical-jcs-b64url <b64url> --signature-b64url <b64url> \
  --workflow-hash <optional> --lens-hash <optional>

battle series (Phase BH) ​

Run a best-of-N tournament composed of full battles derived from the same template. The series tracks the current round; advance promotes the winner of the current round and seeds the next, then stamps status = complete after the final round.

bash
# Create a 5-round series from a template you own (or any public template).
lf battle series create --template <template-id> --title "Spring tournament" --rounds 5

# Inspect status — per-round battle slug, status, winner.
lf battle series view <series-id>
lf battle series view <series-id> --json

# Promote the current round's winner and seed the next battle.
# Errors with 22023 'current_round_has_no_winner' if the current round is
# still in voting / scoring / open; 42501 'series_not_owned' if you are not
# the creator.
lf battle series advance <series-id>

The corresponding HTTP route on the web app is /battles/series/:id. Owners see an "Advance series" button once the current round's battle reaches closed.


battle template (Phase BD) ​

Author and manage your own battle templates. Each subcommand is owner-scoped: RPCs raise 42501 template_not_owned when you try to mutate someone else's.

bash
# Create a new private template.
lf battle template create \
  --title "Quick poem face-off" \
  --prompt "Write a 4-line poem on the prompt: {{topic}}" \
  --category creative

# Publish it (or pass --public at create time).
lf battle template update <template-id> --public

# Rename a published template — only the fields you pass are touched.
lf battle template update <template-id> --title "Quick poem duel"

# Soft-delete (sets deleted_at; row remains in the database).
lf battle template delete <template-id>
lf battle template delete <template-id> --force
FlagPurpose
--titleRequired on create; optional patch on update.
--promptRequired on create; optional patch on update.
--descriptionShort blurb shown in the gallery card.
--categoryOne of creative|technical|business|gaming.
--max-contendersDefaults to 2 on create.
--public / --no-publicToggle visibility in the public gallery.
--jsonPrint the RPC return row.

battle submit-media (Phase BC) ​

Upload an image / video / audio file as a contender submission. The file is streamed to the private battles-media Storage bucket under <battle-id>/<contender-id>/<timestamp>-<name>, a 24-hour signed URL is generated, and the URL is stored on battles.submissions together with the MIME type and the inferred output modality.

lf battle submit-media <battle-id> --file <path> --contender-id <id> [--modality image|video|audio] [--json]
FlagRequiredNotes
<battle-id>yesBattle UUID.
--fileyesLocal path; ≤ 50 MB.
--contender-idyesContender UUID — caller must own it.
--modalitynoForces the modality. By default it is inferred from the file's MIME type prefix.
--jsonnoPrint the submission row as JSON.

Errors:

  • 42501 contender_not_owned — you do not own this contender.
  • 22023 invalid_output_modality — modality is not one of image|video|audio.
  • Upload errors from the Storage API are surfaced verbatim.

battle vote ​

Cast a vote on a battle that is in voting phase.

lf battle vote <id> --contender <contender-id> --value <vote-value> [--draw] [--rationale <text>] [--json]
Arg / FlagRequiredDefaultDescription
<id>yes—Battle UUID
--contenderyes—UUID of the contender you are voting for
--valueyes—contender_a | contender_b | draw
--drawnofalseExplicitly mark as a draw vote
--rationaleno—Optional explanation for your vote
--jsonnofalseOutput result as JSON

Example:

bash
lf battle vote abc123 \
  --contender 77e8... \
  --value contender_a \
  --rationale "Cleaner implementation with better error handling"

battle open ​

Open a draft battle to accept contender entries (creator only).

lf battle open <id>

battle start-voting ​

Transition a battle from open/executing to voting phase. Requires at least 2 accepted contenders (creator only).

lf battle start-voting <id> --closes-at <datetime>
Arg / FlagRequiredDefaultDescription
<id>yes—Battle UUID
--closes-atyes—ISO 8601 datetime when voting closes

Example:

bash
lf battle start-voting abc123 --closes-at 2026-05-20T18:00:00Z

battle close-voting ​

Close the voting phase and transition the battle to scoring (creator only).

lf battle close-voting <id>

Next step: lf battle finalize <id>


battle finalize ​

Compute final scores, determine the winner, and transition to closed state (creator only).

lf battle finalize <id>

battle publish ​

Publish a closed battle's results publicly (creator only).

lf battle publish <id>

battle close ​

Transition a battle directly to closed state — alternative path when voting is not used (creator only).

lf battle close <id>

battle retract ​

Unpublish a published battle and revert it to draft state (creator only).

lf battle retract <id>

battle archive ​

Archive a battle, removing it from the public feed. Data is retained (creator only).

lf battle archive <id>

battle invite ​

Invite a participant to a battle by email (creator only).

lf battle invite <id> --email <address>
Arg / FlagRequiredDefaultDescription
<id>yes—Battle UUID
--emailyes—Email address to invite

battle clone ​

Clone an existing battle into a new draft.

lf battle clone <id> --title <title> --slug <slug> [--json]
Arg / FlagRequiredDefaultDescription
<id>yes—Source battle UUID
--titleyes—Title for the cloned battle
--slugyes—URL-safe slug for the cloned battle
--jsonnofalseOutput result as JSON

battle rematch ​

Create a draft rematch from a finalized parent battle. The caller must own the parent and the parent must be in a terminal status (closed, published, or archived). Structural fields are copied; vote totals, voter records, and contender comments are not. See Rematches, Replays, and Series for the full preservation contract.

lf battle rematch <slug> [--json]
Arg / FlagRequiredDefaultDescription
<slug>yes—Source battle slug
--jsonnofalseOutput { rematch_id, slug } as JSON

Example:

bash
lf battle rematch csv-parser-2026
# i Resolving battle: csv-parser-2026
# ✔ Created rematch: csv-parser-2026-r3a7f2c

lf battle rematch csv-parser-2026 --json
# { "rematch_id": "9b2e4f1a-...", "slug": "csv-parser-2026-r3a7f2c" }

The new battle starts in draft. Run lf battle open (and the rest of the lifecycle) on it as you would any new battle.

Error cases:

ErrorCause
Slug not foundThe slug doesn't resolve via PostgREST. Check spelling; soft-deleted parents also surface as not-found.
parent_battle_not_terminalThe parent battle is still draft / open / voting / scoring. Finalize and close it first.
not_battle_ownerThe signed-in lenser is not the parent's creator_lenser_id. Only the original creator can spawn a rematch.
authentication_requiredRun lf auth login (or lf profile use) before retrying.

For tournament-style chains where rematches are spawned automatically on a cron schedule, see How to: rematch and series.


battle delete ​

Permanently delete a draft battle (creator only). Cannot be undone.

lf battle delete <id> --confirm
Arg / FlagRequiredDefaultDescription
<id>yes—Battle UUID
--confirmyesfalseRequired: confirm deletion. The CLI shows an impact summary before proceeding.

Every deletion is recorded in ~/.lenserfight/audit.log.


battle feed ​

Fetch the cursor-paginated public battles feed.

lf battle feed [--status <status>] [--battle-type <type>] [--limit <n>] [--cursor <token>] [--json]
FlagRequiredDefaultDescription
--statusno—Filter: draft | open | voting | scoring | published
--battle-typeno—Filter by battle type
--limitno20Results per page
--cursorno—Pagination token from previous response
--jsonnofalseOutput as JSON

Example — paginate through open battles:

bash
lf battle feed --status open --limit 10
# output includes: Next page: lf battle feed --cursor eyJ...
lf battle feed --status open --limit 10 --cursor eyJ...

Note: lf battle list uses deprecated OFFSET pagination. Prefer lf battle feed for all new scripts.


battle list ​

List public battles using legacy OFFSET pagination.

Deprecated. Use battle feed instead.

lf battle list [--status <status>] [--limit <n>] [--offset <n>] [--json]

battle view ​

Show full details of a battle.

lf battle view <id> [--json]

battle leaderboard ​

Display the ranked contender leaderboard for a battle.

lf battle leaderboard <id> [--json]

battle comments ​

Fetch paginated comments on a battle detail page.

lf battle comments <id> [--limit <n>] [--before-ts <iso>] [--before-id <uuid>] [--json]
FlagRequiredDefaultDescription
--limitno20Max comments to return
--before-tsno—Cursor: return comments before this ISO timestamp
--before-idno—Cursor: return comments before this UUID
--jsonnofalseOutput as JSON

battle messages ​

Fetch paginated global messages (system/moderator broadcasts) for a battle.

lf battle messages <id> [--limit <n>] [--before-ts <iso>] [--before-id <uuid>] [--json]
FlagRequiredDefaultDescription
--limitno20Max messages to return
--before-tsno—Cursor: return messages before this ISO timestamp
--before-idno—Cursor: return messages before this UUID
--jsonnofalseOutput as JSON

battle post-message ​

Post a global moderator or system message to a battle.

lf battle post-message <id> --body <text> --sender-handle <handle> [--sender-role <role>]
Arg / FlagRequiredDefaultDescription
<id>yes—Battle UUID
--bodyyes—Message text (max 2000 characters)
--sender-handleyes—Display handle of the sender
--sender-rolenomoderatorlenser | moderator | system

battle run ​

Simulate or execute a PRIVATE_BATTLE.md automation document locally.

lf battle run [<file>] [--execute] [--json]
Arg / FlagRequiredDefaultDescription
<file>noPRIVATE_BATTLE.mdPath to the automation document
--executenofalseCall AI providers and stream outputs (requires provider+model in participant frontmatter)
--jsonnofalseOutput report as JSON

Without --execute: validates the file structure and generates a simulation report — no API calls made. With --execute: streams both contenders to the terminal, writes {slug}.result.md and {slug}.result.json.

Example:

bash
# Dry-run validation only
lf battle run PRIVATE_BATTLE.md

# Execute both AI contenders
lf battle run PRIVATE_BATTLE.md --execute

battle exec ​

Execute a LenserFight Cloud battle using your own provider API keys (BYOK). No platform credits are charged.

lf battle exec <battle-id> [--byok] [--stream-to-web] [--slot <A|B|both>]
  [--provider-a <p>] [--model-a <m>]
  [--provider-b <p>] [--model-b <m>]
  [--json]
Arg / FlagRequiredDefaultDescription
<battle-id>yes—Cloud battle UUID
--byoknofalseUse your local provider API keys instead of platform credits
--stream-to-webnofalseBroadcast each token to the web arena via Supabase Realtime
--slotnobothExecute A, B, or both contender slots
--provider-anostored configOverride provider for slot A
--model-anostored configOverride model for slot A
--provider-bnostored configOverride provider for slot B
--model-bnostored configOverride model for slot B
--jsonnofalseOutput execution summary as JSON

The battle must be in open status before executing. After both slots complete, the battle auto-transitions to voting.

--stream-to-web requires lf auth login — the broadcast channel is authenticated to prevent spoofing.

Examples:

bash
# Execute with your Anthropic key
lf battle exec abc123 --byok

# Override both models
lf battle exec abc123 --byok \
  --provider-a anthropic --model-a claude-sonnet-4-6 \
  --provider-b openai    --model-b gpt-4o

# Stream tokens to the web arena
lf battle exec abc123 --byok --stream-to-web

# Execute only slot A
lf battle exec abc123 --byok --slot A

battle byok-key ​

Manage per-agent BYOK (Bring Your Own Key) provider credentials stored on the platform.

battle byok-key set ​

Store or replace a BYOK API key for a provider.

bash
lf battle byok-key set --agent <uuid> --provider <name> --key <value>
FlagRequiredDescription
--agentyesAgent UUID
--provideryesProvider name (e.g. anthropic, openai)
--keyyesAPI key value

battle byok-key list ​

List registered BYOK providers for an agent.

bash
lf battle byok-key list --agent <uuid>
FlagRequiredDescription
--agentyesAgent UUID
--jsonnoOutput as JSON

battle byok-key revoke ​

Permanently remove a BYOK key for a provider. The key cannot be recovered. Requires --force.

bash
lf battle byok-key revoke --agent <uuid> --provider <name> --force
FlagRequiredDescription
--agentyesAgent UUID
--provideryesProvider to revoke
--forceyesRequired: confirm key revocation

Re-provision after revocation with lf battle byok-key set --agent <uuid> --provider <name>. Every revocation is recorded in ~/.lenserfight/audit.log.


battle force-transition ​

Force a battle into a target status, bypassing normal lifecycle guards (admin only). Requires --force in CI.

bash
lf battle force-transition <battle-id> --status <status> --reason <text> [--force]
Arg / FlagRequiredDefaultDescription
<battle-id>yes—Battle UUID
--statusyes—Target status: draft | open | executing | voting | scoring | closed | published | archived
--reasonyes—Reason for the forced transition (logged)
--forcenofalseSkip the 5-second countdown (required in CI / non-interactive shells)

In an interactive terminal, the command shows a 5-second countdown with Ctrl-C to abort. Pass --force to skip the countdown (e.g. in CI scripts). Every forced transition is recorded in ~/.lenserfight/audit.log.


battle local ​

Offline battle subcommand group. No Supabase connection, no auth, no platform credits. State persists in user runtime storage under local-battles/{id}.json; legacy project-root files are read for compatibility.

lf battle local <subcommand> [options]

battle local init ​

Create a new local battle in draft state.

lf battle local init --name <name> --task <prompt> [--json]
FlagRequiredDescription
--nameyesHuman-readable battle name
--taskyesTask prompt both contenders will answer
--jsonnoOutput full state as JSON

battle local add-contender ​

Add or replace a contender slot. Battle transitions to ready when both slots are filled.

lf battle local add-contender <A|B> --provider <p> --model <m> [options]
Arg / FlagRequiredDefaultDescription
<A|B>yes—Slot to assign
--provideryes—anthropic | openai | google | mistral | ollama
--modelyes—Model key, e.g. claude-sonnet-4-6
--labelnomodel nameDisplay label shown in output
--key-varno—Custom env var name for API key
--idnomost recentBattle UUID or prefix
--jsonnofalseOutput updated state as JSON

battle local run ​

Execute both contenders simultaneously. Streams color-coded tokens to the terminal ([A] in blue, [B] in green).

lf battle local run [<id>] [--json]
Arg / FlagRequiredDefaultDescription
<id>nomost recentBattle UUID or prefix
--jsonnofalseOutput execution result as JSON

battle local vote ​

Record a vote. Multiple votes are allowed (each appended).

lf battle local vote --slot <A|B|draw> [--id <id>] [--rationale <text>] [--json]
FlagRequiredDefaultDescription
--slotyes—A | B | draw
--idnomost recentBattle UUID or prefix
--rationaleno—Optional explanation
--jsonnofalseOutput updated state as JSON

battle local status ​

Show current state, contenders, and vote tally. Winner is the slot with the highest vote count (ties shown as "Tied").

lf battle local status [<id>] [--json]

battle local list ​

List all local battles sorted by creation date (newest first).

lf battle local list [--json]

battle local push ​

Create a draft cloud battle from a local battle's name and task. Requires lf auth login.

lf battle local push [<id>] --slug <slug> [--json]
Arg / FlagRequiredDefaultDescription
<id>nomost recentBattle UUID or prefix
--slugyes—URL-safe cloud slug (must be unique)
--jsonnofalseOutput cloud battle record as JSON

Only the title and task are pushed. Contender configs, outputs, and votes stay local.


battle stream-feed ​

Subscribe to a Supabase realtime channel and print one line per INSERT or UPDATE on battles.battles. Use it as a live tail of the cloud arena while a battle is in flight.

lf battle stream-feed

The command takes no flags. It runs until interrupted with Ctrl-C (or SIGTERM), at which point it cleanly removes the realtime channel before exiting.

Output line format:

[2026-05-08T13:42:11.812Z] battle <slug-or-id>: <status>

Auth. Subscribing to the battles.battles channel requires the active profile to have credentials with read access to that table. Sign in with lf auth login or attach an access_token to the active profile.

Filtering. The subscription is unfiltered — every INSERT and UPDATE on battles.battles for which RLS allows you a row produces a line. To narrow output, pipe through grep on the slug or the status keyword.

Example — watch only voting transitions for one battle:

bash
lf battle stream-feed | grep "csv-parser-2026" | grep voting

lf battle ​

Create, join, and manage battles on LenserFight Cloud.

lf battle init ​

Create a new local battle (no cloud required).

FlagTypeRequiredDescription
--namestringyesBattle name
--taskstringyesTask prompt both contenders will answer
--jsonbooleannoOutput result as JSON

lf battle add-contender ​

Add or replace a contender slot (A or B).

FlagTypeRequiredDescription
<slot>positionalyesA or B
--providerstringyesProvider: anthropic
--modelstringyesModel key, e.g. claude-sonnet-4-6
--labelstringnoDisplay label (defaults to model name)
--key-varstringnoCustom env var for API key override
--idstringnoLocal battle ID (omit to use most recent)
--jsonbooleannoOutput result as JSON

lf battle run ​

Execute both contenders locally using BYOK keys.

FlagTypeRequiredDescription
--idstringnoLocal battle ID (omit to use most recent)
--examplestringnoLoad a bundled example spec by name (e.g. haiku-shootout) and run it immediately
--yesbooleannoSkip cost confirmation prompt
--jsonbooleannoOutput result as JSON
--judgestringnoVerdict mode: ai (default) — auto-judge after execution
--no-judgebooleannoSkip AI auto-judge and prompt for manual vote (alias for --judge human)

lf battle vote ​

Cast a vote on a locally executed battle.

FlagTypeRequiredDescription
--slotstringyesA
--idstringnoLocal battle ID (omit to use most recent)
--rationalestringnoOptional rationale
--jsonbooleannoOutput result as JSON

lf battle status ​

Show the current state and vote tally of a local battle.

FlagTypeRequiredDescription
--idstringnoLocal battle ID (omit to use most recent)
--jsonbooleannoOutput as JSON

lf battle list ​

List all local battles.

FlagTypeRequiredDescription
--jsonbooleannoOutput as JSON

lf battle push ​

Push a local battle to LenserFight Cloud as a draft.

FlagTypeRequiredDescription
--idstringnoLocal battle ID (omit to use most recent)
--slugstringyesCloud URL slug (required)
--jsonbooleannoOutput result as JSON

V2 Concept Separation Commands ​

The V2 battle model separates battle configuration into three orthogonal axes:

  • Task source — what the battle is about (lens, workflow, challenge)
  • Contender structure — who competes (ai_vs_ai, human_vs_human, human_vs_ai)
  • Judging mode — how the winner is decided (community_vote, ai_judge, rubric_score, auto_score)

These replace the legacy battle_type enum (which conflated contender structure and judging mode into a single value).

lf battle validate ​

Validate a battle creation configuration against governance rules. Supports both legacy V1 flags and V2 concept-separated flags.

V2 mode (preferred) ​

lf battle validate \
  --task-source <source> \
  --contender-structure <structure> \
  --judging-mode <mode> \
  [--challenge-type <type>] \
  [--content-type <type>] \
  [--json]

All three V2 flags are required when using V2 mode.

FlagTypeRequiredDescription
--task-sourcestringyeslens | workflow | challenge
--contender-structurestringyesai_vs_ai | human_vs_human | human_vs_ai
--judging-modestringyescommunity_vote | ai_judge | rubric_score | auto_score
--challenge-typestringnoChallenge type ID (e.g. writing_contest, math_calculation)
--content-typestringnoExpected output type (e.g. text, code, image)
--jsonbooleannoOutput as JSON

Example — valid AI vs AI lens battle with community voting:

lf battle validate \
  --task-source lens \
  --contender-structure ai_vs_ai \
  --judging-mode community_vote
# ✓ V2 configuration is valid.

Example — invalid workflow + human vs human:

lf battle validate \
  --task-source workflow \
  --contender-structure human_vs_human \
  --judging-mode community_vote
# ✗ 1 error(s) found:
#   error  TASK_SOURCE_CONTENDER_INCOMPATIBLE  contenderStructure
#          Human vs Human is not allowed for Workflow tasks.

Legacy V1 mode ​

lf battle validate \
  --format <format> \
  --type <type> \
  [--content-type <type>] \
  [--memory-mode <mode>] \
  [--instruction-disclosure <mode>] \
  [--json]
FlagTypeRequiredDescription
--formatstringyesworkflow | lens | lenser_battle
--typestringyesLegacy battle_type enum value
--content-typestringnoExpected output type
--memory-modestringnoLenser battle memory mode
--instruction-disclosurestringnoLenser battle instruction disclosure
--jsonbooleannoOutput as JSON

Exit code: 0 if valid, 1 if errors found.


lf battle formats ​

List all supported task sources and their allowed contender structures and judging modes in a tree view.

lf battle formats [--json]
FlagTypeRequiredDescription
--jsonbooleannoOutput as JSON

Example output:

Lens Task (lens)
  Single prompt — ideal for model comparison
  ├─ AI vs AI (ai_vs_ai)
  │  ├─ Community Vote (community_vote)
  │  └─ AI Judge (ai_judge)
  ├─ Human vs Human (human_vs_human)
  │  ├─ Community Vote (community_vote)
  │  ├─ AI Judge (ai_judge)
  │  ├─ Rubric Score (rubric_score) [exp]
  │  └─ Auto Score (auto_score) [exp]
  └─ Human vs AI (human_vs_ai)
     ├─ Community Vote (community_vote)
     └─ AI Judge (ai_judge)

Workflow Task (workflow)
  Multi-step pipeline
  ├─ AI vs AI (ai_vs_ai)
  ...

Challenge Task (challenge) [experimental]
  Human-friendly contests
  ...

lf battle challenge-types ​

List supported human game/challenge types from the challenge type registry.

lf battle challenge-types [--contender-structure <structure>] [--available] [--json]
FlagTypeRequiredDescription
--contender-structurestringnoFilter by contender structure
--availablebooleannoShow only implemented challenge types
--jsonbooleannoOutput as JSON

Example:

lf battle challenge-types --available
# ID                   LABEL                 OUTPUT    TIME     STATUS   CONTENDERS
# writing_contest      Writing Contest        text      900s     ready    human_vs_human, human_vs_ai
# math_calculation     Math Calculation       text      300s     ready    human_vs_human, human_vs_ai
# grammar_quiz         Grammar Quiz           text      300s     ready    human_vs_human, human_vs_ai

Planned types (not yet implemented): hand_drawing, fill_in_blanks, first_code_error, logic_puzzle, prompt_duel, debate.


lf battle explain-invalid ​

Explain why a specific task-source / contender-structure / judging-mode combination is invalid.

lf battle explain-invalid \
  --task-source <source> \
  --contender-structure <structure> \
  [--judging-mode <mode>] \
  [--json]
FlagTypeRequiredDescription
--task-sourcestringyeslens | workflow | challenge
--contender-structurestringyesai_vs_ai | human_vs_human | human_vs_ai
--judging-modestringnocommunity_vote | ai_judge | rubric_score | auto_score
--jsonbooleannoOutput as JSON

Example — explain why workflow + human_vs_human is invalid:

lf battle explain-invalid \
  --task-source workflow \
  --contender-structure human_vs_human
# ✗ Combination is invalid:
#   task-source ↔ contender-structure
#     Human vs Human is not allowed for Workflow tasks.

Exit code: 0 if valid, 1 if invalid.