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.
Files changed (143) hide show
  1. package/.claude-plugin/plugin.json +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +389 -2
  4. package/THIRD_PARTY_NOTICES +14 -0
  5. package/TROUBLESHOOTING.md +225 -0
  6. package/agents/codex/frontend-verifier.toml +352 -0
  7. package/agents/codex/task-checker.toml +345 -0
  8. package/agents/codex/task-executor.toml +157 -0
  9. package/agents/codex/test-writer.toml +149 -0
  10. package/agents/frontend-verifier.md +351 -0
  11. package/agents/task-checker.md +344 -0
  12. package/agents/task-executor.md +156 -0
  13. package/agents/test-writer.md +148 -0
  14. package/assets/playwriter-bridge/build-launcher.ps1 +32 -0
  15. package/assets/playwriter-bridge/doctor.ps1 +450 -0
  16. package/assets/playwriter-bridge/launcher-src.cs +131 -0
  17. package/assets/playwriter-bridge/opener.js +330 -0
  18. package/assets/playwriter-bridge/opener.ps1 +63 -0
  19. package/assets/playwriter-bridge/register-tasks.ps1 +328 -0
  20. package/assets/playwriter-bridge/relay.ps1 +50 -0
  21. package/assets/playwriter-bridge/stop.ps1 +125 -0
  22. package/assets/playwriter-bridge/sync.ps1 +271 -0
  23. package/assets/playwriter-bridge/tunnel.ps1 +325 -0
  24. package/assets/playwriter-bridge/vm-free-port.sh +166 -0
  25. package/assets/terminal-workspace/client-keybindings.md +87 -0
  26. package/assets/terminal-workspace/config.example.yaml +329 -0
  27. package/assets/terminal-workspace/setup.sh +4317 -0
  28. package/cli/README.md +47 -0
  29. package/cli/bin/acstack.js +3 -0
  30. package/cli/dist/index.js +25984 -0
  31. package/cli/dist/index.js.map +7 -0
  32. package/cli/package.json +59 -0
  33. package/docs/adr/0001-ste-writing-standard.md +16 -0
  34. package/docs/adr/0002-version-gate-not-auto-bump.md +35 -0
  35. package/docs/adr/0003-handoff-ack-command.md +19 -0
  36. package/docs/adr/0004-dev-setup-per-host.md +13 -0
  37. package/docs/adr/0005-two-orchestrators-per-host.md +25 -0
  38. package/docs/adr/0006-document-opener-service.md +89 -0
  39. package/docs/adr/0007-lead-check-reads-the-transcript.md +87 -0
  40. package/docs/adr/0008-cli-audits-skill-interviews.md +37 -0
  41. package/docs/adr/0009-verify-the-write-not-the-writer.md +51 -0
  42. package/docs/adr/0010-promote-dev-to-main-by-fast-forward.md +115 -0
  43. package/docs/adr/0011-node-header-is-the-edge-encoding.md +20 -0
  44. package/docs/adr/0012-node-kind-is-a-closed-set-of-five.md +31 -0
  45. package/docs/adr/0013-the-diagram-renderer-is-pure.md +23 -0
  46. package/docs/adr/0014-reading-a-node-hides-its-generated-diagram.md +29 -0
  47. package/docs/adr/0015-a-node-id-links-to-its-issue-through-an-anchor.md +37 -0
  48. package/docs/adr/0016-the-map-root-carries-no-status-of-its-own.md +21 -0
  49. package/docs/adr/0017-call-impeccable-copy-only-its-questions.md +13 -0
  50. package/docs/adr/index.md +17 -0
  51. package/docs/config-reference.md +572 -0
  52. package/docs/dev-setups.md +164 -0
  53. package/docs/migrate-0.8.md +28 -0
  54. package/docs/playwriter-bridge-windows.md +15 -0
  55. package/docs/terminal-workspace-keys.md +137 -0
  56. package/docs/terminal-workspace.md +1774 -0
  57. package/docs/workflows.md +309 -0
  58. package/hooks/ac-writing-style-reminder.sh +86 -0
  59. package/hooks/context-pressure.sh +583 -0
  60. package/hooks/hooks.json +50 -0
  61. package/hooks/session-start-handoff.sh +165 -0
  62. package/hooks/session-start.sh +214 -0
  63. package/orchestrator/AGENTS.md +532 -0
  64. package/output-styles/ac-writing-style.md +286 -0
  65. package/package.json +50 -4
  66. package/pi/agents/frontend-verifier.md +357 -0
  67. package/pi/agents/task-checker.md +348 -0
  68. package/pi/agents/task-executor.md +160 -0
  69. package/pi/agents/test-writer.md +154 -0
  70. package/pi/extension.ts +723 -0
  71. package/profiles/fullstack-python.yaml +29 -0
  72. package/profiles/node-typescript.yaml +23 -0
  73. package/profiles/python.yaml +24 -0
  74. package/skills/archive/SKILL.md +160 -0
  75. package/skills/bootstrap/SKILL.md +109 -0
  76. package/skills/bridge/HANDOFF.md +93 -0
  77. package/skills/bridge/SKILL-VM.md +292 -0
  78. package/skills/bridge/SKILL-WINDOWS.md +526 -0
  79. package/skills/bridge/SKILL.md +121 -0
  80. package/skills/browser-verify/SKILL.md +229 -0
  81. package/skills/code-review/SKILL.md +44 -0
  82. package/skills/commit/SKILL.md +106 -0
  83. package/skills/compact-and-continue/SKILL.md +241 -0
  84. package/skills/explore/SKILL.md +149 -0
  85. package/skills/grilling/SKILL.md +134 -0
  86. package/skills/handoff/SKILL.md +250 -0
  87. package/skills/handoff/launch-herdr.md +197 -0
  88. package/skills/handoff/launch-without-herdr.md +30 -0
  89. package/skills/handoff/lead-check.md +59 -0
  90. package/skills/handoff/outcomes.md +162 -0
  91. package/skills/handoff/running-agent.md +50 -0
  92. package/skills/handoff/watch.md +115 -0
  93. package/skills/host/SKILL.md +247 -0
  94. package/skills/host/detect-host.sh +297 -0
  95. package/skills/host/herdr.md +79 -0
  96. package/skills/host/install-herdr-block.sh +467 -0
  97. package/skills/host/install-orca.sh +512 -0
  98. package/skills/host/notes.md +97 -0
  99. package/skills/host/orca.md +160 -0
  100. package/skills/host/questions.md +163 -0
  101. package/skills/host/report.md +42 -0
  102. package/skills/implement/SKILL.md +230 -0
  103. package/skills/implement/decision-log.md +62 -0
  104. package/skills/implement/design-pass.md +32 -0
  105. package/skills/implement/relaxed-tdd.md +28 -0
  106. package/skills/map/SKILL.md +250 -0
  107. package/skills/map/frontend.md +227 -0
  108. package/skills/map/trackers/files.md +74 -0
  109. package/skills/map/trackers/github.md +180 -0
  110. package/skills/map/visuals.md +387 -0
  111. package/skills/new-worktree/SKILL.md +155 -0
  112. package/skills/new-worktree/detect-orchestrator.sh +328 -0
  113. package/skills/new-worktree/detect-orchestrator.test.sh +894 -0
  114. package/skills/new-worktree/herdr.md +84 -0
  115. package/skills/new-worktree/orca.md +101 -0
  116. package/skills/pre-commit-review/SKILL.md +242 -0
  117. package/skills/pre-commit-review/auto-fix.md +19 -0
  118. package/skills/pre-commit-review/interactive.md +35 -0
  119. package/skills/quick-fix/SKILL.md +125 -0
  120. package/skills/record-context/SKILL.md +106 -0
  121. package/skills/setup/SKILL.md +250 -0
  122. package/skills/setup/browser.md +123 -0
  123. package/skills/setup/design-review.md +97 -0
  124. package/skills/setup/first-run.md +114 -0
  125. package/skills/setup/report.md +68 -0
  126. package/skills/setup/row-fixes.md +196 -0
  127. package/skills/setup/terminal-workspace.md +153 -0
  128. package/skills/sync/SKILL.md +171 -0
  129. package/skills/teardown-worktree/SKILL.md +245 -0
  130. package/skills/teardown-worktree/detector.md +39 -0
  131. package/skills/teardown-worktree/herdr.md +113 -0
  132. package/skills/teardown-worktree/orca.md +61 -0
  133. package/skills/terminal-workspace/SKILL.md +249 -0
  134. package/skills/terminal-workspace/components.md +36 -0
  135. package/skills/terminal-workspace/config-choices.md +69 -0
  136. package/skills/terminal-workspace/emulator-keys.md +48 -0
  137. package/skills/terminal-workspace/headless-clipboard.md +175 -0
  138. package/skills/terminal-workspace/turn-off-and-update.md +104 -0
  139. package/skills/test-conventions/SKILL.md +59 -0
  140. package/skills/visual-docs/SKILL.md +246 -0
  141. package/skills/writing/SKILL.md +231 -0
  142. package/skills/writing/examples.md +153 -0
  143. package/skills/writing/vocabulary.md +96 -0
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env bash
2
+ # vm-free-port.sh – VM-side cleanup for a stuck reverse-forward port.
3
+ #
4
+ # When the Windows tunnel repeatedly hits "remote port forwarding failed
5
+ # for listen port 19988", this script identifies what holds 127.0.0.1:19988
6
+ # on the VM and – if safe – kills it.
7
+ #
8
+ # Guardrail: refuses to kill if the socket is held by the current user's
9
+ # own interactive SSH session. Killing that would drop the shell running
10
+ # the script.
11
+ #
12
+ # Guardrail: refuses to kill anything that still answers HTTP on the port. A
13
+ # forward that is serving is the bridge, not litter, and the operator who runs
14
+ # this out of habit would be taking the browser away from whoever is using it.
15
+ #
16
+ # Scoped strictly to the two ports this setup owns: 19988 (the bridge's CDP
17
+ # port) and 19989 (the document opener). Any other port is refused. This is an
18
+ # allowlist rather than a free --port flag on purpose - the guardrails below
19
+ # reason about what a holder is, and that reasoning is only valid for sockets
20
+ # we put there. Exits non-zero with a clear message on any ambiguous or unsafe
21
+ # situation.
22
+ #
23
+ # Intended to be invoked via the handoff block produced by Windows-side
24
+ # doctor.ps1. Safe to run at any time; no-ops if the port is already free.
25
+
26
+ set -u
27
+
28
+ PORT=19988
29
+ # 64 for a bad argument (the conventional EX_USAGE), because 2 already means
30
+ # something specific here: the port is held by something this script may not
31
+ # kill. Reusing it would make "you typed it wrong" indistinguishable from "the
32
+ # port is genuinely stuck".
33
+ case "${1:-}" in
34
+ --port)
35
+ case "${2:-}" in
36
+ 19988|19989) PORT="$2" ;;
37
+ *) printf 'ERROR: --port accepts only 19988 (bridge) or 19989 (opener), not "%s".\n' "${2:-}" >&2; exit 64 ;;
38
+ esac
39
+ ;;
40
+ "") ;;
41
+ *) printf 'usage: vm-free-port.sh [--port 19988|19989]\n' >&2; exit 64 ;;
42
+ esac
43
+
44
+ log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
45
+ err() { log "ERROR: $*" >&2; }
46
+
47
+ # Inspect 127.0.0.1:$PORT – print owner PID, process name, and parent PID.
48
+ find_holder() {
49
+ ss -H -lntp "sport = :$PORT" 2>/dev/null | head -n1
50
+ }
51
+
52
+ line=$(find_holder)
53
+ if [ -z "$line" ]; then
54
+ log "port $PORT is already free. Nothing to do."
55
+ exit 0
56
+ fi
57
+
58
+ log "current holder: $line"
59
+
60
+ # Guardrail: is the forward still carrying traffic? A holder on this port is not
61
+ # garbage by default - while the tunnel is up it is the bridge, and this is the
62
+ # socket agent-browser drives Chrome through. The question is asked of the port
63
+ # rather than of the process, and asked first, because a non-root ss cannot name
64
+ # the owner of someone else's socket: the checks below would give up at "could
65
+ # not parse PID" and never reach the one guardrail that matters most. doctor.ps1 prints this
66
+ # script as the remedy for an amber "Tunnel logs" row, and that row trails for
67
+ # five minutes after the tunnel has already healed itself, so running the
68
+ # clearer against a healthy forward is the ordinary mistake, not the rare one.
69
+ #
70
+ # Any HTTP answer counts as alive. A 401 or a 404 comes from a CDP endpoint
71
+ # that is serving and merely guarded, or asked for a path it does not have, so
72
+ # --fail is deliberately absent: it would read those as dead and kill them. The
73
+ # probe is bounded at 3 seconds, because a genuinely wedged socket is exactly
74
+ # the one that would otherwise hang the clearer called to free it.
75
+ #
76
+ # There is no override flag for this refusal, on purpose. A live tunnel is
77
+ # released by closing the session holding it, and a switch that skipped the
78
+ # probe would just be the habit the probe exists to stop.
79
+ if curl -s -o /dev/null -m 3 "http://127.0.0.1:$PORT/" 2>/dev/null; then
80
+ err "port $PORT is carrying a live tunnel: something answered HTTP on it just now."
81
+ err "Killing the holder would take the browser away from whoever is using it."
82
+ err "If the tunnel really is finished with, close the SSH session holding the"
83
+ err "forward - on the laptop that is stop.ps1 - and this port frees itself."
84
+ exit 7
85
+ fi
86
+
87
+ # ss -lntp line format includes users:(("<name>",pid=<pid>,fd=<fd>))
88
+ pid=$(printf '%s' "$line" | grep -oE 'pid=[0-9]+' | head -n1 | cut -d= -f2)
89
+ if [ -z "${pid:-}" ]; then
90
+ err "could not parse PID from ss output. Run: sudo ss -lntp 'sport = :$PORT'"
91
+ exit 2
92
+ fi
93
+
94
+ pname=$(ps -o comm= -p "$pid" 2>/dev/null || true)
95
+ ppid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ' || true)
96
+ log "PID $pid ($pname), parent PID $ppid"
97
+
98
+ # Guardrail: is this process part of the current user's active SSH session?
99
+ # If the holder (or any ancestor) is our own sshd session leader, refuse.
100
+ current_sid=$(loginctl show-session "$(loginctl | awk -v u="$(id -un)" '$3==u {print $1; exit}')" -p Leader --value 2>/dev/null || true)
101
+
102
+ # Walk ancestors of the holder. If we meet $$ (this script's own shell) or
103
+ # its session leader, it's us.
104
+ own_tree=0
105
+ cursor=$pid
106
+ for _ in 1 2 3 4 5 6 7 8; do
107
+ [ -z "$cursor" ] && break
108
+ [ "$cursor" = "1" ] && break
109
+ if [ "$cursor" = "$$" ] || [ "$cursor" = "$PPID" ]; then
110
+ own_tree=1
111
+ break
112
+ fi
113
+ if [ -n "${current_sid:-}" ] && [ "$cursor" = "$current_sid" ]; then
114
+ own_tree=1
115
+ break
116
+ fi
117
+ cursor=$(ps -o ppid= -p "$cursor" 2>/dev/null | tr -d ' ' || true)
118
+ done
119
+
120
+ if [ "$own_tree" = "1" ]; then
121
+ err "port $PORT is held by your own interactive session (PID $pid, ancestor of this shell)."
122
+ err "Killing it would drop the SSH session running this script."
123
+ err "Exit this SSH session from another shell, or close the client, then retry."
124
+ exit 3
125
+ fi
126
+
127
+ # Prefer not to kill anything that is not sshd – surface it for review.
128
+ if [ "$pname" != "sshd" ] && [ "$pname" != "sshd-session" ]; then
129
+ err "holder is '$pname', not sshd. Refusing to kill a non-sshd process on port $PORT."
130
+ err "If this is expected (e.g. a local test server), stop it manually."
131
+ exit 4
132
+ fi
133
+
134
+ # Check parent liveness: an orphan sshd forward channel typically has a
135
+ # parent that has exited or has been re-parented.
136
+ if [ -n "${ppid:-}" ] && [ "$ppid" != "1" ]; then
137
+ if ! kill -0 "$ppid" 2>/dev/null; then
138
+ log "parent PID $ppid is gone – holder is an orphan sshd forward channel."
139
+ else
140
+ log "parent PID $ppid is alive – holder is an active sshd session, likely a stale tunnel that has not yet been reaped by ClientAlive."
141
+ fi
142
+ fi
143
+
144
+ log "killing PID $pid ..."
145
+ if kill "$pid" 2>/dev/null; then
146
+ :
147
+ else
148
+ log "kill failed without privilege; retrying with sudo."
149
+ if ! sudo -n kill "$pid" 2>/dev/null; then
150
+ err "could not kill PID $pid. Re-run with sudo, or ask the admin to kill it."
151
+ exit 5
152
+ fi
153
+ fi
154
+
155
+ # Give the kernel a moment to release the socket.
156
+ for _ in 1 2 3 4 5; do
157
+ sleep 1
158
+ if [ -z "$(find_holder)" ]; then
159
+ log "port $PORT is now free."
160
+ exit 0
161
+ fi
162
+ done
163
+
164
+ err "port $PORT still held after kill. Current state:"
165
+ ss -lntp "sport = :$PORT" >&2 || true
166
+ exit 6
@@ -0,0 +1,87 @@
1
+ # Free the keys your terminal emulator swallows
2
+
3
+ Hand this to a coding agent running **on the machine you type on** – the
4
+ Windows, macOS, or Linux desktop you SSH *from*. You cannot do this from the
5
+ remote host. The local terminal emulator intercepts the keys before they ever
6
+ reach the other end.
7
+
8
+ ## 1. The emulator claims the same alt chords the remote host binds
9
+
10
+ On the remote host the user runs herdr, a terminal multiplexer. One program
11
+ holds many terminal sessions and keeps them running after the user
12
+ disconnects. Its setup binds `alt` key combinations, called chords, for panes,
13
+ tabs and popups.
14
+
15
+ Some terminal emulators bind the same chords locally. They swallow the
16
+ keypress, or they act on it **and** forward it, so one press does two things.
17
+
18
+ The known collision is **`alt+shift+d`**, which Windows Terminal binds to
19
+ "duplicate pane" by default. It splits the tab on your own machine and never
20
+ forwards the key, so the remote host sees nothing.
21
+
22
+ The remote setup avoids this one by putting its diff viewer on `alt+f` and
23
+ `alt+shift+f`. Other emulators claim other chords, so the table below still
24
+ has to be free.
25
+
26
+ To tell an emulator collision from a remote misconfiguration, run `cat -v` in
27
+ any pane and press the chord. One that arrives prints an escape sequence such
28
+ as `^[Z`. One that prints nothing never left your machine.
29
+
30
+ ## 2. Unbind, in the terminal emulator only
31
+
32
+ Work in the terminal emulator config only. Unbind every chord in the table
33
+ below that the emulator currently claims. Change nothing on the remote host,
34
+ and do not touch the remote configuration.
35
+
36
+ | Chord | Used remotely for |
37
+ |---|---|
38
+ | `alt+f` / `alt+shift+f` | diff viewer, popup and tab |
39
+ | `alt+x` / `alt+shift+x` | diff comparison picker, popup and tab |
40
+ | `alt+i` / `alt+shift+i` | pull request dashboard, popup and tab |
41
+ | `alt+y` / `alt+shift+y` | file manager, popup and tab |
42
+ | `alt+g` / `alt+shift+g` | git TUI, popup and tab |
43
+ | `alt+h` `alt+j` `alt+k` `alt+l` | move between panes |
44
+ | `alt+left` / `alt+right` | previous and next tab |
45
+ | `alt+up` / `alt+down` | previous and next workspace |
46
+ | `alt+1` ... `alt+9` | jump to an agent |
47
+ | `alt+c` `alt+e` `alt+minus` `alt+z` `alt+s` `alt+a` `alt+n` `alt+u` `alt+w` `alt+r` | new tab, splits, zoom, sidebar, last pane, agent and workspace navigation |
48
+
49
+ A workspace is one herdr container holding tabs and panes.
50
+
51
+ ### Windows Terminal
52
+
53
+ Settings, then "Open JSON file". Add to the `actions` array:
54
+
55
+ ```json
56
+ { "command": "unbound", "keys": "alt+shift+d" }
57
+ ```
58
+
59
+ Windows Terminal binds three more chords from the same family. Check
60
+ `alt+shift+plus`, `alt+shift+minus` and `alt+shift+<arrow>` in the same pass.
61
+
62
+ ### Other emulators
63
+
64
+ - **WezTerm**: `keys` in `.wezterm.lua`, with `action = wezterm.action.DisableDefaultAssignment`
65
+ - **iTerm2**: Preferences, Keys, Key Bindings, or the profile's own Keys tab
66
+ - **kitty**: `map alt+shift+d no_op` in `kitty.conf`
67
+ - **Ghostty**: `keybind = alt+shift+d=ignore` in the config
68
+ - **GNOME Terminal / Konsole**: check the shortcut editor for the chord
69
+
70
+ ## 3. What never to change
71
+
72
+ - **Back up the config before editing**, and show the change before saving.
73
+ - Unbind only. Do not remap these chords to something else, and do not touch
74
+ bindings unrelated to the table above.
75
+
76
+ - Leave a chord alone when the terminal emulator does not bind it locally.
77
+
78
+ ## 4. Confirm exactly one thing happens
79
+
80
+ Reload or restart the terminal, then SSH in and press `alt+shift+f`. Exactly
81
+ one thing should happen.
82
+
83
+ A tab opens on the remote host. A local split, or both at once, means the
84
+ unbind did not take.
85
+
86
+ Report which chords the terminal emulator had bound, what changed, and how to
87
+ undo it.
@@ -0,0 +1,329 @@
1
+ # ACStack terminal workspace.
2
+ # Copy to ~/.config/acstack/terminal-workspace.yaml, edit, then run setup.sh apply.
3
+ #
4
+ # This is machine-level, not per-project. It installs binaries and writes
5
+ # keybindings for your user, so it does not belong in acstack/project.yaml.
6
+ #
7
+ # What each component is, every key, and the traps worth knowing:
8
+ # docs/terminal-workspace.md in the ACStack plugin.
9
+ #
10
+ # A key here never carries a hyphen, so gh-dash is gh_dash. The last two
11
+ # blocks name a feature rather than a binary: markdown is acstack-md, and
12
+ # remote is acstack-clip plus acstack-open.
13
+
14
+ # Master switch. With this false, apply exits without installing anything.
15
+ # It removes nothing either. Use setup.sh uninstall for that.
16
+ enabled: true
17
+
18
+ herdr:
19
+ enabled: true
20
+ # A terminal multiplexer. One program holds many terminal sessions and keeps
21
+ # them running after you disconnect.
22
+ #
23
+ # A chord is one key combination, such as alt+f. herdr normally wants a
24
+ # prefix key first. You press the prefix, release it, then press the second
25
+ # key. A chord modifier replaces those two steps with one combination.
26
+ #
27
+ # Keep "alt". It is the only family measured to work on both attach paths.
28
+ # ctrl+alt does reach herdr when you SSH in and run it on the remote host.
29
+ # But a Windows client attaching with `herdr --remote` never delivers it, so
30
+ # those chords go silently dead.
31
+ # Set "none" to keep herdr's default prefix-only keymap.
32
+ chord_modifier: alt
33
+ # A pane's scrollbar costs it a column. herdr counts that column inside the
34
+ # pane rect but tells the program in the pane it has the full width, so a
35
+ # full-screen app wraps one column early and every row drifts one column
36
+ # left of the row above it. Set true to take the scrollbar back; scrolling
37
+ # itself is unaffected either way, because the mouse wheel and copy mode do
38
+ # not use it.
39
+ pane_scrollbars: false
40
+ # A pane border carries the name of the agent running in it. Several agents
41
+ # in several panes is what this workspace is for, and an unlabelled pane does
42
+ # not say which agent it holds. Set false to take the labels off.
43
+ agent_labels_on_pane_borders: true
44
+ # How the agent panel orders its rows: "priority" puts the agents waiting on
45
+ # you at the top. Empty leaves herdr's own order alone.
46
+ agent_panel_sort: ""
47
+ # Where herdr draws a toast, its own short status message. "herdr" draws it
48
+ # in the terminal itself, which is the only route that survives SSH. Empty
49
+ # leaves herdr's own choice alone.
50
+ toast_delivery: ""
51
+ theme:
52
+ # The colour theme herdr paints. Empty writes no theme at all, so herdr
53
+ # keeps whatever it already uses.
54
+ name: ""
55
+ # One herdr server, two devices, two backgrounds. auto_switch asks the
56
+ # attached client for its background colour over OSC 11 - an escape
57
+ # sequence a terminal answers with the colour it is painting - and picks
58
+ # the dark or the light theme from the answer. So a dark terminal on one
59
+ # device and a light one on another, such as an e-ink panel, each get the
60
+ # right theme without either changing the other.
61
+ #
62
+ # Both keys below are read only when `light` names a theme. Without one
63
+ # there is nothing to switch to, so `name` is written on its own.
64
+ auto_switch: true
65
+ # The theme a light client gets. `name` is what a dark one gets, and what
66
+ # a client that answers nothing at all falls back to.
67
+ light: ""
68
+ worktrees_directory: "~/.herdr/worktrees"
69
+ # Agent state hook, so herdr reads state from the agent instead of the
70
+ # screen. One of: claude, codex, opencode, copilot, none.
71
+ integration: claude
72
+ # Link a small herdr plugin that keeps the sidebar numbers matching the keys
73
+ # you press for them. Opening or closing a workspace or a worktree moves the
74
+ # rows, so without this the number beside a row stops being the number that
75
+ # jumps to it.
76
+ renumber_plugin: true
77
+
78
+ gh_dash:
79
+ enabled: true
80
+ # A pull request dashboard. It lists pull requests in the sections you
81
+ # define below, and hands the selected one to tuicr or to an agent.
82
+ popup_key: "alt+i"
83
+ tab_key: "alt+shift+i"
84
+ sections:
85
+ - title: Mine
86
+ filters: "is:open author:@me"
87
+ - title: Needs review
88
+ filters: "is:open review-requested:@me"
89
+ - title: Failing
90
+ filters: "is:open author:@me status:failure"
91
+ # Local clones. Needed for checkout and for {{.RepoPath}} in keybindings.
92
+ repo_paths: {}
93
+ # Bind PageUp and PageDown to scroll the preview. gh-dash uses ctrl+u and
94
+ # ctrl+d by default, which is fine in vim and awkward everywhere else.
95
+ page_keys: true
96
+ keybindings:
97
+ # Both keys below are what apply binds anyway with no config file present.
98
+ # Change either to move it, or set it to "" to leave the key unbound.
99
+ #
100
+ # Open the selected pull request in tuicr, which reviews it in place.
101
+ review: "D"
102
+ # Hand the selected pull request to an agent. Empty disables it.
103
+ agent_review: "S"
104
+
105
+ diffnav:
106
+ enabled: true
107
+ # A diff viewer with a file tree. The popup compares the branch you are on
108
+ # against origin/dev, committed work only, and names both sides in a banner.
109
+ # Repos without a dev branch fall back to the default branch.
110
+ popup_key: "alt+f"
111
+ # Same comparison, a new tab instead of a popup.
112
+ tab_key: "alt+shift+f"
113
+ # An fzf menu of comparisons: against dev or the default branch, with or
114
+ # without uncommitted work, one commit, or any branch against any branch.
115
+ pick_key: "alt+x"
116
+ # Same menu, a new tab instead of a popup.
117
+ pick_tab_key: "alt+shift+x"
118
+
119
+ delta:
120
+ enabled: true
121
+ # The diff renderer git pages through. Sets core.pager and
122
+ # interactive.diffFilter. This does not change what agents see, because git
123
+ # only pages to a terminal.
124
+ set_git_pager: true
125
+
126
+ tuicr:
127
+ enabled: true
128
+ # Reviews a pull request inside the terminal. gh-dash hands pull requests to
129
+ # it. It is not the file manager, which is yazi below.
130
+ # Starting width of the file list, as a percentage of the content area.
131
+ file_list_width: 30
132
+ # Per-file +added -removed counts in the tree.
133
+ show_file_line_stats: true
134
+ # "dark", "light", or empty to let tuicr work it out for itself.
135
+ #
136
+ # Detection asks the terminal for its background colour over OSC 11 - an
137
+ # escape sequence a terminal answers with the colour it is painting. Under
138
+ # `herdr --remote` nothing answers, so tuicr falls back to a desktop setting
139
+ # that a headless machine reports as light, then paints dark text on a black
140
+ # terminal. Pin the value on any machine you reach over the network.
141
+ appearance: ""
142
+ #
143
+ # TEMPORARY. Two upstream pull requests are still open, plus one local fix
144
+ # with none yet:
145
+ # agavra/tuicr#607 configurable file line stats (antonio2368)
146
+ # agavra/tuicr#633 <leader>L / <leader>H resize (ours)
147
+ # (none yet) blank +/- counts in pull request review mode (ours)
148
+ # Until both merge, the two settings above only exist in a fork build.
149
+ # `setup.sh status` checks both and tells you when to switch this off.
150
+ # With it false, the stock release installs and ignores those two settings.
151
+ build_from_fork: false
152
+ fork: "https://github.com/ac8318740/tuicr"
153
+ fork_branch: "local/daily"
154
+
155
+ lazygit:
156
+ enabled: true
157
+ # A git TUI: stage with space, commit with c, amend with A, push with P.
158
+ # g is reserved for it, so herdr's goto sits on prefix+t instead.
159
+ popup_key: "alt+g"
160
+ # Same tool, a new tab instead of a popup.
161
+ tab_key: "alt+shift+g"
162
+
163
+ harlequin:
164
+ enabled: true
165
+ # A SQL editor in the terminal. Write a query on the left, read the rows on
166
+ # the right, and browse the schema in a tree beside both.
167
+ #
168
+ # It is the one component that is not a downloaded binary. harlequin is a
169
+ # python program, so uv installs it into an environment of its own and puts
170
+ # one command on your PATH. A machine without uv skips this component and
171
+ # apply says so.
172
+ popup_key: "alt+q"
173
+ # Same editor, a new tab instead of a popup.
174
+ tab_key: "alt+shift+q"
175
+ # The databases harlequin can talk to. Each name here installs the
176
+ # harlequin-<name> package alongside it. Adding one and running apply again
177
+ # reinstalls harlequin with the new set.
178
+ adapters: [postgres, snowflake]
179
+ # Your connection profiles. acstack writes a starter file here the first time
180
+ # you press the key, and never writes it again - what is in it is yours.
181
+ #
182
+ # default_profile = "local"
183
+ #
184
+ # [profiles.local]
185
+ # adapter = "postgres"
186
+ # conn_str = "postgres://localhost:5432/postgres"
187
+ #
188
+ # A snowflake profile names an account, user, role, warehouse, database and
189
+ # schema, or a single connection_name pointing at a connection in
190
+ # ~/.snowflake/connections.toml. Prefer the second: it keeps the secret out
191
+ # of this file.
192
+ config_path: "~/.config/acstack/harlequin.toml"
193
+
194
+ pi:
195
+ # Off by default, because it writes into pi's own config in ~/.pi/agent.
196
+ # pi is a coding agent in the terminal. Turning this on installs it with npm
197
+ # if it is missing, then installs ACStack into it, pinned to this plugin's
198
+ # version, with pi-subagents, which ACStack needs to launch a subagent.
199
+ #
200
+ # It also writes one line into pi's context file in ~/.pi/agent (AGENTS.md
201
+ # by default) that lets pi delegate when an ACStack skill says to.
202
+ # It sets "toolActivation": "eager" in
203
+ # ~/.pi/agent/extensions/subagent/config.json. It binds
204
+ # "app.message.dequeue" to "ctrl+enter" in ~/.pi/agent/keybindings.json.
205
+ # These changes sit beside your own settings, which stay.
206
+ #
207
+ # Logging in is yours: run pi, then /login.
208
+ enabled: false
209
+ # pi-compact-tools, which shortens how pi draws each tool call.
210
+ compact_tools: true
211
+ # billion-context, which adds context compression to pi. When bili is on
212
+ # PATH, apply also runs bili's native plugin setup for pi. Claude Code and
213
+ # Codex stay off bili.
214
+ billion_context: true
215
+ # Pi's question tool. rpiv-ask-user-question is used because it can show
216
+ # markdown, code and artifact previews beside a decision, accepts freeform
217
+ # answers and notes, and does not require a full-event telemetry env var.
218
+ question_tool: true
219
+ # rpiv-todo, which adds a todo tool to pi.
220
+ todo: true
221
+ # pi-plugin-manager, which adds pi plugin management commands.
222
+ plugin_manager: true
223
+ # pi-web-access, which adds web search, URL fetching, GitHub repo cloning,
224
+ # PDF extraction and media understanding tools to pi. This is separate from
225
+ # pi.settings.install_telemetry, which controls pi's anonymous install and
226
+ # update reporting and stays false in this example.
227
+ web_access: true
228
+ # pi-slash-anywhere, which lets slash commands run from any prompt.
229
+ slash_anywhere: true
230
+ # agentic-retro's pi extension, which queues each finished pi session for a
231
+ # retro. Needs the agentic-retro plugin installed first, and apply says so
232
+ # when it is not.
233
+ agentic_retro: true
234
+ # Written into ~/.pi/agent/settings.json. Empty leaves pi's own value alone.
235
+ # Every other key in that file stays as you set it, and disable puts back
236
+ # any value of yours that apply replaced.
237
+ settings:
238
+ # Hide the model's thinking in the transcript.
239
+ hide_thinking_block: true
240
+ # How pi draws a mermaid diagram: "off", "final", or "streaming".
241
+ # Quote it, because YAML reads a bare off as false.
242
+ mermaid: streaming
243
+ # pi's anonymous install and update reporting. Kept off by this setup.
244
+ install_telemetry: false
245
+ # A pi theme name. "system" takes its colours from the terminal.
246
+ theme: system
247
+
248
+ neovim:
249
+ # The one component that is off by default, because it writes into a neovim
250
+ # config you built yourself. Every other one owns its files outright.
251
+ enabled: false
252
+ # LazyVim marks an unsaved buffer by recolouring the filename and nothing
253
+ # else, so a glance at the statusline cannot tell you a buffer is modified.
254
+ # Turning this on writes ~/.config/nvim/lua/plugins/acstack.lua, a whole
255
+ # file of acstack's own, which appends a dot to the statusline for a
256
+ # modified buffer, and makes copy work over SSH (osc52_clipboard below).
257
+ # It never edits a file you wrote, and
258
+ # `setup.sh disable neovim` deletes it again.
259
+ # Skipped on a machine with no LazyVim config, because both parts rely on
260
+ # LazyVim.
261
+ #
262
+ # The colour of the dot. The default is the catppuccin red.
263
+ unsaved_dot_color: "#f38ba8"
264
+ # Over SSH, :%y+ fails because neovim finds no clipboard. This sends every
265
+ # copy to the machine you sit at over OSC 52. Section 3.5 of
266
+ # docs/terminal-workspace.md covers paste. Left out when a file of yours
267
+ # already sets vim.g.clipboard.
268
+ osc52_clipboard: true
269
+
270
+ yazi:
271
+ enabled: true
272
+ # A file manager. Markdown renders live in the preview pane through
273
+ # acstack-md, so a document draws as the cursor moves over it. Enter opens
274
+ # the same renderer full width, where more of a diagram fits.
275
+ # A popup, leaving the tab layout alone.
276
+ popup_key: "alt+y"
277
+ # Opens a real tab instead.
278
+ tab_key: "alt+shift+y"
279
+ # Opens the hovered file in $EDITOR. tuicr puts the editor on the same key
280
+ # in both its panels, so one key means edit across the workspace.
281
+ edit_key: "e"
282
+ # Sends the hovered file to your own browser.
283
+ browser_key: "b"
284
+ # Previews markdown as source, with line numbers down the left.
285
+ line_numbers_key: "#"
286
+ # Sends the hovered file to the machine you sit at, over Tailscale Taildrop.
287
+ # Set it to that machine's Tailscale node name, as `tailscale status` on this
288
+ # machine spells it. Empty writes no key at all.
289
+ # Both machines must sit on one tailnet under one Tailscale user account,
290
+ # because Taildrop refuses to cross either boundary.
291
+ download_target: ""
292
+ # The key that sends it. Only read when download_target is set.
293
+ download_key: "D"
294
+ show_hidden: true
295
+
296
+ markdown:
297
+ enabled: true
298
+ # This component installs three tools: acstack-md, mermaid-ascii and glow.
299
+ # acstack-md draws a markdown file in the terminal, calling glow for the
300
+ # prose and mermaid-ascii for the diagrams. A mermaid diagram is a diagram
301
+ # written as text in the file itself. acstack-md can also serve the same
302
+ # file to a real browser, for the diagrams worth studying.
303
+ #
304
+ # Text rather than inline images is deliberate. herdr emits the kitty
305
+ # graphics protocol, and no terminal reachable over SSH from Windows or
306
+ # Android renders it, so an image never arrives. e-ink panels also render
307
+ # text far better than bitmaps. The fast refresh modes an interactive
308
+ # terminal needs are the ones that drop the greyscale depth an image needs.
309
+ #
310
+ # acstack-md --serve takes its port from $ACSTACK_MD_PORT and falls back to
311
+ # 6419. Forward whichever port you serve on, or the link --serve prints is
312
+ # unreachable from your laptop.
313
+
314
+ remote:
315
+ enabled: true
316
+ # Copy and open, on a machine you reach over SSH.
317
+ #
318
+ # The dev machine – the remote machine your agents run on, a virtual machine
319
+ # in this setup – has no display and no clipboard. Anything that shells out
320
+ # to xclip fails, and xdg-open exits 1. Two programs close the gap.
321
+ # acstack-clip sends text to your own terminal's clipboard over OSC 52, an
322
+ # escape sequence a terminal reads as "put this text on the clipboard of the
323
+ # machine I am running on". acstack-open finds whatever browser is actually
324
+ # reachable.
325
+ #
326
+ # Install an xclip stand-in backed by acstack-clip. Programs that only know
327
+ # how to shell out – gh-dash's y and Y among them – then copy correctly.
328
+ # Skipped when the machine has a real xclip or a display of its own.
329
+ clipboard_shim: true