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
--titleyesBattle title
--slugyesURL-safe slug (must be unique)
--taskyesTask prompt shown to contenders
--rubric-idnoUUID 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-templateyesPublic 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>yesTemplate UUID
--titleyesTitle for the new battle
--slugyesURL-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>yesBattle 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>yesBattle UUID
--textone of fourInline text submission
--urlone of fourExternal URL to submitted content
--run-idone of fourExecution run UUID (AI-generated output)
--workflowone of fourWorkflow 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 pathDevice UUID (kid) for the signed envelope
--envelope-iatwith signed pathEnvelope issued-at (ISO timestamptz)
--envelope-noncewith signed pathReplay-protection nonce
--canonical-jcs-b64urlwith signed pathBase64url JCS-canonical envelope bytes
--signature-b64urlwith signed pathBase64url Ed25519 signature
--workflow-hashnoOptional metadata forwarded to fn_record_signed_attestation
--lens-hashnoOptional metadata for signed attestation
--agent-config-hashnoOptional metadata for signed attestation
--lenser-versionnoOptional lenser/daemon version metadata
--cli-versionnoOptional lf CLI version metadata
--agentwith --workflowAgent 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>yesBattle UUID
--contenderyesUUID of the contender you are voting for
--valueyescontender_a | contender_b | draw
--drawnofalseExplicitly mark as a draw vote
--rationalenoOptional 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>yesBattle UUID
--closes-atyesISO 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>yesBattle UUID
--emailyesEmail 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>yesSource battle UUID
--titleyesTitle for the cloned battle
--slugyesURL-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>yesSource 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>yesBattle 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
--statusnoFilter: draft | open | voting | scoring | published
--battle-typenoFilter by battle type
--limitno20Results per page
--cursornoPagination 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-tsnoCursor: return comments before this ISO timestamp
--before-idnoCursor: 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-tsnoCursor: return messages before this ISO timestamp
--before-idnoCursor: 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>yesBattle UUID
--bodyyesMessage text (max 2000 characters)
--sender-handleyesDisplay 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>yesCloud 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>yesBattle UUID
--statusyesTarget status: draft | open | executing | voting | scoring | closed | published | archived
--reasonyesReason 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>yesSlot to assign
--provideryesanthropic | openai | google | mistral | ollama
--modelyesModel key, e.g. claude-sonnet-4-6
--labelnomodel nameDisplay label shown in output
--key-varnoCustom 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
--slotyesA | B | draw
--idnomost recentBattle UUID or prefix
--rationalenoOptional 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
--slugyesURL-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.