feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// The commands around a run, this binary as crafter-review: init (writes,
// refreshes, keeps what it did not write, replaces the old link into the
// submodule, refuses a foreign link, a subdirectory and the home directory
// before it writes anything), doc, stop (a scratch dir, --hash on the
// board), seats on a claude stand-in, and browser's usage.
# include <unistd.h>
import std ;
import Crafter . Review ;
namespace fs = std : : filesystem ;
using namespace Crafter : : Review ;
namespace {
std : : int32_t Failures = 0 ;
void Check ( std : : string_view name , bool cond , std : : string_view detail = { } ) {
std : : println ( " {}{}{} " , cond ? " ok " : " FAIL " , name , ! cond & & ! detail . empty ( ) ? std : : format ( " ({}) " , detail ) : std : : string ( ) ) ;
if ( ! cond ) + + Failures ;
}
void Write ( const fs : : path & path , std : : string_view text ) {
fs : : create_directories ( path . parent_path ( ) ) ;
std : : ofstream ( path , std : : ios : : binary ) < < text ;
}
std : : string Read ( const fs : : path & path ) {
return Platform : : ReadFile ( path ) . value_or ( " " ) ;
}
// A claude that answers a seat probe as the CLI does: a rate_limit_event
// with both windows, and on a Fable request the Fable window too.
constexpr std : : string_view FakeClaude = R " sh(#!/bin/sh
model =
prev =
for a in " $@ " ; do [ " $prev " = - - model ] & & model = $ a ; prev = $ a ; done
cat > / dev / null
now = $ ( date + % s )
fable =
[ " $model " = claude - fable - 5 - 1 ] & & fable = " , \" seven_day_overage_included \" :{ \" utilization \" :0.3, \" resetsAt \" :$((now+172800))} "
echo ' { " type " : " system " , " subtype " : " init " , " session_id " : " 0b6f3c56-1111-4222-8333-944455556666 " } '
echo " { \" type \" : \" rate_limit_event \" , \" rate_limit_info \" :{ \" status \" : \" allowed \" , \" rateLimitType \" : \" five_hour \" , \" resetsAt \" :$((now+3600)), \" unifiedWindows \" :{ \" five_hour \" :{ \" utilization \" :0.42, \" resetsAt \" :$((now+3600))}, \" seven_day \" :{ \" utilization \" :0.13, \" resetsAt \" :$((now+259200))}$fable}}} "
echo ' { " type " : " result " , " subtype " : " success " , " is_error " : false , " result " : " ok " , " total_cost_usd " : 0.0001 } '
) sh " ;
// The environment of every child: git with no user config, this
// checkout as the home, nothing of the shell's review or seat setup.
std : : map < std : : string , std : : string > Env ;
std : : map < std : : string , std : : string > With ( std : : map < std : : string , std : : string > env , const std : : map < std : : string , std : : string > & changes ) {
for ( const auto & [ k , v ] : changes ) env [ k ] = v ;
return env ;
}
Process : : Captured Self ( std : : vector < std : : string > args , const fs : : path & cwd , const std : : map < std : : string , std : : string > & env = { } ) {
args . insert ( args . begin ( ) , Platform : : SelfExe ( ) . string ( ) ) ;
return Process : : Run ( args , cwd , std : : chrono : : seconds ( 60 ) , { } , With ( Env , env ) ) ;
}
std : : int32_t Checks ( ) {
std : : println ( " commands: init, doc, stop, seats on a claude stand-in, browser's usage " ) ;
Platform : : SetMode ( Platform : : Mode : : Native ) ;
const fs : : path checkout = fs : : current_path ( ) ;
SetHome ( checkout ) ;
const fs : : path base = fs : : temp_directory_path ( ) / std : : format ( " crafter-review-commands-{} " , Platform : : PublishedPid ( ) ) ;
fs : : remove_all ( base ) ;
for ( const auto & [ k , v ] : Platform : : Environment ( ) ) {
const bool review = k . starts_with ( " REVIEW_GATES_ " ) | | k . starts_with ( " CRAFTER_REVIEW_ " ) | | k = = " SEATS_ENV_FILE " | | k = = " CLAUDE_BIN " ;
const bool seat = k . starts_with ( " CLAUDE_CODE_OAUTH_TOKEN " ) | | k . starts_with ( " ANTHROPIC_API_KEY " ) | | k . starts_with ( " OPENROUTER_KEY " ) ;
if ( ! review & & ! seat ) Env [ k ] = v ;
}
Env = With ( Env , { { " GIT_CONFIG_GLOBAL " , " /dev/null " } , { " GIT_CONFIG_SYSTEM " , " /dev/null " } , { " CRAFTER_REVIEW_HOME " , checkout . string ( ) } ,
{ " REVIEW_GATES_BOARD " , ( base / " board.json " ) . string ( ) } , { " REVIEW_GATES_SCRATCH " , ( base / " scratch " ) . string ( ) } , { " TZ " , " UTC " } } ) ;
auto repo = [ & ] ( std : : string_view name ) {
const fs : : path dir = base / name ;
fs : : create_directories ( dir ) ;
Process : : Run ( { " git " , " init " , " -q " , " -b " , " main " , dir . string ( ) } , std : : nullopt , std : : chrono : : seconds ( 60 ) , { } , Env ) ;
return dir ;
} ;
const std : : string shipped = Read ( checkout / " process " / " skill " / " prepush " / " SKILL.md " ) ;
const std : : string marker = " <!-- written by crafter-review init " ;
// init on a repository that has nothing yet, then again.
const fs : : path r = repo ( " r " ) ;
const Process : : Captured first = Self ( { " init " } , r ) ;
const std : : string skill = Read ( r / " .claude " / " skills " / " prepush " / " SKILL.md " ) ;
const std : : size_t frontmatter = shipped . find ( " \n --- \n " , 3 ) + 5 ;
2026-10-31 20:09:07 +01:00
Check ( " init: the config from the template (every lens off), the artifact and worktree dirs ignored, the skill with init's marker after its frontmatter, the next steps (turn lenses on; no browser line while none drives the application) " ,
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
first . returnCode = = 0 & & Read ( r / " .review-gates " / " config.json " ) = = Read ( checkout / " process " / " templates " / " config.json " )
& & Read ( r / " .gitignore " ) = = " .review-gates/push/ \n .review-gates/trees/ \n " & & skill . starts_with ( shipped . substr ( 0 , frontmatter ) )
& & skill . substr ( frontmatter ) . starts_with ( marker ) & & skill . ends_with ( shipped . substr ( frontmatter ) ) & & first . out . contains ( " config - until it reports no problem " )
2026-10-31 20:09:07 +01:00
& & first . out . contains ( " turn lenses on in .review-gates/config.json " ) & & ! first . out . contains ( " browser install - " ) ,
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
first . out + first . err ) ;
Write ( r / " .review-gates " / " config.json " , R " ({ " base " : " origin / dev " }) " ) ;
Write ( r / " .gitignore " , " node_modules/ \n /.review-gates/push \n " ) ;
Write ( r / " .claude " / " skills " / " prepush " / " SKILL.md " , std : : format ( " --- \n name: prepush \n --- \n {} 0.0.1: old --> \n old text \n " , marker ) ) ;
const Process : : Captured again = Self ( { " init " } , r ) ;
Check ( " init again: the config and an entry already ignored are kept, the missing one added once, the skill it wrote refreshed " ,
again . returnCode = = 0 & & Read ( r / " .review-gates " / " config.json " ) = = R " ({ " base " : " origin / dev " }) "
& & Read ( r / " .gitignore " ) = = " node_modules/ \n /.review-gates/push \n .review-gates/trees/ \n " & & Read ( r / " .claude " / " skills " / " prepush " / " SKILL.md " ) = = skill
& & again . out . contains ( " refreshed .claude/skills/prepush/SKILL.md " ) ,
again . out + again . err ) ;
Write ( r / " .claude " / " skills " / " prepush " / " SKILL.md " , " my own skill \n " ) ;
const Process : : Captured kept = Self ( { " init " } , r ) ;
const std : : string mine = Read ( r / " .claude " / " skills " / " prepush " / " SKILL.md " ) ;
const Process : : Captured forced = Self ( { " init " , " --force " } , r ) ;
Check ( " init: a SKILL.md init did not write is kept; --force replaces it " ,
kept . returnCode = = 0 & & mine = = " my own skill \n " & & kept . out . contains ( " --force replaces it " ) & & forced . returnCode = = 0 & & Read ( r / " .claude " / " skills " / " prepush " / " SKILL.md " ) = = skill ,
kept . out + forced . out ) ;
// The old setup: the skill dir a link into the submodule.
const fs : : path old = repo ( " old " ) ;
Write ( old / " review-gates " / " process " / " skill " / " prepush " / " SKILL.md " , " the submodule's file \n " ) ;
fs : : create_directories ( old / " .claude " / " skills " ) ;
fs : : create_directory_symlink ( " ../../review-gates/process/skill/prepush " , old / " .claude " / " skills " / " prepush " ) ;
const Process : : Captured relinked = Self ( { " init " } , old ) ;
std : : error_code error ;
Check ( " init: the old link into the submodule goes - a real directory with the skill in its place, the submodule's file untouched " ,
relinked . returnCode = = 0 & & ! fs : : is_symlink ( fs : : symlink_status ( old / " .claude " / " skills " / " prepush " , error ) )
& & Read ( old / " .claude " / " skills " / " prepush " / " SKILL.md " ) = = skill & & Read ( old / " review-gates " / " process " / " skill " / " prepush " / " SKILL.md " ) = = " the submodule's file \n "
& & relinked . out . contains ( " removed the link .claude/skills/prepush -> ../../review-gates/process/skill/prepush " ) ,
relinked . out + relinked . err ) ;
// What init refuses, before it writes anything.
const fs : : path foreign = repo ( " foreign " ) ;
fs : : create_directories ( base / " elsewhere " ) ;
fs : : create_directories ( foreign / " .claude " / " skills " ) ;
fs : : create_directory_symlink ( " ../../../elsewhere " , foreign / " .claude " / " skills " / " prepush " ) ;
const Process : : Captured refused = Self ( { " init " } , foreign ) ;
fs : : create_directories ( r / " sub " ) ;
const Process : : Captured sub = Self ( { " init " } , r / " sub " ) ;
const fs : : path home = repo ( " home " ) ;
const Process : : Captured atHome = Self ( { " init " } , home , { { " HOME " , home . string ( ) } } ) ;
const bool homeUntouched = ! fs : : exists ( home / " .review-gates " ) ;
const Process : : Captured atHomeForced = Self ( { " init " , " --force " } , home , { { " HOME " , home . string ( ) } } ) ;
fs : : create_directories ( base / " plain " ) ;
const Process : : Captured plain = Self ( { " init " } , base / " plain " ) ;
Check ( " init refuses, exit 3, writing nothing: a link that is not into the submodule (--force replaces it), a subdirectory, the home directory (--force goes on), no checkout " ,
refused . returnCode = = 3 & & refused . err . contains ( " .claude/skills/prepush is a link to ../../../elsewhere " ) & & ! fs : : exists ( foreign / " .review-gates " ) & & ! fs : : exists ( foreign / " .gitignore " )
& & sub . returnCode = = 3 & & sub . err . contains ( " from the repository's toplevel " ) & & ! fs : : exists ( r / " sub " / " .review-gates " ) & & atHome . returnCode = = 3
& & atHome . err . contains ( " is your home directory " ) & & homeUntouched & & atHomeForced . returnCode = = 0 & & plain . returnCode = = 3
& & plain . err . contains ( " is not in a git checkout " ) ,
refused . err + sub . err + atHome . err + plain . err ) ;
// doc: the texts, and where they are.
const Process : : Captured prepush = Self ( { " doc " , " prepush " } , base ) ;
const Process : : Captured where = Self ( { " doc " , " --path " , " config " } , base ) ;
const Process : : Captured missing = Self ( { " doc " } , base ) ;
const Process : : Captured wrong = Self ( { " doc " , " nope " } , base ) ;
Check ( " doc: the procedure byte for byte, a template's path, usage errors for no name and an unknown one (exit 3) " ,
prepush . returnCode = = 0 & & prepush . out = = Read ( checkout / " process " / " prepush.md " ) & & where . returnCode = = 0
& & where . out = = std : : format ( " {} \n " , ( checkout / " process " / " templates " / " config.json " ) . string ( ) ) & & missing . returnCode = = 3
& & missing . err . contains ( " the following arguments are required: name " ) & & wrong . returnCode = = 3
& & wrong . err . contains ( " argument name: invalid choice: 'nope' (choose from 'prepush', 'manifest', 'config') " ) ,
prepush . err + where . out + missing . err + wrong . err ) ;
// stop: a live driver is this process; one that is gone, a sleep that
// was reaped.
const std : : string me = Platform : : Self ( ) - > Key ( ) ;
const fs : : path live = base / " scratch " / " abcd1234-run-1 " ;
Write ( live / " .driver " , me ) ;
const std : : int64_t gone = Process : : Start ( { . argv = { " true " } } ) . pid ;
Process : : Wait ( gone ) ;
const fs : : path dead = base / " scratch " / " abcd1234-run-2 " ;
Write ( dead / " .driver " , std : : format ( " {}:{} " , gone , 1 ) ) ;
const Process : : Captured byDir = Self ( { " stop " , live . string ( ) } , base ) ;
const bool wroteDir = fs : : exists ( live / " stop " ) ;
fs : : remove ( live / " stop " ) ;
const Process : : Captured byDead = Self ( { " stop " , dead . string ( ) } , base ) ;
const Process : : Captured byNone = Self ( { " stop " , ( base / " plain " ) . string ( ) } , base ) ;
const Process : : Captured neither = Self ( { " stop " } , base ) ;
Check ( " stop <dir>: the stop file for the live driver its .driver names (exit 0); a driver that is gone, a dir no run holds, no target: nothing written " ,
byDir . returnCode = = 0 & & wroteDir & & byDir . out . contains ( std : : format ( " stop: driver {} " , me ) ) & & byDead . returnCode = = 1 & & byDead . err . contains ( " is no longer running " )
& & ! fs : : exists ( dead / " stop " ) & & byNone . returnCode = = 1 & & byNone . err . contains ( " no .driver marker " ) & & neither . returnCode = = 3 ,
byDir . out + byDir . err + byDead . err + byNone . err ) ;
Json drivers = Json : : object ( ) ;
drivers [ me ] = { { " run " , " commits x @ abcd1234 " } , { " scratch " , live . string ( ) } , { " since " , Platform : : Now ( ) } , { " boot " , Platform : : BootId ( ) } } ;
Write ( base / " board.json " , DumpFile ( { { " version " , 1 } , { " drivers " , drivers } , { " seats " , Json : : object ( ) } } ) ) ;
const std : : string boardBefore = Read ( base / " board.json " ) ;
const Process : : Captured byHash = Self ( { " stop " , " --hash " , " abcd1234ffff " } , base ) ;
const bool wroteHash = fs : : exists ( live / " stop " ) ;
const Process : : Captured otherHash = Self ( { " stop " , " --hash " , " 00000000 " } , base ) ;
Check ( " stop --hash: every live run of the hash on the board gets its stop file (the first 8 characters decide); another hash finds none (exit 1); the board is read, never written " ,
byHash . returnCode = = 0 & & wroteHash & & otherHash . returnCode = = 1 & & otherHash . err . contains ( " no live run of 00000000 on the board " ) & & Read ( base / " board.json " ) = = boardBefore ,
byHash . out + byHash . err + otherHash . err ) ;
// seats on the stand-in: a token from the env file, an API key, the
// CLI's own login.
const fs : : path fake = base / " claude " ;
Write ( fake , FakeClaude ) ;
fs : : permissions ( fake , fs : : perms : : owner_all ) ;
Write ( base / " seats " / " .env " , " CLAUDE_CODE_OAUTH_TOKEN1= \" tok-one \" \n ANTHROPIC_API_KEY1=sk-ant-api-one \n " ) ;
fs : : remove ( base / " board.json " ) ;
const std : : map < std : : string , std : : string > withFake = { { " CLAUDE_BIN " , fake . string ( ) } } ;
const Process : : Captured report = Self ( { " seats " , " --local " , " --env-file " , " .env " } , base / " seats " , withFake ) ;
const Process : : Captured json = Self ( { " seats " , " --json " , " --no-fable " } , base / " seats " , withFake ) ;
Json parsed ;
try {
parsed = Json : : parse ( json . out ) ;
} catch ( const std : : exception & ) {
}
Check ( " seats: every seat probed - the CLI's own login, the env file's token, the API key - its windows read, the API seat beside the driver's ledger, the drivers on the board " ,
report . returnCode = = 0 & & report . out . starts_with ( " seat pool: 3 seat(s) (local, 1, api1) from .env and your own login, probed " )
& & report . out . contains ( " | local | 42% | " ) & & report . out . contains ( " | 1 | 42% | " ) & & report . out . contains ( " | api1 | ok | $0.00 of $200 " ) & & report . out . contains ( " drivers: none running " ) ,
report . out + report . err ) ;
Check ( " seats --json: the probe results as JSON - the seats, the API seats, the eligible ones and the next placement; ./.env read by default; no token, no key printed " ,
json . returnCode = = 0 & & parsed . is_object ( ) & & parsed . at ( " seats " ) . size ( ) = = 1 & & parsed . at ( " seats " ) . at ( 0 ) . at ( " probedWith " ) = = " haiku " & & parsed . at ( " apiSeats " ) . size ( ) = = 1
& & parsed . at ( " eligible " ) = = Json : : array ( { 1 } ) & & parsed . at ( " next " ) . at ( " seat " ) = = 1 & & Dyn : : Str ( parsed . at ( " probedAt " ) ) . ends_with ( " Z " )
& & ! ( report . out + json . out ) . contains ( " tok-one " ) & & ! ( report . out + json . out ) . contains ( " sk-ant-api-one " ) ,
json . out . substr ( 0 , 600 ) + json . err ) ;
const Process : : Captured none = Self ( { " seats " } , base / " plain " , withFake ) ;
const Process : : Captured noFile = Self ( { " seats " , " --env-file " , " missing.env " } , base / " plain " , withFake ) ;
2026-10-31 16:35:26 +01:00
Write ( base / " plain " / " bad.env " , std : : string ( " \xff \n " , 2 ) ) ;
const Process : : Captured badFile = Self ( { " seats " , " --env-file " , " bad.env " } , base / " plain " , withFake ) ;
Check ( " seats: no seat configured is exit 1 with what it looked for; a missing env file and one that is not UTF-8 are exit 1 with the read error " ,
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
none . returnCode = = 1 & & none . out . starts_with ( " no seat configured (CLAUDE_CODE_OAUTH_TOKEN1..6 / ANTHROPIC_API_KEY1..6; --local probes your own login) " )
2026-10-31 16:35:26 +01:00
& & noFile . returnCode = = 1 & & noFile . err . contains ( " FileNotFoundError: [Errno 2] No such file or directory: 'missing.env' " ) & & badFile . returnCode = = 1
& & badFile . err . contains ( " UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte " ) ,
none . out + noFile . err + badFile . err ) ;
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
2026-10-31 16:45:53 +01:00
// What escapes a command ends it with exit 1, never an abort; config's
// help.
const Process : : Captured noHome = Self ( { " doc " , " prepush " } , base , { { " CRAFTER_REVIEW_HOME " , ( base / " nowhere " ) . string ( ) } } ) ;
const Process : : Captured configHelp = Self ( { " config " , " -h " } , base ) ;
Check ( " an error no command catches ends it with exit 1 and its message, never an abort; config -h prints its usage, exit 0 " ,
noHome . returnCode = = 1 & & noHome . err . starts_with ( " crafter-review: " ) & & noHome . err . contains ( " holds no process/lenses " ) & & configHelp . returnCode = = 0
& & configHelp . out . starts_with ( " usage: crafter-review config [-h] " ) ,
noHome . err + configHelp . out + configHelp . err ) ;
feat(cli): seats, init, doc, browser install and stop
- `seats` reports every seat probed (Fable first, Haiku for a seat that
needs it), the API seats beside the driver's ledger, the drivers on the
board, or all of it as JSON. In the sandbox a last line names `stop`,
since the pids above cannot be signalled from there.
- `init` sets a repository up: the config from the template, the artifact
and worktree dirs ignored, the prepush skill with a marker that lets a
later init refresh it. The old link into the review-gates submodule is
replaced by a real directory, never written through. A foreign link, a
subdirectory, the home directory and a dir outside git are refused before
anything is written.
- `doc [--path] prepush|manifest|config` prints a shipped text, or where
the host sees it (the deployment's `active` link in the Flatpak).
- `browser install` runs the installer of the Playwright the pinned MCP
server depends on, and checks the Chromium revision landed.
- `stop <scratch>` and `stop --hash <hash8>` write the stop file a running
driver checks every 2 s, for live drivers only; the board is read, never
written.
The option parser takes positionals, in the usage line, the help and the
errors. The Commands test runs all of them through this binary, seats on a
claude stand-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:45:36 +01:00
// browser: its usage (an install fetches from npm).
const Process : : Captured noAction = Self ( { " browser " } , base ) ;
const Process : : Captured badAction = Self ( { " browser " , " remove " } , base ) ;
Check ( " browser: an action is required, and install is the one there is (exit 3) " ,
noAction . returnCode = = 3 & & noAction . err . contains ( " the following arguments are required: action " ) & & badAction . returnCode = = 3
& & badAction . err . contains ( " argument action: invalid choice: 'remove' (choose from 'install') " ) ,
noAction . err + badAction . err ) ;
fs : : remove_all ( base ) ;
return Failures ? 1 : 0 ;
}
}
int main ( int argc , char * * argv ) {
const std : : vector < std : : string_view > args ( argv , argv + argc ) ;
// With arguments this binary is crafter-review itself, in native mode
// whatever builds it.
if ( args . size ( ) > 1 ) {
Platform : : SystemPaths paths = Platform : : Paths ( ) ;
paths . flatpakInfo = " /nonexistent/.flatpak-info " ;
Platform : : SetPaths ( paths ) ;
return Crafter : : Review : : Main ( args ) ;
}
try {
return Checks ( ) ;
} catch ( const std : : exception & error ) {
std : : println ( std : : cerr , " commands: {} " , error . what ( ) ) ;
return 1 ;
}
}