@statedelta/cli
v0.2.0
Published
CLI for StateDelta - state management, chain resolution, and interactive sessions
Downloads
22
Maintainers
Readme
@statedelta/cli
CLI do ecossistema StateDelta. O
sdresolve documentos, lança runtimes, roda eles como serviços em background sobre Unix socket e fala com eles remotamente — tudo sobre o contratoIRuntime.
⚠️ Status: DEFERRED — Axiom-specialist. O CLI foi escrito quando o launcher e a engine Axiom moravam no mesmo repo. Os comandos
step/run/delta/state/snapshot/ps/remotee todo oserver/expõem o contratoIRuntime/IEngine— API interna da Axiom, não do protocolo 1.0 (@statedelta/protocol), que defineTRunningcomo genérico. Hoje o CLI está fora dos pipes do monorepo (pnpm typecheck/build/testexcluem ele explicitamente). Para reviver: ou migra-se pro repostatedelta-axiomcomo CLI da engine concreta, ou faz-se uma reescrita protocol-only (sem o server, sóresolve/load/init/chain/workspace). Verdocs/WORK-STATUS.md§3.1.
Filosofia
O CLI é um orquestrador fino em cima de três libs do StateDelta:
@statedelta/launcher— transforma URL emIRuntimepronto@statedelta/gateway— I/O (HTTP/HTTPS, filesystem, custom)@statedelta/engine-axiom— adapter de engine padrão pré-conectado
Dois modos de operação:
┌────────────────────────────────┬─────────────────────────────────┐
│ One-shot (sem estado) │ Daemon (com estado) │
│ │ │
│ sd resolve / load / chain │ sd run + sd ps + sd use │
│ Cada comando abre o doc, │ Engine roda como processo em │
│ faz o que precisa, sai. │ background; o CLI conversa │
│ Útil pra inspeção. │ via Unix socket; comandos │
│ │ remotos (sd tick/step/effect) │
│ │ conectam na instância ativa. │
└────────────────────────────────┴─────────────────────────────────┘Duas camadas de organização:
┌────────────────────────────────┬─────────────────────────────────┐
│ Workspace (filesystem) │ Global (~/.sd/) │
│ │ │
│ sd init / state / delta │ sd config │
│ Pasta de projeto com sd.json │ Config global + registry de │
│ + state, deltas/, snapshots/. │ instâncias. Persiste entre │
│ │ shells. │
└────────────────────────────────┴─────────────────────────────────┘Heurísticas:
- Só inspecionando? Passe a URL/path direto pro
sd loadousd chain. Não precisa de workspace. - Editando um projeto? Roda
sd inituma vez, depoissd state/sd delta/sd snapshotoperam nos arquivos do workspace. - Dirigindo um runtime vivo?
sd runpra subir o daemon, depoissd use <id>e qualquersd tick/sd step/sd effectdirige.
Instalação
pnpm add -g @statedelta/cli
# binário: sd (alias: statedelta)Dentro deste monorepo:
pnpm --filter @statedelta/cli build
node packages/cli/dist/bin/sd.js <command>Opções globais
Funcionam na maioria dos comandos:
| Flag | Efeito |
|---|---|
| --json | Saída em JSON (machine-readable) |
| --help | Ajuda do comando |
| --version | Versão do CLI |
Variáveis de ambiente:
| Var | Efeito |
|---|---|
| SD_JSON=1 | Força modo JSON globalmente |
| EDITOR | Editor usado por sd state edit (default: code) |
Índice de comandos
| Categoria | Comandos |
|---|---|
| Workspace | init, workspace info/list, config init/get/set/list/reset/path |
| Documentos | state show/create/edit, delta create/list/show/info/validate, chain, snapshot save/load/list/delete, load |
| Servidor (daemon) | run, ps, use |
| Remoto (cliente) | tick, step, effect, info, stop, resources, rules, handlers, context |
| Definições (DSL) | def resource, def rule, def handler |
| Cache | cache status, cache clear |
Referência
Workspace
sd init [name]
Cria um workspace novo numa pasta.
sd init meuprojeto
sd init meuprojeto --template tictactoe
sd init . --yes| Opção | Default | Descrição |
|---|---|---|
| -t, --template <name> | blank | Um de: blank, tictactoe, battleship |
| -f, --force | false | Sobrescreve pasta existente |
| -y, --yes | false | Pula prompts, usa defaults |
Layout do workspace:
<name>/
├── sd.json # config do workspace (name, version, head, state, chain)
├── state.json # corpo do StateDoc (declaração do universo)
├── deltas/ # DeltaDocs
├── snapshots/ # dumps de snapshot
└── .sd/ # cache localsd.json é o ponto de entrada — todo comando de workspace lê dele.
sd workspace
Inspeciona e lista workspaces.
sd workspace # info do workspace atual
sd workspace info # comando explícito
sd workspace list # lista workspaces conhecidos (~/.sd/workspaces.json)
sd workspace list --jsonO registry global em ~/.sd/workspaces.json é populado por sd init e
por switches manuais.
sd config
Gerencia configuração global em ~/.sd/config.json.
sd config init # inicializa config
sd config get editor # lê valor
sd config set editor vim # grava valor
sd config set defaultEngine [email protected]
sd config list
sd config list --json
sd config reset # restaura defaults
sd config path # imprime path do configChaves comuns:
| Chave | Propósito |
|---|---|
| editor | Editor usado por sd state edit |
| defaultEngine | Engine usado quando o doc não declara requires.engine |
| cacheDir | Override da localização do cache |
Documentos
sd state
Gerencia o corpo do StateDoc do workspace.
sd state # resumo rápido do state.json
sd state show # metadata completa
sd state show --json
sd state create # cria StateDoc vazio
sd state create --from template.json
sd state create --force
sd state edit # abre state.json no $EDITORShape em disco (StateFile):
{
"type": "state",
"name": "meu-projeto",
"version": "1.0.0",
"requires": { "engine": "[email protected]" },
"body": {
"states": [...],
"actions": [...],
"rules": [...]
}
}body é opaco pro CLI — o engine adapter interpreta quando o
launcher boota.
sd delta
Gerencia DeltaDocs em deltas/.
sd delta create chapter-1
sd delta create chapter-2 --extends ./deltas/chapter-1.json
sd delta create scoring -d "Adiciona regras de pontuação"
sd delta create cleanup --no-head # não atualiza head no sd.json
sd delta list
sd delta list --json
sd delta show chapter-1 # conteúdo bruto
sd delta info chapter-1 # resumo parseado
sd delta info chapter-1 --json
sd delta validate ./deltas/foo.json| Subcomando | Opções |
|---|---|
| create <name> | -e, --extends <path>, -d, --description <text>, --no-head |
| list | --json |
| show <name> | — |
| info <name> | --json |
| validate <path> | — |
sd chain [source]
Inspeciona a cadeia de extends (root → head) sem instanciar engine.
sd chain # usa head do workspace
sd chain ./deltas/head.json
sd chain https://example.com/save.json
sd chain --deps # mostra deps[] por node
sd chain --verbose # paths completos + detalhes extras
sd chain --json| Opção | Descrição |
|---|---|
| -d, --deps | Mostra deps[] por node |
| -v, --verbose | Paths completos + metadata |
| --json | Saída em JSON |
Internamente usa partial-fetch (~4 KB por node) via Chain → Gateway.
sd snapshot
Snapshots vivem em snapshots/<name>.snapshot.json dentro do workspace.
sd snapshot save v1
sd snapshot save v1 --force
sd snapshot load v1
sd snapshot list
sd snapshot list --json
sd snapshot delete v1
sd snapshot # default: conta os snapshots| Subcomando | Opções |
|---|---|
| save <name> | -f, --force |
| load <name> | — (aceita path de arquivo ou nome do workspace) |
| list | --json |
| delete <name> | — |
sd load [source]
Pipeline completo — chain → fetch → validação de requires →
instanciação do engine → replay do estado. O runtime é fechado ao
final (one-shot).
sd load # usa head do workspace
sd load ./deltas/save.json
sd load https://example.com/save.json
sd load --full # cadeia completa + marcadores ROOT/HEAD
sd load --state # dump dos state ids → valores
sd load --verify # verifica hashes de integridade
sd load --json| Opção | Descrição |
|---|---|
| -f, --full | Imprime cadeia completa com nomes + marcadores ROOT/HEAD |
| -s, --state | Dump de cada state id após replay |
| --verify | Verifica hashes de integridade (?integrity=…) |
| --json | Saída em JSON |
Servidor (daemon)
sd run [source]
Sobe o engine como processo em background ouvindo num Unix socket
(ou porta TCP). Registra a instância em ~/.sd/instances/.
sd run # usa head do workspace
sd run ./deltas/save.json
sd run -p 7878 # TCP no lugar de socket
sd run -f # foreground, com logs no terminal
sd run -v # output verboso| Opção | Descrição |
|---|---|
| -p, --port <n> | Bind em porta TCP em vez de Unix socket |
| -f, --foreground | Não destacha; streama logs no terminal atual |
| -v, --verbose | Saída de progresso/log verbosa |
A instância recebe um ID auto-gerado. sd ps lista as ativas,
sd use <id> seleciona a instância ativa pros próximos comandos
remotos, sd stop mata.
sd ps
Lista instâncias do engine.
sd ps # só rodando
sd ps -a # inclui paradas/expiradas
sd ps -q # só os IDs
sd ps --json| Opção | Descrição |
|---|---|
| -a, --all | Inclui instâncias paradas |
| -q, --quiet | Imprime só os IDs |
| --json | Saída em JSON |
Cada entrada: id, source, pid, tick, startedAt, uptime,
status.
sd use [id]
Define a instância ativa pros próximos comandos remotos.
sd use # mostra ativa atual
sd use 7f3a # define ativa
sd use --clear # remove ativa
sd use --json| Opção | Descrição |
|---|---|
| -c, --clear | Limpa o ponteiro de instância ativa |
| --json | Saída em JSON |
O ponteiro vive em ~/.sd/active.json e sobrevive entre shells.
Remoto (cliente)
Falam com uma instância rodando (iniciada via sd run). Usam a
instância ativa do sd use ou aceitam -i <id>.
sd tick
Tick atual do engine.
sd tick
sd tick -i 7f3a
sd tick --jsonsd step [n]
Executa um ou mais ticks.
sd step # 1 tick
sd step 10 # 10 ticks
sd step 100 --jsonsd effect <id> [payload]
Aplica um effect externo (tick one-shot).
sd effect player:move '{"dx":1,"dy":0}'
sd effect seed 12345
sd effect "ui:click" '{"x":100,"y":200}' --jsonO payload é parseado como JSON quando parece JSON; caso contrário tratado como string.
sd info
Info do servidor + uptime.
sd info
sd info -i 7f3a --jsonsd stop [instance]
Para um servidor rodando.
sd stop # para a instância ativa
sd stop 7f3a # para uma instância específica
sd stop --all # para tudo
sd stop --jsonsd resources [id] · sd rules [id] · sd handlers [id] · sd context [key]
Inspeciona internals do engine numa instância rodando.
sd resources # todos
sd resources hp # um
sd rules # todas as rules
sd rules my-rule
sd handlers
sd context user.nameEm runtime
closed(default), discovery de actions/rules retornaNOT_GOVERNED. Pra usar essas queries é preciso subir o engine em modogoverned.
Definições (DSL helpers)
Helpers compactos que mutam um servidor rodando antes do tick zerar
(só em tick === -1). Úteis pra experimentação ao vivo; em produção
prefira declarar tudo no body do StateDoc.
sd def resource <id> <type>
sd def resource hp counter --initial 100 --min 0 --max 100
sd def resource board matrix --initial '[[null,null],[null,null]]'
sd def resource info record --spread --initial '{"name":"hero"}'
sd def resource game statemachine \
--initial menu \
--transitions '[["menu","playing"],["playing","won"]]'
sd def resource hp counter -i 7f3a| Opção | Descrição |
|---|---|
| --initial <value> | Valor inicial (JSON) |
| --min <n> / --max <n> | Bounds (counter) |
| --spread | Spread mode (record) |
| --transitions <json> | Transições (statemachine) |
| -i, --instance <id> | Instância alvo |
| --json | Saída em JSON |
sd def rule <id>
sd def rule low-hp \
--when '{"path":"hp:value","op":"lt","value":20}' \
--effects '[{"emit":"warn:low-hp"}]' \
--priority 100
sd def rule on-damage --on damaged \
--effects '[{"state":"score","op":"inc","value":5}]'
sd def rule combo --ttl 5 --change "hp" --effects '[...]'| Opção | Descrição |
|---|---|
| --when <expr> | Condição (JSON) |
| --on <event> | Disparador por evento |
| --effects <json> | Array de effects (JSON) |
| --change <paths> | Paths observados pra trigger por mudança |
| --ttl <n> | Time-to-live (ticks) |
| --priority <n> | Prioridade da rule |
| -i, --instance <id> | Instância alvo |
| --json | Saída em JSON |
sd def handler <id>
sd def handler damage \
--params '{"type":"object","properties":{"amount":{"type":"number"}}}' \
--execute '[{"state":"hp","op":"dec","value":{"$":"params.amount"}}]'| Opção | Descrição |
|---|---|
| --execute <json> | Array de directives (JSON) |
| --params <json> | JSON Schema dos params |
| -i, --instance <id> | Instância alvo |
| --json | Saída em JSON |
Cache
sd cache status # stats do cache
sd cache status --json
sd cache clear # limpa tudo
sd cache clear --headers # só cache de header da chain
sd cache clear --snapshots # só cache de snapshots
sd cache clear --local # só cache do workspace (.sd/)
sd cache clear --global # só cache global (~/.sd/cache/)
sd cache clear -y # pula confirmaçãoAPI REST do servidor
Quando sd run está vivo, o daemon expõe o runtime via HTTP — pelo
Unix socket em ~/.sd/sockets/<id>.sock ou pela porta TCP escolhida
em -p. Todas as respostas têm o envelope:
{
"success": true,
"data": { /* payload específico do endpoint */ }
}ou, em caso de erro:
{
"success": false,
"error": { "code": "STRING_CODE", "message": "..." }
}Inspeção do runtime
| Método | Path | Retorno |
|---|---|---|
| GET | / | { id, source, pid, tick, phase, mode, startedAt, uptime, uptimeFormatted } |
| GET | /info | mesmo de / |
| GET | /tick | { tick: number } |
| GET | /phase | { phase: "created" \| "booted" \| "running" \| "finished" } |
| GET | /idle | { idle: boolean } |
| GET | /capabilities | Inventário de capabilities do runtime (flags + providers) |
| GET | /physics | Leis imutáveis declaradas no realm |
Modo de operação
O runtime tem dois modos: live (gameplay normal, registra effects no
buffer) e recovery (reaplica buffer pra reconstrução determinística;
sem side effects).
| Método | Path | Body / Retorno |
|---|---|---|
| GET | /mode | { mode: "live" \| "recovery" } |
| POST | /mode | Body { mode: "live" \| "recovery" } → { mode } |
Leitura de estado
| Método | Path | Retorno |
|---|---|---|
| GET | /state | { [stateId]: value } — todos os states visíveis |
| GET | /state/ids | { ids: string[] } |
| GET | /state/:id | { id, value } (value = objeto completo do state) |
| GET | /state/:id/:field | { id, field, value } (campo específico) |
Filtros de visibilidade dependem do modo: em closed, só states
exportados pelo realm aparecem; em governed, tudo.
Avanço de tick
| Método | Path | Body | Retorno |
|---|---|---|---|
| POST | /step | { input?, n? } | { tick, stepsExecuted, results[] } |
| POST | /effect | { id, data? } | { applied, tick, result } |
input segue o shape canônico de StepInput:
{
"effects": [{ "id": "...", "data": {...} }],
"provides": [...] // só em modo "recovery"
}n (default 1) executa N steps consecutivos com o mesmo input.
Drenagem do buffer
| Método | Path | Retorno |
|---|---|---|
| GET | /delta | { entries: BufferEntry[] } |
Cada entrada é discriminada (type: "effect" ou type: "provide").
Drena tudo acumulado desde a última chamada (cursor avança).
Snapshot / Recover
Erros vêm como dados (não throw) — mesmo envelope { success, ... }.
| Método | Path | Body / Retorno |
|---|---|---|
| GET | /snapshot | Retorna o RuntimeSnapshot completo { id, mode, capabilities?, realm, persistence? } |
| POST | /recover | Body: RuntimeSnapshot → { recovered, tick } |
Códigos comuns de erro em /recover: SNAPSHOT_ID_MISMATCH,
SNAPSHOT_MODE_MISMATCH, RESTORE_PHASE_INVALID.
Controle do servidor
| Método | Path | Descrição |
|---|---|---|
| POST | /stop | Shutdown gracioso |
Conectando de fora do CLI
Unix socket (default):
curl --unix-socket ~/.sd/sockets/<id>.sock http://localhost/info
curl --unix-socket ~/.sd/sockets/<id>.sock \
-X POST -H 'content-type: application/json' \
-d '{"id":"player:move","data":{"dx":1,"dy":0}}' \
http://localhost/effectTCP (quando subiu com sd run -p 7878):
curl http://localhost:7878/infoAPI programática (Node/TS)
Tudo que o binário faz é alcançável a partir de código.
Entry points
import {
run, // entry do CLI — parseia argv e dispatcha
program, // Program do commander (pra montar subcomandos custom)
} from "@statedelta/cli";
run();
// ou:
program.parse(process.argv);Launcher service
import {
createCliLauncher,
resolveChainOnly,
launchWithRuntime,
isUrl,
resolveSource,
validateSource,
createDefaultProviders,
} from "@statedelta/cli";
// Launcher pré-conectado: FileProvider + HttpProvider + Axiom adapter.
const launcher = createCliLauncher({ maxDepth: 100 });
const docs = await resolveChainOnly("./deltas/save.json");
const result = await launchWithRuntime("./deltas/save.json", {
onProgress: (p) => console.log(`${p.phase}: ${p.percent}%`),
});
const runtime = result.engine;
console.log(runtime.getTick(), runtime.state.get("score", "value"));LauncherServiceOptions:
interface LauncherServiceOptions {
maxDepth?: number;
verify?: boolean;
onProgress?: (progress: LaunchProgress) => void;
defaultEngine?: string;
}Workspace / config services
import {
WorkspaceManagerImpl as WorkspaceManager,
ConfigManagerImpl as ConfigManager,
OutputFormatterImpl as OutputFormatter,
} from "@statedelta/cli";
const ws = await WorkspaceManager.detect(process.cwd());
if (ws.isInWorkspace()) {
console.log(ws.getStatePath(), ws.getHeadPath());
}
const cfg = await ConfigManager.load();
const editor = cfg.get<string>("editor");
const out = new OutputFormatter({ isTTY: process.stdout.isTTY });
out.table(rows);
out.tree(nodes);Tipos exportados
import type {
SDConfig,
SDPreferences,
SDProviders,
SDWorkspace,
SDEngineConfig,
SDChainConfig,
CLIContext,
ConfigManager as IConfigManager,
WorkspaceManager as IWorkspaceManager,
OutputFormatter as IOutputFormatter,
TreeNode,
CommandMeta,
DeltaHeader,
StateFile, // { type, name, version, requires, body }
SnapshotInfo,
} from "@statedelta/cli";Exemplos
Inspecionar um save remoto
sd chain https://example.com/save.json
sd load https://example.com/save.json --state --jsonCriar e dirigir um workspace
sd init mygame --template tictactoe
cd mygame
sd state show
sd delta create chapter-1
sd load --state
# Daemon
sd run -f &
sd ps
sd use $(sd ps -q | head -1)
sd tick
sd effect player:move '{"row":0,"col":0}'
sd step 5
sd stopEmbed programático
import { createCliLauncher } from "@statedelta/cli";
import { writeFileSync } from "node:fs";
const launcher = createCliLauncher();
const result = await launcher.launchWithResult("./save.json");
const snap = result.engine.snapshot();
if (snap.ok) {
writeFileSync("./snap.json", JSON.stringify(snap.value, null, 2));
}Caveats
- Endpoints Vanilla-era (
/resources,/rules,/handlers,/context,/savepoint,/rollback,/commands,/command) foram removidos na migração proIRuntime. A superfície CLI (sd resources/sd rules/sd handlers/sd context) continua existindo mas agora aponta pras queries de discovery do runtime — a maioria égoverned-only e retornaNOT_GOVERNEDemclosed. sd def *escreve num engine rodando antes do tick 0. O adapter do Axiom aceita o caminho imperativo só commainMode: "governed". Sem isso, prefira declarar tudo nobodydo StateDoc.- Snapshots são workspace-scoped por default (vão pra
snapshots/<name>.snapshot.json). Pra destinos arbitrários, use a API programática (runtime.snapshot()).
Ver também
@statedelta/launcher— a API do launcher por baixo@statedelta/core—IRuntime,StateDeltaEnvironment,ResolutionError@statedelta/engine-axiom— adapter padrão que o CLI já conectadocs/protocols/DOCUMENT-PROTOCOL.md— protocolo neutro de documento JSON (StateDoc / DeltaDoc / Snapshot)docs/proposals/PROPOSAL-CORE-RUNTIME-ALIGNMENT.md— porque o contrato é assim
Licença
MIT
