Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
8813c52
feat: complete orchestrator dashboard (11/11 tasks)
Mar 25, 2026
7736b19
feat: align types/data with squire models + vitest infrastructure
Mar 29, 2026
3944361
feat: GlobalStats/TaskList/Timeline components updated + test fixes
Mar 29, 2026
8c9f16b
feat: add RateLimitGauge, CheckpointPanel, TDDProgressBar components …
Mar 29, 2026
863059a
feat: complete TDDProgressBar with tests — all 9 tasks done
Mar 29, 2026
dd6d219
fix: resolve usage testing bugs — pages, GlobalStats, CommitLog
Mar 29, 2026
a59eb7b
fix: add dismiss to AlertBanner and fix initial --:-- in RefreshIndic…
Apr 1, 2026
b904706
fix: prevent alert flash on F5 and fix Invalid Date timestamp
Apr 1, 2026
a8258e0
feat: show sequential task number before task title in TaskList
Apr 1, 2026
22cd9d0
fix: align AlertBanner with canonical Alert type (project_id, task_id)
Apr 1, 2026
c564ba6
feat: progressive pagination for Timeline and CommitLog
Apr 1, 2026
5d884ec
fix: read commits from git log when commits.json is absent
Apr 1, 2026
3581850
feat: expandable file list per commit in CommitLog
Apr 1, 2026
21678de
chore: rename orchestrator-dashboard to squire-dashboard
May 11, 2026
5f3d210
docs: rename orchestrator-dashboard to squire-dashboard
May 11, 2026
8ef8ce7
feat: wire sidebar, RateLimitGauge, TDDProgressBar; drop git-exec fal…
May 11, 2026
a95f587
feat: write commits.json after each task; refresh stale doc anchors
May 11, 2026
1fc7563
feat: surface cost, loop, backend and session features
May 11, 2026
ab4432c
docs: note dashboard as second writer (POST API)
May 11, 2026
d294ea2
feat: A-tier — write actions, HealthStrip, hot polling
May 11, 2026
7da16b7
fix: structured project_id in SessionLock; always write commits.json
May 12, 2026
dfeb00a
docs: refresh anchors shifted by SessionLock/CommitLog field additions
May 12, 2026
76e2165
docs: refresh pre-existing stale squire.py source anchors
May 12, 2026
a501267
feat: point squire at Ollama endpoint; wrapper sources .env
Jun 11, 2026
3f8fdd9
feat: default SQUIRE_STATE_ROOT; tests run without env setup
Jun 11, 2026
d4c4d2e
feat: add squire alerts list/ack/rm
Jun 11, 2026
771638d
feat: add squire doctor health checks with --fix
Jun 11, 2026
4f40fd5
feat: compute approval_first_try_rate; cost accounting regression test
Jun 11, 2026
7075e34
chore: deploy config for VM hosting — port 3101, rw volume, uid override
Jun 11, 2026
7ebd096
Merge branch 'chore/vm-deploy'
Jun 11, 2026
cea49d1
docs: deploy section reflects VM hosting reality
Jun 11, 2026
a3861c9
docs: dashboard production deploy runs on the VM, port 3101, rw volume
Jun 11, 2026
3b70a79
Merge branch 'feat/usable-state'
Jun 11, 2026
29b5583
fix: healthcheck uses 127.0.0.1 — busybox wget resolves localhost to ::1
Jun 11, 2026
1da91e9
feat(cli): non-interactive flags across new/tasks commands
Jun 11, 2026
1b9ae0d
Merge branch 'feat/cli-noninteractive'
Jun 11, 2026
2ad75a5
feat(resilience): infra retry, fence path validation, friendly errors
Jun 11, 2026
a51ff85
Merge branch 'feat/resilience'
Jun 11, 2026
a601894
feat(agent): command queue + squire agent for dashboard-driven ops
Jun 11, 2026
41bcf2d
Merge branch 'feat/command-agent'
Jun 11, 2026
eb6e745
feat(auth): shared-token write gate + /login page
Jun 11, 2026
c739a97
feat(api,ui): task CRUD and project settings from the dashboard
Jun 11, 2026
c36dc1d
feat(commands,ui): queue-backed new project, run controls, AI planning
Jun 11, 2026
65e2a19
docs: agent/queue, non-interactive flags, infra retry, path validatio…
Jun 12, 2026
5bb8034
chore(deploy): DASHBOARD_WRITE_TOKEN in compose; document write surface
Jun 12, 2026
b2e0b4a
Merge branch 'feat/write-features'
Jun 12, 2026
cb90912
fix: squire new generates tasks.json via the real Pydantic model
Jun 12, 2026
fe26a27
fix(data): normalize tasks with model defaults on read
Jun 12, 2026
f809247
Merge branch 'fix/full-task-template'
Jun 12, 2026
67dc460
fix: force-dynamic on home page and health route
Jun 12, 2026
4f240bb
feat(homolog): persist full verdicts in homologation_log.json
Jun 12, 2026
b1ba3f1
feat(fix): squire fix — Claude corrige task bloqueada em ciclo completo
Jun 12, 2026
8844905
feat(triage): BlockedTaskPanel, fix_task command, blocked visibility
Jun 12, 2026
1e753e0
feat(stability): error boundaries, dark mode, resilient polling, 503 UX
Jun 12, 2026
981052d
refactor: dedupe DATA_PATH, write guards, and command enqueueing
Jun 12, 2026
3d583cf
refactor: shared accounting and guarded-git helpers
Jun 12, 2026
3fdc51a
test(e2e): Playwright suite against a seeded state dir
Jun 12, 2026
767b24e
docs: E2E test section in README
Jun 12, 2026
0f70429
docs: squire fix, homologation log, and component table (pt+en)
Jun 12, 2026
1a609c4
Merge branch 'feat/blocked-task-fix'
Jun 12, 2026
613e798
Merge branch 'feat/blocked-triage'
Jun 12, 2026
def801c
fix: surface implement_directly failures; document agent PATH need
Jun 12, 2026
7d06b48
docs: agent unit example needs PATH (claude/opencode) and fix timeout
Jun 12, 2026
e3c5f09
fix(homolog): parse verdict JSON wrapped in prose
Jun 12, 2026
32da8d8
feat: configurable implement_directly timeout (default 600s)
Jun 12, 2026
468b69c
fix: implement_directly demands full files even for tiny fixes; log e…
Jun 12, 2026
f1425df
fix: dynamic layout chrome, HealthStrip copy, PT status labels
Jun 12, 2026
0d83db0
Merge branch 'fix/dashboard-confidence'
Jun 12, 2026
e06626f
fix: approval rate shown at file scale (50 → 50%, not 5000%); Project…
Jun 12, 2026
7504c16
Merge branch 'fix/visual-audit'
Jun 12, 2026
602091a
fix: model attribution from modelUsage; fix cycle counts in approval …
Jun 12, 2026
c3f1956
feat: copyable unblock commands per project and task
Jun 12, 2026
dfd52f6
Merge branch 'feat/cli-hints'
Jun 12, 2026
9ff99a7
feat: containerized two-container stack + DinD escalation seam
Jun 17, 2026
b638373
Add 'dashboard/' from commit 'dfd52f6ae57528a8e5a2f8aae8e08d37a6a5f754'
Jun 17, 2026
fdea4f9
chore: consolidate dashboard into squire/dashboard (monorepo)
Jun 17, 2026
60b233d
fix: address Copilot PR review comments
Jun 17, 2026
b119af9
fix(gitops): surface git add failures in commit_all
Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
// Camada de conveniência: anexar um editor (VS Code / Claude Code Remote)
// ao container workspace pela LAN. A stack continua headless — esta config
// NÃO é dona do ciclo de vida (shutdownAction: none), apenas se conecta ao
// mesmo docker-compose que roda como serviço.
"name": "squire-workspace",
"dockerComposeFile": "../deploy/docker-compose.yml",
"service": "workspace",
"workspaceFolder": "/home/ai-debian/projects",
"remoteUser": "ai-debian",
// Fechar o editor nunca derruba o orquestrador/agente/dashboard.
"shutdownAction": "none",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python"
]
}
}
}
21 changes: 21 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Não levar para o build context / camada da imagem:
.venv/
__pycache__/
**/__pycache__/
*.pyc
.git/
.pytest_cache/
.ruff_cache/
node_modules/
# .env contém segredo — NÃO embutir na imagem (inclui deploy/.env com o
# DASHBOARD_WRITE_TOKEN). Os valores vão via `environment:` do compose.
.env
**/.env
# deploy/ é infra de host (compose); não precisa estar na imagem.
deploy/
# dashboard/ é sub-app Next.js, buildado pelo seu próprio serviço/Dockerfile.
# Excluir do contexto da imagem workspace (evita arrastar fonte + node_modules).
dashboard/
# Estado local de testes e planos não pertencem à imagem.
*.bak
.claude/
41 changes: 22 additions & 19 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
# Squire — environment variables
# Copy to .env and fill in your values (never commit .env)
# Copy to .env and fill in your values (never commit .env).
# The `squire` bash wrapper sources .env automatically at startup.
# Use guarded exports so variables already exported in the shell win:
# export VAR="${VAR:-value}"

