From 74a2c7a32a9400684dfbefbe2f5b97ad03a06d0b Mon Sep 17 00:00:00 2001 From: didericis Date: Thu, 7 May 2026 23:02:34 -0400 Subject: [PATCH] refactor: rename box/boxes to bottle/bottles in config schema and code Co-Authored-By: Claude Opus 4.7 --- cli.sh | 100 ++++++++++++++++++++++----------------------- lib/manifest.sh | 106 ++++++++++++++++++++++++------------------------ 2 files changed, 103 insertions(+), 103 deletions(-) diff --git a/cli.sh b/cli.sh index 399a3f3..b5acbbb 100755 --- a/cli.sh +++ b/cli.sh @@ -96,8 +96,8 @@ cmd_info() { prompt_len="${#prompt_content}" prompt_first_line="$(printf '%s' "$prompt_content" | awk 'NR==1{print; exit}')" - local box_name - box_name="$(manifest_agent_box "$MANIFEST_FILE" "$NAME")" + local bottle_name + bottle_name="$(manifest_agent_bottle "$MANIFEST_FILE" "$NAME")" local ssh_entries=() _se while IFS= read -r _se; do @@ -110,8 +110,8 @@ cmd_info() { info "env (names only): ${env_names:-(none)}" info "skills : ${skill_names[*]:-(none)}" info "prompt : ${prompt_len} chars; first line: ${prompt_first_line:-(empty)}" - if [ -n "$box_name" ]; then - info "box : ${box_name}" + if [ -n "$bottle_name" ]; then + info "bottle : ${bottle_name}" if [ "${#ssh_entries[@]}" -gt 0 ]; then local _n _h _u _p _k _khk for _se in "${ssh_entries[@]}"; do @@ -128,7 +128,7 @@ cmd_info() { info " ssh hosts : (none)" fi else - info "box : (none)" + info "bottle : (none)" fi printf '\n' } @@ -378,16 +378,16 @@ cmd_start() { skills_validate_all "${SKILL_NAMES[@]}" fi - # Resolve the box referenced by this agent and validate it exists. - # A box is required — agents without one are rejected before launch. - local BOX_NAME - BOX_NAME="$(manifest_agent_box "$MANIFEST_FILE" "$NAME")" - if [ -z "$BOX_NAME" ]; then - die "agent '${NAME}' has no 'box' field. Add a box association to this agent in claude-bottle.json." + # Resolve the bottle referenced by this agent and validate it exists. + # A bottle is required — agents without one are rejected before launch. + local BOTTLE_NAME + BOTTLE_NAME="$(manifest_agent_bottle "$MANIFEST_FILE" "$NAME")" + if [ -z "$BOTTLE_NAME" ]; then + die "agent '${NAME}' has no 'bottle' field. Add a bottle association to this agent in claude-bottle.json." fi - manifest_require_box "$MANIFEST_FILE" "$BOX_NAME" + manifest_require_bottle "$MANIFEST_FILE" "$BOTTLE_NAME" - # SSH entries come from the agent's box (empty if no box set). + # SSH entries come from the agent's bottle (empty if no bottle set). local SSH_ENTRIES=() local _se while IFS= read -r _se; do @@ -457,8 +457,8 @@ cmd_start() { else info "skills : (none)" fi - if [ -n "$BOX_NAME" ]; then - info "box : ${BOX_NAME}" + if [ -n "$BOTTLE_NAME" ]; then + info "bottle : ${BOTTLE_NAME}" if [ "${#SSH_ENTRIES[@]}" -gt 0 ]; then local _ssh_names="" _se for _se in "${SSH_ENTRIES[@]}"; do @@ -471,7 +471,7 @@ cmd_start() { info " ssh hosts : (none)" fi else - info "box : (none)" + info "bottle : (none)" fi info "prompt : ${PROMPT_LEN} chars; first line: ${PROMPT_FIRST_LINE:-(empty)}" printf '\n' >&2 @@ -708,34 +708,34 @@ cmd_init() { fi done - # --- Box association --- + # --- Bottle association --- printf '\n' >&2 - printf 'Associate this agent with a box? [y/N] ' >&2 - local _box_yn="" - IFS= read -r _box_yn &2 + local _bottle_yn="" + IFS= read -r _bottle_yn &2 - IFS= read -r BOX_NAME &2 + IFS= read -r BOTTLE_NAME /dev/null 2>&1; then - _box_exists=1 - info "Box '${BOX_NAME}' already exists in ${TARGET_FILE}; agent will reference it." + # Check whether the bottle already exists in the target file. + if [ -f "$TARGET_FILE" ] && jq -e --arg b "$BOTTLE_NAME" '.bottles | has($b)' "$TARGET_FILE" >/dev/null 2>&1; then + _bottle_exists=1 + info "Bottle '${BOTTLE_NAME}' already exists in ${TARGET_FILE}; agent will reference it." else - info "Creating new box '${BOX_NAME}'." + info "Creating new bottle '${BOTTLE_NAME}'." - # --- Env vars (stored on the box) --- + # --- Env vars (stored on the bottle) --- printf '\n' >&2 info "Env vars — enter each var name then its mode. Press Enter with no name to finish." info " Modes: secret (prompt at runtime) | interpolated (read from host env) | literal (hardcoded value)" @@ -779,10 +779,10 @@ cmd_init() { ;; esac - BOX_ENV_JSON="$(printf '%s' "$BOX_ENV_JSON" | jq --arg k "$_vname" --arg v "$_vval" '.[$k] = $v')" + BOTTLE_ENV_JSON="$(printf '%s' "$BOTTLE_ENV_JSON" | jq --arg k "$_vname" --arg v "$_vval" '.[$k] = $v')" done - printf ' Add SSH host entries to this box? [y/N] ' >&2 + printf ' Add SSH host entries to this bottle? [y/N] ' >&2 local _ssh_yn="" IFS= read -r _ssh_yn "$TMP_FILE"; then rm -f "$TMP_FILE" diff --git a/lib/manifest.sh b/lib/manifest.sh index 378ead4..6dc496b 100644 --- a/lib/manifest.sh +++ b/lib/manifest.sh @@ -5,8 +5,8 @@ # The manifest schema is documented in CLAUDE.md "Intended design". In # short: # { -# "boxes": { -# "": { +# "bottles": { +# "": { # "env": { "": , ... }, # "ssh": [ , ... ] # }, @@ -16,14 +16,14 @@ # "": { # "skills": [ "", ... ], # "prompt": "", -# "box": "" +# "bottle": "" # }, # ... # } # } # -# A box groups shared infrastructure (SSH keys, known hosts) that multiple -# agents can reference by name. The "box" field is required on every agent; +# A bottle groups shared infrastructure (SSH keys, known hosts) that multiple +# agents can reference by name. The "bottle" field is required on every agent; # cli.sh start rejects agents that omit it. # # An is a JSON string. Mode is selected by sentinel prefix: @@ -96,9 +96,9 @@ manifest_resolve() { elif [ "$has_cwd" = "0" ] && [ "$has_home" = "1" ]; then cat "$home_file" else - # Merge: home is the base, cwd overrides on name conflict for both boxes and agents. + # Merge: home is the base, cwd overrides on name conflict for both bottles and agents. jq -s '{ - "boxes": ((.[0].boxes // {}) * (.[1].boxes // {})), + "bottles": ((.[0].bottles // {}) * (.[1].bottles // {})), "agents": ((.[0].agents // {}) * (.[1].agents // {})) }' "$home_file" "$cwd_file" fi @@ -130,40 +130,40 @@ manifest_require_agent() { } # manifest_env_names — prints one env-var name per line -# on stdout (the keys of boxes[agent.box].env, in declaration order). No values. -# Prints nothing if the agent has no box or the box has no env. +# on stdout (the keys of bottles[agent.bottle].env, in declaration order). No values. +# Prints nothing if the agent has no bottle or the bottle has no env. manifest_env_names() { local manifest_file="${1:?manifest_env_names: missing manifest file}" local name="${2:?manifest_env_names: missing agent name}" jq -r --arg n "$name" ' - .agents[$n].box as $box | - if ($box == null or $box == "") then empty - else (.boxes[$box].env // {} | keys_unsorted[]) + .agents[$n].bottle as $bottle | + if ($bottle == null or $bottle == "") then empty + else (.bottles[$bottle].env // {} | keys_unsorted[]) end ' "$manifest_file" } # manifest_env_entry — prints the raw # string value of a single env entry on stdout (no quoting, no JSON -# encoding). Env entries live on the agent's box (boxes[agent.box].env). +# encoding). Env entries live on the agent's bottle (bottles[agent.bottle].env). # Used by env_resolve.sh, which classifies the result by sentinel. Dies -# if the agent has no box, or the entry is not a JSON string; the +# if the agent has no bottle, or the entry is not a JSON string; the # prompt-at-runtime form is "?", not JSON null. manifest_env_entry() { local manifest_file="${1:?manifest_env_entry: missing manifest file}" local agent="${2:?manifest_env_entry: missing agent name}" local var="${3:?manifest_env_entry: missing env var name}" - local box - box="$(jq -r --arg a "$agent" '.agents[$a].box // ""' "$manifest_file")" - if [ -z "$box" ]; then - die "env entry ${var} for agent ${agent}: agent has no 'box' field" + local bottle + bottle="$(jq -r --arg a "$agent" '.agents[$a].bottle // ""' "$manifest_file")" + if [ -z "$bottle" ]; then + die "env entry ${var} for agent ${agent}: agent has no 'bottle' field" fi local entry_type - entry_type="$(jq -r --arg b "$box" --arg v "$var" '.boxes[$b].env[$v] | type' "$manifest_file")" + entry_type="$(jq -r --arg b "$bottle" --arg v "$var" '.bottles[$b].env[$v] | type' "$manifest_file")" if [ "$entry_type" != "string" ]; then die "env entry ${var} for agent ${agent} must be a JSON string (was ${entry_type}). Use \"?\" for prompt-at-runtime." fi - jq -r --arg b "$box" --arg v "$var" '.boxes[$b].env[$v]' "$manifest_file" + jq -r --arg b "$bottle" --arg v "$var" '.bottles[$b].env[$v]' "$manifest_file" } # manifest_skills — prints one skill name per line on @@ -183,61 +183,61 @@ manifest_prompt() { jq -r --arg n "$name" '.agents[$n].prompt // ""' "$manifest_file" } -# manifest_agent_box — prints the box name referenced -# by the agent on stdout, or an empty string if the agent has no "box" field. -manifest_agent_box() { - local manifest_file="${1:?manifest_agent_box: missing manifest file}" - local name="${2:?manifest_agent_box: missing agent name}" - jq -r --arg n "$name" '.agents[$n].box // ""' "$manifest_file" +# manifest_agent_bottle — prints the bottle name referenced +# by the agent on stdout, or an empty string if the agent has no "bottle" field. +manifest_agent_bottle() { + local manifest_file="${1:?manifest_agent_bottle: missing manifest file}" + local name="${2:?manifest_agent_bottle: missing agent name}" + jq -r --arg n "$name" '.agents[$n].bottle // ""' "$manifest_file" } -# manifest_has_box — returns 0 if the named box +# manifest_has_bottle — returns 0 if the named bottle # exists in the manifest, else 1. -manifest_has_box() { - local manifest_file="${1:?manifest_has_box: missing manifest file}" - local box_name="${2:?manifest_has_box: missing box name}" - jq -e --arg b "$box_name" '.boxes | has($b)' "$manifest_file" >/dev/null 2>&1 +manifest_has_bottle() { + local manifest_file="${1:?manifest_has_bottle: missing manifest file}" + local bottle_name="${2:?manifest_has_bottle: missing bottle name}" + jq -e --arg b "$bottle_name" '.bottles | has($b)' "$manifest_file" >/dev/null 2>&1 } -# manifest_require_box — like manifest_has_box but -# dies with a useful message (and prints available box names) if the box is +# manifest_require_bottle — like manifest_has_bottle but +# dies with a useful message (and prints available bottle names) if the bottle is # not defined. -manifest_require_box() { - local manifest_file="${1:?manifest_require_box: missing manifest file}" - local box_name="${2:?manifest_require_box: missing box name}" - if ! manifest_has_box "$manifest_file" "$box_name"; then +manifest_require_bottle() { + local manifest_file="${1:?manifest_require_bottle: missing manifest file}" + local bottle_name="${2:?manifest_require_bottle: missing bottle name}" + if ! manifest_has_bottle "$manifest_file" "$bottle_name"; then local available - available="$(jq -r '.boxes // {} | keys_unsorted | join(", ")' "$manifest_file" 2>/dev/null || echo "")" + available="$(jq -r '.bottles // {} | keys_unsorted | join(", ")' "$manifest_file" 2>/dev/null || echo "")" if [ -n "$available" ]; then - die "box '${box_name}' not defined in claude-bottle.json. Available boxes: ${available}" + die "bottle '${bottle_name}' not defined in claude-bottle.json. Available bottles: ${available}" else - die "box '${box_name}' not defined in claude-bottle.json (no boxes defined)." + die "bottle '${bottle_name}' not defined in claude-bottle.json (no bottles defined)." fi fi } -# manifest_box_ssh — prints one compact JSON object -# per line for each ssh entry in boxes[box_name].ssh. Prints nothing if the -# box has no ssh array or it is empty. -manifest_box_ssh() { - local manifest_file="${1:?manifest_box_ssh: missing manifest file}" - local box_name="${2:?manifest_box_ssh: missing box name}" - jq -c --arg b "$box_name" '.boxes[$b].ssh // [] | .[]' "$manifest_file" +# manifest_bottle_ssh — prints one compact JSON object +# per line for each ssh entry in bottles[bottle_name].ssh. Prints nothing if the +# bottle has no ssh array or it is empty. +manifest_bottle_ssh() { + local manifest_file="${1:?manifest_bottle_ssh: missing manifest file}" + local bottle_name="${2:?manifest_bottle_ssh: missing bottle name}" + jq -c --arg b "$bottle_name" '.bottles[$b].ssh // [] | .[]' "$manifest_file" } # manifest_ssh — prints one compact JSON object per line # for each ssh entry associated with the agent. SSH entries are resolved via -# the agent's "box" field: if set, entries come from boxes[box].ssh; if the -# agent has no "box" field, prints nothing. +# the agent's "bottle" field: if set, entries come from bottles[bottle].ssh; if the +# agent has no "bottle" field, prints nothing. # Each object has: Host, IdentityFile, Hostname, User, Port (required); # KnownHostKey (optional). manifest_ssh() { local manifest_file="${1:?manifest_ssh: missing manifest file}" local name="${2:?manifest_ssh: missing agent name}" - local box - box="$(jq -r --arg n "$name" '.agents[$n].box // ""' "$manifest_file")" - if [ -z "$box" ]; then + local bottle + bottle="$(jq -r --arg n "$name" '.agents[$n].bottle // ""' "$manifest_file")" + if [ -z "$bottle" ]; then return 0 fi - jq -c --arg b "$box" '.boxes[$b].ssh // [] | .[]' "$manifest_file" + jq -c --arg b "$bottle" '.bottles[$b].ssh // [] | .[]' "$manifest_file" }