@herbertgao/pi-extensions 2026.9.20 → 2026.10.1

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 (357) hide show
  1. package/README.md +18 -13
  2. package/THIRD_PARTY_NOTICES.md +31 -5
  3. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +1 -1
  4. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +1 -1
  5. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +1 -1
  6. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +1 -1
  7. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +1 -1
  8. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/index.js +4 -0
  9. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +43 -3
  10. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +142 -59
  11. package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
  12. package/node_modules/@dietrichgebert/ponytail/README.es.md +87 -204
  13. package/node_modules/@dietrichgebert/ponytail/README.ja.md +212 -0
  14. package/node_modules/@dietrichgebert/ponytail/README.ko.md +92 -226
  15. package/node_modules/@dietrichgebert/ponytail/README.md +58 -244
  16. package/node_modules/@dietrichgebert/ponytail/README.zh-CN.md +212 -0
  17. package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +3 -3
  18. package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +4 -4
  19. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +63 -16
  20. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +8 -7
  21. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +46 -23
  22. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +39 -7
  23. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +20 -3
  24. package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +3 -3
  25. package/node_modules/@dietrichgebert/ponytail/package.json +2 -2
  26. package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +47 -17
  27. package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +21 -7
  28. package/node_modules/@dietrichgebert/ponytail/skills/plugin.json +5 -0
  29. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +7 -2
  30. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +1 -1
  31. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +16 -14
  32. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +3 -3
  33. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +12 -7
  34. package/node_modules/@herbertgao/pi-bark/README.md +39 -3
  35. package/node_modules/@herbertgao/pi-bark/package.json +1 -1
  36. package/node_modules/@herbertgao/pi-bark/src/index.ts +275 -15
  37. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +50 -50
  38. package/node_modules/@herbertgao/pi-cc-extensions/README.md +7 -7
  39. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +25 -2
  40. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +24 -1
  41. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -4
  42. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +47 -5
  43. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/mcp-chip.ts +136 -0
  44. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +56 -11
  45. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +36 -4
  46. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +11 -2
  47. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +303 -0
  48. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-header.ts +1 -1
  49. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-highlight.ts +36 -0
  50. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
  51. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +2 -2
  52. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +88 -34
  53. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +6 -5
  54. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-execution.ts +1 -1
  55. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +31 -6
  56. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/message-display.ts +3 -3
  57. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +3 -0
  58. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +173 -36
  59. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-text.ts +1 -0
  60. package/node_modules/@herbertgao/pi-cc-extensions/package.json +6 -4
  61. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  62. package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
  63. package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +3 -2
  64. package/node_modules/@herbertgao/sol-pi/CONTRIBUTING.md +22 -0
  65. package/node_modules/@herbertgao/sol-pi/README.md +36 -17
  66. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +10 -6
  67. package/node_modules/@herbertgao/sol-pi/agents-install.md +15 -12
  68. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +24 -8
  69. package/node_modules/@herbertgao/sol-pi/docs/configuration.md +2 -2
  70. package/node_modules/@herbertgao/sol-pi/docs/star-history.md +18 -0
  71. package/node_modules/@herbertgao/sol-pi/package.json +4 -2
  72. package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +1 -1
  73. package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +1 -1
  74. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +23 -4
  75. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +32 -1
  76. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +3 -2
  77. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +1 -1
  78. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +27 -7
  79. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +137 -41
  80. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +1 -0
  81. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +2 -1
  82. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/projection.ts +58 -0
  83. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +28 -6
  84. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +2 -1
  85. package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +15 -2
  86. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  87. package/node_modules/@narumitw/pi-caffeinate/package.json +4 -4
  88. package/node_modules/@pi-plugins/fast-mode/README.md +7 -1
  89. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
  90. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +7 -1
  91. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  92. package/node_modules/@pi-plugins/fast-mode/package.json +5 -5
  93. package/node_modules/pi-antigravity/README.md +3 -0
  94. package/node_modules/pi-antigravity/package.json +8 -8
  95. package/node_modules/pi-antigravity/src/auth/oauth.ts +6 -3
  96. package/node_modules/pi-antigravity/src/index.ts +114 -110
  97. package/node_modules/pi-antigravity/src/stream/stream.ts +1 -1
  98. package/node_modules/pi-antigravity/src/utils/util.ts +18 -1
  99. package/node_modules/pi-multi-account/CHANGELOG.md +20 -0
  100. package/node_modules/pi-multi-account/README.md +46 -8
  101. package/node_modules/pi-multi-account/config-file-transaction.ts +42 -0
  102. package/node_modules/pi-multi-account/index.ts +255 -269
  103. package/node_modules/pi-multi-account/model-pins.ts +36 -0
  104. package/node_modules/pi-multi-account/package.json +9 -7
  105. package/node_modules/pi-multi-account/slot-proxy-auth.ts +12 -2
  106. package/node_modules/pi-multi-account/slot-proxy.ts +27 -5
  107. package/node_modules/pi-typesafe/README.md +1 -1
  108. package/node_modules/pi-typesafe/dist/backends.d.ts +15 -5
  109. package/node_modules/pi-typesafe/dist/backends.js +18 -0
  110. package/node_modules/pi-typesafe/dist/client.d.ts +4 -3
  111. package/node_modules/pi-typesafe/dist/client.js +22 -7
  112. package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
  113. package/node_modules/pi-typesafe/dist/index.js +1 -1
  114. package/node_modules/pi-typesafe/dist/usage.d.ts +14 -4
  115. package/node_modules/pi-typesafe/dist/usage.js +147 -42
  116. package/node_modules/pi-typesafe/package.json +1 -1
  117. package/node_modules/pi-web-access/CHANGELOG.md +65 -0
  118. package/node_modules/pi-web-access/README.md +63 -14
  119. package/node_modules/pi-web-access/brave-rate-limit.ts +176 -0
  120. package/node_modules/pi-web-access/brave.ts +28 -11
  121. package/node_modules/pi-web-access/curator-page.ts +5 -1
  122. package/node_modules/pi-web-access/curator-server.ts +2 -0
  123. package/node_modules/pi-web-access/declared-web-links.ts +2 -2
  124. package/node_modules/pi-web-access/degoog.ts +323 -0
  125. package/node_modules/pi-web-access/dist/index.js +3647 -2740
  126. package/node_modules/pi-web-access/dist/mcp-cli.js +27443 -0
  127. package/node_modules/pi-web-access/exa.ts +5 -1
  128. package/node_modules/pi-web-access/extract.ts +41 -3
  129. package/node_modules/pi-web-access/firecrawl.ts +35 -4
  130. package/node_modules/pi-web-access/gemini-search.ts +36 -16
  131. package/node_modules/pi-web-access/gemini-web.ts +14 -9
  132. package/node_modules/pi-web-access/index.ts +164 -1207
  133. package/node_modules/pi-web-access/keenable.ts +221 -0
  134. package/node_modules/pi-web-access/mcp-cli.ts +40 -0
  135. package/node_modules/pi-web-access/mcp-server.ts +81 -0
  136. package/node_modules/pi-web-access/mcp-tool-definitions.ts +117 -0
  137. package/node_modules/pi-web-access/openai-search.ts +58 -11
  138. package/node_modules/pi-web-access/package.json +23 -5
  139. package/node_modules/pi-web-access/perplexity.ts +64 -56
  140. package/node_modules/pi-web-access/summary-review.ts +8 -2
  141. package/node_modules/pi-web-access/tool-arguments.ts +20 -0
  142. package/node_modules/pi-web-access/video-extract.ts +1 -1
  143. package/node_modules/pi-web-access/web-tool-contract.ts +86 -0
  144. package/node_modules/pi-web-access/web-tool-core.ts +1502 -0
  145. package/node_modules/pi-web-access/youtube-extract.ts +2 -5
  146. package/node_modules/{@herbertgao/resume-from → resume-from}/README.md +16 -12
  147. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/adapter.d.ts +0 -5
  148. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/adapter.js +2 -3
  149. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/contract.d.ts +2 -2
  150. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/reader.d.ts +3 -0
  151. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/reader.js +27 -15
  152. package/node_modules/{@herbertgao/resume-from/dist/adapters/codex → resume-from/dist/adapters/claude-code}/redaction.js +16 -3
  153. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/writer.d.ts +2 -4
  154. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/writer.js +1 -1
  155. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/contract.d.ts +2 -4
  156. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/index.js +1 -2
  157. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/read.d.ts +16 -0
  158. package/node_modules/resume-from/dist/adapters/codex/read.js +723 -0
  159. package/node_modules/{@herbertgao/resume-from/dist/adapters/pi → resume-from/dist/adapters/codex}/redaction.js +16 -3
  160. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/rollout.d.ts +44 -4
  161. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/rollout.js +88 -4
  162. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/write.d.ts +2 -2
  163. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/write.js +2 -2
  164. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/contract.d.ts +5 -1
  165. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/adapter.js +10 -6
  166. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/contract.d.ts +2 -2
  167. package/node_modules/{@herbertgao/resume-from/dist/adapters/claude-code → resume-from/dist/adapters/pi}/redaction.js +16 -3
  168. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/serialize.d.ts +2 -4
  169. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/serialize.js +2 -2
  170. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/render.d.ts +1 -1
  171. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/render.js +4 -4
  172. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/runner.js +2 -2
  173. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/contract.d.ts +2 -2
  174. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/command.js +31 -8
  175. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/wiring.d.ts +1 -1
  176. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/wiring.js +1 -2
  177. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/confirmation.d.ts +4 -1
  178. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/confirmation.js +2 -2
  179. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/contract.d.ts +8 -6
  180. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/contract.d.ts +10 -5
  181. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/finder.d.ts +2 -1
  182. package/node_modules/resume-from/dist/import/discovery/finder.js +269 -0
  183. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/contract.d.ts +3 -3
  184. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/lander.js +3 -3
  185. package/node_modules/resume-from/dist/import/pipeline.d.ts +28 -0
  186. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/pipeline.js +31 -11
  187. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/builder.d.ts +2 -2
  188. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/builder.js +9 -5
  189. package/node_modules/resume-from/dist/import/preview/warnings.d.ts +14 -0
  190. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/warnings.js +26 -20
  191. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/wiring.js +12 -2
  192. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/contract.d.ts +9 -3
  193. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/git.d.ts +4 -4
  194. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/git.js +7 -4
  195. package/node_modules/resume-from/dist/platform/repo/reader.d.ts +3 -0
  196. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/reader.js +60 -20
  197. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/session/contract.d.ts +8 -1
  198. package/node_modules/{@herbertgao/resume-from → resume-from}/package.json +19 -32
  199. package/node_modules/{@herbertgao/resume-from → resume-from}/shims/pi/extensions/resume-from.js +2 -2
  200. package/package.json +21 -21
  201. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +0 -21
  202. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +0 -62
  203. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
  204. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +0 -115
  205. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +0 -27
  206. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +0 -27
  207. package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
  208. package/node_modules/@dietrichgebert/ponytail/assets/retriever-icon.png +0 -0
  209. package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-dark.svg +0 -4
  210. package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-light.svg +0 -4
  211. package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
  212. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
  213. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
  214. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
  215. package/node_modules/@herbertgao/resume-from/dist/adapters/codex/read.js +0 -270
  216. package/node_modules/@herbertgao/resume-from/dist/import/discovery/finder.js +0 -129
  217. package/node_modules/@herbertgao/resume-from/dist/import/pipeline.d.ts +0 -17
  218. package/node_modules/@herbertgao/resume-from/dist/import/preview/warnings.d.ts +0 -11
  219. package/node_modules/@herbertgao/resume-from/dist/platform/repo/reader.d.ts +0 -8
  220. /package/node_modules/{@herbertgao/resume-from → resume-from}/LICENSE +0 -0
  221. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/contract.js +0 -0
  222. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/entries.d.ts +0 -0
  223. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/entries.js +0 -0
  224. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/index.d.ts +0 -0
  225. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/index.js +0 -0
  226. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/layout.d.ts +0 -0
  227. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/layout.js +0 -0
  228. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/readback.d.ts +0 -0
  229. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/readback.js +0 -0
  230. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/redaction.d.ts +0 -0
  231. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/validation.d.ts +0 -0
  232. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/validation.js +0 -0
  233. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/contract.js +0 -0
  234. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/index.d.ts +0 -0
  235. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/readback.d.ts +0 -0
  236. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/readback.js +0 -0
  237. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/redaction.d.ts +0 -0
  238. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/validation.d.ts +0 -0
  239. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/validation.js +0 -0
  240. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/contract.js +0 -0
  241. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/adapter.d.ts +0 -0
  242. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/contract.js +0 -0
  243. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/format.d.ts +0 -0
  244. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/format.js +0 -0
  245. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/index.d.ts +0 -0
  246. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/index.js +0 -0
  247. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/parse.d.ts +0 -0
  248. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/parse.js +0 -0
  249. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/redaction.d.ts +0 -0
  250. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/validate.d.ts +0 -0
  251. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/validate.js +0 -0
  252. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/bin.d.ts +0 -0
  253. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/bin.js +0 -0
  254. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/contract.d.ts +0 -0
  255. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/contract.js +0 -0
  256. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/agents.d.ts +0 -0
  257. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/agents.js +0 -0
  258. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/args.d.ts +0 -0
  259. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/args.js +0 -0
  260. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/contract.d.ts +0 -0
  261. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/contract.js +0 -0
  262. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/index.d.ts +0 -0
  263. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/index.js +0 -0
  264. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/presentation.d.ts +0 -0
  265. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/presentation.js +0 -0
  266. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/runner.d.ts +0 -0
  267. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/contract.js +0 -0
  268. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/entry.d.ts +0 -0
  269. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/entry.js +0 -0
  270. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/index.d.ts +0 -0
  271. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/index.js +0 -0
  272. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/command.d.ts +0 -0
  273. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/contract.d.ts +0 -0
  274. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/contract.js +0 -0
  275. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/index.d.ts +0 -0
  276. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/index.js +0 -0
  277. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/picker.d.ts +0 -0
  278. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/picker.js +0 -0
  279. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/presentation.d.ts +0 -0
  280. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/presentation.js +0 -0
  281. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/register.d.ts +0 -0
  282. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/register.js +0 -0
  283. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/ui.d.ts +0 -0
  284. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/ui.js +0 -0
  285. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/profile.d.ts +0 -0
  286. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/profile.js +0 -0
  287. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/registry.d.ts +0 -0
  288. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/registry.js +0 -0
  289. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/contract.js +0 -0
  290. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/contract.js +0 -0
  291. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/errors.d.ts +0 -0
  292. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/errors.js +0 -0
  293. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/homes.d.ts +0 -0
  294. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/homes.js +0 -0
  295. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/index.d.ts +0 -0
  296. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/index.js +0 -0
  297. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/ordering.d.ts +0 -0
  298. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/ordering.js +0 -0
  299. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/errors.d.ts +0 -0
  300. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/errors.js +0 -0
  301. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/index.d.ts +0 -0
  302. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/index.js +0 -0
  303. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/contract.js +0 -0
  304. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/errors.d.ts +0 -0
  305. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/errors.js +0 -0
  306. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/handover.d.ts +0 -0
  307. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/handover.js +0 -0
  308. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/index.d.ts +0 -0
  309. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/index.js +0 -0
  310. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/lander.d.ts +0 -0
  311. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/marker.d.ts +0 -0
  312. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/marker.js +0 -0
  313. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/contract.d.ts +0 -0
  314. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/contract.js +0 -0
  315. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/format.d.ts +0 -0
  316. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/format.js +0 -0
  317. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/index.d.ts +0 -0
  318. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/index.js +0 -0
  319. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/contract.d.ts +0 -0
  320. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/contract.js +0 -0
  321. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/index.d.ts +0 -0
  322. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/index.js +0 -0
  323. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/rules.d.ts +0 -0
  324. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/rules.js +0 -0
  325. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/wiring.d.ts +0 -0
  326. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/index.d.ts +0 -0
  327. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/index.js +0 -0
  328. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/contract.d.ts +0 -0
  329. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/contract.js +0 -0
  330. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/defaults.d.ts +0 -0
  331. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/defaults.js +0 -0
  332. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/errors.d.ts +0 -0
  333. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/errors.js +0 -0
  334. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/index.d.ts +0 -0
  335. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/index.js +0 -0
  336. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/loader.d.ts +0 -0
  337. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/loader.js +0 -0
  338. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/paths.d.ts +0 -0
  339. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/paths.js +0 -0
  340. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/validate.d.ts +0 -0
  341. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/validate.js +0 -0
  342. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/contract.js +0 -0
  343. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/index.d.ts +0 -0
  344. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/index.js +0 -0
  345. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/contract.d.ts +0 -0
  346. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/contract.js +0 -0
  347. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/file-committer.d.ts +0 -0
  348. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/file-committer.js +0 -0
  349. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/index.d.ts +0 -0
  350. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/index.js +0 -0
  351. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/contract.d.ts +0 -0
  352. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/contract.js +0 -0
  353. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/estimator.d.ts +0 -0
  354. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/estimator.js +0 -0
  355. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/index.d.ts +0 -0
  356. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/index.js +0 -0
  357. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/session/contract.js +0 -0
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  // ponytail — removes state ponytail wrote outside the plugin's own files:
3
- // the mode flag, the config file, the statusLine entry it added to
4
- // settings.json, and its entries in ~/.cursor/hooks.json. Plugin files
5
- // themselves are removed by each host's own uninstall command (see README);
6
- // this only cleans up what those commands can't see.
3
+ // the mode flag, the config file, the statusline script copy, the
4
+ // statusLine entry it added to settings.json, and its entries in
5
+ // ~/.cursor/hooks.json. Plugin files themselves are removed by each host's
6
+ // own uninstall command (see README); this only cleans up what those
7
+ // commands can't see.
7
8
 