# Required: persistent state directory
SQUIRE_STATE_ROOT=/path/to/state
# Persistent state directory (default: /home/ai-debian/squire-state)
export SQUIRE_STATE_ROOT="${SQUIRE_STATE_ROOT:-/path/to/state}"

# LLM local (LiteLLM-compatible endpoint)
SQUIRE_LITELLM_URL=http://localhost:4000/v1
SQUIRE_LITELLM_MODEL=your-model-name
SQUIRE_LITELLM_KEY=sk-local
# Local LLM (any OpenAI-compatible endpoint: LiteLLM, Ollama /v1, llama.cpp server)
export SQUIRE_LITELLM_URL="${SQUIRE_LITELLM_URL:-http://localhost:11434/v1}"
export SQUIRE_LITELLM_MODEL="${SQUIRE_LITELLM_MODEL:-your-model-name}"
export SQUIRE_LITELLM_KEY="${SQUIRE_LITELLM_KEY:-sk-local}"

# Coding backend: litellm | aider | opencode
SQUIRE_CODING_BACKEND=opencode
# Coding backend: litellm | opencode | crush
export SQUIRE_CODING_BACKEND="${SQUIRE_CODING_BACKEND:-opencode}"

# Claude Code binary path
SQUIRE_CLAUDE_BIN=claude
export SQUIRE_CLAUDE_BIN="${SQUIRE_CLAUDE_BIN:-claude}"

