@herbertgao/pi-extensions 2026.8.14 → 2026.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/README.md +19 -13
  2. package/THIRD_PARTY_NOTICES.md +26 -1
  3. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +8 -1
  4. package/node_modules/@czottmann/pi-automode/README.md +4 -0
  5. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +71 -9
  6. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  7. package/node_modules/@herbertgao/pi-bark/LICENSE +21 -0
  8. package/node_modules/@herbertgao/pi-bark/README.md +44 -0
  9. package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
  10. package/node_modules/@herbertgao/pi-bark/package.json +53 -0
  11. package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +20 -0
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +46 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +6 -0
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +5 -0
  17. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +34 -30
  18. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  19. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  20. package/node_modules/@narumitw/pi-btw/README.md +129 -88
  21. package/node_modules/@narumitw/pi-btw/dist/index.ts +283 -59
  22. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  23. package/node_modules/@narumitw/pi-btw/package.json +5 -8
  24. package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
  25. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +264 -24
  26. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
  27. package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
  28. package/node_modules/@narumitw/pi-btw/src/text.ts +18 -0
  29. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +5 -16
  30. package/node_modules/@narumitw/pi-caffeinate/LICENSE +21 -0
  31. package/node_modules/@narumitw/pi-caffeinate/README.md +224 -0
  32. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +1112 -0
  33. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +7 -0
  34. package/node_modules/@narumitw/pi-caffeinate/package.json +53 -0
  35. package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +811 -0
  36. package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +153 -0
  37. package/node_modules/@narumitw/pi-caffeinate/src/index.ts +1 -0
  38. package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +43 -0
  39. package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +167 -0
  40. package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +202 -0
  41. package/node_modules/@tifan/pi-handoff/README.md +39 -13
  42. package/node_modules/@tifan/pi-handoff/package.json +2 -2
  43. package/node_modules/@tifan/pi-handoff/src/index.ts +166 -5
  44. package/node_modules/@tifan/pi-recap/README.md +1 -1
  45. package/node_modules/@tifan/pi-recap/package.json +2 -2
  46. package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
  47. package/node_modules/@tifan/pi-rename/README.md +42 -21
  48. package/node_modules/@tifan/pi-rename/package.json +1 -1
  49. package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
  50. package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
  51. package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
  52. package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
  53. package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
  54. package/node_modules/pi-lens/CHANGELOG.md +320 -0
  55. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
  56. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
  57. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
  58. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
  59. package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
  60. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
  61. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
  62. package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
  63. package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
  64. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
  65. package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
  66. package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
  67. package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
  68. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
  69. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
  70. package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
  71. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
  72. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
  73. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
  74. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
  75. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
  76. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
  77. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
  93. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
  94. package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
  95. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
  96. package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
  97. package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
  98. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
  99. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
  100. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
  101. package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
  102. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
  103. package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
  104. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
  105. package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
  106. package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
  107. package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
  108. package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
  109. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  110. package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
  111. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
  112. package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
  113. package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
  114. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
  115. package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
  116. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
  117. package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
  118. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
  119. package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
  120. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
  121. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
  122. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
  123. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
  124. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
  125. package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
  126. package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
  127. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
  128. package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
  129. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
  130. package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
  131. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
  132. package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
  133. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
  134. package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
  135. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
  136. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
  137. package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
  138. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
  139. package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
  140. package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
  141. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
  142. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
  143. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
  144. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
  145. package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
  146. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
  147. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
  148. package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
  149. package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
  150. package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
  151. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
  152. package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
  153. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
  154. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
  155. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
  156. package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
  157. package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
  158. package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
  159. package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
  160. package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
  161. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
  162. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
  163. package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
  164. package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
  165. package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
  166. package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
  167. package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
  168. package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
  169. package/node_modules/pi-lens/dist/index.js +11243 -6651
  170. package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
  171. package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
  172. package/node_modules/pi-lens/dist/mcp/server.js +6 -1
  173. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
  174. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
  175. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
  176. package/node_modules/pi-lens/docs/agent-guide.md +4 -2
  177. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
  178. package/node_modules/pi-lens/docs/astplayground.md +26 -17
  179. package/node_modules/pi-lens/package.json +5 -1
  180. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
  181. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
  182. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
  183. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
  184. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
  185. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
  186. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
  187. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
  188. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
  189. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
  190. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
  191. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
  192. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
  193. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
  194. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
  195. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
  196. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
  197. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
  198. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
  199. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
  200. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
  201. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
  202. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
  203. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
  204. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
  205. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
  206. package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
  207. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
  208. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
  209. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
  210. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
  211. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
  212. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
  213. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
  214. package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
  215. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
  216. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
  217. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
  218. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
  219. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
  220. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
  221. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
  222. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
  223. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
  224. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
  225. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
  226. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
  227. package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
  228. package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
  229. package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
  230. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
  231. package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
  232. package/node_modules/pi-mcp-adapter/CHANGELOG.md +42 -0
  233. package/node_modules/pi-mcp-adapter/README.md +15 -15
  234. package/node_modules/pi-mcp-adapter/commands.ts +59 -22
  235. package/node_modules/pi-mcp-adapter/config.ts +18 -4
  236. package/node_modules/pi-mcp-adapter/direct-tools.ts +26 -6
  237. package/node_modules/pi-mcp-adapter/dist/config.d.ts +4 -0
  238. package/node_modules/pi-mcp-adapter/dist/config.js +13 -4
  239. package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
  240. package/node_modules/pi-mcp-adapter/dist/types.d.ts +12 -4
  241. package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
  242. package/node_modules/pi-mcp-adapter/error-signal.ts +15 -4
  243. package/node_modules/pi-mcp-adapter/errors.ts +141 -0
  244. package/node_modules/pi-mcp-adapter/host-html-template.ts +58 -5
  245. package/node_modules/pi-mcp-adapter/index.ts +2 -3
  246. package/node_modules/pi-mcp-adapter/init.ts +5 -0
  247. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +105 -32
  248. package/node_modules/pi-mcp-adapter/mcp-panel.ts +30 -9
  249. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +66 -28
  250. package/node_modules/pi-mcp-adapter/mcp-status.ts +2 -0
  251. package/node_modules/pi-mcp-adapter/package.json +2 -1
  252. package/node_modules/pi-mcp-adapter/proxy-modes.ts +88 -25
  253. package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +217 -0
  254. package/node_modules/pi-mcp-adapter/server-manager.ts +343 -6
  255. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +1 -0
  256. package/node_modules/pi-mcp-adapter/types.ts +19 -4
  257. package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +18 -2
  258. package/node_modules/pi-mcp-adapter/ui-server.ts +179 -54
  259. package/node_modules/pi-mcp-adapter/ui-session.ts +28 -8
  260. package/node_modules/pi-web-access/CHANGELOG.md +20 -0
  261. package/node_modules/pi-web-access/README.md +15 -2
  262. package/node_modules/pi-web-access/extract.ts +57 -12
  263. package/node_modules/pi-web-access/github-extract.ts +47 -3
  264. package/node_modules/pi-web-access/index.ts +1 -1
  265. package/node_modules/pi-web-access/package.json +1 -1
  266. package/node_modules/pi-web-access/page-query.ts +61 -8
  267. package/package.json +21 -17
  268. package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
  269. package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
  270. package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
