acstack 0.49.0 → 0.49.2

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 (36) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/assets/terminal-workspace/helpers/acstack-bridge +185 -0
  3. package/assets/terminal-workspace/helpers/acstack-clip +88 -0
  4. package/assets/terminal-workspace/helpers/acstack-dash +25 -0
  5. package/assets/terminal-workspace/helpers/acstack-db +86 -0
  6. package/assets/terminal-workspace/helpers/acstack-diff +268 -0
  7. package/assets/terminal-workspace/helpers/acstack-edit +25 -0
  8. package/assets/terminal-workspace/helpers/acstack-gh +47 -0
  9. package/assets/terminal-workspace/helpers/acstack-herdr-renumber +93 -0
  10. package/assets/terminal-workspace/helpers/acstack-herdr-tab +49 -0
  11. package/assets/terminal-workspace/helpers/acstack-md +401 -0
  12. package/assets/terminal-workspace/helpers/acstack-md-lib +219 -0
  13. package/assets/terminal-workspace/helpers/acstack-open +217 -0
  14. package/assets/terminal-workspace/helpers/acstack-popup +13 -0
  15. package/assets/terminal-workspace/helpers/acstack-view +33 -0
  16. package/assets/terminal-workspace/helpers/xclip +31 -0
  17. package/assets/terminal-workspace/lib/apply-ghdash.sh +78 -0
  18. package/assets/terminal-workspace/lib/apply-herdr-numbers.sh +49 -0
  19. package/assets/terminal-workspace/lib/apply-herdr.sh +308 -0
  20. package/assets/terminal-workspace/lib/apply-tools.sh +293 -0
  21. package/assets/terminal-workspace/lib/apply-tuicr.sh +133 -0
  22. package/assets/terminal-workspace/lib/apply-yazi.sh +389 -0
  23. package/assets/terminal-workspace/lib/commands.sh +303 -0
  24. package/assets/terminal-workspace/lib/core.sh +184 -0
  25. package/assets/terminal-workspace/lib/helper-install.sh +35 -0
  26. package/assets/terminal-workspace/lib/pi-packages.sh +360 -0
  27. package/assets/terminal-workspace/lib/pi-state.sh +479 -0
  28. package/assets/terminal-workspace/lib/versions.sh +254 -0
  29. package/assets/terminal-workspace/pi/acstack-question-blocked.ts +31 -0
  30. package/assets/terminal-workspace/setup.sh +25 -4637
  31. package/cli/package.json +1 -1
  32. package/docs/terminal-workspace.md +10 -1
  33. package/package.json +1 -1
  34. package/skills/handoff/SKILL.md +14 -14
  35. package/skills/handoff/launch-herdr.md +15 -4
  36. package/skills/implement/SKILL.md +5 -0