# Claude Code rate limiting
SQUIRE_CC_MAX_CALLS=10
SQUIRE_CC_WINDOW_MIN=30
export SQUIRE_CC_MAX_CALLS="${SQUIRE_CC_MAX_CALLS:-10}"
export SQUIRE_CC_WINDOW_MIN="${SQUIRE_CC_WINDOW_MIN:-30}"

# Inner loop limits
SQUIRE_INNER_MAX_ATTEMPTS=10
SQUIRE_INNER_TIMEOUT=1200
export SQUIRE_INNER_MAX_ATTEMPTS="${SQUIRE_INNER_MAX_ATTEMPTS:-10}"
export SQUIRE_INNER_TIMEOUT="${SQUIRE_INNER_TIMEOUT:-1200}"

# Homologation
SQUIRE_MAX_HOMOLOG=3
SQUIRE_LOOP_DETECT=3
SQUIRE_NO_PROGRESS=3
export SQUIRE_MAX_HOMOLOG="${SQUIRE_MAX_HOMOLOG:-5}"
export SQUIRE_LOOP_DETECT="${SQUIRE_LOOP_DETECT:-3}"
export SQUIRE_NO_PROGRESS="${SQUIRE_NO_PROGRESS:-3}"

# Session
SQUIRE_LOCK_TTL=60
SQUIRE_HEARTBEAT=300
export SQUIRE_LOCK_TTL="${SQUIRE_LOCK_TTL:-60}"
export SQUIRE_HEARTBEAT="${SQUIRE_HEARTBEAT:-300}"
91 changes: 64 additions & 27 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ para executar projetos de software de forma semi-autônoma:

A proporção-alvo é **30 chamadas locais para cada 1 do Claude Code**.

O primeiro projeto conduzido pelo orquestrador é o **Orchestrator Dashboard**:
O primeiro projeto conduzido pelo orquestrador é o **Squire Dashboard**:
uma página Next.js que mostra o estado dos projetos em tempo real, lendo
arquivos JSON do filesystem. É o projeto se observando nascer.

Expand All @@ -33,13 +33,15 @@ arquivos JSON do filesystem. É o projeto se observando nascer.
- É onde o Claude Code opera e onde o squire executa
- Tem acesso ao filesystem do Unraid via mount