@@ -0,0 +1,153 @@
1
+ import type { Message, MessageBus } from "dbus-native";
2
+
3
+ export const SCREENSAVER_BUS_NAME = "org.freedesktop.ScreenSaver";
4
+ export const SCREENSAVER_INTERFACE = "org.freedesktop.ScreenSaver";
5
+ export const INHIBIT_REASON = "Pi agent is running";
6
+
7
+ const INHIBIT_APPLICATION_NAME = "pi-caffeinate";
8
+ const SCREENSAVER_OBJECT_PATHS = ["/org/freedesktop/ScreenSaver", "/ScreenSaver"];
9
+ const DBUS_CALL_TIMEOUT_MS = 2_000;
10
+
11
+ type DbusFailureHandler = (error: Error) => void;
12
+
13
+ export interface DbusScreenSaverClient {
14
+ setFailureHandler(handler: DbusFailureHandler | undefined): void;
15
+ inhibit(reason: string, signal?: AbortSignal): Promise<void>;
16
+ uninhibit(): Promise<void>;
17
+ close(): Promise<void>;
18
+ }
19
+
20
+ export type DbusScreenSaverFactory = () => Promise<DbusScreenSaverClient>;
21
+
22
+ export async function defaultDbusScreenSaverFactory(): Promise<DbusScreenSaverClient> {
23
+ const { sessionBus } = await import("dbus-native");
24
+ return new NativeScreenSaverClient(sessionBus());
25
+ }
26
+
27
+ class NativeScreenSaverClient implements DbusScreenSaverClient {
28
+ private cookie?: number;
29
+ private objectPath?: string;
30
+ private failureHandler?: DbusFailureHandler;
31
+ private pendingFailure?: Error;
32
+ private lastConnectionError?: Error;
33
+ private closing = false;
34
+
35
+ private readonly handleConnectionError = (error: unknown) => {
36
+ this.lastConnectionError = toError(error, "D-Bus session connection failed");
37
+ if (this.cookie !== undefined) this.reportFailure(this.lastConnectionError);
38
+ };
39
+
40
+ private readonly handleConnectionClose = (error?: unknown) => {
41
+ if (this.closing || this.cookie === undefined) return;
42
+ this.reportFailure(
43
+ toError(error, this.lastConnectionError?.message ?? "D-Bus session connection closed"),
44
+ );
45
+ };
46
+
47
+ constructor(private readonly bus: MessageBus) {
48
+ this.bus.connection.on("error", this.handleConnectionError);
49
+ this.bus.connection.on("close", this.handleConnectionClose);
50
+ }
51
+
52
+ setFailureHandler(handler: DbusFailureHandler | undefined): void {
53
+ this.failureHandler = handler;
54
+ if (!handler || !this.pendingFailure) return;
55
+ const failure = this.pendingFailure;
56
+ this.pendingFailure = undefined;
57
+ handler(failure);
58
+ }
59
+
60
+ async inhibit(reason: string, signal?: AbortSignal): Promise<void> {
61
+ let lastError: unknown;
62
+ for (const objectPath of SCREENSAVER_OBJECT_PATHS) {
63
+ if (signal?.aborted) throw abortReason(signal);
64
+ try {
65
+ this.cookie = await invoke<number>(
66
+ this.bus,
67
+ {
68
+ destination: SCREENSAVER_BUS_NAME,
69
+ path: objectPath,
70
+ interface: SCREENSAVER_INTERFACE,
71
+ member: "Inhibit",
72
+ signature: "ss",
73
+ body: [INHIBIT_APPLICATION_NAME, reason],
74
+ },
75
+ signal,
76
+ );
77
+ this.objectPath = objectPath;
78
+ return;
79
+ } catch (error) {
80
+ if (signal?.aborted) throw abortReason(signal);
81
+ lastError = error;
82
+ }
83
+ }
84
+ throw new Error(`D-Bus idle inhibit failed: ${formatError(lastError)}`, {
85
+ cause: lastError,
86
+ });
87
+ }
88
+
89
+ async uninhibit(): Promise<void> {
90
+ if (this.cookie === undefined || !this.objectPath) return;
91
+ const cookie = this.cookie;
92
+ this.cookie = undefined;
93
+ const objectPath = this.objectPath;
94
+ this.objectPath = undefined;
95
+ await invoke<void>(this.bus, {
96
+ destination: SCREENSAVER_BUS_NAME,
97
+ path: objectPath,
98
+ interface: SCREENSAVER_INTERFACE,
99
+ member: "UnInhibit",
100
+ signature: "u",
101
+ body: [cookie],
102
+ });
103
+ }
104
+
105
+ async close(): Promise<void> {
106
+ this.closing = true;
107
+ this.cookie = undefined;
108
+ this.objectPath = undefined;
109
+ this.failureHandler = undefined;
110
+ try {
111
+ await this.bus.close();
112
+ } finally {
113
+ this.bus.connection.off("error", this.handleConnectionError);
114
+ this.bus.connection.off("close", this.handleConnectionClose);
115
+ }
116
+ }
117
+
118
+ private reportFailure(error: Error): void {
119
+ if (this.cookie === undefined) return;
120
+ this.cookie = undefined;
121
+ this.objectPath = undefined;
122
+ if (this.failureHandler) this.failureHandler(error);
123
+ else this.pendingFailure = error;
124
+ }
125
+ }
126
+
127
+ function invoke<TResult>(
128
+ bus: MessageBus,
129
+ message: Message,
130
+ signal?: AbortSignal,
131
+ ): Promise<TResult> {
132
+ return new Promise<TResult>((resolve, reject) => {
133
+ bus.invoke(message, { signal, timeout: DBUS_CALL_TIMEOUT_MS }, (error, ...values) => {
134
+ if (error) reject(error);
135
+ else resolve(values[0] as TResult);
136
+ });
137
+ });
138
+ }
139
+
140
+ function abortReason(signal: AbortSignal): unknown {
141
+ return signal.reason ?? new DOMException("D-Bus idle inhibit cancelled", "AbortError");
142
+ }
143
+
144
+ function toError(error: unknown, fallback: string): Error {
145
+ return error instanceof Error ? error : new Error(fallback);
146
+ }
147
+
148
+ function formatError(error: unknown) {
149
+ if (error instanceof Error) {
150
+ return error.name === "Error" ? error.message : `${error.name}: ${error.message}`;
151
+ }
152
+ return String(error);
153
+ }
@@ -0,0 +1 @@
1
+ export { default } from "./caffeinate.js";
@@ -0,0 +1,43 @@
1
+ import { type ChildProcess, spawn } from "node:child_process";
2
+ import process from "node:process";
3
+ import type { InhibitorCommand } from "./inhibitors.js";
4
+
5
+ export function startInhibitorProcess(
6
+ command: InhibitorCommand,
7
+ onError: (error: Error) => void,
8
+ onUnexpectedExit: (description: string) => void,
9
+ ): ChildProcess {
10
+ const child = spawn(command.command, command.args, {
11
+ detached: false,
12
+ stdio: [command.releaseOnStdinClose ? "pipe" : "ignore", "pipe", "pipe"],
13
+ });
14
+ child.once("error", onError);
15
+ child.once("exit", (code, signal) => onUnexpectedExit(formatExit(code, signal)));
16
+ return child;
17
+ }
18
+
19
+ export function stopInhibitorProcess(child: ChildProcess, command: InhibitorCommand | undefined) {
20
+ child.removeAllListeners("exit");
21
+ child.removeAllListeners("error");
22
+ if (child.killed) return;
23
+
24
+ if (command?.releaseOnStdinClose && child.stdin && !child.stdin.destroyed) {
25
+ child.stdin.end();
26
+ if (child.exitCode === null && child.signalCode === null) {
27
+ const killTimer = setTimeout(() => {
28
+ if (child.exitCode === null && child.signalCode === null && !child.killed) child.kill();
29
+ }, 2000);
30
+ killTimer.unref();
31
+ child.once("exit", () => clearTimeout(killTimer));
32
+ }
33
+ } else if (process.platform === "win32") {
34
+ child.kill();
35
+ } else {
36
+ child.kill("SIGTERM");
37
+ }
38
+ }
39
+
40
+ function formatExit(code: number | null, signal: NodeJS.Signals | null) {
41
+ if (signal) return `signal ${signal}`;
42
+ return `code ${code ?? "unknown"}`;
43
+ }
@@ -0,0 +1,167 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import type { CaffeinateMode } from "./settings.js";
5
+
6
+ export interface InhibitorCommand {
7
+ command: string;
8
+ args: string[];
9
+ description: string;
10
+ releaseOnStdinClose?: boolean;
11
+ custom?: boolean;
12
+ /** Display mode also needs D-Bus because logind idle inhibition misses some compositors. */
13
+ addDbusIdleInhibit?: boolean;
14
+ }
15
+
16
+ export function getInhibitorCommand(mode: CaffeinateMode): InhibitorCommand | undefined {
17
+ const customCommand = process.env.PI_CAFFEINATE_COMMAND?.trim();
18
+ if (customCommand) {
19
+ const [command, ...args] = splitCommand(customCommand);
20
+ if (command) return { command, args, description: `custom command (${command})`, custom: true };
21
+ }
22
+ if (process.platform === "darwin") {
23
+ return parentBoundUnixCommand(
24
+ "caffeinate",
25
+ macCaffeinateArgs(mode),
26
+ caffeinateDescription(mode),
27
+ );
28
+ }
29
+ if (process.platform === "linux") {
30
+ if (isWsl() && commandExists("powershell.exe")) {
31
+ return windowsPowerInhibitorCommand("powershell.exe", mode);
32
+ }
33
+ if (commandExists("systemd-inhibit")) {
34
+ const what = mode === "display" ? "idle:sleep" : "sleep";
35
+ return parentBoundUnixCommand(
36
+ "systemd-inhibit",
37
+ [
38
+ `--what=${what}`,
39
+ "--who=pi-caffeinate",
40
+ "--why=Pi agent is running",
41
+ "--mode=block",
42
+ "sleep",
43
+ "infinity",
44
+ ],
45
+ `systemd-inhibit (${formatMode(mode)})`,
46
+ mode === "display",
47
+ );
48
+ }
49
+ if (commandExists("caffeinate")) {
50
+ return parentBoundUnixCommand(
51
+ "caffeinate",
52
+ macCaffeinateArgs(mode),
53
+ caffeinateDescription(mode),
54
+ mode === "display",
55
+ );
56
+ }
57
+ }
58
+ if (process.platform === "win32") return windowsPowerInhibitorCommand("powershell.exe", mode);
59
+ return undefined;
60
+ }
61
+
62
+ function macCaffeinateArgs(mode: CaffeinateMode) {
63
+ return mode === "sleep" ? ["-ims"] : ["-dimsu"];
64
+ }
65
+
66
+ function caffeinateDescription(mode: CaffeinateMode) {
67
+ return `caffeinate (${formatMode(mode)})`;
68
+ }
69
+
70
+ function parentBoundUnixCommand(
71
+ command: string,
72
+ args: string[],
73
+ description: string,
74
+ addDbusIdleInhibit = false,
75
+ ): InhibitorCommand {
76
+ return {
77
+ command: "sh",
78
+ args: [
79
+ "-c",
80
+ unixParentBoundScript(),
81
+ "pi-caffeinate-watch",
82
+ String(process.pid),
83
+ command,
84
+ ...args,
85
+ ],
86
+ description,
87
+ ...(addDbusIdleInhibit ? { addDbusIdleInhibit: true } : {}),
88
+ };
89
+ }
90
+
91
+ function unixParentBoundScript() {
92
+ return `parent=$1; shift; "$@" & child=$!; ( while kill -0 "$parent" 2>/dev/null; do sleep 5; done; kill "$child" 2>/dev/null ) & watcher=$!; cleanup() { kill "$watcher" 2>/dev/null; kill "$child" 2>/dev/null; wait "$child" 2>/dev/null; }; trap 'cleanup; exit 0' INT TERM HUP EXIT; wait "$child"; status=$?; kill "$watcher" 2>/dev/null; trap - EXIT; exit "$status"`;
93
+ }
94
+
95
+ function commandExists(command: string) {
96
+ const searchPath = process.env.PATH ?? "";
97
+ const extensions = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
98
+ for (const directory of searchPath.split(process.platform === "win32" ? ";" : ":")) {
99
+ if (!directory) continue;
100
+ for (const extension of extensions) {
101
+ if (existsSync(path.join(directory, `${command}${extension}`))) return true;
102
+ }
103
+ }
104
+ return false;
105
+ }
106
+
107
+ export function splitCommand(input: string) {
108
+ const parts: string[] = [];
109
+ let current = "";
110
+ let quote: '"' | "'" | undefined;
111
+ let escaping = false;
112
+ for (const char of input) {
113
+ if (escaping) {
114
+ current += char;
115
+ escaping = false;
116
+ continue;
117
+ }
118
+ if (char === "\\") {
119
+ escaping = true;
120
+ continue;
121
+ }
122
+ if ((char === '"' || char === "'") && !quote) {
123
+ quote = char;
124
+ continue;
125
+ }
126
+ if (char === quote) {
127
+ quote = undefined;
128
+ continue;
129
+ }
130
+ if (/\s/.test(char) && !quote) {
131
+ if (current) {
132
+ parts.push(current);
133
+ current = "";
134
+ }
135
+ continue;
136
+ }
137
+ current += char;
138
+ }
139
+ if (current) parts.push(current);
140
+ return parts;
141
+ }
142
+
143
+ function windowsPowerInhibitorCommand(command: string, mode: CaffeinateMode): InhibitorCommand {
144
+ return {
145
+ command,
146
+ args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", windowsInhibitorScript(mode)],
147
+ description: `PowerShell SetThreadExecutionState (${formatMode(mode)})`,
148
+ releaseOnStdinClose: true,
149
+ };
150
+ }
151
+
152
+ export function windowsInhibitorScript(mode: CaffeinateMode) {
153
+ const flags = mode === "sleep" ? "0x80000001" : "0x80000003";
154
+ return `$ErrorActionPreference = 'Stop'; Add-Type -Namespace Native -Name Power -MemberDefinition '[DllImport("kernel32.dll")] public static extern uint SetThreadExecutionState(uint esFlags);'; $flags = [uint32]'${flags}'; $release = [uint32]'0x80000000'; $stdin = [Console]::OpenStandardInput(); $buffer = New-Object byte[] 1; $readTask = $stdin.ReadAsync($buffer, 0, 1); try { while ($true) { [Native.Power]::SetThreadExecutionState($flags) | Out-Null; if ($readTask.Wait(30000)) { break } } } finally { [Native.Power]::SetThreadExecutionState($release) | Out-Null }`;
155
+ }
156
+
157
+ function isWsl() {
158
+ try {
159
+ return existsSync("/proc/sys/fs/binfmt_misc/WSLInterop");
160
+ } catch {
161
+ return false;
162
+ }
163
+ }
164
+
165
+ export function formatMode(mode: CaffeinateMode) {
166
+ return mode === "sleep" ? "system-awake" : "display-awake";
167
+ }
@@ -0,0 +1,202 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import { access, lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+ import process from "node:process";
6
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
7
+
8
+ const NEW_SETTINGS_FILE = "pi-caffeinate.json";
9
+ const LEGACY_SETTINGS_FILE = "pi-caffeinate-settings.json";
10
+
11
+ export type CaffeinateMode = "sleep" | "display";
12
+
13
+ export interface CaffeinateSettings {
14
+ mode: CaffeinateMode;
15
+ quiet: boolean;
16
+ updatedAt: number;
17
+ }
18
+
19
+ export interface SettingsFileOperations {
20
+ write(path: string, data: string): Promise<void>;
21
+ rename(source: string, destination: string): Promise<void>;
22
+ }
23
+
24
+ const DEFAULT_FILE_OPERATIONS: SettingsFileOperations = {
25
+ write: (path, data) => writeFile(path, data, "utf8").then(() => undefined),
26
+ rename,
27
+ };
28
+
29
+ export type SettingsLoadResult =
30
+ | { kind: "missing"; notice?: string }
31
+ | { kind: "invalid"; reason: string; notice?: string }
32
+ | { kind: "loaded"; settings: CaffeinateSettings; notice?: string };
33
+
34
+ export async function loadSettings(): Promise<SettingsLoadResult> {
35
+ await settingsSaveQueue;
36
+ const newPath = settingsFilePath();
37
+ const newSettings = await readSettingsFile(newPath);
38
+ if (newSettings.kind !== "missing") return withLegacyIgnoredNotice(newSettings);
39
+
40
+ const legacyPath = legacySettingsFilePath();
41
+ const legacySettings = await readSettingsFile(legacyPath);
42
+ const concurrentlyCreatedSettings = await readSettingsFile(newPath);
43
+ if (concurrentlyCreatedSettings.kind !== "missing") {
44
+ return withLegacyIgnoredNotice(concurrentlyCreatedSettings);
45
+ }
46
+ if (legacySettings.kind === "missing") return { kind: "missing" };
47
+ if (legacySettings.kind === "invalid") return legacySettings;
48
+
49
+ return {
50
+ ...legacySettings,
51
+ notice: `Using legacy ${LEGACY_SETTINGS_FILE}; rename it to ${NEW_SETTINGS_FILE}. Future saves write ${NEW_SETTINGS_FILE} without modifying the legacy file.`,
52
+ };
53
+ }
54
+
55
+ interface SettingsDocumentResult {
56
+ result: SettingsLoadResult;
57
+ document?: Record<string, unknown>;
58
+ }
59
+
60
+ async function readSettingsDocument(filePath: string): Promise<SettingsDocumentResult> {
61
+ let text: string;
62
+ try {
63
+ text = await readFile(filePath, "utf8");
64
+ } catch (error) {
65
+ if (isNodeError(error) && error.code === "ENOENT") return { result: { kind: "missing" } };
66
+ return { result: { kind: "invalid", reason: `${filePath}: ${formatError(error)}` } };
67
+ }
68
+ try {
69
+ const document = JSON.parse(text) as unknown;
70
+ const settings = normalizeCaffeinateSettings(document);
71
+ if (settings) {
72
+ return {
73
+ result: { kind: "loaded", settings },
74
+ document: { ...(document as Record<string, unknown>) },
75
+ };
76
+ }
77
+ return {
78
+ result: {
79
+ kind: "invalid",
80
+ reason: `${filePath}: expected { "mode": "sleep" | "display", optional "quiet": boolean }`,
81
+ },
82
+ };
83
+ } catch (error) {
84
+ return { result: { kind: "invalid", reason: `${filePath}: ${formatError(error)}` } };
85
+ }
86
+ }
87
+
88
+ async function readSettingsFile(filePath: string): Promise<SettingsLoadResult> {
89
+ return (await readSettingsDocument(filePath)).result;
90
+ }
91
+
92
+ async function withLegacyIgnoredNotice(settings: SettingsLoadResult): Promise<SettingsLoadResult> {
93
+ if (!(await fileExists(legacySettingsFilePath()))) return settings;
94
+ return {
95
+ ...settings,
96
+ notice: `pi-caffeinate legacy settings ignored: ${legacySettingsFilePath()} exists, but ${settingsFilePath()} takes precedence. Delete ${LEGACY_SETTINGS_FILE} after confirming your settings.`,
97
+ };
98
+ }
99
+
100
+ async function fileExists(filePath: string) {
101
+ try {
102
+ await access(filePath, constants.F_OK);
103
+ return true;
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ async function pathEntryExists(filePath: string) {
110
+ try {
111
+ await lstat(filePath);
112
+ return true;
113
+ } catch (error) {
114
+ if (isNodeError(error) && error.code === "ENOENT") return false;
115
+ throw error;
116
+ }
117
+ }
118
+
119
+ export function normalizeCaffeinateSettings(value: unknown): CaffeinateSettings | undefined {
120
+ if (!value || typeof value !== "object") return undefined;
121
+ const settings = value as { mode?: unknown; quiet?: unknown; updatedAt?: unknown };
122
+ if (!isCaffeinateMode(settings.mode)) return undefined;
123
+ if (settings.quiet !== undefined && typeof settings.quiet !== "boolean") return undefined;
124
+ if (settings.updatedAt !== undefined && typeof settings.updatedAt !== "number") return undefined;
125
+ return {
126
+ mode: settings.mode,
127
+ quiet: settings.quiet ?? false,
128
+ updatedAt: settings.updatedAt ?? 0,
129
+ };
130
+ }
131
+
132
+ function isCaffeinateMode(value: unknown): value is CaffeinateMode {
133
+ return value === "sleep" || value === "display";
134
+ }
135
+
136
+ let settingsSaveQueue: Promise<unknown> = Promise.resolve();
137
+
138
+ export function saveSettings(
139
+ settings: Omit<CaffeinateSettings, "quiet"> & { quiet?: boolean },
140
+ operations: Partial<SettingsFileOperations> = {},
141
+ ): Promise<CaffeinateSettings> {
142
+ const operation = settingsSaveQueue.then(() => saveSettingsNow(settings, operations));
143
+ settingsSaveQueue = operation.catch(() => undefined);
144
+ return operation;
145
+ }
146
+
147
+ async function saveSettingsNow(
148
+ settings: Omit<CaffeinateSettings, "quiet"> & { quiet?: boolean },
149
+ operations: Partial<SettingsFileOperations>,
150
+ ): Promise<CaffeinateSettings> {
151
+ const filePath = settingsFilePath();
152
+ let current = await readSettingsDocument(filePath);
153
+ const replaceCanonical = current.result.kind !== "missing";
154
+ if (!replaceCanonical) current = await readSettingsDocument(legacySettingsFilePath());
155
+ if (current.result.kind === "invalid") {
156
+ throw new Error(`Cannot save pi-caffeinate settings until you repair ${current.result.reason}`);
157
+ }
158
+ const document = current.document ?? {};
159
+ const quiet =
160
+ settings.quiet ?? (current.result.kind === "loaded" ? current.result.settings.quiet : false);
161
+ const nextSettings = { mode: settings.mode, quiet, updatedAt: settings.updatedAt };
162
+ const nextDocument = {
163
+ ...document,
164
+ ...nextSettings,
165
+ };
166
+ await mkdir(dirname(filePath), { recursive: true });
167
+ const tempFile = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
168
+ try {
169
+ await (operations.write ?? DEFAULT_FILE_OPERATIONS.write)(
170
+ tempFile,
171
+ `${JSON.stringify(nextDocument, null, 2)}\n`,
172
+ );
173
+ if (!replaceCanonical && (await pathEntryExists(filePath))) {
174
+ throw new Error(`${NEW_SETTINGS_FILE} was created concurrently; reopen settings and retry.`);
175
+ }
176
+ await (operations.rename ?? DEFAULT_FILE_OPERATIONS.rename)(tempFile, filePath);
177
+ return nextSettings;
178
+ } catch (error) {
179
+ await rm(tempFile, { force: true }).catch(() => undefined);
180
+ throw error;
181
+ }
182
+ }
183
+
184
+ export function settingsFilePath() {
185
+ return join(agentDir(), NEW_SETTINGS_FILE);
186
+ }
187
+
188
+ function legacySettingsFilePath() {
189
+ return join(agentDir(), LEGACY_SETTINGS_FILE);
190
+ }
191
+
192
+ function agentDir() {
193
+ return getAgentDir();
194
+ }
195
+
196
+ function isNodeError(error: unknown): error is NodeJS.ErrnoException {
197
+ return error instanceof Error && "code" in error;
198
+ }
199
+
200
+ function formatError(error: unknown) {
201
+ return error instanceof Error ? error.message : String(error);
202
+ }
@@ -1,6 +1,6 @@
1
1
  # @tifan/pi-handoff
2
2
 
3
- Start a fresh pi session from a handoff document, and query past sessions for context, decisions, or code changes.
3
+ Start a new pi session from a handoff document. The new session can use the `session_query` tool to look up decisions, files, and next steps from earlier sessions.
4
4
 
5
5
  ## Install
6
6
 
@@ -10,33 +10,59 @@ pi install npm:@tifan/pi-handoff
10
10
 
11
11
  ## Requirements
12
12
 
13
- Matt Pocock's [`handoff` skill](https://github.com/mattpocock/skills/blob/main/skills/productivity/handoff/SKILL.md) is required:
13
+ Install Matt Pocock's `handoff` skill:
14
14
 
15
15
  ```bash
16
16
  npx skills add https://github.com/mattpocock/skills --global --skill handoff
17
17
  ```
18
18
 
19
- ## How it works
20
-
21
- Submit a prompt containing a standalone `-handoff` marker to generate a handoff document from the current session and automatically start a clean session from it. The marker can appear anywhere in the prompt. The handoff is written under the OS temp directory.
22
-
23
- The new session name uses the current session name when available. Otherwise, it uses the next-session focus, sanitized to lowercase, hyphen-separated text under 60 characters.
24
-
25
- The handoff includes the previous session path when available, so the next agent can use `session_query` if the handoff omits a detail.
26
-
27
19
  ## Usage
28
20
 
21
+ Add a standalone `-handoff` marker to a prompt:
22
+
29
23
  ```text
30
24
  fix the auth flow -handoff
31
25
  ```
32
26
 
33
- The standalone `-handoff` marker is consumed before the prompt reaches the agent. The text before and after it becomes the next-session focus. A prompt containing only `-handoff` continues the current work.
27
+ The marker is consumed before the prompt reaches the agent.
28
+
29
+ - Text around the marker becomes the next-session focus.
30
+ - The focus is used in the handoff document.
31
+ - A handoff document is written under the OS temp directory.
32
+ - A new session starts automatically from the document.
33
+ - When Pi runs inside Herdr, the handoff opens in a new tab without changing focus. The current parent session stays available in its original tab.
34
+ - The handoff session keeps the parent-session link and selected provider/model when available.
35
+ - Outside Herdr, the current session is replaced.
36
+
37
+ A prompt containing only `-handoff` continues the current work.
38
+
39
+ ## Session naming
40
+
41
+ `pi-handoff` imports the shared naming code from `@tifan/pi-rename`. Installing pi-handoff installs this dependency automatically, but it does not enable pi-rename's commands.
42
+
43
+ Handoff uses the same naming logic as `/rename`:
44
+
45
+ - Uses the first user message and up to three latest user messages.
46
+ - Reads the model from `$PI_CODING_AGENT_DIR/extensions/pi-rename.json`.
47
+ - Uses `openai-codex/gpt-5.6-luna` when no config exists.
48
+ - Produces a name such as `[handoff] fix-auth-callback`.
49
+ - Keeps the generated base name under 30 characters before adding the prefix.
50
+ - Falls back to the latest user message if the naming model is unavailable.
51
+ - Uses `[handoff] handoff-session` when no usable user text exists.
52
+
53
+ The handoff artifact filename uses the unprefixed base name. If you run `/rename` in the new session, it replaces the complete name and can remove the `[handoff]` prefix.
54
+
55
+ Install `@tifan/pi-rename` separately only if you want its `/rename` and `/rename config` commands.
34
56
 
35
57
  ## `session_query`
36
58
 
37
- `session_query`: Answer a question about a previous pi session, given the full path to its `.jsonl` file and the question to ask. It uses the configured `openai-codex/gpt-5.6-luna` model, which must be available and authenticated.
59
+ `session_query` answers questions about a previous pi session:
60
+
61
+ - Reads the previous `.jsonl` session file.
62
+ - Uses the `openai-codex/gpt-5.6-luna` model.
63
+ - Requires that model to be available and authenticated.
38
64
 
39
- Pi provides session loading and compaction-aware context reconstruction, but not semantic questions about an arbitrary previous session. `session_query` supplies that model-powered query layer.
65
+ Pi provides session loading and compaction-aware context reconstruction. `session_query` provides semantic answers about the previous session.
40
66
 
41
67
  ## Release notes
42
68
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tifan/pi-handoff",
3
- "version": "2.0.1",
3
+ "version": "2.2.1",
4
4
  "description": "Transfer pi session context to a new session and query past sessions.",
5
5
  "keywords": [
6
6
  "handoff",
@@ -29,7 +29,7 @@
29
29
  "provenance": true
30
30
  },
31
31
  "dependencies": {
32
- "@tifan/pi-rename": "^0.5.0"
32
+ "@tifan/pi-rename": "^0.6.0"
33
33
  },
34
34
  "engines": {
35
35
  "node": ">=20"