@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.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 (349) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +1 -1
  3. package/dist/cli.js +4685 -4614
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/model-registry.d.ts +3 -1
  10. package/dist/types/config/models-config-schema.d.ts +18 -6
  11. package/dist/types/config/models-config.d.ts +14 -5
  12. package/dist/types/config/provider-globals.d.ts +2 -2
  13. package/dist/types/config/service-tier.d.ts +5 -1
  14. package/dist/types/config/settings-schema.d.ts +374 -47
  15. package/dist/types/config/settings.d.ts +7 -0
  16. package/dist/types/dap/client.d.ts +20 -0
  17. package/dist/types/exec/bash-executor.d.ts +38 -0
  18. package/dist/types/exec/direnv.d.ts +33 -0
  19. package/dist/types/export/html/args.d.ts +15 -0
  20. package/dist/types/export/html/index.d.ts +11 -26
  21. package/dist/types/export/html/web-palette.d.ts +55 -116
  22. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  23. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  24. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
  25. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
  26. package/dist/types/live/attestation.d.ts +2 -0
  27. package/dist/types/live/controller.d.ts +48 -0
  28. package/dist/types/live/protocol.d.ts +85 -0
  29. package/dist/types/live/protocol.test.d.ts +1 -0
  30. package/dist/types/live/transport.d.ts +35 -0
  31. package/dist/types/live/visualizer.d.ts +30 -0
  32. package/dist/types/lsp/client.d.ts +16 -1
  33. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  34. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  35. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  36. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  37. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  38. package/dist/types/modes/components/assistant-message.d.ts +1 -0
  39. package/dist/types/modes/components/custom-message.d.ts +1 -1
  40. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  41. package/dist/types/modes/components/message-frame.d.ts +8 -4
  42. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  43. package/dist/types/modes/components/session-account-selector.d.ts +11 -0
  44. package/dist/types/modes/components/settings-defs.d.ts +7 -1
  45. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  46. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  47. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  48. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  49. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  50. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  51. package/dist/types/modes/interactive-mode.d.ts +6 -0
  52. package/dist/types/modes/theme/theme.d.ts +1 -1
  53. package/dist/types/modes/types.d.ts +4 -1
  54. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  55. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  56. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  57. package/dist/types/registry/agent-registry.d.ts +11 -4
  58. package/dist/types/sdk.d.ts +15 -1
  59. package/dist/types/secrets/index.d.ts +15 -2
  60. package/dist/types/secrets/obfuscator.d.ts +113 -15
  61. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  62. package/dist/types/session/agent-session-events.d.ts +84 -0
  63. package/dist/types/session/agent-session-types.d.ts +321 -0
  64. package/dist/types/session/agent-session.d.ts +135 -753
  65. package/dist/types/session/async-job-delivery.d.ts +28 -0
  66. package/dist/types/session/auth-storage.d.ts +1 -1
  67. package/dist/types/session/bash-runner.d.ts +75 -0
  68. package/dist/types/session/blob-store.d.ts +11 -1
  69. package/dist/types/session/blob-store.test.d.ts +1 -0
  70. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  71. package/dist/types/session/eval-runner.d.ts +51 -0
  72. package/dist/types/session/irc-bridge.d.ts +46 -0
  73. package/dist/types/session/messages.d.ts +24 -0
  74. package/dist/types/session/model-controls.d.ts +180 -0
  75. package/dist/types/session/prewalk.d.ts +46 -0
  76. package/dist/types/session/queued-messages.d.ts +22 -0
  77. package/dist/types/session/redis-session-storage.d.ts +1 -0
  78. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  79. package/dist/types/session/role-models.d.ts +15 -0
  80. package/dist/types/session/session-advisors.d.ts +228 -0
  81. package/dist/types/session/session-entries.d.ts +8 -0
  82. package/dist/types/session/session-handoff.d.ts +70 -0
  83. package/dist/types/session/session-history-format.d.ts +6 -1
  84. package/dist/types/session/session-maintenance.d.ts +246 -0
  85. package/dist/types/session/session-manager.d.ts +41 -0
  86. package/dist/types/session/session-memory.d.ts +56 -0
  87. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  88. package/dist/types/session/session-stats.d.ts +48 -0
  89. package/dist/types/session/session-tools.d.ts +158 -0
  90. package/dist/types/session/session-workspace.d.ts +32 -0
  91. package/dist/types/session/stream-guards.d.ts +44 -0
  92. package/dist/types/session/streaming-output.d.ts +8 -0
  93. package/dist/types/session/todo-tracker.d.ts +56 -0
  94. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  95. package/dist/types/session/turn-recovery.d.ts +225 -0
  96. package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
  97. package/dist/types/stt/index.d.ts +0 -2
  98. package/dist/types/stt/stt-controller.d.ts +7 -0
  99. package/dist/types/system-prompt.d.ts +2 -0
  100. package/dist/types/task/executor.d.ts +4 -1
  101. package/dist/types/task/output-manager.d.ts +3 -1
  102. package/dist/types/tiny/title-client.d.ts +10 -0
  103. package/dist/types/tools/approval.d.ts +1 -0
  104. package/dist/types/tools/browser/launch.d.ts +5 -0
  105. package/dist/types/tools/builtin-names.d.ts +1 -1
  106. package/dist/types/tools/computer/protocol.d.ts +43 -0
  107. package/dist/types/tools/computer/supervisor.d.ts +32 -0
  108. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  109. package/dist/types/tools/computer/worker.d.ts +15 -0
  110. package/dist/types/tools/computer-renderer.d.ts +22 -0
  111. package/dist/types/tools/computer.d.ts +71 -0
  112. package/dist/types/tools/context.d.ts +2 -0
  113. package/dist/types/tools/default-renderer.d.ts +21 -0
  114. package/dist/types/tools/image-gen.d.ts +4 -4
  115. package/dist/types/tools/image-providers.d.ts +38 -0
  116. package/dist/types/tools/index.d.ts +7 -0
  117. package/dist/types/tools/path-utils.d.ts +8 -0
  118. package/dist/types/tools/todo.d.ts +10 -4
  119. package/dist/types/tools/xdev.d.ts +5 -1
  120. package/dist/types/tts/streaming-player.d.ts +10 -29
  121. package/dist/types/tts/vocalizer.d.ts +5 -0
  122. package/dist/types/utils/jj.d.ts +29 -0
  123. package/dist/types/utils/lang-from-path.d.ts +1 -0
  124. package/dist/types/utils/title-generator.d.ts +30 -0
  125. package/dist/types/utils/tools-manager.d.ts +1 -2
  126. package/dist/types/vibe/runtime.d.ts +36 -8
  127. package/dist/types/web/search/index.d.ts +1 -1
  128. package/dist/types/web/search/provider.d.ts +15 -5
  129. package/dist/types/web/search/types.d.ts +94 -0
  130. package/package.json +12 -12
  131. package/scripts/generate-share-viewer.ts +4 -6
  132. package/src/advisor/__tests__/advisor.test.ts +643 -0
  133. package/src/advisor/runtime.ts +144 -43
  134. package/src/async/job-manager.ts +90 -2
  135. package/src/cli/args.ts +3 -0
  136. package/src/cli/auth-gateway-cli.ts +18 -3
  137. package/src/cli/bench-cli.ts +395 -29
  138. package/src/cli/flag-tables.ts +3 -0
  139. package/src/cli/gc-cli.ts +2 -2
  140. package/src/cli/setup-cli.ts +2 -14
  141. package/src/cli/usage-cli.ts +10 -1
  142. package/src/cli.ts +8 -0
  143. package/src/collab/host.ts +3 -2
  144. package/src/commands/bench.ts +18 -4
  145. package/src/commands/launch.ts +4 -0
  146. package/src/config/model-registry.ts +16 -2
  147. package/src/config/models-config-schema.ts +15 -4
  148. package/src/config/provider-globals.ts +9 -9
  149. package/src/config/service-tier.ts +26 -1
  150. package/src/config/settings-schema.ts +270 -51
  151. package/src/config/settings.ts +81 -2
  152. package/src/dap/client.ts +37 -4
  153. package/src/edit/index.ts +49 -12
  154. package/src/eval/executor-base.ts +1 -0
  155. package/src/eval/js/executor.ts +2 -0
  156. package/src/eval/py/executor.ts +103 -33
  157. package/src/exec/bash-executor.ts +90 -6
  158. package/src/exec/direnv.ts +145 -0
  159. package/src/export/html/args.ts +20 -0
  160. package/src/export/html/index.ts +45 -47
  161. package/src/export/html/template.css +19 -1
  162. package/src/export/html/template.html +6 -0
  163. package/src/export/html/template.js +21 -0
  164. package/src/export/html/tool-views.generated.js +31 -31
  165. package/src/export/html/web-palette.ts +116 -132
  166. package/src/export/share.ts +249 -49
  167. package/src/extensibility/extensions/loader.ts +29 -1
  168. package/src/extensibility/extensions/runner.ts +6 -0
  169. package/src/extensibility/extensions/types.ts +33 -2
  170. package/src/extensibility/extensions/wrapper.ts +68 -12
  171. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
  172. package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
  173. package/src/hindsight/state.ts +64 -6
  174. package/src/internal-urls/local-protocol.ts +2 -1
  175. package/src/internal-urls/memory-protocol.ts +2 -2
  176. package/src/internal-urls/skill-protocol.ts +2 -2
  177. package/src/internal-urls/ssh-protocol.ts +2 -1
  178. package/src/internal-urls/vault-protocol.ts +2 -1
  179. package/src/live/attestation.ts +91 -0
  180. package/src/live/controller.ts +517 -0
  181. package/src/live/prompts/agent-final-message.md +3 -0
  182. package/src/live/prompts/live-instructions.md +23 -0
  183. package/src/live/protocol.test.ts +140 -0
  184. package/src/live/protocol.ts +233 -0
  185. package/src/live/transport.ts +422 -0
  186. package/src/live/visualizer.ts +214 -0
  187. package/src/lsp/client.ts +29 -9
  188. package/src/lsp/index.ts +19 -4
  189. package/src/main.ts +8 -1
  190. package/src/mcp/config-writer.test.ts +43 -0
  191. package/src/mcp/config-writer.ts +109 -82
  192. package/src/mcp/oauth-discovery.ts +10 -2
  193. package/src/mcp/smithery-auth.test.ts +29 -0
  194. package/src/mcp/smithery-auth.ts +3 -2
  195. package/src/memories/index.ts +40 -20
  196. package/src/memory-backend/tool-names.ts +2 -0
  197. package/src/mnemopi/backend.ts +24 -7
  198. package/src/modes/acp/acp-agent.ts +56 -10
  199. package/src/modes/acp/acp-event-mapper.ts +8 -1
  200. package/src/modes/components/agent-hub.ts +1 -1
  201. package/src/modes/components/assistant-message.ts +7 -2
  202. package/src/modes/components/custom-message.ts +4 -1
  203. package/src/modes/components/login-dialog.ts +14 -4
  204. package/src/modes/components/message-frame.ts +14 -8
  205. package/src/modes/components/plan-review-overlay.ts +350 -35
  206. package/src/modes/components/session-account-selector.ts +62 -0
  207. package/src/modes/components/settings-defs.ts +23 -1
  208. package/src/modes/components/settings-selector.ts +169 -1
  209. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  210. package/src/modes/components/status-line/component.ts +123 -2
  211. package/src/modes/components/tool-execution.ts +28 -112
  212. package/src/modes/controllers/command-controller.ts +56 -15
  213. package/src/modes/controllers/event-controller.ts +111 -16
  214. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  215. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  216. package/src/modes/controllers/input-controller.ts +47 -39
  217. package/src/modes/controllers/live-command-controller.ts +255 -0
  218. package/src/modes/controllers/mcp-command-controller.ts +10 -1
  219. package/src/modes/controllers/omfg-controller.ts +44 -40
  220. package/src/modes/controllers/selector-controller.ts +80 -10
  221. package/src/modes/controllers/session-focus-controller.ts +6 -1
  222. package/src/modes/interactive-mode.ts +176 -38
  223. package/src/modes/runtime-init.ts +2 -0
  224. package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
  225. package/src/modes/theme/theme.ts +1 -1
  226. package/src/modes/types.ts +4 -1
  227. package/src/modes/utils/context-usage.ts +23 -7
  228. package/src/modes/utils/ui-helpers.ts +10 -5
  229. package/src/plan-mode/plan-files.ts +40 -0
  230. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  231. package/src/prompts/bench/cache-prefix.md +3 -0
  232. package/src/prompts/bench/cache-suffix.md +1 -0
  233. package/src/prompts/system/computer-safety.md +14 -0
  234. package/src/prompts/system/custom-system-prompt.md +1 -1
  235. package/src/prompts/system/project-prompt.md +8 -1
  236. package/src/prompts/system/subagent-async-pending.md +6 -0
  237. package/src/prompts/system/system-prompt.md +1 -1
  238. package/src/prompts/system/workflow-notice.md +48 -40
  239. package/src/prompts/system/xdev-mount-notice.md +4 -0
  240. package/src/prompts/tools/computer.md +26 -0
  241. package/src/prompts/tools/todo.md +3 -1
  242. package/src/registry/agent-lifecycle.ts +116 -46
  243. package/src/registry/agent-registry.ts +35 -8
  244. package/src/registry/persisted-agents.ts +26 -2
  245. package/src/sdk.ts +215 -136
  246. package/src/secrets/index.ts +127 -4
  247. package/src/secrets/obfuscator.ts +2328 -105
  248. package/src/session/acp-permission-gate.ts +165 -0
  249. package/src/session/agent-session-error-log.test.ts +1 -1
  250. package/src/session/agent-session-events.ts +66 -0
  251. package/src/session/agent-session-types.ts +343 -0
  252. package/src/session/agent-session.ts +1940 -12218
  253. package/src/session/artifacts.ts +6 -5
  254. package/src/session/async-job-delivery.ts +74 -0
  255. package/src/session/auth-storage.ts +1 -0
  256. package/src/session/bash-runner.ts +326 -0
  257. package/src/session/blob-store.test.ts +56 -0
  258. package/src/session/blob-store.ts +18 -2
  259. package/src/session/checkpoint-entries.ts +81 -0
  260. package/src/session/eval-runner.ts +217 -0
  261. package/src/session/indexed-session-storage.ts +9 -2
  262. package/src/session/irc-bridge.ts +203 -0
  263. package/src/session/messages.ts +266 -1
  264. package/src/session/model-controls.ts +714 -0
  265. package/src/session/prewalk.ts +252 -0
  266. package/src/session/queued-messages.ts +93 -0
  267. package/src/session/redis-session-storage.ts +51 -11
  268. package/src/session/retry-fallback-chains.ts +455 -0
  269. package/src/session/role-models.ts +85 -0
  270. package/src/session/session-advisors.ts +1679 -0
  271. package/src/session/session-context.ts +6 -3
  272. package/src/session/session-entries.ts +8 -0
  273. package/src/session/session-handoff.ts +308 -0
  274. package/src/session/session-history-format.ts +20 -9
  275. package/src/session/session-loader.ts +0 -46
  276. package/src/session/session-maintenance.ts +2983 -0
  277. package/src/session/session-manager.ts +390 -30
  278. package/src/session/session-memory.ts +222 -0
  279. package/src/session/session-provider-boundary.ts +307 -0
  280. package/src/session/session-stats.ts +293 -0
  281. package/src/session/session-tools.ts +980 -0
  282. package/src/session/session-workspace.ts +53 -0
  283. package/src/session/stream-guards.ts +417 -0
  284. package/src/session/streaming-output.ts +52 -5
  285. package/src/session/todo-tracker.ts +380 -0
  286. package/src/session/ttsr-coordinator.ts +496 -0
  287. package/src/session/turn-recovery.ts +1629 -0
  288. package/src/slash-commands/builtin-registry.ts +257 -20
  289. package/src/slash-commands/helpers/session-pin.ts +44 -0
  290. package/src/slash-commands/helpers/usage-report.ts +22 -3
  291. package/src/stt/downloader.ts +0 -2
  292. package/src/stt/index.ts +0 -2
  293. package/src/stt/stt-controller.ts +57 -146
  294. package/src/system-prompt.ts +45 -17
  295. package/src/task/executor.ts +285 -70
  296. package/src/task/index.ts +98 -105
  297. package/src/task/output-manager.ts +25 -3
  298. package/src/task/persisted-revive.ts +4 -3
  299. package/src/task/structured-subagent.ts +1 -0
  300. package/src/tiny/title-client.ts +22 -0
  301. package/src/tools/approval.ts +55 -10
  302. package/src/tools/bash-interactive.ts +116 -86
  303. package/src/tools/bash-skill-urls.ts +28 -2
  304. package/src/tools/bash.ts +339 -123
  305. package/src/tools/browser/launch.ts +9 -1
  306. package/src/tools/browser/tab-supervisor.ts +8 -5
  307. package/src/tools/browser.ts +94 -47
  308. package/src/tools/builtin-names.ts +1 -0
  309. package/src/tools/computer/protocol.ts +28 -0
  310. package/src/tools/computer/supervisor.ts +258 -0
  311. package/src/tools/computer/worker-entry.ts +25 -0
  312. package/src/tools/computer/worker.ts +135 -0
  313. package/src/tools/computer-renderer.ts +108 -0
  314. package/src/tools/computer.ts +433 -0
  315. package/src/tools/context.ts +2 -0
  316. package/src/tools/default-renderer.ts +139 -0
  317. package/src/tools/essential-tools.ts +1 -0
  318. package/src/tools/image-gen.ts +20 -28
  319. package/src/tools/image-providers.ts +50 -0
  320. package/src/tools/index.ts +14 -0
  321. package/src/tools/path-utils.ts +49 -2
  322. package/src/tools/read.ts +156 -88
  323. package/src/tools/renderers.ts +9 -1
  324. package/src/tools/todo.ts +101 -11
  325. package/src/tools/vibe.ts +1 -2
  326. package/src/tools/write.ts +51 -1
  327. package/src/tools/xdev.ts +121 -29
  328. package/src/tts/streaming-player.ts +82 -283
  329. package/src/tts/vocalizer.ts +18 -1
  330. package/src/utils/clipboard.ts +1 -30
  331. package/src/utils/jj.ts +125 -4
  332. package/src/utils/lang-from-path.ts +7 -0
  333. package/src/utils/mac-file-urls.applescript +37 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/utils/tool-choice.ts +14 -0
  336. package/src/utils/tools-manager.ts +1 -19
  337. package/src/vibe/runtime.ts +852 -72
  338. package/src/web/search/index.ts +3 -6
  339. package/src/web/search/provider.ts +31 -16
  340. package/src/web/search/providers/xai.ts +7 -1
  341. package/src/web/search/types.ts +3 -0
  342. package/dist/types/stt/recorder.d.ts +0 -30
  343. package/dist/types/stt/transcriber.d.ts +0 -14
  344. package/dist/types/stt/wav.d.ts +0 -29
  345. package/dist/types/tts/player.d.ts +0 -32
  346. package/src/stt/recorder.ts +0 -551
  347. package/src/stt/transcriber.ts +0 -60
  348. package/src/stt/wav.ts +0 -173
  349. package/src/tts/player.ts +0 -137