### LLM local — Qwen via LiteLLM
- **llama.cpp** roda no Zordon com o modelo `Qwen3.5-35B-A3B-Q4_K_M`
- **LiteLLM** é o API gateway: `http://192.168.50.24:4000/v1`
- **Model alias**: `journal-synth` (aponta pro Qwen)
- **API key**: `sk-local` (placeholder, LiteLLM local não exige auth real)
- **Flags do llama.cpp**: `-fa on -ctk q8_0 -ctv q8_0 -ngl all --reasoning-budget -1 --cache-reuse 256`
- **Performance**: ~124 tok/s com reasoning_content visível
### LLM local — Qwen via Ollama
- **Ollama** roda no Zordon servindo o modelo `journal-synth:latest`
(Qwen3.5-35B-A3B, IQ4_NL, `num_ctx 98304`)
- **Endpoint OpenAI-compatible**: `http://192.168.50.24:11434/v1`
- **API key**: `ollama` (placeholder, Ollama não exige auth)
- A configuração local fica em `.env` na raiz do repo (gitignored), que o
wrapper `squire` carrega automaticamente
- Histórico: antes era um gateway LiteLLM na porta 4000 sobre llama.cpp
(desativado em 2026-06)

### Filesystem de estado
```
Expand All @@ -49,7 +51,7 @@ arquivos JSON do filesystem. É o projeto se observando nascer.
├── alerts.json ← alertas ativos
├── global-stats.json ← métricas agregadas
├── projects/
│ ├── orchestrator-dashboard/ ← projeto-piloto
│ ├── squire-dashboard/ ← projeto-piloto
│ │ ├── project.json
│ │ ├── tasks.json
│ │ ├── history.json
Expand Down Expand Up @@ -128,7 +130,7 @@ São duas interações diferentes com o Claude Code:
5 tentativas, me ajuda a desbloquear." Resultado: instruções que voltam
pro LLM local como extra_instructions.

## Projeto-piloto: Orchestrator Dashboard
## Projeto-piloto: Squire Dashboard

### Stack
- **Next.js** (App Router) + TypeScript + Tailwind CSS
Expand All @@ -149,7 +151,7 @@ diretório local com dados de exemplo. Em produção, monta o volume
`/mnt/user/data/squire/` (read-only).

### Tasks do projeto
Ver `projects/orchestrator-dashboard/tasks.json` para o backlog completo.
Ver `projects/squire-dashboard/tasks.json` para o backlog completo.

## Convenções

Expand Down Expand Up @@ -180,6 +182,11 @@ Mapeamento feature → doc (use este atalho antes de editar):
| Viking pattern (`docs/viking/`) | `docs/padrao-viking.md` | `docs/en/viking-pattern.md` |
| Arquitetura / fluxo / componentes | `docs/arquitetura.md` | `docs/en/architecture.md` |
| Failure mode novo / fix conhecido | `docs/troubleshooting.md` | `docs/en/troubleshooting.md` |
| Dashboard (UI Next.js em `dashboard/`) | `dashboard/CLAUDE.md` + `dashboard/README.md` (mesma fonte; sem mirror PT/EN) |

> **Contrato JSON**: ao mudar um schema em `models.py`, atualize o tipo
> espelhado em `dashboard/src/lib/types.ts` **no mesmo commit** (o drift é
> coberto pelo teste `dashboard/src/lib/taskDefaults.test.ts`).

Regras:

Expand Down Expand Up @@ -231,8 +238,13 @@ Regras:
- Não commitar diretamente na `main` — usar branches + merge

### Estrutura de diretórios (dashboard)

O dashboard vive **dentro deste repo**, em `dashboard/` (sub-app: tem seu
próprio `package.json`/`tsconfig`/testes). Briefing próprio em
[`dashboard/CLAUDE.md`](dashboard/CLAUDE.md).

```
orchestrator-dashboard/
dashboard/
├── src/
│ ├── app/
│ │ ├── layout.tsx
Expand Down Expand Up @@ -266,10 +278,10 @@ orchestrator-dashboard/

