@dungle-scrubs/tallow 0.8.23 → 0.8.25

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 (228) hide show
  1. package/dist/config.d.ts +1 -1
  2. package/dist/config.js +1 -1
  3. package/dist/workspace-transition-interactive.d.ts +1 -0
  4. package/dist/workspace-transition-interactive.d.ts.map +1 -1
  5. package/dist/workspace-transition-interactive.js +3 -0
  6. package/dist/workspace-transition-interactive.js.map +1 -1
  7. package/extensions/_shared/__tests__/shell-policy.test.ts +16 -13
  8. package/extensions/_shared/shell-policy.ts +8 -3
  9. package/extensions/hooks/__tests__/stale-cwd.test.ts +108 -0
  10. package/extensions/hooks/index.ts +52 -2
  11. package/extensions/plan-mode-tool/__tests__/index.test.ts +0 -1
  12. package/extensions/plan-mode-tool/__tests__/utils.test.ts +0 -57
  13. package/extensions/plan-mode-tool/extension.json +1 -10
  14. package/extensions/plan-mode-tool/index.ts +11 -284
  15. package/extensions/plan-mode-tool/utils.ts +0 -29
  16. package/extensions/skill-commands/__tests__/shared-skills-dirs.test.ts +113 -0
  17. package/extensions/skill-commands/index.ts +62 -5
  18. package/node_modules/.bun/mime-types@3.0.2/node_modules/mime-db/HISTORY.md +541 -0
  19. package/node_modules/.bun/mime-types@3.0.2/node_modules/mime-db/LICENSE +23 -0
  20. package/node_modules/.bun/mime-types@3.0.2/node_modules/mime-db/README.md +109 -0
  21. package/node_modules/.bun/mime-types@3.0.2/node_modules/mime-db/db.json +9342 -0
  22. package/node_modules/.bun/mime-types@3.0.2/node_modules/mime-db/index.js +12 -0
  23. package/node_modules/.bun/mime-types@3.0.2/node_modules/mime-db/package.json +56 -0
  24. package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/keys.d.ts.map +1 -1
  25. package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/keys.js +59 -7
  26. package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -0
  27. package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/tui.d.ts +13 -0
  28. package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/tui.d.ts.map +1 -1
  29. package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/tui.js +22 -2
  30. package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -0
  31. package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/package.json +1 -1
  32. package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/tui-diff-regression.test.ts +52 -0
  33. package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/keys.ts +71 -7
  34. package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/tui.ts +23 -2
  35. package/package.json +11 -9
  36. package/packages/tallow-tui/node_modules/@types/mime-types/LICENSE +21 -0
  37. package/packages/tallow-tui/node_modules/@types/mime-types/README.md +28 -0
  38. package/packages/tallow-tui/node_modules/@types/mime-types/index.d.ts +9 -0
  39. package/packages/tallow-tui/node_modules/@types/mime-types/package.json +25 -0
  40. package/packages/tallow-tui/node_modules/chalk/license +9 -0
  41. package/packages/tallow-tui/node_modules/chalk/package.json +83 -0
  42. package/packages/tallow-tui/node_modules/chalk/readme.md +297 -0
  43. package/packages/tallow-tui/node_modules/chalk/source/index.d.ts +325 -0
  44. package/packages/tallow-tui/node_modules/chalk/source/index.js +225 -0
  45. package/packages/tallow-tui/node_modules/chalk/source/utilities.js +33 -0
  46. package/packages/tallow-tui/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  47. package/packages/tallow-tui/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  48. package/packages/tallow-tui/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  49. package/packages/tallow-tui/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  50. package/packages/tallow-tui/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  51. package/packages/tallow-tui/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  52. package/packages/tallow-tui/node_modules/get-east-asian-width/index.d.ts +60 -0
  53. package/packages/tallow-tui/node_modules/get-east-asian-width/index.js +30 -0
  54. package/packages/tallow-tui/node_modules/get-east-asian-width/license +9 -0
  55. package/packages/tallow-tui/node_modules/get-east-asian-width/lookup.js +403 -0
  56. package/packages/tallow-tui/node_modules/get-east-asian-width/package.json +70 -0
  57. package/packages/tallow-tui/node_modules/get-east-asian-width/readme.md +65 -0
  58. package/packages/tallow-tui/node_modules/marked/LICENSE.md +44 -0
  59. package/packages/tallow-tui/node_modules/marked/README.md +106 -0
  60. package/packages/tallow-tui/node_modules/marked/bin/main.js +282 -0
  61. package/packages/tallow-tui/node_modules/marked/bin/marked.js +15 -0
  62. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs +2211 -0
  63. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs.map +7 -0
  64. package/packages/tallow-tui/node_modules/marked/lib/marked.d.cts +728 -0
  65. package/packages/tallow-tui/node_modules/marked/lib/marked.d.ts +728 -0
  66. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js +2189 -0
  67. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js.map +7 -0
  68. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js +2213 -0
  69. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js.map +7 -0
  70. package/packages/tallow-tui/node_modules/marked/man/marked.1 +111 -0
  71. package/packages/tallow-tui/node_modules/marked/man/marked.1.md +92 -0
  72. package/packages/tallow-tui/node_modules/marked/marked.min.js +69 -0
  73. package/packages/tallow-tui/node_modules/marked/package.json +111 -0
  74. package/packages/tallow-tui/node_modules/mime-types/HISTORY.md +428 -0
  75. package/packages/tallow-tui/node_modules/mime-types/LICENSE +23 -0
  76. package/packages/tallow-tui/node_modules/mime-types/README.md +126 -0
  77. package/packages/tallow-tui/node_modules/mime-types/index.js +211 -0
  78. package/packages/tallow-tui/node_modules/mime-types/mimeScore.js +57 -0
  79. package/packages/tallow-tui/node_modules/mime-types/package.json +49 -0
  80. package/extensions/__integration__/plan-rejection-feedback.test.ts +0 -272
  81. package/extensions/plan-mode-tool/__tests__/agent-end-execution.test.ts +0 -373
  82. package/packages/tallow-tui/dist/keys.js.map +0 -1
  83. package/packages/tallow-tui/dist/tui.js.map +0 -1
  84. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/README.md +0 -0
  85. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/autocomplete.d.ts +0 -0
  86. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/autocomplete.d.ts.map +0 -0
  87. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/autocomplete.js +0 -0
  88. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/autocomplete.js.map +0 -0
  89. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/border-styles.d.ts +0 -0
  90. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/border-styles.d.ts.map +0 -0
  91. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/border-styles.js +0 -0
  92. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/border-styles.js.map +0 -0
  93. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/bordered-box.d.ts +0 -0
  94. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/bordered-box.d.ts.map +0 -0
  95. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/bordered-box.js +0 -0
  96. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/bordered-box.js.map +0 -0
  97. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/box.d.ts +0 -0
  98. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/box.d.ts.map +0 -0
  99. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/box.js +0 -0
  100. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/box.js.map +0 -0
  101. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/cancellable-loader.d.ts +0 -0
  102. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/cancellable-loader.d.ts.map +0 -0
  103. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/cancellable-loader.js +0 -0
  104. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/cancellable-loader.js.map +0 -0
  105. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/editor.d.ts +0 -0
  106. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/editor.d.ts.map +0 -0
  107. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/editor.js +0 -0
  108. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/editor.js.map +0 -0
  109. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/image.d.ts +0 -0
  110. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/image.d.ts.map +0 -0
  111. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/image.js +0 -0
  112. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/image.js.map +0 -0
  113. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/input.d.ts +0 -0
  114. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/input.d.ts.map +0 -0
  115. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/input.js +0 -0
  116. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/input.js.map +0 -0
  117. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/loader.d.ts +0 -0
  118. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/loader.d.ts.map +0 -0
  119. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/loader.js +0 -0
  120. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/loader.js.map +0 -0
  121. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/markdown.d.ts +0 -0
  122. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/markdown.d.ts.map +0 -0
  123. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/markdown.js +0 -0
  124. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/markdown.js.map +0 -0
  125. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/select-list.d.ts +0 -0
  126. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/select-list.d.ts.map +0 -0
  127. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/select-list.js +0 -0
  128. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/select-list.js.map +0 -0
  129. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/settings-list.d.ts +0 -0
  130. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/settings-list.d.ts.map +0 -0
  131. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/settings-list.js +0 -0
  132. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/settings-list.js.map +0 -0
  133. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/spacer.d.ts +0 -0
  134. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/spacer.d.ts.map +0 -0
  135. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/spacer.js +0 -0
  136. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/spacer.js.map +0 -0
  137. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/text.d.ts +0 -0
  138. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/text.d.ts.map +0 -0
  139. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/text.js +0 -0
  140. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/text.js.map +0 -0
  141. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/truncated-text.d.ts +0 -0
  142. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/truncated-text.d.ts.map +0 -0
  143. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/truncated-text.js +0 -0
  144. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/components/truncated-text.js.map +0 -0
  145. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/editor-component.d.ts +0 -0
  146. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/editor-component.d.ts.map +0 -0
  147. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/editor-component.js +0 -0
  148. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/editor-component.js.map +0 -0
  149. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/fuzzy.d.ts +0 -0
  150. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/fuzzy.d.ts.map +0 -0
  151. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/fuzzy.js +0 -0
  152. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/fuzzy.js.map +0 -0
  153. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/index.d.ts +0 -0
  154. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/index.d.ts.map +0 -0
  155. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/index.js +0 -0
  156. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/index.js.map +0 -0
  157. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/keybindings.d.ts +0 -0
  158. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/keybindings.d.ts.map +0 -0
  159. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/keybindings.js +0 -0
  160. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/keybindings.js.map +0 -0
  161. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/keys.d.ts +0 -0
  162. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/kill-ring.d.ts +0 -0
  163. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/kill-ring.d.ts.map +0 -0
  164. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/kill-ring.js +0 -0
  165. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/kill-ring.js.map +0 -0
  166. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/stdin-buffer.d.ts +0 -0
  167. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/stdin-buffer.d.ts.map +0 -0
  168. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/stdin-buffer.js +0 -0
  169. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/stdin-buffer.js.map +0 -0
  170. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/terminal-image.d.ts +0 -0
  171. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/terminal-image.d.ts.map +0 -0
  172. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/terminal-image.js +0 -0
  173. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/terminal-image.js.map +0 -0
  174. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/terminal.d.ts +0 -0
  175. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/terminal.d.ts.map +0 -0
  176. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/terminal.js +0 -0
  177. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/terminal.js.map +0 -0
  178. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/test-utils/capability-env.d.ts +0 -0
  179. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/test-utils/capability-env.d.ts.map +0 -0
  180. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/test-utils/capability-env.js +0 -0
  181. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/test-utils/capability-env.js.map +0 -0
  182. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/undo-stack.d.ts +0 -0
  183. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/undo-stack.d.ts.map +0 -0
  184. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/undo-stack.js +0 -0
  185. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/undo-stack.js.map +0 -0
  186. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/utils.d.ts +0 -0
  187. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/utils.d.ts.map +0 -0
  188. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/utils.js +0 -0
  189. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/dist/utils.js.map +0 -0
  190. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/__snapshots__/render.test.ts.snap +0 -0
  191. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/editor-border.test.ts +0 -0
  192. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/editor-change-listener.test.ts +0 -0
  193. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/editor-ghost-text.test.ts +0 -0
  194. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/fuzzy.test.ts +0 -0
  195. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/image-component.test.ts +0 -0
  196. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/keys.test.ts +0 -0
  197. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/render.test.ts +0 -0
  198. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/stdin-buffer.test.ts +0 -0
  199. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/terminal-image.test.ts +0 -0
  200. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/tui-render-scheduling.test.ts +0 -0
  201. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/__tests__/utils.test.ts +0 -0
  202. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/autocomplete.ts +0 -0
  203. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/border-styles.ts +0 -0
  204. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/bordered-box.ts +0 -0
  205. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/box.ts +0 -0
  206. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/cancellable-loader.ts +0 -0
  207. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/editor.ts +0 -0
  208. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/image.ts +0 -0
  209. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/input.ts +0 -0
  210. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/loader.ts +0 -0
  211. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/markdown.ts +0 -0
  212. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/select-list.ts +0 -0
  213. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/settings-list.ts +0 -0
  214. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/spacer.ts +0 -0
  215. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/text.ts +0 -0
  216. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/components/truncated-text.ts +0 -0
  217. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/editor-component.ts +0 -0
  218. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/fuzzy.ts +0 -0
  219. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/index.ts +0 -0
  220. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/keybindings.ts +0 -0
  221. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/kill-ring.ts +0 -0
  222. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/stdin-buffer.ts +0 -0
  223. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/terminal-image.ts +0 -0
  224. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/terminal.ts +0 -0
  225. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/test-utils/capability-env.ts +0 -0
  226. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/undo-stack.ts +0 -0
  227. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/src/utils.ts +0 -0
  228. /package/{packages/tallow-tui → node_modules/@mariozechner/pi-tui}/tsconfig.build.json +0 -0
