Files
stack/packages/mosaic
Hermes Agent b4578dcd0a
ci/woodpecker/pr/ci Pipeline was successful
wrapper-guard: close the absence shape at the new boundary; stop giving wrong advice
Round six deleted the code/data parser and scoped what remained on `https?://`.
Review found the failure class had not been eliminated, only relocated: a raw
provider CLI carries no scheme, so the scope gate answered "not my business"
because the URL was ABSENT — the same shape, at the new boundary.

  gh api -X POST repos/a/b/issues -f title=x -f body=y
  gh api -X POST repos/a/b/pulls/1/reviews -f event=APPROVE
  tea api -X POST repos/a/b/issues/1/comments -f body=x
  curl -X POST -d x git.example.invalid/api/v1/repos/a/b/issues

All four were real writes to endpoints a wrapper owns, and all four passed.
Constitution gate 7 names raw provider CLIs explicitly, so they are in scope
rather than something to narrow the docs around. The scope gate now also
triggers on `/api/v{n}` and on the `api` subcommand of the provider CLIs, and
`-f key=value` joins curl's `-d` as an implicit POST. Adding triggers to a scope
gate can only make it stricter — it cannot open a new hole — which is why this
is a list of shapes rather than a model of any one caller.

The boundary is stated in the file rather than left to be discovered: provider
PORCELAIN (`tea pulls create`) is NOT covered, because catching it means
modelling every CLI's verb grammar, which is the parser mistake wearing a new
costume. That is a wrapper-and-review gap, not a thing this hook can hold.

Second finding, and the one I had flagged as my own worry: the endpoint `case`
was prefix-greedy, so `/issues/1/labels` blocked with "use issue-create.sh" —
the wrong wrapper for that call. A block an agent cannot comply with is worse
than no block, because it teaches that the hook is broken and the override is
routine, and an override that is routine is a guard that is off. Issue and PR
subresources now flow through, exactly as /releases and every other endpoint no
wrapper owns already does. This hook enforces "use the wrapper"; where there is
no wrapper it has nothing to enforce, and the gap belongs in the wrapper set.

I am departing from the review on that one deliberately: the review held that
blocking is correct there and only the remediation wrong. Naming a wrapper gap
in a refusal keeps gate-7 pressure, but it makes the override the normal path
for every labels and assignees call, which spends the override's meaning on the
cases where it is least needed.

Also: the APPROVE trap now catches the provider-CLI spelling `-f event=APPROVE`
alongside the JSON body, and still never matches the correct value APPROVED.

79/79 fixtures, locally and inside the CI image, with both blockers pinned in
both directions — the four repros block and name the right wrapper, while a
provider-CLI read, an unwrapped endpoint reached through one, porcelain, and the
`rm -f`/`grep -f` collisions all still pass. The 18-command ordinary sweep
blocks the same three round-six flips and nothing new, so the broader gate cost
nothing on ordinary work. Second over-block documented rather than found: prose
carrying `.post(` near a wrapped URL is refused, which follows from judging the
payload and is now stated next to the quoted-curl cost.

Gates: sanitization (all eight commands green in ci-base), shellcheck clean.
2026-08-12 18:10:19 -05:00
..

@mosaicstack/mosaic

CLI package for the Mosaic self-hosted AI agent platform.

Usage

mosaic wizard           # First-run setup wizard
mosaic gateway install  # Install the gateway daemon
mosaic config show      # View current configuration
mosaic config hooks list  # Manage Claude hooks

Headless / CI Installation

Set MOSAIC_ASSUME_YES=1 (or ensure stdin is not a TTY) to skip all interactive prompts. The following environment variables control the install:

Gateway configuration (mosaic gateway install)

Variable Default Required
MOSAIC_STORAGE_TIER local No
MOSAIC_GATEWAY_PORT 14242 No
MOSAIC_DATABASE_URL (none) Yes if tier=team
MOSAIC_VALKEY_URL (none) Yes if tier=team
MOSAIC_ANTHROPIC_API_KEY (none) No
MOSAIC_CORS_ORIGIN http://localhost:3000 No

Admin user bootstrap

Variable Default Required
MOSAIC_ADMIN_NAME (none) Yes (headless)
MOSAIC_ADMIN_EMAIL (none) Yes (headless)
MOSAIC_ADMIN_PASSWORD (none) Yes (headless)

MOSAIC_ADMIN_PASSWORD must be at least 8 characters. In headless mode a missing or too-short password causes a non-zero exit.

Example: Docker / CI install

export MOSAIC_ASSUME_YES=1
export MOSAIC_ADMIN_NAME="Admin"
export MOSAIC_ADMIN_EMAIL="[email protected]"
export MOSAIC_ADMIN_PASSWORD="securepass123"

mosaic gateway install

Runtime launchers

mosaic claude            # Launch Claude Code with Mosaic injection
mosaic yolo claude       # …with --dangerously-skip-permissions
mosaic codex | opencode | pi

mosaic claudex (EXPERIMENTAL)

Runs GPT models inside the Claude Code harness by pointing Claude Code at a local claude-code-proxy that translates the Anthropic Messages API to a ChatGPT-subscription (Codex OAuth) backend. This is not Anthropic Claude — model behavior, tool use, and output quality may differ. Intended for evaluation, not production delivery.

mosaic claudex           # launch (prompts through the proxy readiness gate)
mosaic yolo claudex      # …with --dangerously-skip-permissions
mosaic claudex --print "hello"   # trailing args are forwarded to Claude Code

Prerequisite: the claude-code-proxy binary must be installed and authenticated (claude-code-proxy codex auth …). mosaic claudex runs a preflight that verifies the binary, the OAuth state (triggering a device re-auth if needed), and a trusted local listener before launching; it fails closed if the proxy cannot be brought up with a verified identity.

Isolation (never touches your real Claude state). claudex always launches against an isolated CLAUDE_CONFIG_DIR (default ~/.config/mosaic/claudex/home). The ambient CLAUDE_CONFIG_DIR is deliberately ignored, and a guard proves the resolved dir can never be — or live under — the real ~/.claude. A claudex session therefore cannot mutate your normal Claude Code config.

No token leakage. claudex never reads the proxy's credential file. Claude Code is handed only ANTHROPIC_AUTH_TOKEN=unused pointed at the loopback proxy; the entire credential-bearing env family (ANTHROPIC_*, AWS_*, GOOGLE_CLOUD_*, GOOGLE_APPLICATION_CREDENTIALS, *_TOKEN, *_KEY, *_SECRET, …) is stripped from the composed environment. The Bedrock/Vertex routing switches (CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, and the _SKIP_*_AUTH pair) are force-removed regardless of value — otherwise their mere presence would route Claude Code to the real Anthropic API via AWS/GCP and bypass the proxy. The proxy holds the real OAuth credential.

Model tiers (override via env).

Tier Env var Default
primary (opus/sonnet) ANTHROPIC_MODEL gpt-5.6-sol
small/fast (haiku) ANTHROPIC_SMALL_FAST_MODEL gpt-5.6-luna

Operator-provided values win over the defaults. Additional overrides: MOSAIC_CLAUDEX_CONFIG_DIR (isolated config dir), ANTHROPIC_BASE_URL (proxy endpoint).

Hooks management

After running mosaic wizard, Claude hooks are installed in ~/.claude/hooks-config.json.

mosaic config hooks list              # Show all hooks and enabled/disabled status
mosaic config hooks disable PostToolUse  # Disable a hook (reversible)
mosaic config hooks enable PostToolUse   # Re-enable a disabled hook

Set CLAUDE_HOME to override the default ~/.claude directory.