8
9
  const fs = require('fs');
9
10
  const os = require('os');
@@ -11,7 +12,7 @@ const path = require('path');
11
12
  const { getConfigPath, getClaudeDir } = require('../hooks/ponytail-config');
12
13
  const cursorHooks = require('./cursor-hooks');
13
14
 
14
- const STATUSLINE_SCRIPT = 'ponytail-statusline';
15
+ const STATUSLINE_SCRIPT = /(?:^|[\s"'\\/])ponytail-statusline\.(?:sh|ps1)(?=$|[\s"'])/;
15
16
 
16
17
  function removeIfExists(filePath, label) {
17
18
  try {
@@ -23,8 +24,21 @@ function removeIfExists(filePath, label) {
23
24
  }
24
25
 
25
26
  removeIfExists(path.join(getClaudeDir(), '.ponytail-active'), 'mode flag');
27
+ removeIfExists(path.join(getClaudeDir(), '.ponytail-statusline-nudged'), 'statusline nudge flag');
26
28
  removeIfExists(path.join(os.homedir(), '.cursor', '.ponytail-active'), 'Cursor mode flag');
29
+ removeIfExists(path.join(os.homedir(), '.qoder', '.ponytail-active'), 'Qoder mode flag');
30
+ for (const dir of [path.join(getClaudeDir(), 'ponytail-modes'), path.join(os.homedir(), '.cursor', 'ponytail-modes'), path.join(process.env.CODEBUDDY_CONFIG_DIR || path.join(os.homedir(), '.codebuddy'), 'ponytail-modes')]) {
31
+ if (fs.existsSync(dir)) {
32
+ fs.rmSync(dir, { recursive: true });
33
+ console.log(`Removed per-project mode flags: ${dir}`);
34
+ }
35
+ }
36
+ removeIfExists(path.join(process.env.CODEBUDDY_CONFIG_DIR || path.join(os.homedir(), '.codebuddy'), '.ponytail-active'), 'CodeBuddy mode flag');
27
37
  removeIfExists(getConfigPath(), 'config file');
38
+ // Statusline script copies the activate hook keeps in the config dir (#1032).
39
+ for (const name of ['ponytail-statusline.sh', 'ponytail-statusline.ps1']) {
40
+ removeIfExists(path.join(getClaudeDir(), name), 'statusline script');
41
+ }
28
42
 
29
43
  // Cursor hooks (#817): drop only ponytail's entries from ~/.cursor/hooks.json,
30
44
  // keep every other hook the user configured there.
@@ -49,12 +63,12 @@ try {
49
63
  // (e.g. caveman && ponytail), keep the other plugin's command intact.
50
64
  // ponytail: splits on && / ; to detect other segments — good enough; a user
51
65
  // piping statuslines together is on their own.
52
- if (typeof cmd === 'string' && cmd.includes(STATUSLINE_SCRIPT)) {
66
+ if (typeof cmd === 'string' && STATUSLINE_SCRIPT.test(cmd)) {
53
67
  const parts = cmd
54
68
  .split(/&&|;/)
55
69
  .map((s) => s.trim())
56
70
  .filter(Boolean);
57
- const others = parts.filter((s) => !s.includes(STATUSLINE_SCRIPT));
71
+ const others = parts.filter((s) => !STATUSLINE_SCRIPT.test(s));
58
72
  if (others.length === 0) {
59
73
  delete settings.statusLine;
60
74
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "ponytail",
3
+ "description": "Lazy senior dev mode. Forces the simplest solution that works: YAGNI, stdlib first, one line over fifty.",
4
+ "skills": "."
5
+ }
@@ -19,6 +19,7 @@ Same as ponytail-review:
19
19
  - `delete:` dead code, unused flexibility, speculative feature. Replacement: nothing.
20
20
  - `stdlib:` hand-rolled thing the standard library ships. Name the function.
21
21
  - `native:` dependency or code doing what the platform already does. Name the feature.
22
+ - `reuse:` equivalent helper, util, or pattern already in this repo. Name the path.
22
23
  - `yagni:` abstraction with one implementation, config nobody sets, layer with one caller.
23
24
  - `shrink:` same logic, fewer lines. Show the shorter form.
24
25
 
@@ -26,11 +27,15 @@ Same as ponytail-review:
26
27
 
27
28
  Deps the stdlib or platform already ships, single-implementation interfaces,
28
29
  factories with one product, wrappers that only delegate, files exporting one
29
- thing, dead flags and config, hand-rolled stdlib.
30
+ thing, dead flags and config, hand-rolled stdlib, helpers duplicating an
31
+ equivalent that already lives in this repo.
32
+ Before emitting `delete:`, grep the whole tree for the symbol, including tests,
33
+ fixtures and string or dynamic references.
30
34
 
31
35
  ## Output
32
36
 
33
- One line per finding, ranked: `<tag> <what to cut>. <replacement>. [path]`.
37
+ One line per finding, numbered and ranked: `<N>. <tag> <what to cut>. <replacement>. [path]`,
38
+ so the user can say "fix 2 and 5".
34
39
  End with `net: -<N> lines, -<M> deps possible.` Nothing to cut: `Lean already. Ship.`
35
40
 
36
41
  ## Boundaries
@@ -17,7 +17,7 @@ can't quietly become permanent.
17
17
  Grep the repo for comment markers, skipping `node_modules`, `.git`, and build
18
18
  output:
19
19
 
20
- `grep -rnE '(#|//) ?ponytail:' .` (add other comment prefixes if your stack uses them)
20
+ `grep -rnE --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build '(#|//|/[*]) ?ponytail:' .` (add other comment prefixes if your stack uses them)
21
21
 
22
22
  Each hit is one ledger row. The comment prefix keeps prose that merely mentions
23
23
  the convention out of the ledger.
@@ -2,7 +2,7 @@
2
2
  name: ponytail-gain
3
3
  description: >
4
4
  Show ponytail's measured impact as a compact scoreboard: less code, less
5
- cost, more speed, from the benchmark medians. One-shot display, not a
5
+ cost, more speed, from the agentic benchmark averages. One-shot display, not a
6
6
  persistent mode, and not a per-repo number. Trigger: /ponytail-gain,
7
7
  "ponytail gain", "what does ponytail save", "show ponytail impact",
8
8
  "ponytail scoreboard".
@@ -13,24 +13,26 @@ description: >
13
13
  Display this scoreboard when invoked. One-shot: do NOT change mode, write flag
14
14
  files, or persist anything.
15
15
 
16
- The figures are the published benchmark medians (5 everyday tasks: email
17
- validator, debounce, CSV sum, countdown timer, rate limiter; three models:
18
- Haiku, Sonnet, Opus). They are measured, not computed from the current repo.
19
- Source: `benchmarks/` and the README.
16
+ The figures are the published agentic benchmark averages: a headless Claude
17
+ Code session (Haiku 4.5) doing 12 feature tickets on a real FastAPI + React
18
+ repo, each task averaged over 4 runs, against the same agent without the
19
+ skill, plus 6 safety tasks. They are measured, not computed from the current
20
+ repo. Source: `benchmarks/results/2026-06-18-agentic.md` and the README.
20
21
 
21
22
  ## Scoreboard
22
23
 
23
- Render plain ASCII bars. The bar length shows the measured range; the label
24
- carries the exact figure:
24
+ Render plain ASCII bars. The bar length shows ponytail as a share of the
25
+ no-skill baseline; the label carries the exact figure:
25
26
 
26
27
  ```
27
- ponytail gain benchmark median · 5 tasks · 3 models
28
+ ponytail gain benchmark average · 12 tasks · Haiku 4.5
28
29
 
29
- Lines of code no-skill ████████████████████ 100%
30
- ponytail ██▌················· 6–20% ▼ 80–94%
31
- Cost no-skill ████████████████████ 100%
32
- ponytail █████▌·············· 23–53% ▼ 47–77%
33
- Speed ponytail ▸ 3–6× faster
30
+ no-skill ████████████████████ 100%
31
+ Lines of code █████████··········· 46% ▼ 54%
32
+ Tokens ████████████████···· 78% ▼ 22%
33
+ Cost ████████████████···· 80% ▼ 20%
34
+ Time ███████████████····· 73% ▼ 27%
35
+ Safety kept 100% (validation, error handling, security)
34
36
 
35
37
  This repo: /ponytail-debt (shortcuts you deferred)
36
38
  /ponytail-audit (what's still cuttable)
@@ -38,7 +40,7 @@ carries the exact figure:
38
40
 
39
41
  ## Honesty boundary
40
42
 
41
- These are benchmark medians, not this repo. NEVER print a per-repo savings
43
+ These are benchmark averages, not this repo. NEVER print a per-repo savings
42
44
  number ("you saved X lines/tokens here"): the unbuilt version was never
43
45
  written, so there is no real baseline to subtract from in a live repo. The
44
46
  only real per-repo figures come from `/ponytail-debt` (a counted ledger), and
@@ -32,9 +32,9 @@ Level sticks until changed or session end.
32
32
  | **ponytail-gain** | `/ponytail-gain` | Measured-impact scoreboard: less code, less cost, more speed. |
33
33
  | **ponytail-help** | `/ponytail-help` | This card. |
34
34
 
35
- Codex uses `@ponytail`, `@ponytail-review`, and `@ponytail-help`; Claude Code
36
- and OpenCode use the slash-command forms above (OpenCode ships all six as
37
- slash commands).
35
+ In Codex CLI and the IDE extension, invoke skills with `$ponytail:ponytail`,
36
+ `$ponytail:ponytail-review`, or `$ponytail:ponytail-help`. Claude Code and OpenCode use the
37
+ slash-command forms above (OpenCode ships all six as slash commands).
38
38
 
39
39
  ## Deactivate
40
40
 
@@ -15,31 +15,36 @@ to cut, what replaces it. The diff's best outcome is getting shorter.
15
15
 
16
16
  ## Format
17
17
 
18
- `L<line>: <tag> <what>. <replacement>.`, or `<file>:L<line>: ...` for
19
- multi-file diffs.
18
+ `<N>. L<line>: <tag> <what>. <replacement>.`, or `<N>. <file>:L<line>: ...` for
19
+ multi-file diffs. Number findings `1.`, `2.`, ... across the whole report, so the
20
+ user can say "fix 2 and 5".
20
21
 
21
22
  Tags:
22
23
 
23
24
  - `delete:` dead code, unused flexibility, speculative feature. Replacement: nothing.
24
25
  - `stdlib:` hand-rolled thing the standard library ships. Name the function.
25
26
  - `native:` dependency or code doing what the platform already does. Name the feature.
27
+ - `reuse:` equivalent helper, util, or pattern already in this repo. Name the path.
26
28
  - `yagni:` abstraction with one implementation, config nobody sets, layer with one caller.
27
29
  - `shrink:` same logic, fewer lines. Show the shorter form.
28
30
 
31
+
29
32
  ## Examples
30
33
 
31
34
  ❌ "This EmailValidator class might be more complex than necessary, have you
32
35
  considered whether all these validation rules are needed at this stage?"
33
36
 
34
- ✅ `L12-38: stdlib: 27-line validator class. "@" in email, 1 line, real validation is the confirmation mail.`
37
+ ✅ `1. L12-38: stdlib: 27-line validator class. "@" in email, 1 line, real validation is the confirmation mail.`
38
+
39
+ ✅ `2. L4: native: moment.js imported for one format call. Intl.DateTimeFormat, 0 deps.`
35
40
 
36
- ✅ `L4: native: moment.js imported for one format call. Intl.DateTimeFormat, 0 deps.`
41
+ ✅ `3. L18-29: reuse: slugify helper duplicates src/lib/slug.ts slugify. Delete it, import the existing one.`
37
42
 
38
- ✅ `repo.py:L88: yagni: AbstractRepository with one implementation. Inline it until a second one exists.`
43
+ ✅ `4. repo.py:L88: yagni: AbstractRepository with one implementation. Inline it until a second one exists.`
39
44
 
40
- ✅ `L52-71: delete: retry wrapper around an idempotent local call. Nothing replaces it.`
45
+ ✅ `5. L52-71: delete: retry wrapper around an idempotent local call. Nothing replaces it.`
41
46
 
42
- ✅ `L30-44: shrink: manual loop builds dict. dict(zip(keys, values)), 1 line.`
47
+ ✅ `6. L30-44: shrink: manual loop builds dict. dict(zip(keys, values)), 1 line.`
43
48
 
44
49
  ## Scoring
45
50
 
@@ -1,6 +1,6 @@
1
1
  # @herbertgao/pi-bark
2
2
 
3
- Bark notifications for user-facing Pi sessions. It sends a notification when Pi fully settles and when [`@juicesharp/rpiv-ask-user-question`](https://github.com/juicesharp/rpiv-mono) opens a questionnaire. Requests follow Bark's official [POST form API](https://github.com/Finb/Bark/blob/master/docs/en-us/tutorial.md#request-methods).
3
+ Bark notifications for user-facing Pi sessions. It sends a notification when Pi fully settles and when Pi blocks on a user-facing dialog — an approval, a select, an input prompt, a custom panel, or an [`@juicesharp/rpiv-ask-user-question`](https://github.com/juicesharp/rpiv-mono) questionnaire. Requests follow Bark's official [POST form API](https://github.com/Finb/Bark/blob/master/docs/en-us/tutorial.md#request-methods).
4
4
 
5
5
  ## Install
6
6
 
@@ -21,17 +21,53 @@ Create `$PI_CODING_AGENT_DIR/bark.json` (normally `~/.pi/agent/bark.json`):
21
21
  "locale": "zh-CN",
22
22
  "events": {
23
23
  "agentSettled": true,
24
- "askUserQuestion": true
24
+ "needsInput": true
25
25
  },
26
26
  "params": {
27
27
  "group": "pi",
28
28
  "icon": "https://raw.githubusercontent.com/HerbertGao/pi-extensions/master/packages/pi-bark/assets/pi-icon.png"
29
29
  },
30
+ "title": {
31
+ "source": "session",
32
+ "maxLength": 40
33
+ },
34
+ "minDurationMs": 30000,
30
35
  "timeoutMs": 4000
31
36
  }
32
37
  ```
33
38
 
34
- `endpoint` is required. `locale` accepts `auto` (default), `zh-CN`, `zh-TW`, or `en`; unsupported languages fall back to English. `params` accepts additional Bark POST parameters such as `group`, `sound`, `icon`, and `level`; dynamic `title` and `body` values always take precedence. Set `"enabled": false` to disable all notifications without removing the package.
39
+ `endpoint` is required. `locale` accepts `auto` (default), `zh-CN`, `zh-TW`, or `en`; unsupported languages fall back to English. `params` accepts additional Bark POST parameters such as `group`, `sound`, `icon`, and `level`; dynamic `title` and `body` values always take precedence. Set `"enabled": false` to disable all notifications without removing the package. `events.needsInput` covers every blocking dialog; `events.askUserQuestion` is the older name and is still honored as an alias.
40
+
41
+ ### Session titles
42
+
43
+ The default `title.source` is `"fixed"`, which keeps Pi's localized copy (`✅ Pi 跑完了` / `🟡 Pi 等你回答`) as the notification title. Set it to `"session"` to title the notification with the session instead — Pi's session name when one is set, otherwise the first user message, which is the label hosts such as Paseo already show for the session:
44
+
45
+ ```json
46
+ { "title": { "source": "session", "maxLength": 40 } }
47
+ ```
48
+
49
+ `maxLength` defaults to `40`, counts Unicode code points without splitting emoji surrogate pairs, and includes the ellipsis. Non-finite values or values below `1` fall back to the default. With `"session"`, the event state moves to the first line of the body:
50
+
51
+ ```
52
+ title: Please reply with exactly the single wo…
53
+ body: ✅ 已完成 · 用时 2m14s
54
+ 💻 MacBook Pro M1 Max
55
+ 📁 /Users/herbertgao/VSCodeProject/pi
56
+ ```
57
+
58
+ The title is resolved when the notification is sent, so a session that is renamed or that has only just received its first message still gets the right one.
59
+
60
+ ### Short runs
61
+
62
+ `minDurationMs` defaults to `0` (always notify). Raise it to skip the settle notification when the run finished faster, so one-line replies stop producing notification noise:
63
+
64
+ ```json
65
+ { "minDurationMs": 30000 }
66
+ ```
67
+
68
+ `minDurationMs` never suppresses needs-input notifications. Input notifications wait 400ms so an unsupported, cancelled, or quickly answered UI call does not send a stale alert. Pending alerts are cancelled when the prompt, run, or session ends. Questionnaire fallback events use a 1.5s de-duplication window because they have no shared dialog ID; separate fallback-only prompts inside that window may be merged. Native dialogs are tracked independently.
69
+
70
+ Completion notifications await the Bark request before Pi finishes settling, including during session switching or exit. This can delay those operations up to `timeoutMs` (default 4 seconds). Errors and timeouts are ignored; delivery is best-effort, with no persistent queue or retries.
35
71
 
36
72
  The notification body uses `💻` for the configured machine name and `📁` for Pi's full working directory. Questionnaire text is intentionally not sent because it may contain sensitive context. Only sessions with a user-facing UI notify, so nested `pi-subagents` sessions do not create duplicate pushes.
37
73
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-bark",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Send Bark notifications when Pi finishes or needs user input.",
5
5
  "keywords": [
6
6
  "bark",
@@ -1,39 +1,75 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent"
2
5
  import { readFileSync } from "node:fs"
3
6
  import { homedir, hostname } from "node:os"
4
7
  import { join } from "node:path"
5
8
 
6
9
  const ASK_USER_PROMPT_EVENT = "rpiv:ask-user:prompt"
10
+ /** Give closed/unsupported UI calls time to finish before notifying. */
11
+ const NEEDS_INPUT_DELAY_MS = 400
12
+ // ponytail: fallback events lack dialog IDs; this window can merge distinct fallback prompts.
13
+ // Replace it with dialog IDs if the event source provides them.
14
+ const NEEDS_INPUT_DEDUP_MS = 1_500
7
15
  const DEFAULT_TIMEOUT_MS = 4_000
16
+ const DEFAULT_MAX_TITLE_LENGTH = 40
17
+ const DEFAULT_MIN_DURATION_MS = 0
8
18
 
9
19
  export type BarkLocale = "en" | "zh-CN" | "zh-TW"
10
- type NotificationEvent = "agentSettled" | "askUserQuestion"
20
+ type NotificationEvent = "agentSettled" | "needsInput"
21
+ export type BarkTitleSource = "fixed" | "session"
11
22
 
12
- const COPY: Record<BarkLocale, Record<NotificationEvent, string>> = {
23
+ const COPY: Record<
24
+ BarkLocale,
25
+ {
26
+ agentSettled: string
27
+ settledWithDuration: (duration: string) => string
28
+ needsInput: string
29
+ needsInputBody: string
30
+ }
31
+ > = {
13
32
  en: {
14
33
  agentSettled: "✅ Pi finished",
15
- askUserQuestion: "🟡 Pi needs your input",
34
+ settledWithDuration: (duration) => `✅ Pi finished · ${duration}`,
35
+ needsInput: "🟡 Pi needs your input",
36
+ needsInputBody: "🟡 Needs your reply",
16
37
  },
17
38
  "zh-CN": {
18
39
  agentSettled: "✅ Pi 跑完了",
19
- askUserQuestion: "🟡 Pi 等你回答",
40
+ settledWithDuration: (duration) => `✅ 已完成 · 用时 ${duration}`,
41
+ needsInput: "🟡 Pi 等你回答",
42
+ needsInputBody: "🟡 需要你回复",
20
43
  },
21
44
  "zh-TW": {
22
45
  agentSettled: "✅ Pi 已完成",
23
- askUserQuestion: "🟡 Pi 等你回覆",
46
+ settledWithDuration: (duration) => `✅ 已完成 · 耗時 ${duration}`,
47
+ needsInput: "🟡 Pi 等你回覆",
48
+ needsInputBody: "🟡 需要你回覆",
24
49
  },
25
50
  }
26
51
 
52
+ export interface BarkTitleOptions {
53
+ /** "fixed" keeps the localized Pi copy as the title (default). "session" uses the session title. */
54
+ source: BarkTitleSource
55
+ /** Maximum title length before truncation. */
56
+ maxLength: number
57
+ }
58
+
27
59
  export interface BarkConfig {
28
60
  endpoint: string
29
61
  machine: string
30
62
  locale: BarkLocale
31
63
  events: {
32
64
  agentSettled: boolean
33
- askUserQuestion: boolean
65
+ /** Any blocking user-facing dialog: approval, select, input, custom, ask_user_question. */
66
+ needsInput: boolean
34
67
  }
35
68
  params: Record<string, string | number | boolean>
36
69
  timeoutMs: number
70
+ title: BarkTitleOptions
71
+ /** Skip the settle notification when the run finished faster than this. 0 notifies always. */
72
+ minDurationMs: number
37
73
  }
38
74
 
39
75
  export function getConfigPath(): string {
@@ -61,6 +97,85 @@ export function notificationTitle(
61
97
  return COPY[locale][event]
62
98
  }
63
99
 
100
+ /** Render a bounded, human-readable duration such as `45s`, `2m14s`, or `1h3m`. */
101
+ export function formatDuration(ms: number): string {
102
+ const total = Math.max(0, Math.round(ms / 1000))
103
+ if (total < 60) return `${total}s`
104
+ const minutes = Math.floor(total / 60)
105
+ const seconds = total % 60
106
+ if (minutes < 60) return seconds ? `${minutes}m${seconds}s` : `${minutes}m`
107
+ const hours = Math.floor(minutes / 60)
108
+ const remainder = minutes % 60
109
+ return remainder ? `${hours}h${remainder}m` : `${hours}h`
110
+ }
111
+
112
+ function normalizeText(value: unknown): string {
113
+ if (typeof value !== "string") return ""
114
+ return value
115
+ .replace(/[\r\n\t]+/g, " ")
116
+ .replace(/\s{2,}/g, " ")
117
+ .trim()
118
+ }
119
+
120
+ export function truncateTitle(text: string, maxLength: number): string {
121
+ const characters = Array.from(normalizeText(text))
122
+ if (!maxLength || characters.length <= maxLength) return characters.join("")
123
+ return `${characters
124
+ .slice(0, maxLength - 1)
125
+ .join("")
126
+ .trimEnd()}…`
127
+ }
128
+
129
+ /** Read the text of a message content value (string or content-part array). */
130
+ export function readMessageText(content: unknown): string {
131
+ if (typeof content === "string") return content
132
+ if (!Array.isArray(content)) return ""
133
+ return content
134
+ .filter(
135
+ (part) =>
136
+ part &&
137
+ typeof part === "object" &&
138
+ (part as { type?: string }).type === "text" &&
139
+ typeof (part as { text?: unknown }).text === "string",
140
+ )
141
+ .map((part) => (part as { text: string }).text)
142
+ .join(" ")
143
+ }
144
+
145
+ /**
146
+ * Pi session name if the user set one, else the first user message — the same
147
+ * label most hosts show for the session.
148
+ */
149
+ export function resolveSessionTitle(
150
+ ctx: Pick<ExtensionContext, "sessionManager"> | undefined,
151
+ getSessionName?: () => string | undefined,
152
+ ): string {
153
+ try {
154
+ const named = getSessionName?.()
155
+ if (typeof named === "string" && named.trim()) return named.trim()
156
+ } catch {
157
+ // fall through
158
+ }
159
+ try {
160
+ const manager = ctx?.sessionManager
161
+ const named = manager?.getSessionName?.()
162
+ if (typeof named === "string" && named.trim()) return named.trim()
163
+ } catch {
164
+ // fall through
165
+ }
166
+ try {
167
+ const entries = ctx?.sessionManager?.getEntries?.() ?? []
168
+ for (const entry of entries) {
169
+ if (entry?.type !== "message" || entry.message?.role !== "user") continue
170
+ const text = normalizeText(readMessageText(entry.message.content))
171
+ if (text) return text
172
+ }
173
+ } catch {
174
+ // fall through
175
+ }
176
+ return ""
177
+ }
178
+
64
179
  export function normalizeConfig(input: unknown): BarkConfig | undefined {
65
180
  if (!input || typeof input !== "object") return undefined
66
181
  const source = input as Record<string, unknown>
@@ -92,6 +207,10 @@ export function normalizeConfig(input: unknown): BarkConfig | undefined {
92
207
  source.timeoutMs > 0
93
208
  ? source.timeoutMs
94
209
  : DEFAULT_TIMEOUT_MS
210
+ const titleSource =
211
+ source.title && typeof source.title === "object"
212
+ ? (source.title as Record<string, unknown>)
213
+ : {}
95
214
 
96
215
  return {
97
216
  endpoint: source.endpoint,
@@ -102,10 +221,27 @@ export function normalizeConfig(input: unknown): BarkConfig | undefined {
102
221
  locale: resolveLocale(source.locale),
103
222
  events: {
104
223
  agentSettled: eventSource.agentSettled !== false,
105
- askUserQuestion: eventSource.askUserQuestion !== false,
224
+ // `askUserQuestion` is the pre-0.2 name and is still honored.
225
+ needsInput:
226
+ (eventSource.needsInput ?? eventSource.askUserQuestion) !== false,
106
227
  },
107
228
  params,
108
229
  timeoutMs,
230
+ title: {
231
+ source: titleSource.source === "session" ? "session" : "fixed",
232
+ maxLength:
233
+ typeof titleSource.maxLength === "number" &&
234
+ Number.isFinite(titleSource.maxLength) &&
235
+ titleSource.maxLength >= 1
236
+ ? Math.floor(titleSource.maxLength)
237
+ : DEFAULT_MAX_TITLE_LENGTH,
238
+ },
239
+ minDurationMs:
240
+ typeof source.minDurationMs === "number" &&
241
+ Number.isFinite(source.minDurationMs) &&
242
+ source.minDurationMs >= 0
243
+ ? source.minDurationMs
244
+ : DEFAULT_MIN_DURATION_MS,
109
245
  }
110
246
  }
111
247
 
@@ -145,34 +281,158 @@ export default function piBark(pi: ExtensionAPI): void {
145
281
  let config: BarkConfig | undefined
146
282
  let cwd = process.cwd()
147
283
  let userFacingSession = false
284
+ let cachedTitle = ""
285
+ let runStartedAt: number | null = null
286
+ let pendingNeedsInput: ReturnType<typeof setTimeout> | undefined
287
+ let uiPromptActive = false
288
+ let lastFallbackAt = 0
289
+ let lastUIPromptEndAt = 0
148
290
 
149
- const notify = async (title: string): Promise<void> => {
291
+ const refreshTitle = (ctx: ExtensionContext | undefined): string => {
292
+ const resolved = resolveSessionTitle(ctx, () => pi.getSessionName?.())
293
+ if (resolved) cachedTitle = resolved
294
+ return cachedTitle
295
+ }
296
+
297
+ const titleFor = (
298
+ ctx: ExtensionContext | undefined,
299
+ event: NotificationEvent,
300
+ ): string => {
301
+ if (!config) return ""
302
+ if (config.title.source === "fixed")
303
+ return notificationTitle(config.locale, event)
304
+ const session = ctx ? refreshTitle(ctx) : cachedTitle
305
+ return truncateTitle(session || config.machine, config.title.maxLength)
306
+ }
307
+
308
+ const notify = async (title: string, body: string): Promise<void> => {
150
309
  if (!config || !userFacingSession) return
151
310
  try {
152
- await sendBark(config, title, formatBody(config, cwd))
311
+ await sendBark(config, title, body)
153
312
  } catch {
154
313
  // Notifications are best-effort and must never interrupt Pi.
155
314
  }
156
315
  }
157
316
 
317
+ const cancelNeedsInput = () => {
318
+ clearTimeout(pendingNeedsInput)
319
+ pendingNeedsInput = undefined
320
+ }
321
+
322
+ const resetNeedsInput = () => {
323
+ cancelNeedsInput()
324
+ uiPromptActive = false
325
+ lastFallbackAt = 0
326
+ lastUIPromptEndAt = 0
327
+ }
328
+
329
+ const scheduleNeedsInput = (ctx: ExtensionContext | undefined) => {
330
+ if (!config?.events.needsInput || !userFacingSession) return
331
+ cancelNeedsInput()
332
+ pendingNeedsInput = setTimeout(() => {
333
+ pendingNeedsInput = undefined
334
+ if (!config?.events.needsInput || !userFacingSession) return
335
+ if (!ctx) lastFallbackAt = Date.now()
336
+ const prefix =
337
+ config.title.source === "session"
338
+ ? `${COPY[config.locale].needsInputBody}\n`
339
+ : ""
340
+ void notify(
341
+ titleFor(ctx, "needsInput"),
342
+ `${prefix}${formatBody(config, cwd)}`,
343
+ )
344
+ }, NEEDS_INPUT_DELAY_MS)
345
+ }
346
+
158
347
  const unsubscribeAskUser = pi.events.on(ASK_USER_PROMPT_EVENT, () => {
159
- if (config?.events.askUserQuestion)
160
- void notify(notificationTitle(config.locale, "askUserQuestion"))
348
+ const now = Date.now()
349
+ if (
350
+ uiPromptActive ||
351
+ now - lastUIPromptEndAt < NEEDS_INPUT_DEDUP_MS ||
352
+ now - lastFallbackAt < NEEDS_INPUT_DEDUP_MS
353
+ )
354
+ return
355
+ scheduleNeedsInput(undefined)
356
+ })
357
+
358
+ // Native prompt lifecycle cancels notifications for UI calls that finish
359
+ // without opening a dialog, or that the user answers before the grace period.
360
+ pi.on("ui_prompt_start", (_event, ctx) => {
361
+ cwd = ctx.cwd
362
+ uiPromptActive = true
363
+ cancelNeedsInput()
364
+ if (Date.now() - lastFallbackAt >= NEEDS_INPUT_DEDUP_MS)
365
+ scheduleNeedsInput(ctx)
366
+ })
367
+
368
+ pi.on("ui_prompt_end", () => {
369
+ uiPromptActive = false
370
+ lastFallbackAt = 0
371
+ lastUIPromptEndAt = Date.now()
372
+ cancelNeedsInput()
161
373
  })
162
374
 
163
375
  pi.on("session_start", (_event, ctx) => {
376
+ resetNeedsInput()
164
377
  config = loadConfig()
165
378
  cwd = ctx.cwd
166
379
  userFacingSession = ctx.hasUI
380
+ runStartedAt = null
381
+ cachedTitle = ""
382
+ refreshTitle(ctx)
383
+ })
384
+
385
+ // Pi persists the user entry at message_end, not on submit, so the title cache has to be
386
+ // refreshed on message/turn events rather than only at agent_start.
387
+ pi.on("session_info_changed", (_event, ctx) => {
388
+ refreshTitle(ctx)
167
389
  })
168
390
 
169
- pi.on("agent_settled", (_event, ctx) => {
391
+ pi.on("message_end", (_event, ctx) => {
392
+ refreshTitle(ctx)
393
+ })
394
+
395
+ pi.on("turn_end", (_event, ctx) => {
396
+ refreshTitle(ctx)
397
+ })
398
+
399
+ pi.on("agent_start", (_event, ctx) => {
400
+ if (runStartedAt === null) {
401
+ resetNeedsInput()
402
+ runStartedAt = Date.now()
403
+ }
404
+ refreshTitle(ctx)
405
+ })
406
+
407
+ pi.on("agent_settled", async (_event, ctx) => {
170
408
  cwd = ctx.cwd
171
- if (config?.events.agentSettled)
172
- void notify(notificationTitle(config.locale, "agentSettled"))
409
+ resetNeedsInput()
410
+ const startedAt = runStartedAt
411
+ runStartedAt = null
412
+ if (!config?.events.agentSettled) return
413
+
414
+ const elapsed = startedAt === null ? null : Date.now() - startedAt
415
+ if (elapsed !== null && elapsed < config.minDurationMs) return
416
+
417
+ const copy = COPY[config.locale]
418
+ const prefix =
419
+ config.title.source === "session"
420
+ ? `${
421
+ elapsed === null
422
+ ? copy.agentSettled
423
+ : copy.settledWithDuration(formatDuration(elapsed))
424
+ }\n`
425
+ : ""
426
+ // Await the request so a settle that races process shutdown still delivers.
427
+ await notify(
428
+ titleFor(ctx, "agentSettled"),
429
+ `${prefix}${formatBody(config, cwd)}`,
430
+ )
173
431
  })
174
432
 
175
433
  pi.on("session_shutdown", () => {
434
+ userFacingSession = false
435
+ resetNeedsInput()
176
436
  unsubscribeAskUser()
177
437
  })
178
438
  }