One binary: the lint and the trainer are commands of portal
Test / test (pull_request) Successful in 1m7s

portal serve runs a site; portal lint is what iris was; portal plan,
build, report and the rest are what portal-trainer was. One source at
one version, so the three never disagree about what content means.
Both histories are merged in (subtree). The release attaches the bare
binary for a content repo's CI. The trainer's plans, intakes, engines
and reference prompt come along; its runs stay in the old repo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
This commit is contained in:
Bendik Aagaard Lynghaug
2026-09-30 15:29:30 +02:00
co-authored by Claude Fable 5.1
parent d0d1cbf23d
commit 96b3746643
1733 changed files with 183 additions and 150652 deletions
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
# LLM backend for portal-trainer: prompt on stdin, text on stdout.
# Needs ANTHROPIC_API_KEY, curl and jq. MODEL and MAX_TOKENS override.
set -eu
: "${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY}"
jq -Rs --arg model "${MODEL:-claude-sonnet-5}" --argjson max "${MAX_TOKENS:-16000}" \
'{model: $model, max_tokens: $max, messages: [{role: "user", content: .}]}' |
curl -sS --fail-with-body --max-time "${TIMEOUT:-600}" https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" -H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" --data-binary @- |
jq -er '[.content[] | select(.type == "text") | .text] | join("")'
+22
View File
@@ -0,0 +1,22 @@
#!/bin/sh
# LLM backend for portal-trainer on Claude Code's own login (a Max or
# Pro subscription): prompt on stdin, text on stdout. No API key
# needed: an ANTHROPIC_API_KEY in the environment would outrank the
# login, so it is dropped here. Log the CLI in once with
# `claude auth login`; `claude auth status` shows what it uses.
# MODEL is an alias or id (sonnet, opus, claude-sonnet-5); EFFORT is
# low, medium or high. Not --bare: that mode skips the stored login.
#
# The generator's SEED and TEMPERATURE hints cannot be honoured;
# FORMAT=json is turned into an instruction.
set -eu
unset ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN CLAUDECODE CLAUDE_CODE_ENTRYPOINT
prompt=$(cat)
if [ "${FORMAT:-}" = json ]; then
prompt="$prompt
Reply with the JSON object only, no prose, no code fences."
fi
printf '%s' "$prompt" | claude -p --model "${MODEL:-sonnet}" --effort "${EFFORT:-low}" \
--output-format text --permission-mode dontAsk --no-session-persistence \
--tools ""
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# LLM backend that hands each prompt to whoever watches $MAILBOX:
# prompt-N.md goes out, the reply is read from reply-N.md. For driving
# the generator from an agent session, or by hand.
set -eu
: "${MAILBOX:?set MAILBOX to a directory}"
mkdir -p "$MAILBOX"
n=$(( $(ls "$MAILBOX" | grep -c '^prompt-' || true) + 1 ))
cat > "$MAILBOX/prompt-$n.md"
until [ -s "$MAILBOX/reply-$n.md" ]; do sleep 2; done
sleep 1
cat "$MAILBOX/reply-$n.md"
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# LLM backend for portal-trainer on a local Ollama: prompt on stdin,
# text on stdout. MODEL, OLLAMA_HOST, NUM_CTX and TIMEOUT override.
# Thinking is off: the generator wants files, the answerer wants JSONL.
# The simulator sets SEED (so the same case replays the same way
# against two versions of a site), FORMAT=json and TEMPERATURE.
set -eu
jq -Rs --arg model "${MODEL:-qwen3:8b}" --argjson ctx "${NUM_CTX:-32768}" \
--argjson temp "${TEMPERATURE:-0.2}" --arg seed "${SEED:-}" --arg format "${FORMAT:-}" \
'{model: $model, stream: false, think: false,
options: ({num_ctx: $ctx, temperature: $temp}
+ (if $seed == "" then {} else {seed: ($seed | tonumber)} end)),
messages: [{role: "user", content: .}]}
+ (if $format == "json" then {format: "json"} else {} end)' |
curl -sS --fail-with-body --max-time "${TIMEOUT:-1800}" \
"${OLLAMA_HOST:-http://127.0.0.1:11434}/api/chat" \
-H "content-type: application/json" --data-binary @- |
jq -er '.message.content'