acstack 0.0.0-stage → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +19 -0
- package/LICENSE +21 -0
- package/README.md +389 -2
- package/THIRD_PARTY_NOTICES +14 -0
- package/TROUBLESHOOTING.md +225 -0
- package/agents/codex/frontend-verifier.toml +352 -0
- package/agents/codex/task-checker.toml +345 -0
- package/agents/codex/task-executor.toml +157 -0
- package/agents/codex/test-writer.toml +149 -0
- package/agents/frontend-verifier.md +351 -0
- package/agents/task-checker.md +344 -0
- package/agents/task-executor.md +156 -0
- package/agents/test-writer.md +148 -0
- package/assets/playwriter-bridge/build-launcher.ps1 +32 -0
- package/assets/playwriter-bridge/doctor.ps1 +450 -0
- package/assets/playwriter-bridge/launcher-src.cs +131 -0
- package/assets/playwriter-bridge/opener.js +330 -0
- package/assets/playwriter-bridge/opener.ps1 +63 -0
- package/assets/playwriter-bridge/register-tasks.ps1 +328 -0
- package/assets/playwriter-bridge/relay.ps1 +50 -0
- package/assets/playwriter-bridge/stop.ps1 +125 -0
- package/assets/playwriter-bridge/sync.ps1 +271 -0
- package/assets/playwriter-bridge/tunnel.ps1 +325 -0
- package/assets/playwriter-bridge/vm-free-port.sh +166 -0
- package/assets/terminal-workspace/client-keybindings.md +87 -0
- package/assets/terminal-workspace/config.example.yaml +329 -0
- package/assets/terminal-workspace/setup.sh +4317 -0
- package/cli/README.md +47 -0
- package/cli/bin/acstack.js +3 -0
- package/cli/dist/index.js +25984 -0
- package/cli/dist/index.js.map +7 -0
- package/cli/package.json +59 -0
- package/docs/adr/0001-ste-writing-standard.md +16 -0
- package/docs/adr/0002-version-gate-not-auto-bump.md +35 -0
- package/docs/adr/0003-handoff-ack-command.md +19 -0
- package/docs/adr/0004-dev-setup-per-host.md +13 -0
- package/docs/adr/0005-two-orchestrators-per-host.md +25 -0
- package/docs/adr/0006-document-opener-service.md +89 -0
- package/docs/adr/0007-lead-check-reads-the-transcript.md +87 -0
- package/docs/adr/0008-cli-audits-skill-interviews.md +37 -0
- package/docs/adr/0009-verify-the-write-not-the-writer.md +51 -0
- package/docs/adr/0010-promote-dev-to-main-by-fast-forward.md +115 -0
- package/docs/adr/0011-node-header-is-the-edge-encoding.md +20 -0
- package/docs/adr/0012-node-kind-is-a-closed-set-of-five.md +31 -0
- package/docs/adr/0013-the-diagram-renderer-is-pure.md +23 -0
- package/docs/adr/0014-reading-a-node-hides-its-generated-diagram.md +29 -0
- package/docs/adr/0015-a-node-id-links-to-its-issue-through-an-anchor.md +37 -0
- package/docs/adr/0016-the-map-root-carries-no-status-of-its-own.md +21 -0
- package/docs/adr/0017-call-impeccable-copy-only-its-questions.md +13 -0
- package/docs/adr/index.md +17 -0
- package/docs/config-reference.md +572 -0
- package/docs/dev-setups.md +164 -0
- package/docs/migrate-0.8.md +28 -0
- package/docs/playwriter-bridge-windows.md +15 -0
- package/docs/terminal-workspace-keys.md +137 -0
- package/docs/terminal-workspace.md +1774 -0
- package/docs/workflows.md +309 -0
- package/hooks/ac-writing-style-reminder.sh +86 -0
- package/hooks/context-pressure.sh +583 -0
- package/hooks/hooks.json +50 -0
- package/hooks/session-start-handoff.sh +165 -0
- package/hooks/session-start.sh +214 -0
- package/orchestrator/AGENTS.md +532 -0
- package/output-styles/ac-writing-style.md +286 -0
- package/package.json +50 -4
- package/pi/agents/frontend-verifier.md +357 -0
- package/pi/agents/task-checker.md +348 -0
- package/pi/agents/task-executor.md +160 -0
- package/pi/agents/test-writer.md +154 -0
- package/pi/extension.ts +723 -0
- package/profiles/fullstack-python.yaml +29 -0
- package/profiles/node-typescript.yaml +23 -0
- package/profiles/python.yaml +24 -0
- package/skills/archive/SKILL.md +160 -0
- package/skills/bootstrap/SKILL.md +109 -0
- package/skills/bridge/HANDOFF.md +93 -0
- package/skills/bridge/SKILL-VM.md +292 -0
- package/skills/bridge/SKILL-WINDOWS.md +526 -0
- package/skills/bridge/SKILL.md +121 -0
- package/skills/browser-verify/SKILL.md +229 -0
- package/skills/code-review/SKILL.md +44 -0
- package/skills/commit/SKILL.md +106 -0
- package/skills/compact-and-continue/SKILL.md +241 -0
- package/skills/explore/SKILL.md +149 -0
- package/skills/grilling/SKILL.md +134 -0
- package/skills/handoff/SKILL.md +250 -0
- package/skills/handoff/launch-herdr.md +197 -0
- package/skills/handoff/launch-without-herdr.md +30 -0
- package/skills/handoff/lead-check.md +59 -0
- package/skills/handoff/outcomes.md +162 -0
- package/skills/handoff/running-agent.md +50 -0
- package/skills/handoff/watch.md +115 -0
- package/skills/host/SKILL.md +247 -0
- package/skills/host/detect-host.sh +297 -0
- package/skills/host/herdr.md +79 -0
- package/skills/host/install-herdr-block.sh +467 -0
- package/skills/host/install-orca.sh +512 -0
- package/skills/host/notes.md +97 -0
- package/skills/host/orca.md +160 -0
- package/skills/host/questions.md +163 -0
- package/skills/host/report.md +42 -0
- package/skills/implement/SKILL.md +230 -0
- package/skills/implement/decision-log.md +62 -0
- package/skills/implement/design-pass.md +32 -0
- package/skills/implement/relaxed-tdd.md +28 -0
- package/skills/map/SKILL.md +250 -0
- package/skills/map/frontend.md +227 -0
- package/skills/map/trackers/files.md +74 -0
- package/skills/map/trackers/github.md +180 -0
- package/skills/map/visuals.md +387 -0
- package/skills/new-worktree/SKILL.md +155 -0
- package/skills/new-worktree/detect-orchestrator.sh +328 -0
- package/skills/new-worktree/detect-orchestrator.test.sh +894 -0
- package/skills/new-worktree/herdr.md +84 -0
- package/skills/new-worktree/orca.md +101 -0
- package/skills/pre-commit-review/SKILL.md +242 -0
- package/skills/pre-commit-review/auto-fix.md +19 -0
- package/skills/pre-commit-review/interactive.md +35 -0
- package/skills/quick-fix/SKILL.md +125 -0
- package/skills/record-context/SKILL.md +106 -0
- package/skills/setup/SKILL.md +250 -0
- package/skills/setup/browser.md +123 -0
- package/skills/setup/design-review.md +97 -0
- package/skills/setup/first-run.md +114 -0
- package/skills/setup/report.md +68 -0
- package/skills/setup/row-fixes.md +196 -0
- package/skills/setup/terminal-workspace.md +153 -0
- package/skills/sync/SKILL.md +171 -0
- package/skills/teardown-worktree/SKILL.md +245 -0
- package/skills/teardown-worktree/detector.md +39 -0
- package/skills/teardown-worktree/herdr.md +113 -0
- package/skills/teardown-worktree/orca.md +61 -0
- package/skills/terminal-workspace/SKILL.md +249 -0
- package/skills/terminal-workspace/components.md +36 -0
- package/skills/terminal-workspace/config-choices.md +69 -0
- package/skills/terminal-workspace/emulator-keys.md +48 -0
- package/skills/terminal-workspace/headless-clipboard.md +175 -0
- package/skills/terminal-workspace/turn-off-and-update.md +104 -0
- package/skills/test-conventions/SKILL.md +59 -0
- package/skills/visual-docs/SKILL.md +246 -0
- package/skills/writing/SKILL.md +231 -0
- package/skills/writing/examples.md +153 -0
- package/skills/writing/vocabulary.md +96 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ACStack host setup: install Orca as a headless server for this user.
|
|
3
|
+
#
|
|
4
|
+
# Orca is an agent integrated development environment. This script installs it
|
|
5
|
+
# as a background service that serves its interface over the network, so a
|
|
6
|
+
# machine with no screen - a development virtual machine, say - can host it
|
|
7
|
+
# while you pair a browser or a phone with it from somewhere else.
|
|
8
|
+
#
|
|
9
|
+
# Everything it touches lives under $HOME and no step needs root:
|
|
10
|
+
#
|
|
11
|
+
# ~/.local/opt/orca/orca-linux.AppImage the binary
|
|
12
|
+
# ~/.local/bin/orca-ide, ~/.local/bin/orca symlinks onto PATH
|
|
13
|
+
# ~/.config/systemd/user/orca.service the systemd user unit
|
|
14
|
+
#
|
|
15
|
+
# There are two modes and no default. --plan prints the five steps with the
|
|
16
|
+
# status each one would have and writes nothing at all; --apply does the work.
|
|
17
|
+
# Every step is idempotent, so a second --apply reports every step as skipped
|
|
18
|
+
# and leaves the machine byte for byte as it was. Plan mode cannot write by
|
|
19
|
+
# accident, because every write in this script lives inside a step's doer
|
|
20
|
+
# function and a doer is only ever called on the apply path.
|
|
21
|
+
#
|
|
22
|
+
# A step that cannot finish says so on stderr and stops the run non-zero. It
|
|
23
|
+
# never carries on to the next step, because a half-configured service that
|
|
24
|
+
# reports success is worse than one that is missing.
|
|
25
|
+
#
|
|
26
|
+
# Every tool it needs is looked up on PATH and every path it writes derives from
|
|
27
|
+
# $HOME. That is what makes it testable: tests/test-host-install.sh runs it under
|
|
28
|
+
# `env -i` with a PATH of fake executables and a temporary HOME, so the result is
|
|
29
|
+
# entirely determined by what the test wired up. Reaching past PATH or $HOME to a
|
|
30
|
+
# real location would defeat that, so don't.
|
|
31
|
+
|
|
32
|
+
set -u
|
|
33
|
+
|
|
34
|
+
PROG="install-orca.sh"
|
|
35
|
+
TOTAL=5
|
|
36
|
+
|
|
37
|
+
# --- defaults --------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
# Upstream's own documented download link. The asset name carries no version, so
|
|
40
|
+
# the `latest` alias resolves to whatever the current release is.
|
|
41
|
+
DEFAULT_APPIMAGE_URL="https://github.com/stablyai/orca/releases/latest/download/orca-linux.AppImage"
|
|
42
|
+
DEFAULT_PORT="6768"
|
|
43
|
+
# Where the kernel release string lives on Linux. An option overrides it, which
|
|
44
|
+
# is what lets a test hand the Windows Subsystem for Linux check its own file.
|
|
45
|
+
DEFAULT_OSRELEASE_FILE="/proc/sys/kernel/osrelease"
|
|
46
|
+
|
|
47
|
+
usage() {
|
|
48
|
+
cat <<'USAGE'
|
|
49
|
+
install-orca.sh - install Orca as a headless systemd user service.
|
|
50
|
+
|
|
51
|
+
Usage:
|
|
52
|
+
install-orca.sh --plan [options] Print the steps and their status. Writes nothing.
|
|
53
|
+
install-orca.sh --apply [options] Do the work. Every step is idempotent.
|
|
54
|
+
install-orca.sh --help Print this help and exit.
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
--appimage-path PATH Install this local AppImage instead of downloading one.
|
|
58
|
+
--appimage-url URL Download the AppImage from here. Defaults to the latest
|
|
59
|
+
orca-linux.AppImage on the upstream releases page.
|
|
60
|
+
--port N Port the server listens on. Default: 6768.
|
|
61
|
+
--pairing-address ADDR Address to advertise in the pairing URL, a Tailscale
|
|
62
|
+
address for example. Left out of the unit when unset.
|
|
63
|
+
--mobile-pairing Also advertise the server to the Orca mobile app.
|
|
64
|
+
--osrelease-file PATH Kernel release file the Windows Subsystem for Linux
|
|
65
|
+
check reads. Default: /proc/sys/kernel/osrelease.
|
|
66
|
+
|
|
67
|
+
Exactly one of --plan and --apply is required; neither is the default, because
|
|
68
|
+
this script changes a machine and guessing which way is not a kindness.
|
|
69
|
+
|
|
70
|
+
Everything is installed under your home directory and no step needs root:
|
|
71
|
+
~/.local/opt/orca/orca-linux.AppImage
|
|
72
|
+
~/.local/bin/orca-ide, ~/.local/bin/orca
|
|
73
|
+
~/.config/systemd/user/orca.service
|
|
74
|
+
|
|
75
|
+
Exit codes: 0 all good, 1 a step failed, 3 this machine is not Linux,
|
|
76
|
+
64 the command line was wrong.
|
|
77
|
+
USAGE
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# --- small helpers ---------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
err() { printf '%s: %s\n' "$PROG" "$1" >&2; }
|
|
83
|
+
|
|
84
|
+
# A step could not finish. Say so and stop: nothing further is attempted.
|
|
85
|
+
fail() { err "$1"; exit 1; }
|
|
86
|
+
|
|
87
|
+
die_usage() {
|
|
88
|
+
err "$1"
|
|
89
|
+
err "run with --help for usage."
|
|
90
|
+
exit 64
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# $1 wrapped in single quotes, safe to paste inside a /bin/sh command string.
|
|
94
|
+
# An embedded single quote is closed, escaped and reopened, which is the only
|
|
95
|
+
# form /bin/sh accepts. Home directories with a space in them are ordinary, so
|
|
96
|
+
# the AppImage path genuinely needs this.
|
|
97
|
+
shq() {
|
|
98
|
+
local s="$1"
|
|
99
|
+
printf "'%s'" "${s//\'/\'\\\'\'}"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# --- argument parsing ------------------------------------------------------
|
|
103
|
+
#
|
|
104
|
+
# Parsed in full before anything else happens, so --help and a bad command line
|
|
105
|
+
# cost nothing: no platform detection, no systemctl call, no file touched.
|
|
106
|
+
|
|
107
|
+
MODE=""
|
|
108
|
+
APPIMAGE_URL="$DEFAULT_APPIMAGE_URL"
|
|
109
|
+
APPIMAGE_PATH=""
|
|
110
|
+
PORT="$DEFAULT_PORT"
|
|
111
|
+
PAIRING_ADDRESS=""
|
|
112
|
+
MOBILE_PAIRING=0
|
|
113
|
+
OSRELEASE_FILE="$DEFAULT_OSRELEASE_FILE"
|
|
114
|
+
|
|
115
|
+
# $1=option name, $2=how many arguments are left including the option itself.
|
|
116
|
+
need_value() {
|
|
117
|
+
[ "$2" -ge 2 ] || die_usage "$1 needs a value."
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
set_mode() {
|
|
121
|
+
[ -z "$MODE" ] || die_usage "choose exactly one of --plan and --apply."
|
|
122
|
+
MODE="$1"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
while [ "$#" -gt 0 ]; do
|
|
126
|
+
case "$1" in
|
|
127
|
+
--plan) set_mode "plan" ;;
|
|
128
|
+
--apply) set_mode "apply" ;;
|
|
129
|
+
--appimage-path) need_value "$1" "$#"; APPIMAGE_PATH="$2"; shift ;;
|
|
130
|
+
--appimage-url) need_value "$1" "$#"; APPIMAGE_URL="$2"; shift ;;
|
|
131
|
+
--port) need_value "$1" "$#"; PORT="$2"; shift ;;
|
|
132
|
+
--pairing-address) need_value "$1" "$#"; PAIRING_ADDRESS="$2"; shift ;;
|
|
133
|
+
--mobile-pairing) MOBILE_PAIRING=1 ;;
|
|
134
|
+
--osrelease-file) need_value "$1" "$#"; OSRELEASE_FILE="$2"; shift ;;
|
|
135
|
+
--help|-h) usage; exit 0 ;;
|
|
136
|
+
*) die_usage "unknown argument: $1" ;;
|
|
137
|
+
esac
|
|
138
|
+
shift
|
|
139
|
+
done
|
|
140
|
+
|
|
141
|
+
[ -n "$MODE" ] || die_usage "one of --plan and --apply is required."
|
|
142
|
+
|
|
143
|
+
case "$PORT" in
|
|
144
|
+
''|*[!0-9]*) die_usage "--port takes a number, not: $PORT" ;;
|
|
145
|
+
esac
|
|
146
|
+
if [ "$PORT" -lt 1 ] || [ "$PORT" -gt 65535 ]; then
|
|
147
|
+
die_usage "--port must be between 1 and 65535, not: $PORT"
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# The pairing address is written into the unit unquoted, so that the unit reads
|
|
151
|
+
# the way the upstream examples do. Keep it to the characters a host name or an
|
|
152
|
+
# IP address can hold, and nothing that a shell would act on can get in.
|
|
153
|
+
case "$PAIRING_ADDRESS" in
|
|
154
|
+
'') ;;
|
|
155
|
+
*[!A-Za-z0-9._:-]*) die_usage "--pairing-address may hold only letters, digits, dot, colon, hyphen and underscore, not: $PAIRING_ADDRESS" ;;
|
|
156
|
+
esac
|
|
157
|
+
|
|
158
|
+
[ -n "${HOME:-}" ] || fail "HOME is not set, so there is nowhere to install to."
|
|
159
|
+
|
|
160
|
+
# --- platform gate ---------------------------------------------------------
|
|
161
|
+
#
|
|
162
|
+
# systemd user units and AppImages are Linux things. Anywhere else this script
|
|
163
|
+
# would produce a confident-looking mess, so it stops before touching anything.
|
|
164
|
+
|
|
165
|
+
PLATFORM="$(uname -s 2>/dev/null || true)"
|
|
166
|
+
if [ "$PLATFORM" != "Linux" ]; then
|
|
167
|
+
printf '%s: this installer supports Linux only. This machine reports: %s\n' \
|
|
168
|
+
"$PROG" "${PLATFORM:-unknown}"
|
|
169
|
+
printf '%s: nothing has been changed.\n' "$PROG"
|
|
170
|
+
exit 3
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# --- the Windows Subsystem for Linux is not supported ----------------------
|
|
174
|
+
#
|
|
175
|
+
# The Windows Subsystem for Linux is a Linux environment running on Windows. It
|
|
176
|
+
# answers `Linux` to `uname -s`, so the gate above lets it straight through, and
|
|
177
|
+
# neither a systemd user unit nor an AppImage behaves there the way this install
|
|
178
|
+
# assumes. Two markers give it away and either one on its own is enough.
|
|
179
|
+
|
|
180
|
+
WSL_EVIDENCE=""
|
|
181
|
+
if [ -n "${WSL_DISTRO_NAME:-}" ]; then
|
|
182
|
+
WSL_EVIDENCE="the WSL_DISTRO_NAME environment variable is set to: $WSL_DISTRO_NAME"
|
|
183
|
+
elif [ -f "$OSRELEASE_FILE" ] && [ -r "$OSRELEASE_FILE" ] &&
|
|
184
|
+
grep -qi microsoft "$OSRELEASE_FILE" 2>/dev/null; then
|
|
185
|
+
# Matched without regard to case: the kernel release reads `microsoft` on WSL2
|
|
186
|
+
# and `Microsoft` on WSL1.
|
|
187
|
+
WSL_EVIDENCE="the kernel release in $OSRELEASE_FILE names microsoft: $(head -n 1 "$OSRELEASE_FILE" 2>/dev/null)"
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
# An osrelease file that is not there says nothing about the environment, so a
|
|
191
|
+
# machine without one carries on rather than being refused on a missing file.
|
|
192
|
+
if [ -n "$WSL_EVIDENCE" ]; then
|
|
193
|
+
printf '%s: this is the Windows Subsystem for Linux (WSL), which is not supported.\n' "$PROG"
|
|
194
|
+
printf '%s: %s\n' "$PROG" "$WSL_EVIDENCE"
|
|
195
|
+
printf '%s: nothing has been changed.\n' "$PROG"
|
|
196
|
+
exit 3
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
# --- the paths this script owns --------------------------------------------
|
|
200
|
+
|
|
201
|
+
APPIMAGE_DIR="$HOME/.local/opt/orca"
|
|
202
|
+
APPIMAGE="$APPIMAGE_DIR/orca-linux.AppImage"
|
|
203
|
+
BIN_DIR="$HOME/.local/bin"
|
|
204
|
+
LINK_IDE="$BIN_DIR/orca-ide"
|
|
205
|
+
LINK_ORCA="$BIN_DIR/orca"
|
|
206
|
+
UNIT_DIR="$HOME/.config/systemd/user"
|
|
207
|
+
UNIT_NAME="orca.service"
|
|
208
|
+
UNIT="$UNIT_DIR/$UNIT_NAME"
|
|
209
|
+
JOURNAL_CMD="journalctl --user -u $UNIT_NAME -n 100 --no-pager"
|
|
210
|
+
|
|
211
|
+
HAVE_SYSTEMCTL=0
|
|
212
|
+
command -v systemctl >/dev/null 2>&1 && HAVE_SYSTEMCTL=1
|
|
213
|
+
|
|
214
|
+
if [ "$MODE" = "apply" ] && [ "$HAVE_SYSTEMCTL" -eq 0 ]; then
|
|
215
|
+
fail "systemctl is not on PATH, so the user service cannot be installed. Nothing has been changed."
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# --- the unit file ---------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
# The server invocation, as one /bin/sh command string.
|
|
221
|
+
ORCA_CMD="$(shq "$APPIMAGE") serve --port $PORT"
|
|
222
|
+
[ -n "$PAIRING_ADDRESS" ] && ORCA_CMD="$ORCA_CMD --pairing-address $PAIRING_ADDRESS"
|
|
223
|
+
[ "$MOBILE_PAIRING" -eq 1 ] && ORCA_CMD="$ORCA_CMD --mobile-pairing"
|
|
224
|
+
|
|
225
|
+
# Built once, up front: step 3 compares it against what is on disk, and step 4
|
|
226
|
+
# needs to know whether step 3 will rewrite the file.
|
|
227
|
+
UNIT_TEXT="$(cat <<UNIT
|
|
228
|
+
[Unit]
|
|
229
|
+
Description=Orca headless server
|
|
230
|
+
Documentation=https://github.com/stablyai/orca
|
|
231
|
+
After=network-online.target
|
|
232
|
+
Wants=network-online.target
|
|
233
|
+
|
|
234
|
+
[Service]
|
|
235
|
+
Type=simple
|
|
236
|
+
# Orca prints its readiness block - the pairing URL among it - only when it
|
|
237
|
+
# believes it is talking to a terminal. Started plainly, the server runs fine
|
|
238
|
+
# but the journal holds nothing, and there is then no way to retrieve the
|
|
239
|
+
# pairing URL. /usr/bin/script gives the server a pseudo-terminal and copies
|
|
240
|
+
# everything written to it onto stdout, which systemd captures into the journal.
|
|
241
|
+
# -q drops script's own start and done banners, -e makes script exit with the
|
|
242
|
+
# server's status so Restart= behaves, -c is the command to run, and the
|
|
243
|
+
# trailing /dev/null is where script's transcript file goes, because we want the
|
|
244
|
+
# journal copy and not a second one on disk.
|
|
245
|
+
ExecStart=/usr/bin/script -qec "$ORCA_CMD" /dev/null
|
|
246
|
+
Environment=ORCA_TELEMETRY_DISABLED=1
|
|
247
|
+
Restart=on-failure
|
|
248
|
+
RestartSec=5
|
|
249
|
+
|
|
250
|
+
[Install]
|
|
251
|
+
WantedBy=default.target
|
|
252
|
+
UNIT
|
|
253
|
+
)"
|
|
254
|
+
|
|
255
|
+
# --- step state ------------------------------------------------------------
|
|
256
|
+
#
|
|
257
|
+
# Every step's status is worked out here, before any of them runs. Two reasons:
|
|
258
|
+
# plan mode and apply mode then report from exactly the same reasoning, and
|
|
259
|
+
# step 4 can see whether step 3 is about to rewrite the unit.
|
|
260
|
+
|
|
261
|
+
# Step 1: the AppImage. An existing file counts only when it is non-empty and
|
|
262
|
+
# executable - a truncated download or a lost permission bit is not an install.
|
|
263
|
+
STEP1_NEEDED=1
|
|
264
|
+
STEP1_REASON=""
|
|
265
|
+
if [ -f "$APPIMAGE" ] && [ -s "$APPIMAGE" ] && [ -x "$APPIMAGE" ]; then
|
|
266
|
+
STEP1_NEEDED=0
|
|
267
|
+
STEP1_REASON="already present and executable"
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
# Step 2: the two symlinks. Anything that is not a symlink pointing at the
|
|
271
|
+
# AppImage - a stale link, a copied binary, nothing at all - is replaced.
|
|
272
|
+
points_at_appimage() {
|
|
273
|
+
[ -L "$1" ] && [ "$(readlink "$1")" = "$APPIMAGE" ]
|
|
274
|
+
}
|
|
275
|
+
STEP2_NEEDED=1
|
|
276
|
+
STEP2_REASON=""
|
|
277
|
+
if points_at_appimage "$LINK_IDE" && points_at_appimage "$LINK_ORCA"; then
|
|
278
|
+
STEP2_NEEDED=0
|
|
279
|
+
STEP2_REASON="both links already point at the AppImage"
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
# Step 3: the unit file, compared whole. Any difference at all - a changed port,
|
|
283
|
+
# an added pairing address, an edit by hand - is a rewrite.
|
|
284
|
+
STEP3_NEEDED=1
|
|
285
|
+
STEP3_REASON=""
|
|
286
|
+
if [ -f "$UNIT" ] && [ "$(cat "$UNIT" 2>/dev/null)" = "$UNIT_TEXT" ]; then
|
|
287
|
+
STEP3_NEEDED=0
|
|
288
|
+
STEP3_REASON="already exactly this unit"
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
# Step 4: reload, enable, start. The unit's current state comes from systemd
|
|
292
|
+
# itself; these are queries and change nothing.
|
|
293
|
+
UNIT_ENABLED=0
|
|
294
|
+
UNIT_ACTIVE=0
|
|
295
|
+
if [ "$HAVE_SYSTEMCTL" -eq 1 ]; then
|
|
296
|
+
systemctl --user is-enabled "$UNIT_NAME" >/dev/null 2>&1 && UNIT_ENABLED=1
|
|
297
|
+
systemctl --user is-active "$UNIT_NAME" >/dev/null 2>&1 && UNIT_ACTIVE=1
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
# It skips only when the unit is already enabled and already running AND step 3
|
|
301
|
+
# is not rewriting the unit in this same run. When step 3 does rewrite it, the
|
|
302
|
+
# running server is still on the old unit until systemd is told otherwise.
|
|
303
|
+
STEP4_NEEDED=1
|
|
304
|
+
STEP4_REASON=""
|
|
305
|
+
if [ "$UNIT_ENABLED" -eq 1 ] && [ "$UNIT_ACTIVE" -eq 1 ] && [ "$STEP3_NEEDED" -eq 0 ]; then
|
|
306
|
+
STEP4_NEEDED=0
|
|
307
|
+
STEP4_REASON="already enabled and running on this unit"
|
|
308
|
+
fi
|
|
309
|
+
|
|
310
|
+
# Step 5: the pairing URL - the link a client uses to connect to this server.
|
|
311
|
+
# Orca prints it once, when the server starts, so it is worth reading back
|
|
312
|
+
# exactly when step 4 has just started or restarted the server. An untouched
|
|
313
|
+
# server's pairing URL has not changed, so there is nothing to re-read.
|
|
314
|
+
#
|
|
315
|
+
# This step only ever reads. It is a convenience on the end of an install that
|
|
316
|
+
# has already succeeded, so nothing it runs into can fail the run: a missing
|
|
317
|
+
# journalctl or a journal with no readiness line in it yet reports as skipped,
|
|
318
|
+
# says why, and prints the command to run by hand.
|
|
319
|
+
STEP5_NEEDED="$STEP4_NEEDED"
|
|
320
|
+
STEP5_REASON="the server was left running untouched, so its pairing URL has not changed"
|
|
321
|
+
PAIRING_URL=""
|
|
322
|
+
SHOW_JOURNAL_HINT=0
|
|
323
|
+
|
|
324
|
+
# --- the doers -------------------------------------------------------------
|
|
325
|
+
#
|
|
326
|
+
# Every write in this script is in one of these three functions, and they are
|
|
327
|
+
# only ever called from the apply path. That is what makes plan mode inert.
|
|
328
|
+
|
|
329
|
+
do_step1() {
|
|
330
|
+
mkdir -p "$APPIMAGE_DIR" || { err "could not create $APPIMAGE_DIR"; return 1; }
|
|
331
|
+
|
|
332
|
+
local tmp
|
|
333
|
+
# Staged next to its destination, so the move into place is atomic and a
|
|
334
|
+
# failed download never leaves a half-written AppImage where a working one
|
|
335
|
+
# is expected.
|
|
336
|
+
tmp="$(mktemp "$APPIMAGE_DIR/.orca-install.XXXXXX")" || {
|
|
337
|
+
err "could not create a temporary file in $APPIMAGE_DIR"; return 1; }
|
|
338
|
+
|
|
339
|
+
if [ -n "$APPIMAGE_PATH" ]; then
|
|
340
|
+
if [ ! -f "$APPIMAGE_PATH" ]; then
|
|
341
|
+
rm -f "$tmp"; err "no AppImage at $APPIMAGE_PATH"; return 1
|
|
342
|
+
fi
|
|
343
|
+
if [ ! -s "$APPIMAGE_PATH" ]; then
|
|
344
|
+
rm -f "$tmp"; err "the AppImage at $APPIMAGE_PATH is empty"; return 1
|
|
345
|
+
fi
|
|
346
|
+
if ! cp -f "$APPIMAGE_PATH" "$tmp"; then
|
|
347
|
+
rm -f "$tmp"; err "could not copy $APPIMAGE_PATH into $APPIMAGE_DIR"; return 1
|
|
348
|
+
fi
|
|
349
|
+
else
|
|
350
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
351
|
+
rm -f "$tmp"
|
|
352
|
+
err "curl is not on PATH, so the AppImage cannot be downloaded. Pass --appimage-path to install a local copy instead."
|
|
353
|
+
return 1
|
|
354
|
+
fi
|
|
355
|
+
if ! curl -fL --retry 2 --connect-timeout 20 --output "$tmp" "$APPIMAGE_URL"; then
|
|
356
|
+
rm -f "$tmp"; err "failed to download the Orca AppImage from $APPIMAGE_URL"; return 1
|
|
357
|
+
fi
|
|
358
|
+
if [ ! -s "$tmp" ]; then
|
|
359
|
+
rm -f "$tmp"; err "the download from $APPIMAGE_URL produced an empty file"; return 1
|
|
360
|
+
fi
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
if ! chmod +x "$tmp"; then
|
|
364
|
+
rm -f "$tmp"; err "could not make $tmp executable"; return 1
|
|
365
|
+
fi
|
|
366
|
+
if ! mv -f "$tmp" "$APPIMAGE"; then
|
|
367
|
+
rm -f "$tmp"; err "could not move the AppImage into $APPIMAGE"; return 1
|
|
368
|
+
fi
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
do_step2() {
|
|
372
|
+
mkdir -p "$BIN_DIR" || { err "could not create $BIN_DIR"; return 1; }
|
|
373
|
+
# -n so that an existing symlink is replaced rather than followed, which is
|
|
374
|
+
# what makes a link pointing at a directory safe to overwrite.
|
|
375
|
+
ln -sfn "$APPIMAGE" "$LINK_IDE" || { err "could not link $LINK_IDE"; return 1; }
|
|
376
|
+
ln -sfn "$APPIMAGE" "$LINK_ORCA" || { err "could not link $LINK_ORCA"; return 1; }
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
do_step3() {
|
|
380
|
+
mkdir -p "$UNIT_DIR" || { err "could not create $UNIT_DIR"; return 1; }
|
|
381
|
+
printf '%s\n' "$UNIT_TEXT" > "$UNIT" || { err "could not write $UNIT"; return 1; }
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
do_step4() {
|
|
385
|
+
systemctl --user daemon-reload || { err "systemctl --user daemon-reload failed"; return 1; }
|
|
386
|
+
if [ "$UNIT_ENABLED" -eq 0 ] || [ "$UNIT_ACTIVE" -eq 0 ]; then
|
|
387
|
+
# Not enabled, or enabled but not running: --now does both jobs at once.
|
|
388
|
+
systemctl --user enable --now "$UNIT_NAME" || {
|
|
389
|
+
err "systemctl --user enable --now $UNIT_NAME failed"; return 1; }
|
|
390
|
+
else
|
|
391
|
+
# Already enabled and running, and we are here only because the unit was
|
|
392
|
+
# rewritten: pick the new unit up without touching the enablement.
|
|
393
|
+
systemctl --user restart "$UNIT_NAME" || {
|
|
394
|
+
err "systemctl --user restart $UNIT_NAME failed"; return 1; }
|
|
395
|
+
fi
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
# Orca announces itself with one line of JSON carrying an "orca_server_ready"
|
|
399
|
+
# event and a "pairing" value. jq is one of the things a bare machine may not
|
|
400
|
+
# have, and needing a JSON tool to finish installing the tools would be a poor
|
|
401
|
+
# joke, so the value is cut out with sed. The last matching line wins: the
|
|
402
|
+
# journal is chronological, so the newest readiness line is the one belonging to
|
|
403
|
+
# the server running now.
|
|
404
|
+
extract_pairing_url() { # $1 = journal text
|
|
405
|
+
printf '%s\n' "$1" \
|
|
406
|
+
| grep -F 'orca_server_ready' \
|
|
407
|
+
| tail -n 1 \
|
|
408
|
+
| sed -n 's/.*"pairing"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p'
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
# Reads the journal and sets PAIRING_URL. On any outcome but success it turns
|
|
412
|
+
# step 5 into a skip that explains itself, and never returns non-zero: see the
|
|
413
|
+
# note on STEP5_NEEDED above.
|
|
414
|
+
read_pairing_url() {
|
|
415
|
+
if ! command -v journalctl >/dev/null 2>&1; then
|
|
416
|
+
STEP5_NEEDED=0
|
|
417
|
+
STEP5_REASON="journalctl is not on PATH, so the pairing URL cannot be read here"
|
|
418
|
+
SHOW_JOURNAL_HINT=1
|
|
419
|
+
return 0
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
# The server has only just been told to start, and Orca takes a moment to get
|
|
423
|
+
# as far as printing its readiness line. Look again a few times before giving
|
|
424
|
+
# up. `sleep` is a command like any other and not on every PATH; where it is
|
|
425
|
+
# missing we settle for a single look rather than spinning.
|
|
426
|
+
local attempts=1 n=1 journal=""
|
|
427
|
+
command -v sleep >/dev/null 2>&1 && attempts=10
|
|
428
|
+
|
|
429
|
+
while [ "$n" -le "$attempts" ]; do
|
|
430
|
+
journal="$(journalctl --user -u "$UNIT_NAME" -n 100 --no-pager 2>/dev/null || true)"
|
|
431
|
+
PAIRING_URL="$(extract_pairing_url "$journal")"
|
|
432
|
+
[ -n "$PAIRING_URL" ] && return 0
|
|
433
|
+
n=$((n + 1))
|
|
434
|
+
[ "$n" -le "$attempts" ] && sleep 1
|
|
435
|
+
done
|
|
436
|
+
|
|
437
|
+
STEP5_NEEDED=0
|
|
438
|
+
STEP5_REASON="journalctl showed no readiness line for $UNIT_NAME yet"
|
|
439
|
+
SHOW_JOURNAL_HINT=1
|
|
440
|
+
return 0
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# --- reporting -------------------------------------------------------------
|
|
444
|
+
|
|
445
|
+
# One line per step: number, what it is, and a bracketed status. Plan mode says
|
|
446
|
+
# what would happen ([todo] / [skip: ...]), apply mode says what did
|
|
447
|
+
# ([done] / [skipped: ...]).
|
|
448
|
+
step_line() { # $1=number $2=description $3=needed $4=reason
|
|
449
|
+
local status
|
|
450
|
+
if [ "$3" -eq 1 ]; then
|
|
451
|
+
if [ "$MODE" = "plan" ]; then status="[todo]"; else status="[done]"; fi
|
|
452
|
+
else
|
|
453
|
+
if [ "$MODE" = "plan" ]; then status="[skip: $4]"; else status="[skipped: $4]"; fi
|
|
454
|
+
fi
|
|
455
|
+
printf '[%s/%s] %s %s\n' "$1" "$TOTAL" "$2" "$status"
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
# Does the step when there is work and we are applying, then reports it. The
|
|
459
|
+
# work comes first so that a failure never gets to print [done].
|
|
460
|
+
run_step() { # $1=number $2=description $3=needed $4=reason $5=doer
|
|
461
|
+
if [ "$3" -eq 1 ] && [ "$MODE" = "apply" ]; then
|
|
462
|
+
"$5" || fail "step $1 failed: $2"
|
|
463
|
+
fi
|
|
464
|
+
step_line "$1" "$2" "$3" "$4"
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
print_notes() {
|
|
468
|
+
cat <<'NOTES'
|
|
469
|
+
|
|
470
|
+
Notes:
|
|
471
|
+
Orca rewrites ~/.claude/settings.json on its first run, to add its own hooks,
|
|
472
|
+
and keeps the file it found as ~/.claude/settings.json.bak. That is Orca's
|
|
473
|
+
own doing and this script never touches either file, but it is worth knowing
|
|
474
|
+
before you start the server.
|
|
475
|
+
The pairing URL is printed once, when the server starts. The journalctl
|
|
476
|
+
command in step 5 is how you read it back afterwards.
|
|
477
|
+
Nothing here needs root: every path is under your home directory.
|
|
478
|
+
NOTES
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
# --- run -------------------------------------------------------------------
|
|
482
|
+
|
|
483
|
+
if [ "$MODE" = "plan" ]; then
|
|
484
|
+
printf '%s: plan only. Nothing on this machine is changed.\n\n' "$PROG"
|
|
485
|
+
else
|
|
486
|
+
printf '%s: applying.\n\n' "$PROG"
|
|
487
|
+
fi
|
|
488
|
+
|
|
489
|
+
run_step 1 "AppImage at $APPIMAGE" \
|
|
490
|
+
"$STEP1_NEEDED" "$STEP1_REASON" do_step1
|
|
491
|
+
run_step 2 "symlinks orca-ide and orca in $BIN_DIR" \
|
|
492
|
+
"$STEP2_NEEDED" "$STEP2_REASON" do_step2
|
|
493
|
+
run_step 3 "systemd user unit $UNIT" \
|
|
494
|
+
"$STEP3_NEEDED" "$STEP3_REASON" do_step3
|
|
495
|
+
run_step 4 "systemctl --user daemon-reload, then enable and start $UNIT_NAME" \
|
|
496
|
+
"$STEP4_NEEDED" "$STEP4_REASON" do_step4
|
|
497
|
+
# Step 5 is reported by hand rather than through run_step, because reading the
|
|
498
|
+
# journal can turn the step into a skip and run_step's status is fixed before
|
|
499
|
+
# its doer runs. Plan mode never gets here, so it never calls journalctl.
|
|
500
|
+
if [ "$MODE" = "apply" ] && [ "$STEP5_NEEDED" -eq 1 ]; then
|
|
501
|
+
read_pairing_url
|
|
502
|
+
fi
|
|
503
|
+
step_line 5 "pairing URL: $JOURNAL_CMD" "$STEP5_NEEDED" "$STEP5_REASON"
|
|
504
|
+
if [ -n "$PAIRING_URL" ]; then
|
|
505
|
+
printf ' %s\n' "$PAIRING_URL"
|
|
506
|
+
fi
|
|
507
|
+
if [ "$SHOW_JOURNAL_HINT" -eq 1 ]; then
|
|
508
|
+
printf ' read it yourself, once the server has settled, with:\n'
|
|
509
|
+
printf ' %s\n' "$JOURNAL_CMD"
|
|
510
|
+
fi
|
|
511
|
+
|
|
512
|
+
print_notes
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Host notes
|
|
2
|
+
|
|
3
|
+
Read this file when a `config set` call fails, or when the user asks one of three things:
|
|
4
|
+
|
|
5
|
+
- Where the answers live
|
|
6
|
+
- Why the skill finds the plugin root through a symlink
|
|
7
|
+
- What a declaration means
|
|
8
|
+
|
|
9
|
+
## Why this is per machine, not per project
|
|
10
|
+
|
|
11
|
+
The same repository gets opened on several machines, and those machines differ.
|
|
12
|
+
A laptop with a screen can launch a visible browser; a headless build server
|
|
13
|
+
cannot. One machine has herdr installed, another has Orca, another has neither.
|
|
14
|
+
|
|
15
|
+
None of that is a property of the project, so none of it belongs in the
|
|
16
|
+
project's config file.
|
|
17
|
+
|
|
18
|
+
The answers therefore go to the global config of the ACStack command-line
|
|
19
|
+
interface. That is a JSON file at `~/.config/acstack/config.json`, or under
|
|
20
|
+
`$XDG_CONFIG_HOME` when you set that environment variable. Run
|
|
21
|
+
`~/.claude/acstack/bin/acstack config path` to print the real location.
|
|
22
|
+
|
|
23
|
+
They do not go in `acstack/project.yaml`.
|
|
24
|
+
|
|
25
|
+
Project concerns stay in `project.yaml`. In particular
|
|
26
|
+
`frontend.browser.mode` stays there as the *project's preference*, while
|
|
27
|
+
`host.browser.*` says what this *machine* can actually do. The two are
|
|
28
|
+
different questions, and they live in different files.
|
|
29
|
+
|
|
30
|
+
## Why Step 1 resolves the plugin root from a symlink
|
|
31
|
+
|
|
32
|
+
The path resolution looks odd, so here is why. The plugin's own files live in a
|
|
33
|
+
versioned cache directory. Its path changes on every release, so nobody can
|
|
34
|
+
hardcode it.
|
|
35
|
+
|
|
36
|
+
The one invariant path is `~/.claude/acstack/bin/acstack`, a symlink to the CLI
|
|
37
|
+
inside the current cache. The SessionStart hook re-points that symlink at the
|
|
38
|
+
start of every agent session.
|
|
39
|
+
|
|
40
|
+
The command in Step 1 of `SKILL.md` therefore derives the plugin root from the
|
|
41
|
+
symlink rather than from a written-down path.
|
|
42
|
+
|
|
43
|
+
## When a `config set` call fails (Step 4)
|
|
44
|
+
|
|
45
|
+
- **A rejected value.** The message names the allowed values for a key the tool
|
|
46
|
+
knows. Re-ask rather than guessing at a different spelling – enum values are
|
|
47
|
+
matched case-sensitively, so it is `stagewise`, never `Stagewise`.
|
|
48
|
+
|
|
49
|
+
- **An unknown key.** The message reads `Unknown config key "<key>"` and lists
|
|
50
|
+
the host keys the tool does know. That is version skew: the command line tool
|
|
51
|
+
in this plugin cache is older than these skills. `host.orchestrators.herdr`,
|
|
52
|
+
`host.orchestrators.orca` and `host.orca.topology` are the newest axes, so
|
|
53
|
+
they are the ones an old cached tool rejects.
|
|
54
|
+
|
|
55
|
+
- **Recovering from version skew.** Do not re-ask – the answer is not the
|
|
56
|
+
problem, and re-asking a boolean axis only loops. Tell the user to restart
|
|
57
|
+
their agent session, so the SessionStart hook re-points
|
|
58
|
+
`~/.claude/acstack/bin/acstack` at the current cache.
|
|
59
|
+
|
|
60
|
+
Skip the axis and carry on. Do not report it to the user as their mistake.
|
|
61
|
+
|
|
62
|
+
## Notes
|
|
63
|
+
|
|
64
|
+
**Declared means installed.** Each of the two declarations says that this
|
|
65
|
+
machine has that orchestrator installed. They are independent, so both can be
|
|
66
|
+
true at once. Neither says that the orchestrator is hosting the current
|
|
67
|
+
session.
|
|
68
|
+
|
|
69
|
+
At worktree time, `/acstack:new-worktree` and `/acstack:teardown-worktree` run
|
|
70
|
+
the same detector. It reads the environment markers an orchestrator sets in the
|
|
71
|
+
terminals it opens, and those markers name the orchestrator hosting this
|
|
72
|
+
session. The markers are `HERDR_ENV` or `HERDR_PANE_ID` for herdr, and
|
|
73
|
+
`ORCA_PANE_KEY` for Orca.
|
|
74
|
+
|
|
75
|
+
When no marker holds a value, those skills use plain
|
|
76
|
+
git worktrees and say so. An environment variable for an orchestrator that was
|
|
77
|
+
never declared is worth a warning, not a refusal.
|
|
78
|
+
|
|
79
|
+
**The host declares, the project prefers.** `host.browser.*` says what this
|
|
80
|
+
machine can do. `frontend.browser.mode` in `acstack/project.yaml` says what the
|
|
81
|
+
project would like.
|
|
82
|
+
|
|
83
|
+
Today the only thing that compares the two is the ACStack command-line
|
|
84
|
+
interface's own health check, `~/.claude/acstack/bin/acstack config check`.
|
|
85
|
+
|
|
86
|
+
It passes when the host declares the project's preferred mode available. It
|
|
87
|
+
passes with a note when the host does not. The note names the first mode the
|
|
88
|
+
host does declare, in the order remote, headless, local, as the one that would
|
|
89
|
+
stand in.
|
|
90
|
+
|
|
91
|
+
It fails when the host declares none of the three. That is a report about the
|
|
92
|
+
setup, not a choice made on the verifier's behalf.
|
|
93
|
+
|
|
94
|
+
Nothing under `agents/` or `skills/browser-verify/` reads `host.browser.*` yet,
|
|
95
|
+
so the frontend verifier still takes `frontend.browser.mode` at its word.
|
|
96
|
+
Teaching it to resolve the project's preference against the host, in the order
|
|
97
|
+
the check already uses, is still to come.
|