@huseynsnmz/pi-board 0.15.3 → 0.17.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 (216) hide show
  1. package/CHANGELOG.md +24 -4
  2. package/CONTRIBUTING.md +7 -0
  3. package/README.md +55 -419
  4. package/SECURITY.md +19 -0
  5. package/docs/how-it-works.md +31 -0
  6. package/docs/reference.md +277 -0
  7. package/package.json +5 -11
  8. package/src/commands/agent-board.ts +26 -2
  9. package/src/commands/attach-flow.ts +4 -0
  10. package/src/core/double-press.mjs +6 -2
  11. package/src/core/slash-commands.mjs +52 -0
  12. package/src/index.ts +1 -1
  13. package/src/runtime/service.mjs +12 -0
  14. package/src/ui/dashboard.ts +59 -25
  15. package/src/ui/pty-attach.ts +8 -3
  16. package/IMPLEMENTATION_PLAN.md +0 -920
  17. package/PRD.md +0 -484
  18. package/PROGRESS.md +0 -142
  19. package/VERIFY.md +0 -115
  20. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  21. package/docs/EXPLORATION.md +0 -187
  22. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  23. package/docs/RELEASE.md +0 -148
  24. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  25. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  26. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  27. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  28. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  29. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  30. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  31. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  32. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  33. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  34. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  35. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  36. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  37. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  38. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  39. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  40. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  41. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  42. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  43. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  44. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  45. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  46. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  47. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  48. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  49. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  50. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  51. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  52. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  53. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  54. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  55. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  56. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  57. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  58. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  59. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  60. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  61. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  62. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  63. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  64. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  65. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  66. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  67. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  68. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  69. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  70. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  71. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  72. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  73. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  74. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  75. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  76. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  77. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  78. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  79. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  80. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  81. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  82. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  83. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  84. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  85. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  86. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  87. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  88. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  89. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  90. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  91. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  92. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  93. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  94. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  95. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  96. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  97. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  98. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  99. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  100. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  101. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  102. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  103. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  104. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  105. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  106. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  107. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  108. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  109. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  110. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  111. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  112. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  113. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  114. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  115. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  116. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  117. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  118. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  119. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  120. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  121. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  122. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  123. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  124. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  125. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  126. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  127. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  128. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  129. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  130. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  131. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  132. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  133. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  134. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  135. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  136. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  137. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  138. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  139. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  140. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  141. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  142. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  143. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  144. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  145. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  146. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  147. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  148. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  149. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  150. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  151. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  152. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  153. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  154. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  155. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  156. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  157. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  158. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  159. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  160. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  161. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  162. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  163. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  164. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  165. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  166. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  167. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  168. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  169. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  170. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  171. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  172. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  173. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  174. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  175. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  176. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  177. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  178. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  179. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  180. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  181. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  182. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  183. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  184. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  185. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  186. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  187. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  188. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  189. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  190. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  191. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  192. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  193. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  194. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  195. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  196. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  197. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  198. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  199. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  200. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  201. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  202. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  203. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  204. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  205. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  206. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  207. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  208. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  209. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  210. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  211. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  212. package/scripts/budget-audit.mjs +0 -135
  213. package/scripts/release.mjs +0 -446
  214. package/scripts/release_helper.mjs +0 -309
  215. package/scripts/run-perf-gate.mjs +0 -40
  216. package/scripts/run-tests.mjs +0 -375
@@ -9,7 +9,9 @@
9
9
  import { readFileSync } from "node:fs";
10
10
  import { CustomEditor } from "@earendil-works/pi-coding-agent";
11
11
  import type { Component, EditorTheme, KeybindingsManager, TUI } from "@earendil-works/pi-tui";
