Land Phase 0 launch chain: SSH -> desktop Terminal -> claude -> phone
Phase 0 proven by hand (N=3) across multiple rooms. - scripts/gui-launch.sh: open a desktop Terminal via osascript so claude runs in the GUI session (login Keychain + real TTY), avoiding a long-lived token (D11). - scripts/launch-claude.sh: name the session `claude -n "<repo> - <topic>"` so Remote Control's phone conversation index is readable. - .env.example: bot credential schema (real .env stays gitignored). - AGENTS.md / ROADMAP.md: D11, Phase 0 results, Phase 1 carry-overs.
This commit is contained in:
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/zsh -l
|
||||
# gui-launch.sh — matrix-bridge GUI launcher (Approach B: "open a real desktop terminal").
|
||||
#
|
||||
# Invoked over SSH by the bot: gui-launch.sh <repo_dir> <prompt...>
|
||||
#
|
||||
# Opens a Terminal.app window on the logged-in desktop that runs launch-claude.sh, so
|
||||
# `claude` runs INSIDE the GUI session: it gets the login Keychain (no long-lived token
|
||||
# needed) and a real TTY. Claude Code Remote Control then surfaces the session to the phone.
|
||||
#
|
||||
# Requires: the Mac logged into its desktop, and a one-time "Allow ssh to control
|
||||
# Terminal" Automation grant (System Settings > Privacy & Security > Automation).
|
||||
#
|
||||
# Layering: gui-launch.sh owns the GUI/session seam; launch-claude.sh owns the
|
||||
# environment seam (cd + exec claude). Keep that split.
|
||||
|
||||
script_dir="${0:A:h}"
|
||||
inner="$script_dir/launch-claude.sh"
|
||||
|
||||
repo_dir="$1"
|
||||
shift
|
||||
prompt="$*"
|
||||
|
||||
if [[ -z "$repo_dir" || -z "$prompt" ]]; then
|
||||
print -u2 "usage: gui-launch.sh <repo_dir> <prompt>"
|
||||
exit 2
|
||||
fi
|
||||
# Fail loud on a bad directory — never open a session in the wrong place (guardrail).
|
||||
if [[ ! -d "$repo_dir" ]]; then
|
||||
print -u2 "gui-launch: no such repo dir: $repo_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Embed repo+prompt in a throwaway script via %q so user text NEVER crosses the
|
||||
# AppleScript string layer — only a safe mktemp path does. This sidesteps the
|
||||
# SSH -> osascript -> shell -> wrapper multi-shell quoting trap the spec warns about.
|
||||
launch_script="$(mktemp -t mb-launch)"
|
||||
{
|
||||
print -r -- '#!/bin/zsh -l'
|
||||
printf 'exec %q %q %q\n' "$inner" "$repo_dir" "$prompt"
|
||||
} >| "$launch_script"
|
||||
chmod +x "$launch_script"
|
||||
|
||||
# mktemp paths contain no spaces/quotes, so embedding the path directly is safe.
|
||||
osascript -e "tell application \"Terminal\" to do script \"$launch_script\"" \
|
||||
-e 'tell application "Terminal" to activate'
|
||||
|
||||
print -- "gui-launch: opened Terminal for $repo_dir"
|
||||
@@ -21,4 +21,17 @@ fi
|
||||
# Fail loud on a bad directory — never launch Claude in the wrong place.
|
||||
cd "$repo_dir" || { print -u2 "launch-claude: no such repo dir: $repo_dir"; exit 1; }
|
||||
|
||||
exec claude "$prompt"
|
||||
# Name the session "<repo> - <topic>" so it's identifiable in Remote Control's
|
||||
# conversation index on the phone. Topic defaults to a trimmed slice of the message;
|
||||
# the Phase-1 bot can override it with a curated topic via $MB_SESSION_NAME.
|
||||
repo_name="${${repo_dir%/}:t}"
|
||||
if [[ -n "$MB_SESSION_NAME" ]]; then
|
||||
session_name="$MB_SESSION_NAME"
|
||||
else
|
||||
topic="${prompt//$'\n'/ }" # collapse newlines to keep the name one line
|
||||
topic="${topic[1,60]}" # cap length for a tidy index entry
|
||||
[[ ${#prompt} -gt 60 ]] && topic="${topic}…"
|
||||
session_name="${repo_name} - ${topic}"
|
||||
fi
|
||||
|
||||
exec claude -n "$session_name" "$prompt"
|
||||
|
||||
Reference in New Issue
Block a user