@@ -0,0 +1,360 @@
1
+ acstack_version() {
2
+ json_field "$SCRIPT_DIR/../../.claude-plugin/plugin.json" version
3
+ }
4
+
5
+ # Installs one pi package, or moves its pin. Ownership is taken only when the
6
+ # package was absent: one the user installed stays theirs, pin and all.
7
+ pi_add() { # pi_add <source> <identity>
8
+ local cur
9
+ cur=$(pi_state pkg "$2")
10
+ if [ -n "$cur" ]; then
11
+ # Unpinned, presence is enough: a pin the user chose stays.
12
+ case "$1" in "$2"@*) ;; *) say "pi: $2 already installed"; return 0 ;; esac
13
+ [ "$cur" = "$1" ] && { say "pi: $1 already installed"; return 0; }
14
+ fi
15
+ if ! pi install "$1" >/dev/null 2>&1; then
16
+ say "pi: install of $1 failed, run: pi install $1"
17
+ return 1
18
+ fi
19
+ if [ -n "$cur" ]; then
20
+ pi_state pin "$2" "$cur" "$1"
21
+ say "pi: $cur moved to $1"
22
+ else
23
+ pi_state own pkg "$2"
24
+ say "pi: $1 installed"
25
+ fi
26
+ }
27
+
28
+ # Puts back a pin apply moved, while the pin is still the one apply set.
29
+ pi_unpin() { # pi_unpin <identity>
30
+ local rec prior ours cur
31
+ rec=$(pi_state pin-get "$1") || return 0
32
+ prior="${rec%%$'\t'*}" ours="${rec#*$'\t'}"
33
+ cur=$(pi_state pkg "$1")
34
+ if [ "$cur" = "$ours" ]; then
35
+ have pi || { say "pi: not on PATH, so $cur stays pinned"; return 0; }
36
+ pi install "$prior" >/dev/null 2>&1 \
37
+ || { say "pi: could not put back $prior, run: pi install $prior"; return 0; }
38
+ say "pi: $cur put back to $prior"
39
+ fi
40
+ pi_state pin-forget "$1"
41
+ }
42
+
43
+ # Removes a package this setup installed. One the user installed is left.
44
+ pi_drop() { # pi_drop <identity>
45
+ local cur
46
+ pi_state owns pkg "$1" || return 0
47
+ cur=$(pi_state pkg "$1")
48
+ if [ -n "$cur" ]; then
49
+ have pi || { say "pi: not on PATH, so $cur stays installed"; return 0; }
50
+ pi remove "$cur" >/dev/null 2>&1 || { say "pi: remove of $cur failed, run: pi remove $cur"; return 0; }
51
+ say "pi: $cur removed"
52
+ fi
53
+ pi_state disown pkg "$1"
54
+ }
55
+
56
+ pi_npm_package() { # pi_npm_package <name> -> npm package identity, or nothing
57
+ version_table | awk -F'\t' -v n="$1" '$1==n && $2=="pi" && $3=="npm"{print "npm:" $4; found=1; exit} END{exit !found}'
58
+ }
59
+
60
+ # A package pi holds at a fixed version, such as npm:pi-subagents@1.0.0.
61
+ pi_npm_pin() { # pi_npm_pin <name>
62
+ local pkg src
63
+ pkg=$(pi_npm_package "$1") || return 1
64
+ src=$(pi_state pkg "$pkg") || return 1
65
+ [ "$src" != "$pkg" ]
66
+ }
67
+
68
+ pi_suggest_todo_discipline() {
69
+ local needle="When using a todo list, keep it current as work completes."
70
+ grep -qsF "$needle" "$PI_DIR"/AGENTS.override.md "$PI_DIR"/AGENTS.md "$PI_DIR"/AGENTS.MD \
71
+ "$PI_DIR"/CLAUDE.md "$PI_DIR"/CLAUDE.MD && return 0
72
+ say "pi: todo tool installed. For stricter todo discipline, ask the agent"
73
+ say "pi: running this setup to hand-edit $PI_DIR/AGENTS.md and add:"
74
+ say "pi: $needle Before reporting a task as done, check the todo list"
75
+ say "pi: and mark every completed step completed. Do not leave stale"
76
+ say "pi: in-progress or pending todos after a commit, PR, merge or workflow gate."
77
+ }
78
+
79
+ # The pin apply would move to, or nothing when the installed one is current
80
+ # or newer. A newer pin is one the user chose, so it is never moved back.
81
+ pi_acstack_behind() {
82
+ local cur ver pin
83
+ ver=$(acstack_version); [ -n "$ver" ] || return 0
84
+ cur=$(pi_state pkg "$PI_ACSTACK") || return 0
85
+ case "$cur" in "$PI_ACSTACK"@*) pin="${cur##*@}" ;; *) return 0 ;; esac
86
+ [ "$pin" = "$ver" ] && return 0
87
+ [ "$(printf '%s\n%s\n' "$pin" "$ver" | sort -V | tail -1)" = "$ver" ] || return 0
88
+ printf '%s\t%s' "$pin" "$ver"
89
+ }
90
+
91
+ bili_bin() {
92
+ if [ -x "$PI_DIR/npm/node_modules/.bin/bili" ]; then
93
+ printf '%s' "$PI_DIR/npm/node_modules/.bin/bili"
94
+ elif have bili; then
95
+ command -v bili
96
+ else
97
+ return 1
98
+ fi
99
+ }
100
+
101
+ bili_status() { # bili_status <bin> <client> -> installed|absent|unknown
102
+ local line
103
+ line=$("$1" plugin list 2>/dev/null | awk -v c="$2" '$1==c{print; exit}') || true
104
+ [ -n "$line" ] || { echo unknown; return 0; }
105
+ case "$line" in *"not installed"*) echo absent ;; *) echo installed ;; esac
106
+ }
107
+
108
+ bili_client() { # bili_client install|remove <client>
109
+ local op="$1" client="$2" bin marker status
110
+ marker="bili:$client"
111
+ if [ "$op" = "install" ]; then
112
+ [ "$client" = "pi" ] || have "$client" || return 0
113
+ pi_state owns file "$marker" && return 0
114
+ if bin=$(bili_bin); then
115
+ status=$(bili_status "$bin" "$client")
116
+ if [ "$status" = "installed" ]; then
117
+ say "pi: billion-context already enabled for $client"
118
+ return 0
119
+ fi
120
+ if "$bin" plugin install "$client" >/dev/null 2>&1; then
121
+ pi_state own file "$marker"
122
+ say "pi: billion-context enabled for $client"
123
+ else
124
+ say "pi: billion-context $client setup failed, run: bili plugin install $client"
125
+ fi
126
+ else
127
+ say "pi: bili command not found, so billion-context $client setup was skipped"
128
+ fi
129
+ elif pi_state owns file "$marker"; then
130
+ if bin=$(bili_bin); then
131
+ if "$bin" plugin remove "$client" >/dev/null 2>&1; then
132
+ pi_state disown file "$marker"
133
+ say "pi: billion-context disabled for $client"
134
+ else
135
+ say "pi: billion-context $client removal failed, run: bili plugin remove $client"
136
+ fi
137
+ else
138
+ say "pi: bili command not found, so billion-context $client setup was left installed"
139
+ fi
140
+ fi
141
+ }
142
+
143
+ pi_question_bridge() { # pi_question_bridge install|remove
144
+ local fresh=0 rc
145
+ if [ "$1" = "install" ]; then
146
+ if { [ -e "$PI_QUESTION_BRIDGE" ] || [ -L "$PI_QUESTION_BRIDGE" ]; } \
147
+ && ! pi_state owns file "$PI_QUESTION_BRIDGE"; then
148
+ say "pi: $PI_QUESTION_BRIDGE already exists, so the question bridge was left alone"
149
+ return 0
150
+ fi
151
+ [ -e "$PI_QUESTION_BRIDGE" ] || [ -L "$PI_QUESTION_BRIDGE" ] || fresh=1
152
+ mkdir -p "$(dirname "$PI_QUESTION_BRIDGE")" \
153
+ && cp "$SCRIPT_DIR/pi/acstack-question-blocked.ts" "$PI_QUESTION_BRIDGE" || return $?
154
+ if pi_state own file "$PI_QUESTION_BRIDGE"; then
155
+ say "pi: question blocked bridge written to $PI_QUESTION_BRIDGE"
156
+ else
157
+ rc=$?
158
+ [ "$fresh" = 1 ] && rm -f "$PI_QUESTION_BRIDGE"
159
+ say "pi: question blocked bridge ownership registration failed for $PI_QUESTION_BRIDGE"
160
+ return "$rc"
161
+ fi
162
+ elif pi_state owns file "$PI_QUESTION_BRIDGE"; then
163
+ rm -f "$PI_QUESTION_BRIDGE" \
164
+ && pi_state disown file "$PI_QUESTION_BRIDGE" \
165
+ && say "pi: question blocked bridge removed"
166
+ fi
167
+ }
168
+
169
+ apply_pi() {
170
+ find_pi
171
+ if ! have pi; then
172
+ have npm || {
173
+ say "npm not installed, skipping pi"
174
+ say "install Node.js, then run apply again"
175
+ return 0
176
+ }
177
+ npm install -g @earendil-works/pi-coding-agent >/dev/null 2>&1 \
178
+ || { say "pi install failed, run: npm install -g @earendil-works/pi-coding-agent"; return 0; }
179
+ have pi || { say "pi installed, but npm's global bin is not on PATH"; return 0; }
180
+ say "pi installed"
181
+ fi
182
+ # pi rewrites settings.json on every install. Handing it a file it cannot
183
+ # parse risks the user's settings, so concede the whole component instead.
184
+ if ! pi_state parses "$PI_SETTINGS"; then
185
+ say "pi: $PI_SETTINGS does not parse, so nothing was installed into pi"
186
+ say "fix that file, then run apply again"
187
+ return 0
188
+ fi
189
+
190
+ patch_pi_win32_paste
191
+
192
+ # ACStack cannot launch a subagent on pi without pi-subagents.
193
+ pi_add npm:pi-subagents npm:pi-subagents
194
+ if [ "$(cfg_get pi.todo true)" = "true" ]; then
195
+ pi_add npm:@juicesharp/rpiv-todo npm:@juicesharp/rpiv-todo
196
+ pi_suggest_todo_discipline
197
+ else
198
+ pi_drop npm:@juicesharp/rpiv-todo
199
+ fi
200
+ if [ "$(cfg_get pi.plugin_manager true)" = "true" ]; then
201
+ pi_add npm:pi-plugin-manager npm:pi-plugin-manager
202
+ else
203
+ pi_drop npm:pi-plugin-manager
204
+ fi
205
+ # bili's proxy dies with the session that started it, cutting off every
206
+ # other Claude Code or Codex session. Keep it in pi; undo older installs.
207
+ bili_client remove codex
208
+ bili_client remove claude
209
+ if [ "$(cfg_get pi.billion_context true)" = "true" ]; then
210
+ pi_state merge "$BILLION_CONFIG" billion '{"pi.subagents": false}'
211
+ if [ "$(json_field "$BILLION_CONFIG" pi.subagents)" = "False" ]; then
212
+ pi_add "$PI_BILLION" "$PI_BILLION"
213
+ bili_client install pi
214
+ else
215
+ say "pi: billion-context acp_delegate could not be disabled, so billion-context setup was skipped"
216
+ fi
217
+ else
218
+ bili_client remove pi
219
+ pi_state merge "$BILLION_CONFIG" billion '{"pi.subagents": null}'
220
+ pi_drop "$PI_BILLION"
221
+ fi
222
+ if [ "$(cfg_get pi.question_tool true)" = "true" ]; then
223
+ pi_add npm:@juicesharp/rpiv-ask-user-question npm:@juicesharp/rpiv-ask-user-question
224
+ pi_question_bridge install
225
+ else
226
+ pi_question_bridge remove
227
+ pi_drop npm:@juicesharp/rpiv-ask-user-question
228
+ fi
229
+ if [ "$(cfg_get pi.web_access true)" = "true" ]; then
230
+ pi_add npm:pi-web-access npm:pi-web-access
231
+ else
232
+ pi_drop npm:pi-web-access
233
+ fi
234
+ if [ "$(cfg_get pi.lens false)" = "true" ]; then
235
+ pi_add npm:pi-lens npm:pi-lens
236
+ else
237
+ pi_drop npm:pi-lens
238
+ fi
239
+ if [ "$(cfg_get pi.slash_anywhere true)" = "true" ]; then
240
+ pi_add "$PI_SLASH_ANYWHERE" "$PI_SLASH_ANYWHERE"
241
+ else
242
+ pi_drop "$PI_SLASH_ANYWHERE"
243
+ fi
244
+ local old
245
+ if old=$(pi_state pkg "$PI_ACSTACK_GIT"); then
246
+ say "pi: $old serves old ACStack skills, run: pi remove $old"
247
+ fi
248
+ local ver cur; ver=$(acstack_version)
249
+ if [ -n "$ver" ]; then
250
+ if cur=$(pi_state pkg "$PI_ACSTACK") && [ -z "$(pi_acstack_behind)" ]; then
251
+ say "pi: $cur already installed"
252
+ else
253
+ pi_add "$PI_ACSTACK@$ver" "$PI_ACSTACK"
254
+ fi
255
+ else
256
+ say "pi: no plugin version beside this script, so ACStack was not installed into pi"
257
+ fi
258
+ if [ "$(cfg_get pi.compact_tools true)" = "true" ]; then
259
+ pi_add npm:pi-compact-tools npm:pi-compact-tools
260
+ else
261
+ pi_drop npm:pi-compact-tools
262
+ fi
263
+ [ "$(cfg_get pi.settings.theme alex-mid-grey)" = "alex-mid-grey" ] && pi_write_theme
264
+ if [ "$(cfg_get pi.zentui true)" = "true" ]; then
265
+ pi_add "$PI_ZENTUI" "$PI_ZENTUI"
266
+ pi_state merge "$PI_ZENTUI_CFG" zentui "$(pi_zentui_wanted)"
267
+ else
268
+ pi_state merge "$PI_ZENTUI_CFG" zentui "$(pi_zentui_release)"
269
+ pi_drop "$PI_ZENTUI"
270
+ fi
271
+ if [ "$(cfg_get pi.hide_lens_lsp_status true)" = "true" ]; then
272
+ pi_state merge "$PI_LENS_CFG" pi_lens '{"ui.hideLspStatus": true, "widget.visible": false}'
273
+ else
274
+ pi_state merge "$PI_LENS_CFG" pi_lens '{"ui.hideLspStatus": null, "widget.visible": null}'
275
+ fi
276
+ apply_lens_extra_tools
277
+
278
+ # agentic-retro writes its own extension file. This setup only asks it to,
279
+ # and owns the file only when it was not there before.
280
+ if [ "$(cfg_get pi.agentic_retro true)" = "true" ]; then
281
+ if have agentic-retro; then
282
+ local fresh=0; [ -e "$PI_RETRO_EXT" ] || fresh=1
283
+ if agentic-retro install-pi >/dev/null 2>&1; then
284
+ [ "$fresh" = "1" ] && pi_state own file "$PI_RETRO_EXT"
285
+ say "pi: agentic-retro extension written"
286
+ else
287
+ say "pi: agentic-retro install-pi failed, run it yourself"
288
+ fi
289
+ else
290
+ say "pi: agentic-retro not on PATH, so its pi extension was skipped"
291
+ say "install the agentic-retro plugin first, then run apply again"
292
+ fi
293
+ elif pi_state owns file "$PI_RETRO_EXT"; then
294
+ rm -f "$PI_RETRO_EXT"
295
+ pi_state disown file "$PI_RETRO_EXT"
296
+ say "pi: agentic-retro extension removed"
297
+ fi
298
+
299
+ pi_state merge "$PI_SETTINGS" settings "$(pi_settings_wanted)"
300
+ pi_state merge "$PI_KEYBINDINGS" keybindings '{"app.message.dequeue": "ctrl+enter"}'
301
+ pi_state merge "$PI_SUBAGENT_CFG" subagent '{"toolActivation": "eager"}'
302
+ pi_state md write
303
+ patch_pi_win32_paste
304
+
305
+ # Logging in is interactive and stores a credential, so it stays the user's.
306
+ [ -f "$PI_DIR/auth.json" ] || say "pi: not logged in yet. Run pi, then /login"
307
+ return 0
308
+ }
309
+
310
+ # disable pi and uninstall both come here. It undoes what apply_pi recorded
311
+ # and leaves the pi binary, its logins and its sessions alone.
312
+ remove_pi() {
313
+ find_pi
314
+ pi_question_bridge remove
315
+ if pi_state parses "$PI_SETTINGS"; then
316
+ pi_unpin "$PI_ACSTACK"
317
+ pi_drop "$PI_ACSTACK"
318
+ bili_client remove codex
319
+ bili_client remove claude
320
+ bili_client remove pi
321
+ pi_drop "$PI_BILLION"
322
+ pi_drop npm:pi-plugin-manager
323
+ pi_drop npm:@juicesharp/rpiv-todo
324
+ pi_drop npm:pi-compact-tools
325
+ pi_drop "$PI_ZENTUI"
326
+ pi_drop npm:@juicesharp/rpiv-ask-user-question
327
+ pi_drop npm:pi-web-access
328
+ pi_drop npm:pi-lens
329
+ pi_drop "$PI_SLASH_ANYWHERE"
330
+ pi_drop npm:pi-subagents
331
+ else
332
+ say "pi: $PI_SETTINGS does not parse, so its packages were left installed"
333
+ fi
334
+ pi_state merge "$BILLION_CONFIG" billion '{"pi.subagents": null}'
335
+ if pi_state owns file "$PI_RETRO_EXT"; then
336
+ rm -f "$PI_RETRO_EXT"
337
+ pi_state disown file "$PI_RETRO_EXT"
338
+ say "pi: agentic-retro extension removed"
339
+ fi
340
+ pi_state merge "$PI_SETTINGS" settings \
341
+ '{"hideThinkingBlock": null, "markdown.mermaid": null, "enableInstallTelemetry": null, "theme": null}'
342
+ pi_state merge "$PI_ZENTUI_CFG" zentui "$(pi_zentui_release)"
343
+ pi_state merge "$PI_LENS_CFG" pi_lens '{"ui.hideLspStatus": null, "widget.visible": null}'
344
+ if pi_state owns file "$PI_THEME"; then
345
+ rm -f "$PI_THEME"
346
+ pi_state disown file "$PI_THEME"
347
+ say "pi: ACStack theme removed"
348
+ fi
349
+ pi_state merge "$PI_KEYBINDINGS" keybindings '{"app.message.dequeue": null}'
350
+ pi_state merge "$PI_SUBAGENT_CFG" subagent '{"toolActivation": null}'
351
+ pi_state md strip
352
+ return 0
353
+ }
354
+
355
+ # Every tool with a version worth comparing.
356
+ # name | component key | source | reference | release asset match
357
+ # mermaid-ascii is deliberately absent: it has no version flag at all, so
358
+ # nothing here can read what is installed. `upgrade mermaid-ascii` still works,
359
+ # because upgrade_one carries an arm of its own for it. A tool that is in
360
+ # neither place is one upgrade cannot reach, whatever the docs promise.