12
- import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
12
+ import { CombinedAutocompleteProvider, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
13
+ import { createDoublePress, QUIT_PRESS_MS } from "../core/double-press.mjs";
14
+ import { runsInPi } from "../core/slash-commands.mjs";
13
15
  import { readConfig, writeConfig } from "../core/config.mjs";
14
16
  import { editExternally } from "./external-editor.ts";
15
17
  import { requestDashboardRender } from "../core/dashboard-render.mjs";
@@ -57,7 +59,11 @@ interface ThemeLike {
57
59
  strikethrough(text: string): string;
58
60
  }
59
61
 
60
- export type DashboardResult = { action: "exit" } | { action: "attach"; viewId: string; stopFirst: boolean };
62
+ export type DashboardResult =
63
+ | { action: "exit" }
64
+ | { action: "attach"; viewId: string; stopFirst: boolean }
65
+ | { action: "quit" }
66
+ | { action: "command"; text: string };
61
67
 
62
68
  type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
63
69
  type LaunchModel = {
@@ -125,6 +131,7 @@ export interface DashboardDeps {
125
131
  currentThinkingLevel: ThinkingLevel;
126
132
  initialSelectedId?: string | null;
127
133
  initialNotice?: string;
134
+ commands?: { name: string; description?: string; source: string }[];
128
135
  }
129
136
 
130
137
  export class DashboardComponent implements Component {
@@ -137,10 +144,11 @@ export class DashboardComponent implements Component {
137
144
  private filterQuery = "";
138
145
  private pending: PendingConfirm | null = null;
139
146
  private deleteArm: { id: string; at: number } | null = null;
140
- private exitArmedAt: number | null = null;
147
+ private readonly quitPresses = createDoublePress(Date.now, QUIT_PRESS_MS);
141
148
  private groupBy: "state" | "directory" = readConfig().groupBy === "directory" ? "directory" : "state";
142
149
  private readonly groupOf = new Map<string, string>();
143
150
  private readonly neighborsOf = new Map<string, string>();
151
+ private readonly folderOf = new Map<string, string>();
144
152
  private helpReturnMode: ReturnableMode = "list";
145
153
  private ptyHelpReturnMode: ReturnableMode = "list";
146
154
  private peekId: string | null = null;
@@ -181,6 +189,7 @@ export class DashboardComponent implements Component {
181
189
  }
182
190
  };
183
191
  this.editor.onSubmit = (text) => this.submitEditor(text);
192
+ this.editor.setAutocompleteProvider(this.commandMenu());
184
193
  this.selectedId = deps.initialSelectedId ?? null;
185
194
  // Issue #147: the post-delete reopen carries the Deleted notice here —
186
195
  // the component is new each open, so an inline notice cannot travel.
@@ -202,15 +211,17 @@ export class DashboardComponent implements Component {
202
211
  this.rows = this.filterQuery ? filterRows(all, this.filterQuery) : all;
203
212
  this.groupOf.clear();
204
213
  this.neighborsOf.clear();
205
- const place = (group: string, leaf: string, ids: string[]) => {
214
+ this.folderOf.clear();
215
+ const place = (group: string, folder: string, ids: string[]) => {
206
216
  for (const id of ids) {
207
217
  this.groupOf.set(id, group);
208
- this.neighborsOf.set(id, `${group}\0${leaf}`);
218
+ this.folderOf.set(id, folder);
219
+ this.neighborsOf.set(id, `${group}\0${folder}`);
209
220
  }
210
221
  return ids;
211
222
  };
212
223
  this.orderedIds = this.groupBy === "directory"
213
- ? groupRowsByDirectory(this.rows, Date.now()).flatMap((f) => place(f.key, "", f.rows.map((r) => r.id)))
224
+ ? groupRowsByDirectory(this.rows, Date.now()).flatMap((f) => place(f.key, f.key, f.rows.map((r) => r.id)))
214
225
  : groupRowsByFolder(this.rows, Date.now()).flatMap((g) => g.folders.flatMap((f) => place(g.state, f.key, f.rows.map((r) => r.id))));
215
226
  const visibleIds = new Set(this.orderedIds);
216
227
  this.selectedIds = new Set([...this.selectedIds].filter((id) => visibleIds.has(id)));
@@ -332,6 +343,8 @@ export class DashboardComponent implements Component {
332
343
  // ---- input --------------------------------------------------------------
333
344
 
334
345
  handleInput(data: string): void {
346
+ if (!matchesKey(data, Key.ctrl("c"))) this.quitPresses.reset();
347
+ else if (this.quitPresses.press()) return this.done({ action: "quit" });
335
348
  switch (this.mode) {
336
349
  case "list":
337
350
  this.handleListKey(data);
@@ -378,11 +391,10 @@ export class DashboardComponent implements Component {
378
391
 
379
392
  private handleListKey(data: string): void {
380
393
  if (!matchesKey(data, Key.ctrl("x"))) this.deleteArm = null;
381
- if (!matchesKey(data, Key.ctrl("c"))) this.exitArmedAt = null;
382
394
  if (this.handleRowKey(data)) return;
383
395
  if (matchesKey(data, Key.right) || matchesKey(data, Key.left) || matchesKey(data, Key.enter)) return this.attachSelected();
384
396
  if (matchesKey(data, Key.space)) return this.openPeek();
385
- if (data === "/" || matchesKey(data, Key.ctrl("f"))) return this.startFilter();
397
+ if (matchesKey(data, Key.ctrl("f"))) return this.startFilter();
386
398
  if (data === "?") return this.openHelp("list");
387
399
  if (data === "!") return this.openPtyHelp("list");
388
400
  if (matchesKey(data, Key.ctrl("r"))) return this.startRename();
@@ -399,16 +411,10 @@ export class DashboardComponent implements Component {
399
411
  if (matchesKey(data, Key.ctrl("b"))) return this.toggleHold();
400
412
  if (matchesKey(data, Key.ctrl("k"))) return this.confirmDeleteState();
401
413
  for (let n = 1; n <= 9; n++) {
402
- if (matchesKey(data, Key.alt(String(n) as never))) return this.attachRow(n - 1);
414
+ if (matchesKey(data, Key.alt(String(n) as never))) return this.attachInFolder(n - 1);
403
415
  }
404
416
  if (matchesKey(data, Key.escape)) return this.done({ action: "exit" });
405
- if (matchesKey(data, Key.ctrl("c"))) {
406
- const now = Date.now();
407
- if (this.exitArmedAt !== null && now - this.exitArmedAt <= DELETE_DOUBLE_PRESS_MS) return this.done({ action: "exit" });
408
- this.exitArmedAt = now;
409
- this.notifyInputState("Press ctrl+c again to exit", "accent");
410
- return;
411
- }
417
+ if (matchesKey(data, Key.ctrl("c"))) return this.notifyInputState("Press ctrl+c again to quit Pi", "accent");
412
418
  if (isPrintable(data) || isBracketedPaste(data)) {
413
419
  this.mode = "dispatch";
414
420
  this.handleEditorInput(data);
@@ -496,13 +502,22 @@ export class DashboardComponent implements Component {
496
502
  this.selectIndex(index);
497
503
  }
498
504
 
499
- private attachRow(index: number): void {
500
- if (!this.orderedIds[index]) return;
501
- this.selectIndex(index);
505
+ /** Counts the rows in the selected session's folder. */
506
+ private attachInFolder(index: number): void {
507
+ const folder = this.folderOf.get(this.selectedId ?? "");
508
+ const id = this.orderedIds.filter((other) => this.folderOf.get(other) === folder)[index];
509
+ if (!id) return;
510
+ this.selectIndex(this.orderedIds.indexOf(id));
502
511
  this.attachSelected();
503
512
  }
504
513
 
505
514
  private handleDispatchKey(data: string): void {
515
+ // The menu has its own use for Enter, Tab, Esc and the arrows.
516
+ if (this.editor.isShowingAutocomplete() && !matchesKey(data, Key.ctrl("c"))) {
517
+ this.handleEditorInput(data);
518
+ if (!this.input && this.mode === "dispatch") this.mode = "list";
519
+ return;
520
+ }
506
521
  if (matchesKey(data, Key.ctrl("n"))) return this.openLaunchDialog();
507
522
  if (matchesKey(data, Key.ctrl("g"))) return void this.openExternalEditor();
508
523
  if (matchesKey(data, Key.ctrl("enter"))) return this.startNow(true);
@@ -795,6 +810,11 @@ export class DashboardComponent implements Component {
795
810
 
796
811
  /** In this folder, with this session's model. Ctrl+N is there for anything else. */
797
812
  private startNow(attach = false): void {
813
+ const text = this.input.trim();
814
+ if (runsInPi(text, this.deps.commands ?? [])) {
815
+ this.setInput("");
816
+ return this.done({ action: "command", text });
817
+ }
798
818
  const cwd = this.deps.defaultCwd;
799
819
  const { model, thinking } = this.buildLaunchState(cwd, this.deps.currentModel, this.deps.currentThinkingLevel);
800
820
  this.submitDispatch({ cwd, model: model ? canonicalModelRef(model) : null, thinkingLevel: thinking, attach });
@@ -930,6 +950,17 @@ export class DashboardComponent implements Component {
930
950
  this.handleEditorInput(data);
931
951
  }
932
952
 
953
+ /** Only for a task that starts with `/`: the same editor takes filters, names and replies. */
954
+ private commandMenu(): CombinedAutocompleteProvider {
955
+ const menu = new CombinedAutocompleteProvider(this.deps.commands ?? [], this.deps.defaultCwd);
956
+ const suggest = menu.getSuggestions.bind(menu);
957
+ menu.getSuggestions = async (lines, line, col, options) => {
958
+ if (this.mode !== "dispatch" || line !== 0 || !lines[0]?.startsWith("/")) return null;
959
+ return suggest(lines, line, col, options);
960
+ };
961
+ return menu;
962
+ }
963
+
933
964
  private handleEditorInput(data: string): void {
934
965
  this.editor.handleInput(data);
935
966
  this.input = this.editor.getExpandedText();
@@ -1435,7 +1466,7 @@ export class DashboardComponent implements Component {
1435
1466
  if (this.mode === "select") {
1436
1467
  return [`${this.selectionCount()} selected`, ...dots, "space toggle", "a all", "u clear", "d done", "ctrl+x delete", "esc cancel"];
1437
1468
  }
1438
- return [selected ? (live ? "enter attach live" : "enter resume") : "type to start", "space peek", "/ filter", "ctrl+x stop", "? keys", ...dots];
1469
+ return [selected ? (live ? "enter attach live" : "enter resume") : "type to start", "space peek", "ctrl+f filter", "ctrl+x stop", "? keys", ...dots];
1439
1470
  }
1440
1471
 
1441
1472
  private hintLine(label: string, color: InputNoticeColor, hints: string[]): string {
@@ -1479,8 +1510,10 @@ export class DashboardComponent implements Component {
1479
1510
  const contentWidth = Math.max(1, width - visibleWidth(opts.prompt));
1480
1511
  return [clip(`${opts.prompt}${clip(this.theme.fg("muted", opts.placeholder ?? ""), contentWidth)}`, width)];
1481
1512
  }
1482
- // Its first and last lines are its own rules, and the frame already draws those.
1483
- const rendered = this.editor.render(width).slice(1, -1);
1513
+ // Its first line and the one above any command menu are its own rules, and the frame already draws those.
1514
+ const all = this.editor.render(width);
1515
+ const menu = (this.editor as unknown as { renderedAutocompleteHeight: number }).renderedAutocompleteHeight;
1516
+ const rendered = [...all.slice(1, all.length - menu - 1), ...all.slice(all.length - menu)];
1484
1517
  return opts.editing ? rendered : rendered.map((line) => stripInactiveEditorCursor(line));
1485
1518
  }
1486
1519
 
@@ -1798,9 +1831,10 @@ export class DashboardComponent implements Component {
1798
1831
  ["pgup / pgdn", "Move by a screenful"],
1799
1832
  ["home / end", "First or last row"],
1800
1833
  ["alt+↑ / alt+↓", "Previous or next group"],
1801
- ["alt+1..9", "Attach to that row"],
1834
+ ["alt+1..9", "Attach to that row in this folder"],
1802
1835
  ["space", "Open or close the peek panel"],
1803
- ["/ or ctrl+f", "Filter the list"],
1836
+ ["/", "Pi's commands. They run here; a template or skill starts a session"],
1837
+ ["ctrl+f", "Filter the list"],
1804
1838
  ["ctrl+s", "Group by state or by directory"],
1805
1839
  ["shift+↑ / ↓", "Move the row up or down its group"],
1806
1840
  ["ctrl+g", "Write the task in $VISUAL or $EDITOR"],
@@ -1814,7 +1848,7 @@ export class DashboardComponent implements Component {
1814
1848
  ["ctrl+e", "Evidence and diagnostics"],
1815
1849
  ["ctrl+k", "Delete every inactive session in this state"],
1816
1850
  ["esc", "Close, clear the input, or leave the board"],
1817
- ["ctrl+c", "Clear the input; press twice to leave"],
1851
+ ["ctrl+c", "Clear the input; press twice to quit Pi, here or in a session"],
1818
1852
  ["!", "node-pty diagnostics"],
1819
1853
  ["?", "This help"],
1820
1854
  ["In select", "↑↓ move · space toggle · a all · u clear · d done · ctrl+x delete · esc cancel"],
@@ -6,6 +6,7 @@ import { createConnection, type Socket } from "node:net";
6
6
  import type { Component, KeybindingsManager, RgbColor, TUI, TerminalColorScheme, TuiMouseEvent, TuiMouseEventResult } from "@earendil-works/pi-tui";
7
7
  import { CURSOR_MARKER, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
8
8
  import { resolveAttachDeleteKey } from "../core/attach-delete.mjs";
9
+ import { createDoublePress, QUIT_PRESS_MS } from "../core/double-press.mjs";
9
10
  import { findHttpUrlAtCells, findWordRangeAtCells } from "../core/pty-links.mjs";
10
11
  import { canPublishAttachView, createAttachOutputRenderScheduler, decideAttachPresentation, detectCursorDesync, isPtyCursorHidden, nextAttachRender, projectPtyCursor, shouldScheduleAttachRenderForMessage } from "../core/pty-attach-render.mjs";
11
12
  import { evaluateAttachReconnect, shouldEscapeAttach } from "../core/pty-attach-reconnect.mjs";
@@ -18,7 +19,7 @@ import { backgroundRgbFromTerminalColors, buildSettleSchemePatch, colorSchemeFor
18
19
  import { REPLY_GUARD_WINDOW_MS, createReplyGuard, isReplyGuardEnabled } from "../core/terminal-reply-guard.mjs";
19
20
  import { clampInt, encodeWheelReport, fullscreenMouseEnableSequence, historyFloorAfterHydrate, isFullscreenSelectionEnd, resolveAttachMouseAction, resolveAttachWheelRoute, resolvePasteSelectionSource, resolveWheelLines, sameMousePoint, scrollableHistoryLines, scrollViewportTop, selectionDragScrollLines, selectionReleaseIsDrag, shouldOwnOuterMouseMode, splitAttachInput, wheelScrollLinesUp } from "../core/pty-scroll.mjs";
20
21
 
21
- export type PtyAttachResult = { action: "detached" } | { action: "closed"; exitCode?: number | null } | { action: "delete" };
22
+ export type PtyAttachResult = { action: "detached" } | { action: "closed"; exitCode?: number | null } | { action: "delete" } | { action: "quit" };
22
23
 
23
24
  type ThemeLike = {
24
25
  fg(color: string, text: string): string;
@@ -400,6 +401,7 @@ export class PtyAttachComponent implements Component {
400
401
  private selectionAutoScrollLines = 0;
401
402
  private selectionAutoScrollMouse: { row: number; col: number } | null = null;
402
403
  private pendingClickTimer: ReturnType<typeof setTimeout> | null = null;
404
+ private readonly quitPresses = createDoublePress(Date.now, QUIT_PRESS_MS);
403
405
  private lastClickPoint: MousePoint | null = null;
404
406
  private lastClickAt = 0;
405
407
  /** Authoritative editor emptiness pushed by the child Pi extension via the
@@ -577,6 +579,9 @@ export class PtyAttachComponent implements Component {
577
579
  this.send({ type: "input", data });
578
580
  return;
579
581
  }
582
+ // The first goes to the session as usual. The second never does, so the session lives on.
583
+ if (!matchesKey(data, Key.ctrl("c"))) this.quitPresses.reset();
584
+ else if (this.quitPresses.press()) return this.detach({ action: "quit" });
580
585
  if (matchesKey(data, "ctrl+\\")) {
581
586
  // Unconditional, terminal-independent escape (issue #126): 0x1c is a
582
587
  // single raw ASCII byte every terminal forwards unchanged in raw
@@ -761,7 +766,7 @@ export class PtyAttachComponent implements Component {
761
766
  this.scheduleRender();
762
767
  }
763
768
 
764
- private detach(): void {
769
+ private detach(result: PtyAttachResult = { action: "detached" }): void {
765
770
  // Restore a held PTY before ending the control socket. The runner closes
766
771
  // the socket immediately after receiving detach, so sending detach first
767
772
  // can drop the G3 restore resize (issue #42).
@@ -772,7 +777,7 @@ export class PtyAttachComponent implements Component {
772
777
  this.send({ type: "detach" });
773
778
  }
774
779
  this.close(true);
775
- this.done({ action: "detached" });
780
+ this.done(result);
776
781
  }
777
782
 
778
783
  /** Install the component's public ?2026 observers; false return keeps xterm defaults. */