Remove the 8 non-bright and 1 bright-black colors from all color maps.
Rename the remaining 7 bright-* colors to their base names (e.g.
bright-green → green) so the palette is smaller and always vibrant.
Update _init_color_pairs in tui.py to always apply A_BOLD (all palette
entries are now bright variants), and fix all tests to match.
The git-gate copies the identity file at start time and surfaces a
clear failure then; the pre-launch presence check was redundant.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_validate_git_entries was written for static keys (PRD 0008) and ran
os.path.isfile() on every entry's IdentityFile. gitea-provider repos
(PRD 0047/0048) create their deploy key at provision time, so
IdentityFile is empty at parse — tripping the check with an empty path
("git upstream key file not found for '<name>': "). Gate the host-file
check on the static provider; gitea entries have nothing to verify here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the lossy _entry_to_raw round-trip with a repos_cache threaded
alongside the ManifestBottle cache in _resolve_one_bottle. Each bottle's
effective git-gate.repos is stored as raw dicts keyed by name, so a child
field-merges directly against its parent's raw repos instead of
reconstructing them from parsed ManifestGitEntry objects.
_resolve_repos_raw now owns the union/clear/inherit semantics on plain
dicts; _merge_bottles just injects the precomputed merged set before
parsing. Drops _entry_to_raw entirely, removing the maintenance hazard
where a new ManifestGitEntry field would silently vanish from inherited
repos.
Addresses review feedback on #238.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgEFTXcWZjA8n7ntq2zHQQ
Replace the bespoke _pre_merge_git_repos loop and _merge_git_remotes
with a single _merge_git_repos_raw that does a name-keyed union merge
at the raw dict level: build parent_repos from _entry_to_raw, then
for each name in set(child) | set(parent) produce {**parent.get(n,{}),
**child.get(n,{})}. child.git after from_dict already has the full
merged set, so _merge_git_remotes is no longer needed.
When a child bottle declares a git-gate repo with the same name as a
parent repo, merge field-by-field (child wins, parent provides fallback)
instead of letting the child entry silently replace the parent entry.
This lets a child override only `key:` without repeating `url:` and
`host_key:`. Change the merge key in _merge_git_remotes from UpstreamHost
to Name, which is the natural unique identity for a repo entry.
Closes#237
Replace the two mutually-exclusive repo keys (identity and
provisioned_key) with a single required key block. key.provider
is "static" (path to host SSH key) or "gitea" (deploy-key lifecycle
via provisioner_token env var, replacing token_env).
Internal fields: ManifestProvisionedKeyConfig → ManifestKeyConfig;
ProvisionedKey field removed from ManifestGitEntry; Key field added.
git_gate.py checks entry.Key.provider == "gitea" instead of
entry.ProvisionedKey is not None.
`container system info` is not a valid subcommand and always returned
non-zero, causing a false-positive on the service check. Switch to
`container system status` which is the correct command.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fail early with a clear message when the Apple Container system service
isn't running, instead of surfacing an opaque XPC connection error mid-build.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When $old != zero and $new is not a descendant of $old (detected via
git merge-base --is-ancestor), the hook now forwards +$new:$ref so the
upstream accepts the force push instead of rejecting it as a
non-fast-forward.
Closes#233
Add backend-agnostic terminal color support via OSC escape sequences:
- New backend/terminal.py with palette_printf() and exec_shell_script()
shared by both Docker and smolmachines bottle backends
- Emits OSC 4 (indexed palette) + OSC 11 (default background tint)
before launching; resets both on agent exit via OSC 104/111
- OSC 11 background tint is visible even when the TUI uses true/24-bit
colors (which bypass the palette), as Codex does for its chrome
- Fix Codex [tui] config: status_line=["model-with-reasoning"],
theme="ansi" (dark-ansi and cwd/directory were invalid identifiers)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace prompt-injection for display identity with native UI wiring:
- Claude: writes a statusline shell script + custom theme JSON, wired up
via settings.json so label/color show in the status bar and theme
- Codex: writes [tui] block into codex-config.toml (status_line,
terminal_title, dark-ansi theme)
- Both backends set the terminal title via ANSI OSC 0 escape before
exec-ing the agent when a label is present
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop the parallel fields passed through prepare() → _resolve_plan and
read everything from agent_provision instead. The provider plugin now
declares its own guest_home (so the backend stops hardcoding
"/home/node") and the wrapper that builds the provision plan accepts
instance_name and prompt_file, which providers store on the plan.
DockerBottlePlan and SmolmachinesBottlePlan expose container_name /
machine_name, image / agent_image, dockerfile_path /
agent_dockerfile_path, and prompt_file as properties that delegate to
agent_provision so existing call sites keep working unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BottleBackend.prepare was calling resolve_manifest_dockerfile("", spec)
for every bottle where the manifest did not set agent_provider.dockerfile.
That resolves an empty string against user_cwd, returning the cwd
itself — which docker then tried to read as a Dockerfile, giving
"is a directory" errors during image build.
When the manifest doesn't override, use the provider plugin's bundled
Dockerfile path (next to its agent_provider.py module) — mirroring
the pre-refactor behavior.
BottleBackend.prepare computed slug and resolved_env but never passed
them to _resolve_plan. The concrete docker/smolmachines _resolve_plan
methods still had the old (spec, *, stage_dir) signature too, so
prepare's kwargs blew up with "unexpected keyword argument
'instance_name'" the moment cli.py start was invoked.
Update the abstract _resolve_plan signature and both backend
implementations to accept the full kwarg set prepare passes, and
forward to resolve_plan.resolve_plan() with everything.
The recent refactor partially removed workspace planning and
capability-apply logic. This commit finishes the cleanup so the
test suite imports cleanly:
- Comment out workspace_plan field/property on BottlePlan and the
provision_workspace dispatch.
- Comment out workspace usages in docker.util (build_image_with_cwd),
smolmachines.provision.workspace, agent_provider.provision_git,
smolmachines.backend.
- Comment out capability_apply imports in cli.start and cli.supervise;
add a local CapabilityApplyError placeholder so the supervise CLI
module still imports.
- Break the bottle_state → backend.docker → backend circular import
by lazy-loading docker_mod inside bottle_identity, and by moving the
resolve_common import inside BottleBackend.prepare.
- Delete tests for workspace and capability_apply (unit + integration).
- Update test fixtures to drop removed kwargs (container_name_pinned,
derived_image, env_file, workspace_plan, agent_image_ref) from
DockerBottlePlan / SmolmachinesBottlePlan constructors.
- Delete the obsolete test_smolmachines_prepare.py (tested the old
resolve_plan signature; the shared prepare flow now lives in
BottleBackend.prepare).
- Adjust test_supervise.py for the new Supervise.prepare signature
(dockerfile_content arg removed).
925 → 897 tests, all passing.
guest_home is now a field on AgentProvisionPlan (set by each provider's
provision_plan() method). BottlePlan.guest_home becomes a read-only
property delegating to agent_provision.guest_home so existing callers
(provision_git, provision_skills, provision_prompt) are unchanged.
Both resolve_plan.py files drop guest_home from the plan constructor
call; the local variable still exists as an intermediary for the
workspace_plan call that precedes agent_provision_plan.