### Squire (Python)
```bash
SQUIRE_STATE_ROOT=/mnt/user/data/squire
SQUIRE_LITELLM_URL=http://192.168.50.24:4000/v1
SQUIRE_LITELLM_MODEL=journal-synth
SQUIRE_LITELLM_KEY=sk-local
SQUIRE_STATE_ROOT=/home/ai-debian/squire-state
SQUIRE_LITELLM_URL=http://192.168.50.24:11434/v1
SQUIRE_LITELLM_MODEL=journal-synth:latest
SQUIRE_LITELLM_KEY=ollama
SQUIRE_INNER_MAX_ATTEMPTS=10
SQUIRE_INNER_TIMEOUT=300
SQUIRE_CLAUDE_BIN=claude
Expand All @@ -282,7 +294,7 @@ SQUIRE_HEARTBEAT=300

### Dashboard (Next.js)
```bash
ORCHESTRATOR_DATA_PATH=/mnt/user/data/squire
SQUIRE_DATA_PATH=/home/ai-debian/squire-state
NEXT_PUBLIC_REFRESH_INTERVAL=30000
```

Expand All @@ -291,28 +303,53 @@ NEXT_PUBLIC_REFRESH_INTERVAL=30000
### Squire
```bash
cd /caminho/do/squire
python squire.py orchestrator-dashboard # execução normal
python squire.py orchestrator-dashboard --dry-run # simula sem executar
python squire.py orchestrator-dashboard --resume # retoma de crash
python squire.py squire-dashboard # execução normal
python squire.py squire-dashboard --dry-run # simula sem executar
python squire.py squire-dashboard --resume # retoma de crash
```

### Dashboard (dev)
```bash
cd /caminho/do/orchestrator-dashboard
cd /caminho/do/squire/dashboard
npm install
npm run dev
```

### Dashboard (produção)

Canônico: a **stack** em `deploy/docker-compose.yml` sobe workspace +
dashboard juntos (ver "Stack em container" acima). O dashboard roda **na VM
Ai-Debian** (não no Unraid: o estado fica no disco local da VM).

```bash
docker build -t orchestrator-dashboard .
# Pedir ao Danilo para criar o container no Unraid com:
# - Imagem: orchestrator-dashboard
# - Porta: 3100:3000
# - Volume: /mnt/user/data/squire:/data:ro
# - Env: ORCHESTRATOR_DATA_PATH=/data
cd /home/ai-debian/squire
docker compose -f deploy/docker-compose.yml up -d --build
# Porta: 3101:3000 (3100 está ocupada pelo browserless na VM)
# Estado: volume nomeado squire-state em /data (rw — o dashboard escreve
# ack/dismiss de alertas via POST /api/alerts/ack)
# user: 1000:1000 (arquivos de estado são 0600 ai-debian)
# URL: http://<ip-da-vm>:3101
```

O `dashboard/docker-compose.yml` (standalone, build context `.`) ainda serve
para rodar só o dashboard em dev, mas é **superseded** pela stack.

### Stack em container (workspace + dashboard)

Alternativa que isola o squire inteiro do host: stack docker-compose de
dois containers em `deploy/docker-compose.yml` — `workspace` (orquestrador
+ agente + sshd + toolchains + repos, SSH em `:2222`, supervisão s6-overlay
substituindo o unit systemd) e o `dashboard` (imagem existente, `:3101`).
Ambos compartilham o volume nomeado `squire-state` em `/data`. Detalhes,
recuperação de lock após restart e o seam de Docker-in-Docker em
[docs/estado-e-recuperacao.md](docs/estado-e-recuperacao.md).

> **REGRA CRÍTICA (DinD)**: o `workspace` **não** tem Docker e **nunca**
> deve montar o socket do host/Unraid — isso daria controle do Docker do
> host ao container e anularia a contenção. Tasks que precisam buildar
> imagem são escaladas ao humano (alerta `requires_container_build`). A
> única evolução futura aceitável é DinD **rootless** dentro do container.

## Notas para o Claude Code

1. **Você é o tech lead**. Planeja, revisa, e decide. O trabalho braçal de
Expand Down
107 changes: 107 additions & 0 deletions Dockerfile.workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Dockerfile.workspace — container "workspace" do squire.
#
# Contém o orquestrador (squire.py), o agente da fila de comandos
# (agent_cli.py), sshd e os toolchains que o inner loop precisa
# (python, git, node, claude, opencode). É o container no qual o
# operador faz SSH; o dashboard roda em container separado e ambos
# compartilham o volume de estado.
#
# Base Debian bookworm (glibc): o binário opencode é glibc-prebuilt,
# então Alpine/musl está fora.
FROM python:3.11-slim-bookworm

# ── Versões fixadas (reprodutibilidade) ─────────────────────────────
# claude: casa com o `claude --version` do host (2.1.179).
# node: 22.x para casar com o host onde os projetos foram criados.
ARG CLAUDE_VERSION=2.1.179
ARG NODE_MAJOR=22
ARG S6_OVERLAY_VERSION=3.2.0.2
# Fixado na versão do host (opencode --version). Deixe vazio para "latest".
ARG OPENCODE_VERSION=1.15.13

ENV DEBIAN_FRONTEND=noninteractive \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1

# ── Pacotes de sistema (uma camada) ─────────────────────────────────
# procps → ps (squire/doctor usam); xz-utils → extrair s6; openssh-server
# → acesso externo; build-essential FICA DE FORA (adicione via projeto
# se um npm module nativo bloquear).
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
openssh-server \
ca-certificates \
curl \
xz-utils \
procps \
sudo \
less \
nano \
&& curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# ── s6-overlay (PID 1: reaping de zumbis + supervisão de serviços) ──
ADD "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" /tmp/s6-noarch.tar.xz
ADD "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz" /tmp/s6-x86_64.tar.xz
RUN tar -C / -Jxpf /tmp/s6-noarch.tar.xz \
&& tar -C / -Jxpf /tmp/s6-x86_64.tar.xz \
&& rm -f /tmp/s6-noarch.tar.xz /tmp/s6-x86_64.tar.xz

# ── claude (homologação/escalação) ──────────────────────────────────
# Instalado via npm global e fixado; o homologator parseia o
# `claude --print --output-format json`, então a versão importa.
RUN npm install -g "@anthropic-ai/claude-code@${CLAUDE_VERSION}" \
&& npm cache clean --force

# ── Usuário ai-debian (uid/gid 1000) ────────────────────────────────
# Casa com os arquivos de estado (0600 uid 1000) e com o user 1000:1000
# do container do dashboard.
RUN groupadd -g 1000 ai-debian \
&& useradd -m -u 1000 -g 1000 -s /bin/bash ai-debian \
&& mkdir -p /home/ai-debian/.ssh /home/ai-debian/projects \
&& chown -R ai-debian:ai-debian /home/ai-debian \
&& chmod 700 /home/ai-debian/.ssh

# ── opencode (backend de coding padrão) ─────────────────────────────
# Instalado como ai-debian no ~/.opencode/bin (instalador oficial).
# VERSION="" → instala latest; senão fixa a release (o instalador trata
# "latest" como a tag literal vlatest, que não existe).
USER ai-debian
RUN if [ -n "${OPENCODE_VERSION}" ]; then \
curl -fsSL https://opencode.ai/install | VERSION="${OPENCODE_VERSION}" bash; \
else \
curl -fsSL https://opencode.ai/install | bash; \
fi
USER root

# ── Código do squire + venv ─────────────────────────────────────────
# Copiamos o repo (self-contained: doctor passa no primeiro boot). O
# compose pode sobrepor com um bind-mount do repo para dev ao vivo.
COPY --chown=ai-debian:ai-debian . /home/ai-debian/squire
RUN python -m venv /home/ai-debian/squire/.venv \
&& /home/ai-debian/squire/.venv/bin/pip install --no-cache-dir -e /home/ai-debian/squire \
&& chown -R ai-debian:ai-debian /home/ai-debian/squire/.venv \
&& ln -sf /home/ai-debian/squire/squire /usr/local/bin/squire

# ── sshd: só chave pública, sem root, sem senha ─────────────────────
RUN mkdir -p /run/sshd \
&& sed -i \
-e 's/^#\?PermitRootLogin.*/PermitRootLogin no/' \
-e 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' \
-e 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/' \
/etc/ssh/sshd_config \
&& printf '\nAllowUsers ai-debian\n' >> /etc/ssh/sshd_config

# ── Init scripts (cont-init.d, oneshot, root) + serviços (services.d)
COPY docker/cont-init.d/ /etc/cont-init.d/
COPY docker/services.d/ /etc/services.d/
COPY docker/profile.d/squire.sh /etc/profile.d/squire.sh
RUN chmod +x /etc/cont-init.d/* /etc/services.d/*/run /etc/profile.d/squire.sh

# Porta SSH (mapeada para 2222 no host pelo compose).
EXPOSE 22

# s6 inicia tudo: cont-init.d (chown/ssh-keygen) → services.d (sshd, agent).
ENTRYPOINT ["/init"]
4 changes: 4 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ squire/
├── progress.py ← progress.txt generator
├── tasks_cli.py ← `squire tasks` subcommands
├── tests/ ← pytest
├── dashboard/ ← Next.js app (read-write state UI) — sub-app
├── deploy/ ← docker-compose stack (workspace + dashboard)
├── docker/ ← s6 init/services for the workspace container
├── Dockerfile.workspace ← orchestrator image (SSH, toolchains)
└── docs/ ← documentation
└── en/ ← English mirror (this language)
```
Expand Down
Loading