Round seven fixed "wrong wrapper advice" by letting every path under a numbered issue or PR flow through, on the stated reasoning that no wrapper owned any of them. Review checked that reasoning against the directory and it was false: gh api -X PATCH repos/a/b/issues/1 -f title=x curl -X PATCH -d @b https://host/api/v1/repos/a/b/issues/1 gh api -X PATCH repos/a/b/issues/1/labels -f labels[]=bug gh api -X POST repos/a/b/issues/1/assignees -f assignees[]=u issue-edit.sh takes --title/--body/--labels/--milestone and issue-assign.sh takes assignee/labels/milestone, so all four are wrapped calls and all four returned 0. The guard answered "allow" because wrapper ownership had been ASSUMED absent rather than looked up — the same absence-driven allow this file exists to remove, committed inside the fix for it. I withdraw the round-seven departure: the reviewer's position was right on the evidence, and my argument for it was sound reasoning applied to a fact I never checked. The endpoint map is now an inventory read off tools/git/*.sh and their flags: assignees to issue-assign.sh, labels to issue-edit.sh (naming issue-assign.sh alongside it, since both set them), a numbered issue to issue-edit.sh (naming issue-close.sh/issue-reopen.sh for state), a numbered PR to pr-close.sh (with the PR title/body gap stated in the message rather than papered over), and /milestones/{n} to milestone-close.sh instead of the create wrapper. The residue is defined by SUBTRACTION, not by listing provider API surface: everything a wrapper owns is consumed by an arm above, so a numbered path that reaches the end is owned by nothing and still flows through — times, stopwatch, reactions, a comment edit at /issues/comments/{id}. A list would rot the moment a provider adds an endpoint, and rot in the blocking direction with wrong advice. That residue test is a regex, deliberately. `case` globs cannot express a path SEGMENT, so the natural allow arm *"/issues/"[0-9]*"/"* clears gh api -X PATCH repos/a/b/issues/1 -f body="see /docs" on the strength of a slash inside the body. An allow decided by a glob over the whole command is the fail-open shape again; the regex pins the segment to the number, and that command is pinned as a fixture. Also: `-f labels[]=bug` was not read as a body at all, because the key class stopped at the bracket. The array spelling is what the provider CLIs use for repeated fields, so an implicit POST carrying only array fields was invisible. And the reason six rounds of this were invisible: the harness read the exit code and nothing else, so a block naming the WRONG wrapper passed every run. Fixtures may now state the wrapper the message must name, and the wrapped ones do. The assertion was negative-controlled — pointing one fixture at the wrong wrapper fails that fixture and only that fixture. 89/89 (was 79), locally and in ci-base. All eight sanitization commands green in-image. The 18-command ordinary sweep blocks the same three round-six flips and nothing new, so the tighter map cost nothing on ordinary work. Gates: sanitization (all eight green in ci-base), shellcheck clean at warning+.
@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.