Fix end-to-end startup: project registration, credentials, trust dialog, ready marker

- start.sh: auto-register project in ~/.config/context-studio/projects/ before
  launching Electron — without this acquireProjectLock() silently skips writing
  the lock file, waitForServers() never finds the registry port, all agent ports
  stay null (localhost:null errors)

- start.sh: mount all known Claude Code credential locations into container
  (~/.claude/.credentials.json, ~/.claude.json, $CLAUDE_CONFIG_DIR variants)
  not just ~/.anthropic which was empty on this system

- bin/claude: create /tmp/cs-ready-<agentId> on host after 3s delay so CS Core's
  CLI ready marker poll resolves instead of timing out after 10s

- workflow.sh: add hasTrustDialogAccepted:true to all agent settings.json so
  claude goes straight to priming without the folder trust dialog

- prereqs.sh: add ensure_api_key() — checks all credential locations, prompts
  with masked input if none found, offers to save to shell profile

- wizard.sh: trap SIGINT for graceful abort — gum confirm popup, reverts created
  project dir and cloned core dir, leaves installed packages untouched

- core.sh: set _WIZARD_CORE_CLONED=true before clone for cleanup tracking

- electron-config.js: increase serverStartupTimeout 30s→90s (config file in
  core/config/, not source — safe to edit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Eli 2026-03-09 21:20:25 +01:00
commit 7c9b61bfce
7 changed files with 325 additions and 80 deletions

View file

@ -41,6 +41,21 @@ else
WORKDIR="\$PROJECT_DIR"
fi
# ── CS Core ready marker ────────────────────────────────────────────────
# CS Core polls /tmp/cs-ready-<agentId> on the host to know when the CLI
# banner is visible and /prime can be injected. Claude runs inside the
# container so it cannot create this file on the host itself.
# We infer the agent ID from the PTY working directory (CS Core sets it to
# workflow/agents/<agentId>) and create the marker after a short delay.
_is_interactive=true
for _arg in "\$@"; do
case "\$_arg" in --version|--help|-h|-v) _is_interactive=false; break ;; esac
done
if [[ "\$_is_interactive" == "true" && "\$PWD" == "\$PROJECT_DIR/workflow/agents/"* ]]; then
_AGENT_ID="\$(basename "\$PWD")"
(sleep 3 && touch "/tmp/cs-ready-\$_AGENT_ID") &
fi
# Pass through TTY if available, relay working directory into container
if [ -t 0 ]; then
exec "\$RUNTIME" exec -it --workdir "\$WORKDIR" "\$CONTAINER_NAME" claude "\$@"
@ -82,6 +97,22 @@ fi
# ── Ensure ~/.anthropic exists (Claude Code stores auth/config here) ─────
mkdir -p "\$HOME/.anthropic"
# ── Build credential mounts ───────────────────────────────────────────────
# Claude Code may store credentials in various locations depending on version
# and whether CLAUDE_CONFIG_DIR is set. Mount whichever files exist.
_CREDS_ARGS=()
_CREDS_ARGS+=("-v" "\$HOME/.anthropic:\$HOME/.anthropic:ro")
_claude_dir="\${CLAUDE_CONFIG_DIR:-\$HOME/.claude}"
if [[ -f "\$_claude_dir/.credentials.json" ]]; then
_CREDS_ARGS+=("-v" "\$_claude_dir/.credentials.json:\$_claude_dir/.credentials.json:ro")
fi
if [[ -f "\$HOME/.claude.json" ]]; then
_CREDS_ARGS+=("-v" "\$HOME/.claude.json:\$HOME/.claude.json:ro")
fi
if [[ -n "\${CLAUDE_CONFIG_DIR:-}" && -f "\$CLAUDE_CONFIG_DIR/.claude.json" ]]; then
_CREDS_ARGS+=("-v" "\$CLAUDE_CONFIG_DIR/.claude.json:\$CLAUDE_CONFIG_DIR/.claude.json:ro")
fi
# ── Start agents container ───────────────────────────────────────────────
# Mount project at the same absolute path so host and container paths match.
# CS Core sets agent working dirs to host paths; the wrapper relays PWD.
@ -92,7 +123,7 @@ echo "→ Starting agents container '\$CONTAINER_NAME'..."
--name "\$CONTAINER_NAME" \\
--user "\$(id -u):\$(id -g)" \\
-v "\$PROJECT_DIR:\$PROJECT_DIR" \\
-v "\$HOME/.anthropic:\$HOME/.anthropic:ro" \\
"\${_CREDS_ARGS[@]}" \\
-e ANTHROPIC_API_KEY="\${ANTHROPIC_API_KEY:-}" \\
-e CS_WORKFLOW_DIR="\$PROJECT_DIR/workflow" \\
-e PROJECT_ROOT_DIR="\$PROJECT_DIR" \\
@ -121,6 +152,45 @@ if [[ ! -d "\$CS_CORE/app/node_modules" ]]; then
(cd "\$CS_CORE" && npm install) || { echo "npm install failed." >&2; exit 1; }
fi
# ── Register project with Context Studio (required for lock file to be written) ──
# CS Core's acquireProjectLock() skips writing the lock file if the project
# isn't registered in ~/.config/context-studio/projects/<uuid>.json.
# Without the lock file, waitForServers() can never find the registry port
# and always times out — causing localhost:null errors in the UI.
_CS_PROJECTS_DIR="\$HOME/.config/context-studio/projects"
mkdir -p "\$_CS_PROJECTS_DIR"
_WORKFLOW_DIR="\$PROJECT_DIR/workflow"
_already_registered=false
for _f in "\$_CS_PROJECTS_DIR"/*.json; do
if [[ -f "\$_f" ]] && python3 -c "
import json,sys
d=json.load(open(sys.argv[1]))
sys.exit(0 if d.get('workflowDir') == sys.argv[2] else 1)
" "\$_f" "\$_WORKFLOW_DIR" 2>/dev/null; then
_already_registered=true
break
fi
done
if [[ "\$_already_registered" == "false" ]]; then
_UUID=\$(python3 -c "import uuid; print(uuid.uuid4())")
_NOW=\$(python3 -c "from datetime import datetime,timezone; print(datetime.now(timezone.utc).isoformat())")
python3 -c "
import json, sys
data = {
'id': sys.argv[1],
'name': sys.argv[2],
'workflowDir': sys.argv[3],
'user': 'default',
'created': sys.argv[4],
'lastOpened': sys.argv[4]
}
with open(sys.argv[5], 'w') as f:
json.dump(data, f, indent=2)
f.write('\n')
" "\$_UUID" "\$(basename "\$PROJECT_DIR")" "\$_WORKFLOW_DIR" "\$_NOW" "\$_CS_PROJECTS_DIR/\$_UUID.json"
echo "→ Registered project with Context Studio"
fi
# ── Check display for Electron UI ───────────────────────────────────────
if [[ -z "\${DISPLAY:-}" && -z "\${WAYLAND_DISPLAY:-}" ]]; then
echo "⚠ No display detected (DISPLAY / WAYLAND_DISPLAY not set)."

View file

@ -21,6 +21,7 @@ setup_core() {
info "Cloning context-studio-core → $CS_CORE_DIR"
mkdir -p "$CS_HOME"
_WIZARD_CORE_CLONED=true
spin "Cloning context-studio-core..." \
git clone "$CS_CORE_REPO" "$CS_CORE_DIR" \
|| die "Failed to clone context-studio-core. Check your SSH key and network."

View file

@ -98,8 +98,73 @@ _install_docker() {
warn "Added $USER to docker group — log out and back in for it to take effect."
}
ensure_api_key() {
if [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then
success "ANTHROPIC_API_KEY is set"
return
fi
# Resolve config dir — CLAUDE_CONFIG_DIR overrides default ~/.claude
local claude_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
local creds_file=""
if [[ -f "$claude_dir/.credentials.json" ]]; then creds_file="$claude_dir/.credentials.json"
elif [[ -f "$HOME/.claude.json" ]]; then creds_file="$HOME/.claude.json"
elif [[ -f "$claude_dir/.claude.json" ]]; then creds_file="$claude_dir/.claude.json"
elif [[ -f "$HOME/.anthropic/.credentials.json" ]]; then creds_file="$HOME/.anthropic/.credentials.json"
fi
if [[ -n "$creds_file" ]]; then
success "Anthropic credentials found ($creds_file)"
return
fi
warn "ANTHROPIC_API_KEY is not set."
echo ""
gum style --foreground "$C_SKY" --margin "0 4" \
"Claude Code needs an Anthropic API key to run inside the container." \
"Get one at: https://console.anthropic.com/settings/api-keys"
echo ""
local key
key=$(gum input \
--password \
--placeholder "sk-ant-..." \
--prompt " " \
--prompt.foreground "$C_MAUVE" \
--cursor.foreground "$C_MAUVE" \
--header " Anthropic API key" \
--header.foreground "$C_SKY" \
--width 70) || true
if [[ -z "$key" ]]; then
warn "No API key entered — set ANTHROPIC_API_KEY before running ./start.sh"
return
fi
export ANTHROPIC_API_KEY="$key"
success "ANTHROPIC_API_KEY set for this session"
ask_yn _save "Save to shell profile (~/.zshrc / ~/.bashrc)?" "y"
if [[ "$_save" == "y" ]]; then
local profile
if [[ "$SHELL" == */zsh ]]; then
profile="$HOME/.zshrc"
else
profile="$HOME/.bashrc"
fi
if grep -q "ANTHROPIC_API_KEY" "$profile" 2>/dev/null; then
warn "ANTHROPIC_API_KEY already in $profile — not adding again."
else
printf '\nexport ANTHROPIC_API_KEY="%s"\n' "$key" >> "$profile"
success "Saved to $profile"
fi
fi
}
check_prerequisites() {
header "Prerequisites"
ensure_git
ensure_container_runtime
ensure_api_key
}

View file

@ -127,6 +127,7 @@ _create_agent_dir() {
{
"model": "sonnet",
"spinnerTipsEnabled": false,
"hasTrustDialogAccepted": true,
"permissions": {
"allow": [
"Bash(*)",
@ -162,5 +163,6 @@ clone_workflow() {
spin "Cloning workflow..." \
git clone "$repo_url" "$workflow_dir" \
|| die "Failed to clone workflow repo: $repo_url"
success "Workflow cloned"
}