@@ -0,0 +1,145 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { $which, logger } from "@oh-my-pi/pi-utils";
4
+
5
+ /** Default cap on a single `direnv` invocation. The first export for a devenv
6
+ * `.envrc` can build a shell; callers may raise this via `bash.direnvLoadTimeoutMs`. */
7
+ export const DEFAULT_DIRENV_TIMEOUT_MS = 30_000;
8
+
9
+ /** Walk up from `startDir` to the nearest directory containing an `.envrc`. */
10
+ export async function findEnvrc(startDir: string): Promise<string | null> {
11
+ let dir = path.resolve(startDir);
12
+ for (;;) {
13
+ const candidate = path.join(dir, ".envrc");
14
+ try {
15
+ if ((await fs.stat(candidate)).isFile()) return candidate;
16
+ } catch {
17
+ // no .envrc here — keep walking up
18
+ }
19
+ const parent = path.dirname(dir);
20
+ if (parent === dir) return null;
21
+ dir = parent;
22
+ }
23
+ }
24
+
25
+ export interface DirenvExportDiff {
26
+ /** Variables direnv sets to a concrete value. */
27
+ set: Record<string, string>;
28
+ /** Variables direnv removes (JSON `null`). */
29
+ unset: string[];
30
+ }
31
+
32
+ /** Parse `direnv export json` output (`{VAR: value|null}`) into set/unset halves. */
33
+ export function parseDirenvExport(jsonText: string): DirenvExportDiff {
34
+ const trimmed = jsonText.trim();
35
+ if (trimmed.length === 0) return { set: {}, unset: [] };
36
+ let parsed: unknown;
37
+ try {
38
+ parsed = JSON.parse(trimmed);
39
+ } catch {
40
+ return { set: {}, unset: [] };
41
+ }
42
+ const set: Record<string, string> = {};
43
+ const unset: string[] = [];
44
+ if (parsed && typeof parsed === "object") {
45
+ for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {
46
+ if (value === null) unset.push(key);
47
+ else if (typeof value === "string") set[key] = value;
48
+ }
49
+ }
50
+ return { set, unset };
51
+ }
52
+
53
+ let direnvLookup: { bin: string | null } | undefined;
54
+ function direnvBinary(): string | null {
55
+ if (!direnvLookup) direnvLookup = { bin: $which("direnv") };
56
+ return direnvLookup.bin;
57
+ }
58
+
59
+ /** direnv computes its diff relative to the spawning env; strip any inherited
60
+ * direnv state so it loads the target `.envrc` from a clean baseline. */
61
+ function cleanSpawnEnv(): Record<string, string> {
62
+ const out: Record<string, string> = {};
63
+ for (const [key, value] of Object.entries(Bun.env)) {
64
+ if (value !== undefined && !key.startsWith("DIRENV_")) out[key] = value;
65
+ }
66
+ return out;
67
+ }
68
+
69
+ async function runDirenv(
70
+ bin: string,
71
+ args: string[],
72
+ cwd: string,
73
+ timeoutMs: number,
74
+ env: Record<string, string>,
75
+ signal?: AbortSignal,
76
+ ): Promise<{ exitCode: number; stdout: string; stderr: string }> {
77
+ // Bail on the caller's cancellation as well as the per-invocation cap so a
78
+ // cold `.envrc` load can't outlive an aborted / short-timeout bash call.
79
+ const abortSignal = signal
80
+ ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)])
81
+ : AbortSignal.timeout(timeoutMs);
82
+ const proc = Bun.spawn([bin, ...args], {
83
+ cwd,
84
+ env,
85
+ stdout: "pipe",
86
+ stderr: "pipe",
87
+ signal: abortSignal,
88
+ });
89
+ // Drain stdout AND stderr concurrently: a cold `use devenv`/Nix load emits
90
+ // enough diagnostics to fill the stderr pipe and block the child forever if
91
+ // only stdout is read (it would then wait out `timeoutMs`).
92
+ const [stdout, stderr] = await Promise.all([
93
+ new Response(proc.stdout as ReadableStream<Uint8Array>).text(),
94
+ new Response(proc.stderr as ReadableStream<Uint8Array>).text(),
95
+ ]);
96
+ const exitCode = await proc.exited;
97
+ return { exitCode, stdout, stderr };
98
+ }
99
+
100
+ /**
101
+ * Resolve the nearest `.envrc` from `cwd` and return its `direnv export` diff
102
+ * (variables to set, and variables direnv removes). Returns `null` when there
103
+ * is no `.envrc`, `direnv` is not installed, the `.envrc` is not on direnv's
104
+ * allow list, or the export fails/times out.
105
+ *
106
+ * direnv's own allow list is honored — an `.envrc` the user has not
107
+ * `direnv allow`ed is NEVER executed or auto-allowed. This keeps OMP's trust
108
+ * boundary identical to the user's own shell: cloning a repo with a poisoned
109
+ * `.envrc` grants it nothing until the user explicitly allows it.
110
+ *
111
+ * Always re-invokes `direnv export json` rather than serving a cached diff:
112
+ * direnv is fast once warm, and its own watch/mtime invalidation is the
113
+ * authoritative freshness signal (a content-hash cache here would go stale when
114
+ * a `watch_file` target changes without the `.envrc` text changing).
115
+ */
116
+ export async function loadDirenvEnv(
117
+ cwd: string,
118
+ opts?: { timeoutMs?: number; signal?: AbortSignal },
119
+ ): Promise<DirenvExportDiff | null> {
120
+ const envrcPath = await findEnvrc(cwd);
121
+ if (!envrcPath) return null;
122
+ const bin = direnvBinary();
123
+ if (!bin) return null;
124
+
125
+ const dir = path.dirname(envrcPath);
126
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_DIRENV_TIMEOUT_MS;
127
+ const env = cleanSpawnEnv();
128
+ try {
129
+ const { exitCode, stdout, stderr } = await runDirenv(bin, ["export", "json"], dir, timeoutMs, env, opts?.signal);
130
+ if (exitCode !== 0) {
131
+ // A not-yet-allowed .envrc is an expected steady state (the user opted
132
+ // out by never running `direnv allow`), not an error worth warning on.
133
+ if (stderr.includes("is blocked")) {
134
+ logger.debug("direnv .envrc not allowed; skipping", { dir });
135
+ } else {
136
+ logger.warn("direnv export failed", { dir, exitCode });
137
+ }
138
+ return null;
139
+ }
140
+ return parseDirenvExport(stdout);
141
+ } catch (err) {
142
+ logger.warn("direnv load failed", { dir, error: err instanceof Error ? err.message : String(err) });
143
+ return null;
144
+ }
145
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `/export` argument parsing, split from `./index.ts` so slash-command
3
+ * registries can parse arguments without eagerly loading the export module's
4
+ * embedded template/tool-view text.
5
+ */
6
+
7
+ /** Dark and light TUI theme names bundled into a dual-theme export. */
8
+ export interface ExportThemeNames {
9
+ dark: string;
10
+ light: string;
11
+ }
12
+
13
+ /** Parse `/export [--themes] [path]`; paths containing spaces were never supported. */
14
+ export function parseExportArgs(args: string): { outputPath?: string; useUserThemes: boolean } {
15
+ const parts = args.trim().split(/\s+/).filter(Boolean);
16
+ const useUserThemes = parts.includes("--themes");
17
+ const paths = parts.filter(part => part !== "--themes");
18
+ if (paths.length > 1) throw new Error("Usage: /export [--themes] [path]");
19
+ return { outputPath: paths[0], useUserThemes };
20
+ }
@@ -6,6 +6,7 @@ import { getResolvedThemeColors, getThemeExportColors } from "../../modes/theme/
6
6
  import type { SessionEntry, SessionHeader } from "../../session/session-entries";
7
7
  import { loadEntriesFromFile } from "../../session/session-loader";
8
8
  import { SessionManager } from "../../session/session-manager";
9
+ import type { ExportThemeNames } from "./args";
9
10
  import templateCss from "./template.css" with { type: "text" };
10
11
  import templateHtml from "./template.html" with { type: "text" };
11
12
  import templateJs from "./template.js" with { type: "text" };
@@ -14,6 +15,8 @@ import templateJs from "./template.js" with { type: "text" };
14
15
  import toolViewsJs from "./tool-views.generated.js" with { type: "text" };
15
16
  import { webExportThemeVars } from "./web-palette";
16
17
 
18
+ export { type ExportThemeNames, parseExportArgs } from "./args";
19
+
17
20
  let cachedTemplate: string | undefined;
18
21
 
19
22
  /** Compose the standalone export template: minified CSS, tool renderers, and viewer JS inlined. */
@@ -37,20 +40,12 @@ export function getTemplate(): string {
37
40
 
38
41
  export interface ExportOptions {
39
42
  outputPath?: string;
40
- /**
41
- * Which color palette the export ships with.
42
- * - `"web"` (default) — the omp brand identity (collab-web pink/purple),
43
- * so public HTML exports and the `/s/<id>` share viewer match the live
44
- * `my.omp.sh` client. See `web-palette.ts`.
45
- * - `"theme"` — derive from `themeName` (or the active TUI theme), preserving
46
- * the pre-15.12 behavior where an export mirrored the user's terminal.
47
- */
43
+ /** `"web"` bundles the omp web themes; `"theme"` bundles TUI themes. */
48
44
  palette?: "web" | "theme";
49
- /**
50
- * TUI theme to derive colors from when `palette: "theme"`. Ignored for the
51
- * default `"web"` palette. Resolves to the active TUI theme when omitted.
52
- */
45
+ /** Legacy single TUI theme name. Prefer `themeNames` for dual-theme exports. */
53
46
  themeName?: string;
47
+ /** Dark and light TUI themes to bundle when `palette` is `"theme"`. */
48
+ themeNames?: ExportThemeNames;
54
49
  /** Embed subagent session transcripts found next to the session file (default true). */
55
50
  includeSubSessions?: boolean;
56
51
  }
@@ -116,50 +111,49 @@ function deriveExportColors(baseColor: string): { pageBg: string; cardBg: string
116
111
  }
117
112
 
118
113
  /**
119
- * Generate CSS custom properties for the export `:root`.
120
- *
121
- * Two call shapes:
122
- * • `generateThemeVars("web" | "theme", themeName?)` — explicit palette.
123
- * `"web"` (the default for public artifacts) returns the fixed omp brand
124
- * palette from `web-palette.ts` — collab-web pink/purple identity, shared
125
- * with the live `my.omp.sh` client, so exports and the share viewer render
126
- * identically to it. `"theme"` derives from the TUI theme via
127
- * `getResolvedThemeColors(themeName)` plus the three
128
- * `export.{pageBg,cardBg,infoBg}` surface overrides.
129
- * • `generateThemeVars(themeName)` — legacy single-arg form: derive from the
130
- * named TUI theme. Kept so existing callers (and the theme-islight test)
131
- * keep working; equivalent to `generateThemeVars("theme", themeName)`.
114
+ * Generate CSS custom properties for one export theme.
132
115
  *
133
- * Exported for the share-viewer build script.
116
+ * The single-argument theme-name form remains available to callers that need
117
+ * one TUI palette. Standalone HTML uses `generateThemeStyles()` below.
134
118
  */
135
119
  export async function generateThemeVars(
136
120
  palette: "web" | "theme" | (string & {}) = "web",
137
121
  themeName?: string,
138
122
  ): Promise<string> {
139
- // Legacy single-arg form: `generateThemeVars("my-theme")` the first arg
140
- // is a theme name, not a palette. Route it to the themed path.
141
- if (palette !== "web" && palette !== "theme") {
142
- return generateThemeVars("theme", palette);
143
- }
144
- if (palette === "web") return webExportThemeVars();
123
+ if (palette !== "web" && palette !== "theme") return generateThemeVars("theme", palette);
124
+ if (palette === "web") return webExportThemeVars("dark");
145
125
 
146
126
  const colors = await getResolvedThemeColors(themeName);
147
- const lines: string[] = [];
148
- for (const key in colors) {
149
- lines.push(`--${key}: ${colors[key]};`);
150
- }
151
-
127
+ const lines = Object.entries(colors).map(([key, value]) => `--${key}: ${value};`);
152
128
  const themeExport = await getThemeExportColors(themeName);
153
- const userMessageBg = colors.userMessageBg || "#343541";
154
- const derived = deriveExportColors(userMessageBg);
129
+ const derived = deriveExportColors(colors.userMessageBg || "#343541");
155
130
 
156
131
  lines.push(`--body-bg: ${themeExport.pageBg ?? derived.pageBg};`);
157
132
  lines.push(`--container-bg: ${themeExport.cardBg ?? derived.cardBg};`);
158
133
  lines.push(`--info-bg: ${themeExport.infoBg ?? derived.infoBg};`);
159
-
160
134
  return lines.join(" ");
161
135
  }
162
136
 
137
+ /** Generate dark, light, and auto-following CSS rules for a standalone viewer. */
138
+ export async function generateThemeStyles(
139
+ palette: "web" | "theme",
140
+ themeNames?: ExportThemeNames,
141
+ legacyThemeName?: string,
142
+ ): Promise<string> {
143
+ const [dark, light] =
144
+ palette === "web"
145
+ ? [webExportThemeVars("dark"), webExportThemeVars("light")]
146
+ : await Promise.all([
147
+ generateThemeVars("theme", themeNames?.dark ?? legacyThemeName ?? "dark"),
148
+ generateThemeVars("theme", themeNames?.light ?? legacyThemeName ?? "light"),
149
+ ]);
150
+ return [
151
+ `:root, :root[data-theme="dark"] { color-scheme: dark; ${dark} }`,
152
+ `:root[data-theme="light"] { color-scheme: light; ${light} }`,
153
+ `@media (prefers-color-scheme: light) { :root:not([data-theme]) { color-scheme: light; ${light} } }`,
154
+ ].join("\n");
155
+ }
156
+
163
157
  /** Embedded subagent session transcript, keyed by slash-joined agent path in `SessionData.subSessions`. */
164
158
  export interface SubSession {
165
159
  /** Bare agent id (session file stem), e.g. "ToolAsk". */
@@ -240,15 +234,19 @@ async function collectSubSessionsFromDir(
240
234
  }
241
235
 
242
236
  /** Generate HTML from bundled template with runtime substitutions. */
243
- async function generateHtml(sessionData: SessionData, palette: "web" | "theme", themeName?: string): Promise<string> {
244
- const themeVars = await generateThemeVars(palette, themeName);
237
+ async function generateHtml(
238
+ sessionData: SessionData,
239
+ palette: "web" | "theme",
240
+ themeNames?: ExportThemeNames,
241
+ themeName?: string,
242
+ ): Promise<string> {
243
+ const themeStyles = await generateThemeStyles(palette, themeNames, themeName);
245
244
  const sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toBase64();
246
245
 
247
246
  // Use function replacements so `$'`, `$&`, `$$`, `$n`, etc. in the
248
- // substituted CSS/base64 are not interpreted as substitution patterns
249
- // (see https://mdn.io/String.replace).
247
+ // substituted CSS/base64 are not interpreted as substitution patterns.
250
248
  return getTemplate()
251
- .replace("<theme-vars/>", () => `<style>:root { ${themeVars} }</style>`)
249
+ .replace("<theme-vars/>", () => `<style>${themeStyles}</style>`)
252
250
  .replace("{{SESSION_DATA}}", () => sessionDataBase64);
253
251
  }
254
252
 
@@ -270,7 +268,7 @@ export async function exportSessionToHtml(
270
268
  }
271
269
 
272
270
  const palette = opts.palette ?? (opts.themeName ? "theme" : "web");
273
- const html = await generateHtml(sessionData, palette, opts.themeName);
271
+ const html = await generateHtml(sessionData, palette, opts.themeNames, opts.themeName);
274
272
  const outputPath = opts.outputPath || `${APP_NAME}-session-${path.basename(sessionFile, ".jsonl")}.html`;
275
273
 
276
274
  await Bun.write(outputPath, html);
@@ -300,7 +298,7 @@ export async function exportFromFile(inputPath: string, options?: ExportOptions
300
298
  }
301
299
 
302
300
  const palette = opts.palette ?? (opts.themeName ? "theme" : "web");
303
- const html = await generateHtml(sessionData, palette, opts.themeName);
301
+ const html = await generateHtml(sessionData, palette, opts.themeNames, opts.themeName);
304
302
  const outputPath = opts.outputPath || `${APP_NAME}-session-${path.basename(inputPath, ".jsonl")}.html`;
305
303
 
306
304
  await Bun.write(outputPath, html);
@@ -104,11 +104,14 @@
104
104
  }
105
105
 
106
106
  .sidebar-controls {
107
+ display: flex;
108
+ gap: 6px;
107
109
  padding: 0 0 6px;
108
110
  }
109
111
 
110
112
  .sidebar-search {
111
- width: 100%;
113
+ flex: 1;
114
+ min-width: 0;
112
115
  box-sizing: border-box;
113
116
  padding: 5px 9px;
114
117
  font-size: 11px;
@@ -138,6 +141,21 @@
138
141
  color: var(--muted);
139
142
  }
140
143
 
144
+ .theme-select {
145
+ padding: 5px 8px;
146
+ font: inherit;
147
+ font-size: 10px;
148
+ background: var(--body-bg);
149
+ color: var(--text);
150
+ border: 1px solid var(--hairline);
151
+ border-radius: var(--radius-ctl);
152
+ cursor: pointer;
153
+ }
154
+
155
+ .theme-select:hover {
156
+ border-color: var(--hairline-strong);
157
+ }
158
+
141
159
  .sidebar-filters {
142
160
  display: flex;
143
161
  gap: 4px;
@@ -8,6 +8,7 @@
8
8
  <title>Session Export</title>
9
9
  <template-css/>
10
10
  <theme-vars/>
11
+ <script>try{const t=localStorage.getItem("omp-export-theme");if(t==="light"||t==="dark")document.documentElement.dataset.theme=t}catch{}</script>
11
12
  </head>
12
13
  <body>
13
14
  <button id="hamburger" title="Open sidebar" aria-label="Open sidebar"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true"><path d="M3 6h18M3 12h18M3 18h18"/></svg></button>
@@ -17,6 +18,11 @@
17
18
  <div class="sidebar-header">
18
19
  <div class="sidebar-controls">
19
20
  <input type="text" class="sidebar-search" id="tree-search" placeholder="Search entries…" autocomplete="off" spellcheck="false">
21
+ <select class="theme-select" id="theme-select" title="Color theme" aria-label="Color theme">
22
+ <option value="auto">Auto</option>
23
+ <option value="light">Light</option>
24
+ <option value="dark">Dark</option>
25
+ </select>
20
26
  </div>
21
27
  <div class="sidebar-filters">
22
28
  <button class="filter-btn active" data-filter="default" title="Hide settings entries">Default</button>
@@ -1,6 +1,27 @@
1
1
  (function() {
2
2
  'use strict';
3
3
 
4
+ const THEME_STORAGE_KEY = 'omp-export-theme';
5
+ const themeSelect = document.getElementById('theme-select');
6
+ let themePreference = 'auto';
7
+ try {
8
+ const stored = localStorage.getItem(THEME_STORAGE_KEY);
9
+ if (stored === 'light' || stored === 'dark' || stored === 'auto') themePreference = stored;
10
+ } catch {}
11
+
12
+ function applyThemePreference(next) {
13
+ themePreference = next;
14
+ if (next === 'light' || next === 'dark') document.documentElement.dataset.theme = next;
15
+ else delete document.documentElement.dataset.theme;
16
+ if (themeSelect) themeSelect.value = next;
17
+ try {
18
+ localStorage.setItem(THEME_STORAGE_KEY, next);
19
+ } catch {}
20
+ }
21
+
22
+ applyThemePreference(themePreference);
23
+ if (themeSelect) themeSelect.addEventListener('change', () => applyThemePreference(themeSelect.value));
24
+
4
25
  // ============================================================
5
26
  // BOOT
6
27
  // ============================================================