@@ -9,8 +9,7 @@
9
9
  * - Strict fail-closed tool allowlist while plan mode is active
10
10
  * - Bash restricted to allowlisted read-only commands
11
11
  * - Extracts numbered plan steps from "Plan:" sections
12
- * - [DONE:n] markers to complete steps during execution
13
- * - Progress tracking widget during execution
12
+ * - Delegates execution tracking to the tasks extension
14
13
  */
15
14
 
16
15
  import type { AgentMessage } from "@mariozechner/pi-agent-core";
@@ -37,7 +36,6 @@ import {
37
36
  extractTodoItems,
38
37
  isPlanModeToolAllowed,
39
38
  isSafeCommand,
40
- markCompletedSteps,
41
39
  PLAN_MODE_ALLOWED_TOOLS,
42
40
  stripPlanIntent,
43
41
  type TodoItem,
@@ -103,9 +101,7 @@ class PlanModeEditor extends CustomEditor {
103
101
  */
104
102
  export default function planModeExtension(pi: ExtensionAPI): void {
105
103
  let planModeEnabled = false;
106
- let executionMode = false;
107
104
  let todoItems: TodoItem[] = [];
108
- let currentStepIndex: number | null = null;
109
105
  let normalModeTools: string[] = [];
110
106
 
111
107
  /**
@@ -154,45 +150,6 @@ export default function planModeExtension(pi: ExtensionAPI): void {
154
150
  return normalModeTools;
155
151
  }
156
152
 
157
- /**
158
- * Computes the current step index from todo completion state.
159
- *
160
- * @param items - Ordered todo items
161
- * @returns Zero-based index of first incomplete step, or null when complete
162
- */
163
- function computeCurrentStepIndex(items: readonly TodoItem[]): number | null {
164
- const nextIndex = items.findIndex((item) => !item.completed);
165
- return nextIndex >= 0 ? nextIndex : null;
166
- }
167
-
168
- /**
169
- * Synchronizes currentStepIndex with todo completion state.
170
- *
171
- * @returns Updated current step index
172
- */
173
- function syncCurrentStepIndex(): number | null {
174
- currentStepIndex = computeCurrentStepIndex(todoItems);
175
- return currentStepIndex;
176
- }
177
-
178
- /**
179
- * Gets the current step item for execution guidance.
180
- *
181
- * @returns Current step todo item, or null if all steps are complete
182
- */
183
- function getCurrentStep(): TodoItem | null {
184
- if (todoItems.length === 0) return null;
185
- if (
186
- currentStepIndex === null ||
187
- currentStepIndex < 0 ||
188
- currentStepIndex >= todoItems.length ||
189
- todoItems[currentStepIndex]?.completed
190
- ) {
191
- syncCurrentStepIndex();
192
- }
193
- return currentStepIndex === null ? null : (todoItems[currentStepIndex] ?? null);
194
- }
195
-
196
153
  pi.registerFlag("plan", {
197
154
  description: "Start in plan mode (read-only exploration)",
198
155
  type: "boolean",
@@ -206,7 +163,7 @@ export default function planModeExtension(pi: ExtensionAPI): void {
206
163
  * @param ctx - The extension context
207
164
  */
208
165
  function updateStatus(ctx: ExtensionContext): void {
209
- // Footer status — plan mode only; execution mode defers to tasks extension
166
+ // Footer status — plan mode only
210
167
  if (planModeEnabled) {
211
168
  ctx.ui.setStatus(
212
169
  "plan-mode",
@@ -226,7 +183,6 @@ export default function planModeExtension(pi: ExtensionAPI): void {
226
183
  }
227
184
 
228
185
  // Full-width banner above editor — plan mode only.
229
- // Execution mode does not show a banner; the tasks extension owns progress tracking.
230
186
  if (planModeEnabled) {
231
187
  ctx.ui.setWidget("plan-banner", (_tui, theme) => {
232
188
  const label = " PLAN MODE — READ ONLY ";
@@ -241,7 +197,6 @@ export default function planModeExtension(pi: ExtensionAPI): void {
241
197
  ctx.ui.setWidget("plan-banner", undefined);
242
198
  }
243
199
 
244
- // Todo list widget — plan mode only (execution delegates to tasks extension)
245
200
  ctx.ui.setWidget("plan-todos", undefined);
246
201
  }
247
202
 
@@ -251,9 +206,7 @@ export default function planModeExtension(pi: ExtensionAPI): void {
251
206
  */
252
207
  function togglePlanMode(ctx: ExtensionContext): void {
253
208
  planModeEnabled = !planModeEnabled;
254
- executionMode = false;
255
209
  todoItems = [];
256
- currentStepIndex = null;
257
210
 
258
211
  if (planModeEnabled) {
259
212
  captureNormalModeTools();
@@ -273,10 +226,8 @@ export default function planModeExtension(pi: ExtensionAPI): void {
273
226
  function persistState(): void {
274
227
  pi.appendEntry("plan-mode", {
275
228
  enabled: planModeEnabled,
276
- executing: executionMode,
277
229
  normalTools: normalModeTools,
278
230
  todos: todoItems,
279
- currentStepIndex,
280
231
  });
281
232
  }
282
233
 
@@ -341,7 +292,7 @@ Use action "enable" to enter plan mode, "disable" to exit, or "status" to check
341
292
  const { action } = params;
342
293
 
343
294
  if (action === "status") {
344
- const mode = executionMode ? "executing" : planModeEnabled ? "planning" : "normal";
295
+ const mode = planModeEnabled ? "planning" : "normal";
345
296
  const tools = planModeEnabled ? getPlanModeTools() : pi.getActiveTools();
346
297
  return {
347
298
  content: [
@@ -373,9 +324,7 @@ Use action "enable" to enter plan mode, "disable" to exit, or "status" to check
373
324
  }
374
325
 
375
326
  planModeEnabled = shouldEnable;
376
- executionMode = false;
377
327
  todoItems = [];
378
- currentStepIndex = null;
379
328
 
380
329
  let activeTools: string[];
381
330
  if (planModeEnabled) {
@@ -431,8 +380,8 @@ Use action "enable" to enter plan mode, "disable" to exit, or "status" to check
431
380
 
432
381
  // Auto-enable plan mode when user expresses planning intent in natural language
433
382
  pi.on("input", async (event, ctx) => {
434
- // No-op if already in plan mode or execution mode
435
- if (planModeEnabled || executionMode) {
383
+ // No-op if already in plan mode
384
+ if (planModeEnabled) {
436
385
  return { action: "continue" as const };
437
386
  }
438
387
 
@@ -513,188 +462,10 @@ Do NOT attempt to make changes - just describe what you would do.`,
513
462
  },
514
463
  };
515
464
  }
516
-
517
- if (executionMode && todoItems.length > 0) {
518
- syncCurrentStepIndex();
519
- const currentStep = getCurrentStep();
520
- const remaining = todoItems.filter((t) => !t.completed);
521
- const todoList = remaining.map((t) => `${t.step}. ${t.text}`).join("\n");
522
- const currentStepText = currentStep
523
- ? `${currentStep.step}. ${currentStep.text}`
524
- : "(all steps completed)";
525
- return {
526
- message: {
527
- customType: "plan-execution-context",
528
- content: `[EXECUTING PLAN - Full tool access enabled]
529
-
530
- Current step:
531
- ${currentStepText}
532
-
533
- Remaining steps:
534
- ${todoList}
535
-
536
- Execute each step in order.
537
- After completing a step, include a [DONE:n] tag in your response.
538
- If you receive [PLAN GUIDANCE — Step n: ...], treat it as user steering for that step and apply it before continuing.`,
539
- display: false,
540
- },
541
- };
542
- }
543
- });
544
-
545
- // Track progress after each turn
546
- pi.on("turn_end", async (event, ctx) => {
547
- if (!executionMode || todoItems.length === 0) return;
548
- if (!isAssistantMessage(event.message)) return;
549
-
550
- const text = getTextContent(event.message);
551
- const completedCount = markCompletedSteps(text, todoItems);
552
- if (completedCount > 0) {
553
- syncCurrentStepIndex();
554
- updateStatus(ctx);
555
- }
556
- persistState();
557
- });
558
-
559
- // Offer user steering when a tool is blocked during tracked execution.
560
- pi.on("tool_result", async (event, ctx) => {
561
- if (!executionMode || !event.isError || !ctx.hasUI || todoItems.length === 0) return;
562
-
563
- const currentStep = getCurrentStep();
564
- if (!currentStep) return;
565
-
566
- const errorText = Array.isArray(event.content)
567
- ? event.content
568
- .filter((block): block is TextContent => block.type === "text")
569
- .map((block) => block.text)
570
- .join("\n")
571
- .trim()
572
- : "";
573
-
574
- const shouldProvideGuidance = await ctx.ui.confirm(
575
- "Plan step blocked",
576
- [
577
- `Tool "${event.toolName}" was blocked while executing step ${currentStep.step}.`,
578
- "",
579
- currentStep.text,
580
- "",
581
- `Error: ${errorText || "No error details provided."}`,
582
- "",
583
- "Provide guidance before execution continues?",
584
- ].join("\n")
585
- );
586
- if (!shouldProvideGuidance) return;
587
-
588
- const guidance = await ctx.ui.editor(`Guidance for step ${currentStep.step}:`, "");
589
- const trimmedGuidance = guidance?.trim();
590
- if (!trimmedGuidance) return;
591
-
592
- pi.sendUserMessage(
593
- [
594
- `[PLAN GUIDANCE — Step ${currentStep.step}: ${currentStep.text}]`,
595
- `Tool "${event.toolName}" was blocked.`,
596
- "",
597
- "User guidance:",
598
- trimmedGuidance,
599
- ].join("\n"),
600
- { deliverAs: "steer" }
601
- );
602
465
  });
603
466
 
604
467
  // Handle plan completion and plan mode UI
605
468
  pi.on("agent_end", async (event, ctx) => {
606
- // Check if execution is complete
607
- if (executionMode && todoItems.length > 0) {
608
- if (todoItems.every((t) => t.completed)) {
609
- const completedList = todoItems.map((t) => `~~${t.text}~~`).join("\n");
610
- pi.sendMessage(
611
- {
612
- customType: "plan-complete",
613
- content: `**Plan Complete!** ${getIcon("success")}\n\n${completedList}`,
614
- display: true,
615
- },
616
- { triggerTurn: false }
617
- );
618
- executionMode = false;
619
- todoItems = [];
620
- currentStepIndex = null;
621
- restoreNormalModeTools();
622
- updateStatus(ctx);
623
- persistState();
624
- } else if (ctx.hasUI) {
625
- // Agent finished its turn but not all steps are marked complete.
626
- // Show progress summary and prompt for next action.
627
- const completed = todoItems.filter((t) => t.completed);
628
-
629
- updateStatus(ctx);
630
- ctx.ui.setWorkingMessage(Loader.HIDE);
631
-
632
- const choice = await ctx.ui.select(
633
- `Plan execution paused (${completed.length}/${todoItems.length} done)`,
634
- ["Continue execution", "Provide guidance", "Mark plan as done", "Abort plan"]
635
- );
636
-
637
- if (choice === "Continue execution") {
638
- const nextStep = getCurrentStep();
639
- const continueMessage =
640
- nextStep !== null
641
- ? `Continue executing the plan. Next: step ${nextStep.step}: ${nextStep.text}`
642
- : "Continue executing the remaining plan steps.";
643
- pi.sendMessage(
644
- { customType: "plan-mode-execute", content: continueMessage, display: true },
645
- { triggerTurn: true }
646
- );
647
- } else if (choice === "Provide guidance") {
648
- const nextStep = getCurrentStep();
649
- const stepLabel = nextStep ? `step ${nextStep.step} (${nextStep.text})` : "next step";
650
- const guidance = await ctx.ui.editor(`Guidance for ${stepLabel}:`, "");
651
- const trimmedGuidance = guidance?.trim();
652
- if (trimmedGuidance && nextStep) {
653
- pi.sendUserMessage(
654
- [
655
- `[PLAN GUIDANCE — Step ${nextStep.step}: ${nextStep.text}]`,
656
- "",
657
- "User guidance:",
658
- trimmedGuidance,
659
- ].join("\n")
660
- );
661
- } else if (trimmedGuidance) {
662
- pi.sendUserMessage(trimmedGuidance);
663
- } else {
664
- ctx.ui.notify("No guidance provided. Plan unchanged.", "info");
665
- }
666
- } else if (choice === "Mark plan as done") {
667
- const completedList = todoItems
668
- .map((t) => (t.completed ? `~~${t.text}~~` : t.text))
669
- .join("\n");
670
- pi.sendMessage(
671
- {
672
- customType: "plan-complete",
673
- content: `**Plan Complete!** ${getIcon("success")}\n\n${completedList}`,
674
- display: true,
675
- },
676
- { triggerTurn: false }
677
- );
678
- executionMode = false;
679
- todoItems = [];
680
- currentStepIndex = null;
681
- restoreNormalModeTools();
682
- updateStatus(ctx);
683
- persistState();
684
- } else {
685
- // Abort plan (or dismissed/escaped the select)
686
- executionMode = false;
687
- todoItems = [];
688
- currentStepIndex = null;
689
- restoreNormalModeTools();
690
- updateStatus(ctx);
691
- persistState();
692
- ctx.ui.notify("Plan aborted.", "info");
693
- }
694
- }
695
- return;
696
- }
697
-
698
469
  if (!(planModeEnabled && ctx.hasUI)) return;
699
470
 
700
471
  // Extract todos from last assistant message
@@ -727,7 +498,7 @@ If you receive [PLAN GUIDANCE — Step n: ...], treat it as user steering for th
727
498
  ctx.ui.setWorkingMessage(Loader.HIDE);
728
499
 
729
500
  const choice = await ctx.ui.select("Plan mode - what next?", [
730
- todoItems.length > 0 ? "Execute the plan (track progress)" : "Execute the plan",
501
+ "Execute the plan",
731
502
  "Stay in plan mode",
732
503
  "Refine the plan",
733
504
  ]);
@@ -736,21 +507,17 @@ If you receive [PLAN GUIDANCE — Step n: ...], treat it as user steering for th
736
507
  ctx.ui.setWidget("plan-steps", undefined);
737
508
 
738
509
  if (choice?.startsWith("Execute")) {
510
+ const steps = [...todoItems];
739
511
  planModeEnabled = false;
740
- executionMode = todoItems.length > 0;
741
- if (executionMode) {
742
- syncCurrentStepIndex();
743
- } else {
744
- currentStepIndex = null;
745
- }
512
+ todoItems = [];
746
513
  restoreNormalModeTools();
747
514
  updateStatus(ctx);
748
515
  persistState();
749
516
 
750
- const currentStep = getCurrentStep();
517
+ const stepList = steps.map((t) => `${t.step}. ${t.text}`).join("\n");
751
518
  const execMessage =
752
- currentStep !== null
753
- ? `Execute the plan. Start with step ${currentStep.step}: ${currentStep.text}`
519
+ steps.length > 0
520
+ ? `Execute this plan. Create tasks to track each step, then work through them:\n\n${stepList}`
754
521
  : "Execute the plan you just created.";
755
522
  pi.sendMessage(
756
523
  { customType: "plan-mode-execute", content: execMessage, display: true },
@@ -786,56 +553,16 @@ If you receive [PLAN GUIDANCE — Step n: ...], treat it as user steering for th
786
553
  | {
787
554
  data?: {
788
555
  enabled?: boolean;
789
- executing?: boolean;
790
556
  normalTools?: string[];
791
557
  todos?: TodoItem[];
792
- currentStepIndex?: number | null;
793
558
  };
794
559
  }
795
560
  | undefined;
796
561
 
797
562
  if (planModeEntry?.data) {
798
563
  planModeEnabled = planModeEntry.data.enabled ?? planModeEnabled;
799
- executionMode = planModeEntry.data.executing ?? executionMode;
800
564
  normalModeTools = planModeEntry.data.normalTools ?? normalModeTools;
801
565
  todoItems = planModeEntry.data.todos ?? todoItems;
802
- currentStepIndex = planModeEntry.data.currentStepIndex ?? currentStepIndex;
803
- }
804
-
805
- // On resume: re-scan messages to rebuild completion state
806
- // Only scan messages AFTER the last "plan-mode-execute" to avoid picking up [DONE:n] from previous plans
807
- const isResume = planModeEntry !== undefined;
808
- if (isResume && executionMode && todoItems.length > 0) {
809
- // Find the index of the last plan-mode-execute entry (marks when current execution started)
810
- let executeIndex = -1;
811
- for (let i = entries.length - 1; i >= 0; i--) {
812
- const entry = entries[i] as { type: string; customType?: string };
813
- if (entry.customType === "plan-mode-execute") {
814
- executeIndex = i;
815
- break;
816
- }
817
- }
818
-
819
- // Only scan messages after the execute marker
820
- const messages: AssistantMessage[] = [];
821
- for (let i = executeIndex + 1; i < entries.length; i++) {
822
- const entry = entries[i];
823
- if (
824
- entry.type === "message" &&
825
- "message" in entry &&
826
- isAssistantMessage(entry.message as AgentMessage)
827
- ) {
828
- messages.push(entry.message as AssistantMessage);
829
- }
830
- }
831
- const allText = messages.map(getTextContent).join("\n");
832
- markCompletedSteps(allText, todoItems);
833
- }
834
-
835
- if (executionMode && todoItems.length > 0) {
836
- syncCurrentStepIndex();
837
- } else {
838
- currentStepIndex = null;
839
566
  }
840
567
 
841
568
  if (normalModeTools.length === 0) {
@@ -255,32 +255,3 @@ export function extractTodoItems(message: string): TodoItem[] {
255
255
  }
256
256
  return items;
257
257
  }
258
-
259
- /**
260
- * Extracts [DONE:n] step markers from a message.
261
- * @param message - The message text to search
262
- * @returns Array of step numbers that were marked as done
263
- */
264
- export function extractDoneSteps(message: string): number[] {
265
- const steps: number[] = [];
266
- for (const match of message.matchAll(/\[DONE:(\d+)\]/gi)) {
267
- const step = Number(match[1]);
268
- if (Number.isFinite(step)) steps.push(step);
269
- }
270
- return steps;
271
- }
272
-
273
- /**
274
- * Marks todo items as completed based on [DONE:n] markers in text.
275
- * @param text - The text containing [DONE:n] markers
276
- * @param items - The todo items to update
277
- * @returns The number of items that were marked as completed
278
- */
279
- export function markCompletedSteps(text: string, items: TodoItem[]): number {
280
- const doneSteps = extractDoneSteps(text);
281
- for (const step of doneSteps) {
282
- const item = items.find((t) => t.step === step);
283
- if (item) item.completed = true;
284
- }
285
- return doneSteps.length;
286
- }
@@ -0,0 +1,113 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { resolveSharedSkillsDirsFromSettings } from "../index.js";
6
+
7
+ /**
8
+ * Create a temporary directory for test fixtures.
9
+ *
10
+ * @returns Path to the newly created temp directory
11
+ */
12
+ function createTmpDir(): string {
13
+ return mkdtempSync(join(tmpdir(), "skill-cmds-shared-"));
14
+ }
15
+
16
+ /**
17
+ * Write a settings.json file with the given content.
18
+ *
19
+ * @param dir - Directory to write settings.json into
20
+ * @param settings - Settings object to serialize
21
+ * @returns Path to the written settings.json
22
+ */
23
+ function writeSettings(dir: string, settings: Record<string, unknown>): string {
24
+ const path = join(dir, "settings.json");
25
+ writeFileSync(path, JSON.stringify(settings, null, 2));
26
+ return path;
27
+ }
28
+
29
+ describe("resolveSharedSkillsDirsFromSettings", () => {
30
+ let tmp: string;
31
+
32
+ beforeEach(() => {
33
+ tmp = createTmpDir();
34
+ });
35
+
36
+ afterEach(() => {
37
+ rmSync(tmp, { recursive: true, force: true });
38
+ });
39
+
40
+ it("returns empty array when settings file does not exist", () => {
41
+ expect(resolveSharedSkillsDirsFromSettings(join(tmp, "nope.json"))).toEqual([]);
42
+ });
43
+
44
+ it("returns empty array when settings file is invalid JSON", () => {
45
+ const path = join(tmp, "settings.json");
46
+ writeFileSync(path, "not json {{");
47
+ expect(resolveSharedSkillsDirsFromSettings(path)).toEqual([]);
48
+ });
49
+
50
+ it("returns empty array when sharedSkillsDirs is missing", () => {
51
+ const path = writeSettings(tmp, { theme: "nord" });
52
+ expect(resolveSharedSkillsDirsFromSettings(path)).toEqual([]);
53
+ });
54
+
55
+ it("returns empty array when sharedSkillsDirs is not an array", () => {
56
+ const path = writeSettings(tmp, { sharedSkillsDirs: "/some/path" });
57
+ expect(resolveSharedSkillsDirsFromSettings(path)).toEqual([]);
58
+ });
59
+
60
+ it("resolves absolute paths that exist as directories", () => {
61
+ const skillsDir = join(tmp, "my-skills");
62
+ mkdirSync(skillsDir);
63
+ const path = writeSettings(tmp, { sharedSkillsDirs: [skillsDir] });
64
+
65
+ expect(resolveSharedSkillsDirsFromSettings(path)).toEqual([skillsDir]);
66
+ });
67
+
68
+ it("skips non-existent directories silently", () => {
69
+ const path = writeSettings(tmp, {
70
+ sharedSkillsDirs: [join(tmp, "does-not-exist")],
71
+ });
72
+ expect(resolveSharedSkillsDirsFromSettings(path)).toEqual([]);
73
+ });
74
+
75
+ it("skips paths that are files, not directories", () => {
76
+ const filePath = join(tmp, "not-a-dir");
77
+ writeFileSync(filePath, "hello");
78
+ const path = writeSettings(tmp, { sharedSkillsDirs: [filePath] });
79
+
80
+ expect(resolveSharedSkillsDirsFromSettings(path)).toEqual([]);
81
+ });
82
+
83
+ it("rejects relative paths", () => {
84
+ const path = writeSettings(tmp, {
85
+ sharedSkillsDirs: ["relative/path", "./also-relative", "no-slash"],
86
+ });
87
+ expect(resolveSharedSkillsDirsFromSettings(path)).toEqual([]);
88
+ });
89
+
90
+ it("rejects non-string and empty entries", () => {
91
+ const path = writeSettings(tmp, {
92
+ sharedSkillsDirs: [42, null, "", " ", true],
93
+ });
94
+ expect(resolveSharedSkillsDirsFromSettings(path)).toEqual([]);
95
+ });
96
+
97
+ it("handles mixed valid and invalid entries", () => {
98
+ const validDir = join(tmp, "valid");
99
+ mkdirSync(validDir);
100
+ const path = writeSettings(tmp, {
101
+ sharedSkillsDirs: [validDir, "relative", join(tmp, "nonexistent"), 42],
102
+ });
103
+
104
+ expect(resolveSharedSkillsDirsFromSettings(path)).toEqual([validDir]);
105
+ });
106
+
107
+ it("expands tilde paths (skips when dir does not exist)", () => {
108
+ const path = writeSettings(tmp, {
109
+ sharedSkillsDirs: ["~/.nonexistent-skills-test-dir-99999"],
110
+ });
111
+ expect(resolveSharedSkillsDirsFromSettings(path)).toEqual([]);
112
+ });
113
+ });
@@ -125,24 +125,81 @@ function disableBuiltinSkillCommands(): void {
125
125
  }
126
126
  }
127
127
 
128
+ /**
129
+ * Resolve shared skill directories from global settings.
130
+ *
131
+ * Reads `sharedSkillsDirs`, tilde-expands each entry, and validates
132
+ * that it exists and is a directory. Mirrors the logic in `sdk.ts`
133
+ * so slash-command registration sees the same skills as the system prompt.
134
+ *
135
+ * @param settingsPath - Path to global settings.json
136
+ * @returns Array of validated, resolved directory paths
137
+ */
138
+ export function resolveSharedSkillsDirsFromSettings(settingsPath: string): string[] {
139
+ if (!fs.existsSync(settingsPath)) return [];
140
+ let settings: Record<string, unknown>;
141
+ try {
142
+ settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
143
+ } catch {
144
+ return [];
145
+ }
146
+ const raw = settings.sharedSkillsDirs;
147
+ if (!Array.isArray(raw)) return [];
148
+
149
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
150
+ const resolved: string[] = [];
151
+
152
+ for (const entry of raw) {
153
+ if (typeof entry !== "string" || !entry.trim()) continue;
154
+ const trimmed = entry.trim();
155
+ let expanded: string;
156
+ if (trimmed === "~") {
157
+ expanded = home;
158
+ } else if (trimmed.startsWith("~/")) {
159
+ expanded = join(home, trimmed.slice(2));
160
+ } else if (trimmed.startsWith("/")) {
161
+ expanded = trimmed;
162
+ } else {
163
+ continue;
164
+ }
165
+ try {
166
+ const stats = fs.statSync(expanded);
167
+ if (stats.isDirectory()) resolved.push(expanded);
168
+ } catch {
169
+ // statSync failed — skip this entry
170
+ }
171
+ }
172
+ return resolved;
173
+ }
174
+
128
175
  export default function (pi: ExtensionAPI) {
129
176
  disableBuiltinSkillCommands();
130
177
 
178
+ const agentDir =
179
+ process.env.PI_CODING_AGENT_DIR ??
180
+ join(process.env.HOME ?? process.env.USERPROFILE ?? "~", ".tallow");
181
+ const settingsPath = join(agentDir, "settings.json");
182
+
183
+ // Shared skill directories from global settings (e.g. ~/dev/skills)
184
+ const sharedSkillsDirs = resolveSharedSkillsDirsFromSettings(settingsPath);
185
+
131
186
  // Include .claude/skills/ directories for Claude Code compatibility
132
- const claudeSkillPaths: string[] = [];
187
+ const extraSkillPaths: string[] = [...sharedSkillsDirs];
133
188
  const userClaudeSkills = join(
134
189
  process.env.HOME ?? process.env.USERPROFILE ?? "~",
135
190
  ".claude",
136
191
  "skills"
137
192
  );
138
193
  const projectClaudeSkills = join(process.cwd(), ".claude", "skills");
139
- if (fs.existsSync(userClaudeSkills)) claudeSkillPaths.push(userClaudeSkills);
194
+ if (fs.existsSync(userClaudeSkills)) extraSkillPaths.push(userClaudeSkills);
140
195
  if (isProjectTrusted(process.cwd()) && fs.existsSync(projectClaudeSkills)) {
141
- claudeSkillPaths.push(projectClaudeSkills);
196
+ extraSkillPaths.push(projectClaudeSkills);
142
197
  }
143
198
 
144
- // Load skills synchronously during extension init for autocomplete to work
145
- const { skills } = loadSkills({ skillPaths: claudeSkillPaths });
199
+ // Load skills synchronously during extension init for autocomplete to work.
200
+ // includeDefaults: true picks up ~/.tallow/skills/ and ./skills/ (project).
201
+ // extraSkillPaths adds shared dirs + Claude bridge paths.
202
+ const { skills } = loadSkills({ agentDir, skillPaths: extraSkillPaths });
146
203
 
147
204
  for (const skill of skills) {
148
205
  // Validate name before registration — invalid names produce broken commands