@pi-unipi/unipi 3.0.0-alpha.2 → 3.0.0-alpha.21

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 (359) hide show
  1. package/CHANGELOG.md +292 -0
  2. package/README.md +8 -11
  3. package/docs/prefix-cache-architecture.md +14 -0
  4. package/package.json +22 -24
  5. package/packages/ask-user/README.md +55 -107
  6. package/packages/ask-user/ask-ui.ts +306 -732
  7. package/packages/ask-user/clipboard.ts +50 -0
  8. package/packages/ask-user/config.ts +74 -78
  9. package/packages/ask-user/package.json +2 -2
  10. package/packages/ask-user/questions.ts +151 -0
  11. package/packages/ask-user/skills/ask-user/SKILL.md +59 -133
  12. package/packages/ask-user/tools.ts +198 -432
  13. package/packages/autocomplete/package.json +1 -1
  14. package/packages/autocomplete/src/constants.ts +38 -43
  15. package/packages/autocomplete/src/sorting.ts +8 -0
  16. package/packages/background-tasks/package.json +2 -2
  17. package/packages/background-tasks/src/config.ts +0 -73
  18. package/packages/background-tasks/src/index.ts +20 -16
  19. package/packages/background-tasks/src/registry.ts +1 -171
  20. package/packages/background-tasks/src/types.ts +0 -32
  21. package/packages/btw/README.md +13 -56
  22. package/packages/btw/extensions/btw.ts +403 -1796
  23. package/packages/btw/package.json +5 -2
  24. package/packages/btw/skills/btw/SKILL.md +7 -143
  25. package/packages/compactor/README.md +56 -287
  26. package/packages/compactor/package.json +5 -9
  27. package/packages/compactor/skills/compactor/SKILL.md +13 -20
  28. package/packages/compactor/src/card.ts +90 -0
  29. package/packages/compactor/src/commands/index.ts +73 -130
  30. package/packages/compactor/src/compaction/compact-args.ts +5 -3
  31. package/packages/compactor/src/compaction/filter-noise.ts +1 -1
  32. package/packages/compactor/src/compaction/hooks.ts +487 -311
  33. package/packages/compactor/src/compaction/source.ts +178 -0
  34. package/packages/compactor/src/compaction/summarize.ts +586 -52
  35. package/packages/compactor/src/config/manager.ts +130 -127
  36. package/packages/compactor/src/config/schema.ts +32 -48
  37. package/packages/compactor/src/index.ts +40 -431
  38. package/packages/compactor/src/info-screen.ts +44 -190
  39. package/packages/compactor/src/session/recall-blocks.ts +1 -1
  40. package/packages/compactor/src/stats.ts +76 -0
  41. package/packages/compactor/src/tools/context-budget.ts +17 -34
  42. package/packages/compactor/src/tools/ctx-doctor.ts +45 -36
  43. package/packages/compactor/src/tools/register.ts +29 -269
  44. package/packages/compactor/src/types.ts +47 -186
  45. package/packages/core/README.md +25 -0
  46. package/packages/core/child-agent.ts +1 -0
  47. package/packages/core/command-echo.ts +63 -0
  48. package/packages/core/compaction-context.ts +41 -0
  49. package/packages/core/constants.ts +8 -38
  50. package/packages/core/events.ts +0 -53
  51. package/packages/core/fusion-status.ts +4 -0
  52. package/packages/core/global.d.ts +10 -0
  53. package/packages/core/harness-messages.ts +392 -0
  54. package/packages/core/index.ts +14 -0
  55. package/packages/core/kanboard-status.ts +49 -0
  56. package/packages/core/long-horizon-owner-status.ts +46 -0
  57. package/packages/core/model-cache.ts +81 -7
  58. package/packages/core/package.json +6 -3
  59. package/packages/core/src/attach/detect.ts +163 -0
  60. package/packages/core/src/child-agent/child-args.ts +31 -0
  61. package/packages/core/src/child-agent/delivery.ts +41 -0
  62. package/packages/core/src/child-agent/index.ts +5 -0
  63. package/packages/core/src/child-agent/readers.ts +203 -0
  64. package/packages/core/src/child-agent/runtime.ts +584 -0
  65. package/packages/core/src/evidence.ts +78 -0
  66. package/packages/core/src/hints/browser.ts +278 -0
  67. package/packages/core/src/hints/crab-data.ts +19 -0
  68. package/packages/core/src/hints/index.ts +865 -0
  69. package/packages/core/src/hints/lines.ts +607 -0
  70. package/packages/core/src/hints/store.ts +120 -0
  71. package/packages/core/src/jev/chatter.ts +24 -0
  72. package/packages/core/src/jev/settings.ts +177 -14
  73. package/packages/core/src/package-colors.ts +2 -0
  74. package/packages/core/src/settings/catalog.ts +39 -15
  75. package/packages/core/src/settings/engine.ts +48 -2
  76. package/packages/core/src/settings/hub.ts +83 -20
  77. package/packages/core/src/settings/open.ts +49 -0
  78. package/packages/core/src/settings/schema.ts +27 -7
  79. package/packages/core/src/tui/hub-kit.ts +18 -5
  80. package/packages/core/src/tui/kit.ts +198 -0
  81. package/packages/core/src/tui/progress.ts +73 -0
  82. package/packages/core/src/turn/arbiter.ts +290 -0
  83. package/packages/footer/README.md +4 -6
  84. package/packages/footer/package.json +3 -3
  85. package/packages/footer/src/glance-editor.ts +19 -11
  86. package/packages/footer/src/help.ts +1 -1
  87. package/packages/footer/src/index.ts +36 -3
  88. package/packages/footer/src/presets.ts +3 -4
  89. package/packages/footer/src/rendering/icons.ts +0 -20
  90. package/packages/footer/src/segments/compactor.ts +52 -142
  91. package/packages/footer/src/segments/status-ext.ts +0 -1
  92. package/packages/fusion/README.md +14 -4
  93. package/packages/fusion/package.json +4 -3
  94. package/packages/fusion/src/child-args.ts +1 -0
  95. package/packages/fusion/src/index.ts +161 -8
  96. package/packages/fusion/src/pi-settings.ts +57 -0
  97. package/packages/fusion/src/picker.ts +186 -51
  98. package/packages/fusion/src/preset.ts +56 -2
  99. package/packages/fusion/src/prompts.ts +1 -1
  100. package/packages/fusion/src/sidekick-runtime.ts +1 -420
  101. package/packages/fusion/src/tools.ts +107 -128
  102. package/packages/fusion/src/transcript.ts +27 -85
  103. package/packages/info-screen/package.json +2 -2
  104. package/packages/input-shortcuts/README.md +18 -30
  105. package/packages/input-shortcuts/package.json +2 -2
  106. package/packages/input-shortcuts/src/burst.ts +88 -0
  107. package/packages/input-shortcuts/src/chord-overlay.ts +20 -70
  108. package/packages/input-shortcuts/src/cleared-input.ts +97 -0
  109. package/packages/input-shortcuts/src/index.ts +99 -193
  110. package/packages/input-shortcuts/src/last-response.ts +40 -0
  111. package/packages/input-shortcuts/src/registers.ts +8 -23
  112. package/packages/input-shortcuts/src/types.ts +4 -16
  113. package/packages/input-shortcuts/src/undo-redo.ts +59 -47
  114. package/packages/kanboard/README.md +51 -47
  115. package/packages/kanboard/index.ts +293 -55
  116. package/packages/kanboard/package.json +8 -8
  117. package/packages/kanboard/skills/kanboard/SKILL.md +61 -27
  118. package/packages/kanboard/src/badges.ts +93 -0
  119. package/packages/kanboard/src/bin.ts +4 -2
  120. package/packages/kanboard/src/commands.ts +218 -84
  121. package/packages/kanboard/src/debug.ts +20 -0
  122. package/packages/kanboard/src/guard.ts +267 -109
  123. package/packages/kanboard/src/monitor.ts +330 -0
  124. package/packages/kanboard/src/notice-buffer.ts +74 -0
  125. package/packages/kanboard/src/progress.ts +27 -0
  126. package/packages/kanboard/src/reminders.ts +234 -0
  127. package/packages/kanboard/src/settings.ts +69 -15
  128. package/packages/kanboard/src/shapes.ts +5 -0
  129. package/packages/long-horizon/index.ts +190 -13
  130. package/packages/long-horizon/package.json +3 -3
  131. package/packages/long-horizon/src/commands.ts +103 -7
  132. package/packages/long-horizon/src/compaction-brief.ts +35 -0
  133. package/packages/long-horizon/src/engine/continuation.ts +84 -15
  134. package/packages/long-horizon/src/engine/goal-state.ts +64 -13
  135. package/packages/long-horizon/src/engine/nudge-stash.ts +71 -0
  136. package/packages/long-horizon/src/engine/ralph.ts +3 -2
  137. package/packages/long-horizon/src/engine/runaway.ts +68 -11
  138. package/packages/long-horizon/src/engine/verifier.ts +19 -0
  139. package/packages/long-horizon/src/gate.ts +37 -12
  140. package/packages/long-horizon/src/judge/resolve.ts +1 -0
  141. package/packages/long-horizon/src/owner.ts +35 -2
  142. package/packages/long-horizon/src/progress.ts +87 -0
  143. package/packages/long-horizon/src/prompts/goal.ts +1 -1
  144. package/packages/long-horizon/src/runtime.ts +172 -18
  145. package/packages/long-horizon/src/settings.ts +54 -39
  146. package/packages/long-horizon/src/tools/goal.ts +6 -0
  147. package/packages/long-horizon/src/tools/graph.ts +1 -1
  148. package/packages/long-horizon/src/tools/swarm.ts +3 -3
  149. package/packages/mcp/package.json +2 -2
  150. package/packages/memory/commands.ts +7 -2
  151. package/packages/memory/index.ts +210 -32
  152. package/packages/memory/package.json +3 -3
  153. package/packages/memory/save-session.ts +264 -0
  154. package/packages/memory/settings.ts +17 -0
  155. package/packages/memory/tools.ts +253 -203
  156. package/packages/notify/package.json +2 -2
  157. package/packages/skill-registry/README.md +53 -16
  158. package/packages/skill-registry/index.ts +230 -0
  159. package/packages/skill-registry/package.json +19 -8
  160. package/packages/skill-registry/src/editor.ts +206 -0
  161. package/packages/skill-registry/src/judge.ts +289 -0
  162. package/packages/skill-registry/src/registry.ts +87 -0
  163. package/packages/skill-registry/src/settings.ts +259 -0
  164. package/packages/skill-registry/src/vault.ts +80 -0
  165. package/packages/subagents/README.md +39 -119
  166. package/packages/subagents/package.json +6 -13
  167. package/packages/subagents/skills/subagents/SKILL.md +18 -42
  168. package/packages/subagents/src/agents.ts +213 -0
  169. package/packages/subagents/src/cards.ts +182 -0
  170. package/packages/subagents/src/index.ts +609 -1084
  171. package/packages/subagents/src/manager.ts +429 -0
  172. package/packages/subagents/src/profiles.ts +130 -0
  173. package/packages/subagents/src/transcript.ts +154 -0
  174. package/packages/subagents/src/ui.ts +389 -0
  175. package/packages/unipi/bundled.js +39756 -45569
  176. package/packages/unipi/index.ts +55 -10
  177. package/packages/updater/package.json +2 -2
  178. package/packages/utility/README.md +76 -69
  179. package/packages/utility/package.json +2 -2
  180. package/packages/utility/skills/image/SKILL.md +37 -0
  181. package/packages/utility/skills/utility/SKILL.md +17 -60
  182. package/packages/utility/src/answer/extract.ts +128 -0
  183. package/packages/utility/src/answer/index.ts +213 -0
  184. package/packages/utility/src/answer/reply.ts +133 -0
  185. package/packages/utility/src/answer/web.ts +167 -0
  186. package/packages/utility/src/attach/index.ts +205 -0
  187. package/packages/utility/src/commands.ts +38 -142
  188. package/packages/utility/src/diagnostics/engine.ts +50 -5
  189. package/packages/utility/src/image/index.ts +33 -0
  190. package/packages/utility/src/image/models.ts +221 -0
  191. package/packages/{image/src → utility/src/image}/recognize.ts +2 -2
  192. package/packages/utility/src/image/settings.ts +178 -0
  193. package/packages/{image/src/image-source.ts → utility/src/image/source.ts} +1 -1
  194. package/packages/utility/src/image/tools.ts +168 -0
  195. package/packages/utility/src/image/transports.ts +186 -0
  196. package/packages/utility/src/index.ts +68 -389
  197. package/packages/utility/src/lifecycle/cleanup.ts +116 -311
  198. package/packages/utility/src/rename/gate.ts +102 -0
  199. package/packages/utility/src/rename/index.ts +191 -0
  200. package/packages/utility/src/rename/session.ts +170 -0
  201. package/packages/utility/src/render/delegated.ts +285 -0
  202. package/packages/utility/src/render/harness.ts +683 -0
  203. package/packages/utility/src/render/parse.ts +156 -0
  204. package/packages/utility/src/render/pi-settings.ts +38 -0
  205. package/packages/utility/src/render/reply-bg.ts +307 -0
  206. package/packages/utility/src/render/simple.ts +724 -0
  207. package/packages/utility/src/render/spacing.ts +235 -0
  208. package/packages/utility/src/render/styled.ts +251 -0
  209. package/packages/utility/src/render/tools.ts +353 -0
  210. package/packages/utility/src/settings.ts +166 -233
  211. package/packages/utility/src/types.ts +1 -222
  212. package/packages/watchdog/index.ts +17 -6
  213. package/packages/watchdog/package.json +3 -3
  214. package/packages/watchdog/src/config.ts +3 -2
  215. package/packages/web-api/package.json +2 -2
  216. package/packages/workflow/README.md +3 -0
  217. package/packages/workflow/index.ts +0 -2
  218. package/packages/workflow/package.json +2 -2
  219. package/packages/workflow/src/permission/decide.ts +6 -4
  220. package/packages/workflow/src/permission/index.ts +17 -7
  221. package/packages/workflow/src/permission/settings.ts +39 -4
  222. package/packages/workflow/src/plan/index.ts +19 -4
  223. package/packages/background-tasks/extensions/delegate-child.ts +0 -1
  224. package/packages/background-tasks/src/context-parent-snapshot.ts +0 -142
  225. package/packages/background-tasks/src/context-token-budget.ts +0 -903
  226. package/packages/background-tasks/src/context-visible-conversation-v2.ts +0 -551
  227. package/packages/background-tasks/src/delegate/artifacts.ts +0 -487
  228. package/packages/background-tasks/src/delegate/budget.ts +0 -415
  229. package/packages/background-tasks/src/delegate/hook-contract-evidence.json +0 -18
  230. package/packages/background-tasks/src/delegate/hook-contract.ts +0 -154
  231. package/packages/background-tasks/src/delegate/launch.ts +0 -484
  232. package/packages/background-tasks/src/delegate/result-package.ts +0 -459
  233. package/packages/background-tasks/src/delegate/runner.ts +0 -430
  234. package/packages/background-tasks/src/delegate/seed.ts +0 -423
  235. package/packages/background-tasks/src/delegate/types.ts +0 -323
  236. package/packages/background-tasks/src/delegate-child-extension.ts +0 -978
  237. package/packages/background-tasks/src/delegate-extension.ts +0 -654
  238. package/packages/background-tasks/src/fixtures/delegate-context-incident.json +0 -17
  239. package/packages/compactor/skills/compactor-detail/SKILL.md +0 -116
  240. package/packages/compactor/skills/compactor-doctor/SKILL.md +0 -71
  241. package/packages/compactor/skills/compactor-stats/SKILL.md +0 -51
  242. package/packages/compactor/src/compaction/build-sections.ts +0 -76
  243. package/packages/compactor/src/compaction/extract/goals.ts +0 -36
  244. package/packages/compactor/src/compaction/extract/preferences.ts +0 -40
  245. package/packages/compactor/src/compaction/format.ts +0 -53
  246. package/packages/compactor/src/compaction/merge.ts +0 -143
  247. package/packages/compactor/src/config/presets.ts +0 -160
  248. package/packages/compactor/src/executor/executor.ts +0 -233
  249. package/packages/compactor/src/executor/runtime.ts +0 -115
  250. package/packages/compactor/src/security/evaluator.ts +0 -89
  251. package/packages/compactor/src/security/policy.ts +0 -96
  252. package/packages/compactor/src/security/scanner.ts +0 -65
  253. package/packages/compactor/src/session/auto-inject.ts +0 -60
  254. package/packages/compactor/src/session/db.ts +0 -325
  255. package/packages/compactor/src/session/extract.ts +0 -144
  256. package/packages/compactor/src/session/resume-inject.ts +0 -64
  257. package/packages/compactor/src/session/snapshot.ts +0 -327
  258. package/packages/compactor/src/tools/ctx-batch-execute.ts +0 -41
  259. package/packages/compactor/src/tools/ctx-execute-file.ts +0 -23
  260. package/packages/compactor/src/tools/ctx-execute.ts +0 -23
  261. package/packages/compactor/src/tools/ctx-stats.ts +0 -96
  262. package/packages/image/README.md +0 -99
  263. package/packages/image/package.json +0 -61
  264. package/packages/image/skills/image/SKILL.md +0 -76
  265. package/packages/image/src/generate.ts +0 -256
  266. package/packages/image/src/index.ts +0 -135
  267. package/packages/image/src/models.ts +0 -519
  268. package/packages/image/src/openai-images-api.ts +0 -282
  269. package/packages/image/src/register-providers.ts +0 -209
  270. package/packages/image/src/settings.ts +0 -173
  271. package/packages/image/src/tools.ts +0 -326
  272. package/packages/input-shortcuts/src/clipboard.ts +0 -119
  273. package/packages/kanboard/src/runner.ts +0 -817
  274. package/packages/skill-registry/skills/auto/SKILL.md +0 -465
  275. package/packages/skill-registry/skills/brainstorm/SKILL.md +0 -219
  276. package/packages/skill-registry/skills/chore-create/SKILL.md +0 -315
  277. package/packages/skill-registry/skills/chore-execute/SKILL.md +0 -314
  278. package/packages/skill-registry/skills/consolidate/SKILL.md +0 -142
  279. package/packages/skill-registry/skills/consultant/SKILL.md +0 -97
  280. package/packages/skill-registry/skills/debug/SKILL.md +0 -228
  281. package/packages/skill-registry/skills/document/SKILL.md +0 -120
  282. package/packages/skill-registry/skills/fix/SKILL.md +0 -212
  283. package/packages/skill-registry/skills/gather-context/SKILL.md +0 -145
  284. package/packages/skill-registry/skills/plan/SKILL.md +0 -231
  285. package/packages/skill-registry/skills/quick-fix/SKILL.md +0 -135
  286. package/packages/skill-registry/skills/quick-work/SKILL.md +0 -110
  287. package/packages/skill-registry/skills/research/SKILL.md +0 -251
  288. package/packages/skill-registry/skills/review-work/SKILL.md +0 -187
  289. package/packages/skill-registry/skills/scan-issues/SKILL.md +0 -195
  290. package/packages/skill-registry/skills/work/SKILL.md +0 -218
  291. package/packages/skill-registry/skills/worktree-create/SKILL.md +0 -69
  292. package/packages/skill-registry/skills/worktree-list/SKILL.md +0 -78
  293. package/packages/skill-registry/skills/worktree-merge/SKILL.md +0 -89
  294. package/packages/subagents/agents/delegate.md +0 -14
  295. package/packages/subagents/agents/oracle.md +0 -78
  296. package/packages/subagents/agents/researcher.md +0 -52
  297. package/packages/subagents/agents/reviewer.md +0 -80
  298. package/packages/subagents/agents/scout.md +0 -50
  299. package/packages/subagents/agents/worker.md +0 -59
  300. package/packages/subagents/prompts/council.md +0 -48
  301. package/packages/subagents/prompts/gather-context-and-clarify.md +0 -13
  302. package/packages/subagents/prompts/parallel-cleanup.md +0 -59
  303. package/packages/subagents/prompts/parallel-research.md +0 -50
  304. package/packages/subagents/prompts/parallel-review.md +0 -54
  305. package/packages/subagents/prompts/review-loop.md +0 -43
  306. package/packages/subagents/skills/council-mode/SKILL.md +0 -230
  307. package/packages/subagents/skills/subagents/references/constraints-and-recipes.md +0 -259
  308. package/packages/subagents/skills/subagents/references/execution-controls.md +0 -454
  309. package/packages/subagents/skills/subagents/references/management-authoring-rpc.md +0 -161
  310. package/packages/subagents/skills/subagents/references/multi-lane-orchestration.md +0 -39
  311. package/packages/subagents/skills/subagents/references/prompting-and-roles.md +0 -267
  312. package/packages/subagents/src/acceptance.ts +0 -331
  313. package/packages/subagents/src/agent-manager.ts +0 -405
  314. package/packages/subagents/src/agent-memory.ts +0 -300
  315. package/packages/subagents/src/agent-overrides.ts +0 -183
  316. package/packages/subagents/src/agent-runner.ts +0 -271
  317. package/packages/subagents/src/async-runner.ts +0 -422
  318. package/packages/subagents/src/authority-policy.ts +0 -47
  319. package/packages/subagents/src/budgets.ts +0 -285
  320. package/packages/subagents/src/child-safety.ts +0 -140
  321. package/packages/subagents/src/config.ts +0 -249
  322. package/packages/subagents/src/conversation-viewer.ts +0 -281
  323. package/packages/subagents/src/core-compat.ts +0 -94
  324. package/packages/subagents/src/custom-agents.ts +0 -343
  325. package/packages/subagents/src/file-system-retry.ts +0 -50
  326. package/packages/subagents/src/fleet-data.ts +0 -105
  327. package/packages/subagents/src/fleet-view.ts +0 -322
  328. package/packages/subagents/src/foreground-detach.ts +0 -59
  329. package/packages/subagents/src/fork-context.ts +0 -285
  330. package/packages/subagents/src/global.d.ts +0 -7
  331. package/packages/subagents/src/guide.ts +0 -129
  332. package/packages/subagents/src/mission-state.ts +0 -133
  333. package/packages/subagents/src/mission-store.ts +0 -436
  334. package/packages/subagents/src/output-limits.ts +0 -142
  335. package/packages/subagents/src/parity-types.ts +0 -315
  336. package/packages/subagents/src/pi-args.ts +0 -167
  337. package/packages/subagents/src/result-files.ts +0 -232
  338. package/packages/subagents/src/result-watcher.ts +0 -221
  339. package/packages/subagents/src/retained-children.ts +0 -176
  340. package/packages/subagents/src/run-fanout-budget.ts +0 -356
  341. package/packages/subagents/src/scheduled-runs.ts +0 -349
  342. package/packages/subagents/src/schemas.ts +0 -263
  343. package/packages/subagents/src/slash-commands.ts +0 -80
  344. package/packages/subagents/src/supervisor-channel.ts +0 -249
  345. package/packages/subagents/src/tool-handler.ts +0 -1144
  346. package/packages/subagents/src/types.ts +0 -238
  347. package/packages/subagents/src/widget.ts +0 -457
  348. package/packages/subagents/src/workflow-script.ts +0 -737
  349. package/packages/subagents/src/workflow-worker.ts +0 -384
  350. package/packages/subagents/src/worktree.ts +0 -614
  351. package/packages/utility/src/analytics/collector.ts +0 -293
  352. package/packages/utility/src/info-screen.ts +0 -80
  353. package/packages/utility/src/lifecycle/process.ts +0 -162
  354. package/packages/utility/src/prefix-cache.ts +0 -263
  355. package/packages/utility/src/skill-discovery.ts +0 -542
  356. package/packages/utility/src/tools/env.ts +0 -113
  357. package/packages/utility/src/tui/name-badge-state.ts +0 -315
  358. package/packages/utility/src/tui/name-badge.ts +0 -109
  359. /package/packages/{subagents/src → core/src/child-agent}/pi-spawn.ts +0 -0
@@ -1,282 +0,0 @@
1
- /**
2
- * @pi-unipi/image — Generic OpenAI-compatible images adapter
3
- *
4
- * ONE adapter for every provider, rather than per-provider code. It speaks the
5
- * OpenAI `POST {baseUrl}/images/generations` shape, which every gateway we have
6
- * tested implements (OpenAI itself, OpenRouter, and OmniRoute's fan-out to
7
- * openrouter/antigravity/codex/fal-ai backends).
8
- *
9
- * Why not pi-ai's built-in `api/openrouter-images`?
10
- * Despite the name it drives `chat.completions` with `modalities:["image"]`.
11
- * Gateways that do not implement that extension answer HTTP 200 with the model
12
- * *narrating* the image ("Here's the image with the circle changed…") while
13
- * silently dropping `message.images`. That is invisible data loss, so we use
14
- * the dedicated images endpoint instead.
15
- *
16
- * Editing rides the same endpoint: `POST /images/generations` with an `image`
17
- * array. `/images/edits` (multipart) is NOT used — gateways reject it for most
18
- * providers ("Image edit is not supported for built-in provider ...").
19
- */
20
-
21
- import type { ImageGenModel } from "./models.js";
22
-
23
- /** pi-ai's `ImagesContext` input parts. */
24
- export interface ImagesInputPart {
25
- type: string;
26
- text?: string;
27
- data?: string;
28
- mimeType?: string;
29
- }
30
-
31
- export interface ImagesContextLike {
32
- input: ImagesInputPart[];
33
- }
34
-
35
- export interface ImagesOptionsLike {
36
- apiKey?: string;
37
- signal?: AbortSignal;
38
- headers?: Record<string, string | null>;
39
- timeoutMs?: number;
40
- /** Injectable fetch, for tests. */
41
- fetchImpl?: typeof fetch;
42
- }
43
-
44
- /** pi-ai's `AssistantImages`. */
45
- export interface AssistantImagesLike {
46
- api: string;
47
- provider: string;
48
- model: string;
49
- output: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;
50
- stopReason: "stop" | "error" | "aborted";
51
- errorMessage?: string;
52
- timestamp: number;
53
- }
54
-
55
- /** Images are slow — a minute is not unusual for a large model. */
56
- const DEFAULT_TIMEOUT_MS = 240_000;
57
-
58
- /**
59
- * One returned image, normalized.
60
- *
61
- * Gateways disagree on the item shape; all three observed forms are accepted:
62
- * - `{ b64_json, media_type }` — openrouter/* (note `media_type`, not `mimeType`)
63
- * - `{ b64_json, revised_prompt }` — antigravity/*
64
- * - `{ url: "data:image/png;base64,…" }` — codex/*
65
- * A plain http(s) `url` is also tolerated and reported as text, since we cannot
66
- * inline bytes we did not fetch.
67
- */
68
- interface RawImageItem {
69
- b64_json?: unknown;
70
- url?: unknown;
71
- media_type?: unknown;
72
- mime_type?: unknown;
73
- mimeType?: unknown;
74
- revised_prompt?: unknown;
75
- }
76
-
77
- function asString(value: unknown): string | undefined {
78
- return typeof value === "string" && value.length > 0 ? value : undefined;
79
- }
80
-
81
- /** Pull `{ data, mimeType }` out of one response item, whatever its shape. */
82
- export function normalizeImageItem(
83
- item: RawImageItem,
84
- ): { data: string; mimeType: string } | { text: string } | null {
85
- const declared =
86
- asString(item.media_type) ?? asString(item.mime_type) ?? asString(item.mimeType);
87
-
88
- const b64 = asString(item.b64_json);
89
- if (b64) return { data: b64, mimeType: declared ?? "image/png" };
90
-
91
- const url = asString(item.url);
92
- if (!url) return null;
93
-
94
- // codex/* returns the bytes as a data: URL rather than b64_json.
95
- const dataUrl = /^data:([^;,]+)(?:;[^,]*)*,(.*)$/s.exec(url);
96
- if (dataUrl) {
97
- const [, mime, payload] = dataUrl;
98
- if (payload) return { data: payload, mimeType: declared ?? mime ?? "image/png" };
99
- return null;
100
- }
101
-
102
- // A remote URL: surface it rather than silently dropping the result.
103
- return { text: `Image available at: ${url}` };
104
- }
105
-
106
- /** Strip a trailing slash so `${base}/images/generations` is well-formed. */
107
- function joinUrl(baseUrl: string, suffix: string): string {
108
- return `${baseUrl.replace(/\/+$/, "")}/${suffix.replace(/^\/+/, "")}`;
109
- }
110
-
111
- /**
112
- * Model ids are sent to the gateway verbatim.
113
- *
114
- * Do NOT try to "repair" a doubled-looking segment. OmniRoute genuinely serves
115
- * `fal-ai/fal-ai/nano-banana-pro` (provider `fal-ai` + model `fal-ai/nano-...`),
116
- * and rewriting it to `fal-ai/nano-banana-pro` yields a 404. Confusingly the
117
- * gateway *also* advertises a `fal/...` alias in `/v1/models` that the images
118
- * endpoint then rejects with "Invalid image model" — an upstream inconsistency
119
- * we surface rather than guess around, because a wrong guess turns a clear
120
- * error into a silently different model.
121
- */
122
- export function normalizeModelId(id: string): string {
123
- return id;
124
- }
125
-
126
- /** Merge caller headers, dropping keys explicitly suppressed with null. */
127
- function buildHeaders(
128
- apiKey: string,
129
- extra?: Record<string, string | null>,
130
- ): Record<string, string> {
131
- const headers: Record<string, string> = {
132
- Authorization: `Bearer ${apiKey}`,
133
- "Content-Type": "application/json",
134
- };
135
- for (const [key, value] of Object.entries(extra ?? {})) {
136
- if (value === null) delete headers[key];
137
- else headers[key] = value;
138
- }
139
- return headers;
140
- }
141
-
142
- /** Best-effort extraction of a provider error message. */
143
- function describeError(status: number, statusText: string, body: string): string {
144
- let detail = body.slice(0, 300);
145
- try {
146
- const parsed = JSON.parse(body) as { error?: { message?: string } | string };
147
- if (typeof parsed.error === "string") detail = parsed.error;
148
- else if (parsed.error?.message) detail = parsed.error.message;
149
- } catch {
150
- // Non-JSON body — the truncated text is the best we have.
151
- }
152
- return `${status} ${statusText}${detail ? `: ${detail}` : ""}`;
153
- }
154
-
155
- /** Add guidance for the gateway's confusing model-id errors. */
156
- function annotateModelError(message: string, model: ImageGenModel): string {
157
- if (/invalid image model|not found|unknown model/i.test(message)) {
158
- return (
159
- `${message}\n` +
160
- `→ Model id sent: "${model.id}" (provider "${model.provider}").\n` +
161
- "→ Some gateways list aliases they cannot serve. Try the id exactly as it " +
162
- "appears in the provider's own catalog, or pick another with /unipi:settings."
163
- );
164
- }
165
- return message;
166
- }
167
-
168
- /**
169
- * Generate (or edit) images against an OpenAI-compatible endpoint.
170
- *
171
- * Satisfies pi-ai's `ProviderImages` interface, so the result is returned —
172
- * never thrown — with `stopReason: "error"` on failure.
173
- */
174
- export async function generateImages(
175
- model: ImageGenModel,
176
- context: ImagesContextLike,
177
- options?: ImagesOptionsLike,
178
- ): Promise<AssistantImagesLike> {
179
- const result: AssistantImagesLike = {
180
- api: model.api || "openai-images",
181
- provider: model.provider,
182
- model: model.id,
183
- output: [],
184
- stopReason: "stop",
185
- timestamp: Date.now(),
186
- };
187
-
188
- const fetchImpl = options?.fetchImpl ?? fetch;
189
- const controller = new AbortController();
190
- const timer = setTimeout(
191
- () => controller.abort(),
192
- options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
193
- );
194
- const onAbort = () => controller.abort();
195
- options?.signal?.addEventListener("abort", onAbort, { once: true });
196
-
197
- try {
198
- const apiKey = options?.apiKey;
199
- if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);
200
- if (!model.baseUrl) {
201
- throw new Error(`No baseUrl for image model ${model.provider}/${model.id}`);
202
- }
203
-
204
- // Text parts form the prompt; image parts switch the request into edit mode.
205
- const prompt = context.input
206
- .filter((part) => part.type === "text" && part.text)
207
- .map((part) => part.text as string)
208
- .join("\n")
209
- .trim();
210
-
211
- const images = context.input
212
- .filter((part) => part.type === "image" && part.data)
213
- .map((part) => `data:${part.mimeType || "image/png"};base64,${part.data}`);
214
-
215
- if (!prompt) throw new Error("A non-empty prompt is required.");
216
-
217
- const body: Record<string, unknown> = {
218
- model: normalizeModelId(model.id),
219
- prompt,
220
- };
221
- // Only send `image` for edits; some backends reject an empty array.
222
- if (images.length > 0) body.image = images;
223
-
224
- const response = await fetchImpl(joinUrl(model.baseUrl, "images/generations"), {
225
- method: "POST",
226
- headers: buildHeaders(apiKey, options?.headers),
227
- body: JSON.stringify(body),
228
- signal: controller.signal,
229
- });
230
-
231
- if (!response.ok) {
232
- throw new Error(
233
- describeError(response.status, response.statusText, await response.text()),
234
- );
235
- }
236
-
237
- const payload = (await response.json()) as {
238
- data?: RawImageItem[];
239
- error?: { message?: string };
240
- };
241
-
242
- if (payload.error?.message) throw new Error(payload.error.message);
243
-
244
- for (const item of payload.data ?? []) {
245
- const normalized = normalizeImageItem(item);
246
- if (!normalized) continue;
247
- if ("text" in normalized) {
248
- result.output.push({ type: "text", text: normalized.text });
249
- } else {
250
- result.output.push({
251
- type: "image",
252
- data: normalized.data,
253
- mimeType: normalized.mimeType,
254
- });
255
- }
256
- const revised = asString(item.revised_prompt);
257
- if (revised && revised !== prompt) {
258
- result.output.push({ type: "text", text: `Revised prompt: ${revised}` });
259
- }
260
- }
261
-
262
- if (result.output.every((part) => part.type !== "image")) {
263
- throw new Error("The provider returned no image data.");
264
- }
265
-
266
- return result;
267
- } catch (error) {
268
- const aborted = options?.signal?.aborted || controller.signal.aborted;
269
- result.stopReason = options?.signal?.aborted ? "aborted" : "error";
270
- result.errorMessage =
271
- aborted && !options?.signal?.aborted
272
- ? "Image request timed out."
273
- : annotateModelError(
274
- error instanceof Error ? error.message : String(error),
275
- model,
276
- );
277
- return result;
278
- } finally {
279
- clearTimeout(timer);
280
- options?.signal?.removeEventListener("abort", onAbort);
281
- }
282
- }
@@ -1,209 +0,0 @@
1
- /**
2
- * @pi-unipi/image — Bridge pi's chat providers into pi-ai's images collection
3
- *
4
- * pi-ai ships exactly one image provider (openrouter), so out of the box image
5
- * generation demands an OpenRouter account even when the user has half a dozen
6
- * other providers configured. pi's own registry knows those providers and their
7
- * credentials, so we re-register each one as an *images* provider backed by the
8
- * single generic OpenAI-compatible adapter.
9
- *
10
- * The result: any OpenAI-compatible provider the user configures in pi can
11
- * generate and edit images with no image-specific setup, and no per-provider
12
- * code here.
13
- *
14
- * ## Why capability detection stays heuristic
15
- * pi's model registry cannot tell us which models emit images.
16
- * `provider-composer.ts` builds each registered model from an explicit field
17
- * list — `{id, name, api, provider, baseUrl, reasoning, input, cost,
18
- * contextWindow, maxTokens, headers, compat}` — so an extension that attaches
19
- * `output: ["image"]` has it silently dropped. `ProviderModelConfig` has no
20
- * `output` field at all. Hence `looksLikeImageGenerator()` name-matching, plus
21
- * explicit "provider/model-id" entry as the always-available escape hatch.
22
- */
23
-
24
- import * as imagesApi from "./openai-images-api.js";
25
- import {
26
- getImagesModels,
27
- listRegistryImageGenModels,
28
- type ChatModelRegistry,
29
- type ImageGenModel,
30
- } from "./models.js";
31
-
32
- /** pi-ai's `createImagesProvider`, kept structural to avoid type coupling. */
33
- interface CreateImagesProviderFn {
34
- (input: {
35
- id: string;
36
- name?: string;
37
- auth: unknown;
38
- models: readonly ImageGenModel[];
39
- api: unknown;
40
- }): unknown;
41
- }
42
-
43
- /** The subset of a registry provider we need. */
44
- interface ProviderLike {
45
- id: string;
46
- name?: string;
47
- baseUrl?: string;
48
- }
49
-
50
- let registered = false;
51
-
52
- /** Reset registration state. Test-only. */
53
- export function __resetRegistrationForTests(): void {
54
- registered = false;
55
- }
56
-
57
- /**
58
- * Group discovered generator models by provider, attaching the provider's
59
- * baseUrl so the adapter knows where to POST.
60
- */
61
- export function groupModelsByProvider(
62
- models: ImageGenModel[],
63
- baseUrlFor: (provider: string) => string | undefined,
64
- ): Map<string, { baseUrl: string; models: ImageGenModel[] }> {
65
- const grouped = new Map<string, { baseUrl: string; models: ImageGenModel[] }>();
66
-
67
- for (const model of models) {
68
- const baseUrl = model.baseUrl ?? baseUrlFor(model.provider);
69
- // Without an endpoint the adapter cannot issue a request; skip rather than
70
- // register a provider that is guaranteed to fail.
71
- if (!baseUrl) continue;
72
-
73
- let entry = grouped.get(model.provider);
74
- if (!entry) {
75
- entry = { baseUrl, models: [] };
76
- grouped.set(model.provider, entry);
77
- }
78
- entry.models.push({ ...model, baseUrl });
79
- }
80
-
81
- return grouped;
82
- }
83
-
84
- /** Read a provider's baseUrl out of pi's registry. */
85
- function providerBaseUrlLookup(
86
- registry: ChatModelRegistry,
87
- ): (provider: string) => string | undefined {
88
- const cache = new Map<string, string | undefined>();
89
-
90
- return (provider: string) => {
91
- if (cache.has(provider)) return cache.get(provider);
92
-
93
- let baseUrl: string | undefined;
94
- try {
95
- const models = (registry.getAvailable?.() ?? registry.getAll()) as Array<{
96
- provider?: string;
97
- baseUrl?: string;
98
- }>;
99
- baseUrl = models.find((m) => m?.provider === provider && m.baseUrl)?.baseUrl;
100
- } catch {
101
- baseUrl = undefined;
102
- }
103
-
104
- cache.set(provider, baseUrl);
105
- return baseUrl;
106
- };
107
- }
108
-
109
- /**
110
- * Register every pi provider that looks capable of image generation into
111
- * pi-ai's images collection.
112
- *
113
- * Idempotent and best-effort: a failure here must never break the extension,
114
- * since generation still works for pi-ai's built-in providers.
115
- *
116
- * @returns the provider ids registered.
117
- */
118
- export async function registerRegistryImageProviders(
119
- registry: ChatModelRegistry | undefined,
120
- options?: { force?: boolean },
121
- ): Promise<string[]> {
122
- if (!registry) return [];
123
- if (registered && !options?.force) return [];
124
-
125
- const images = await getImagesModels();
126
- if (!images) return [];
127
-
128
- // `setProvider` is on MutableImagesModels; the built-in collection provides
129
- // it, but guard in case a future pi-ai hands back an immutable one.
130
- const mutable = images as unknown as {
131
- setProvider?: (provider: unknown) => void;
132
- getProvider?: (id: string) => unknown;
133
- };
134
- if (typeof mutable.setProvider !== "function") return [];
135
-
136
- let createImagesProvider: CreateImagesProviderFn;
137
- try {
138
- const mod = (await import("@earendil-works/pi-ai")) as unknown as {
139
- createImagesProvider?: CreateImagesProviderFn;
140
- };
141
- if (typeof mod.createImagesProvider !== "function") return [];
142
- createImagesProvider = mod.createImagesProvider;
143
- } catch {
144
- return [];
145
- }
146
-
147
- const discovered = listRegistryImageGenModels(registry);
148
- if (discovered.length === 0) {
149
- registered = true;
150
- return [];
151
- }
152
-
153
- const grouped = groupModelsByProvider(discovered, providerBaseUrlLookup(registry));
154
- const added: string[] = [];
155
-
156
- for (const [providerId, { models }] of grouped) {
157
- // Never shadow a provider pi-ai serves natively — its own implementation
158
- // is better informed than our generic adapter.
159
- try {
160
- if (mutable.getProvider?.(providerId)) continue;
161
- } catch {
162
- // Treat a lookup failure as "not present" and attempt registration.
163
- }
164
-
165
- try {
166
- const provider = createImagesProvider({
167
- id: providerId,
168
- name: providerId,
169
- models,
170
- api: imagesApi,
171
- auth: {
172
- apiKey: {
173
- name: `${providerId} API key`,
174
- // Resolve through pi's own auth storage so the user never logs in
175
- // twice. `resolve` MUST return an AuthResult (`{ auth: {...} }`);
176
- // returning a bare key fails silently at request time.
177
- resolve: async () => {
178
- const key = await resolveProviderKey(registry, providerId);
179
- return key ? { auth: { apiKey: key }, source: `pi:${providerId}` } : undefined;
180
- },
181
- },
182
- },
183
- });
184
-
185
- mutable.setProvider(provider);
186
- added.push(providerId);
187
- } catch {
188
- // One bad provider must not stop the rest.
189
- }
190
- }
191
-
192
- registered = true;
193
- return added;
194
- }
195
-
196
- /** Resolve a provider key from pi's auth storage, falling back to the env. */
197
- async function resolveProviderKey(
198
- registry: ChatModelRegistry,
199
- provider: string,
200
- ): Promise<string | undefined> {
201
- try {
202
- const key = await registry.getApiKeyForProvider?.(provider);
203
- if (key) return key;
204
- } catch {
205
- // Fall through to the environment.
206
- }
207
- const envName = `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
208
- return process.env[envName] || undefined;
209
- }
@@ -1,173 +0,0 @@
1
- /**
2
- * @pi-unipi/image — Settings
3
- *
4
- * Config lives at `~/.unipi/config/image/config.json`. Every read is
5
- * try/catch-to-defaults so a corrupt file can never break the tools.
6
- *
7
- * The config directory is overridable via `UNIPI_IMAGE_CONFIG_DIR` so tests
8
- * do not have to reach into the real home directory.
9
- */
10
-
11
- import * as fs from "node:fs";
12
- import * as os from "node:os";
13
- import * as path from "node:path";
14
-
15
- /** Default system prompt for image recognition. */
16
- export const DEFAULT_RECOGNIZE_SYSTEM_PROMPT =
17
- "You are a precise image analyst assisting a software engineer. " +
18
- "Describe what is actually visible — never speculate about what is not shown. " +
19
- "For screenshots, transcribe visible text, UI structure, and any errors verbatim. " +
20
- "For diagrams, describe the components and their relationships. " +
21
- "For photographs, describe the subject, setting, and notable detail. " +
22
- "Be specific and concise; lead with the single most important observation.";
23
-
24
- /** Default image-generation model. */
25
- export const DEFAULT_GENERATE_MODEL = "openrouter/google/gemini-3-pro-image";
26
-
27
- export interface GenerateSettings {
28
- /** Whether the image_generate tool is registered. */
29
- enabled: boolean;
30
- /** Model as "provider/model-id". */
31
- model: string;
32
- /** Directory for saved images. `~` is expanded. */
33
- outputDir: string;
34
- /** Whether to also write generated images to disk. */
35
- saveToDisk: boolean;
36
- }
37
-
38
- export interface RecognizeSettings {
39
- /** Whether the image_recognize tool is registered. */
40
- enabled: boolean;
41
- /** Model as "provider/model-id". Empty = use the session's current model. */
42
- model: string;
43
- /** System prompt sent with every recognition request. */
44
- systemPrompt: string;
45
- }
46
-
47
- export interface ImageConfig {
48
- generate: GenerateSettings;
49
- recognize: RecognizeSettings;
50
- }
51
-
52
- export const DEFAULT_CONFIG: ImageConfig = {
53
- generate: {
54
- enabled: true,
55
- model: DEFAULT_GENERATE_MODEL,
56
- outputDir: "~/.unipi/images",
57
- saveToDisk: true,
58
- },
59
- recognize: {
60
- enabled: true,
61
- model: "",
62
- systemPrompt: DEFAULT_RECOGNIZE_SYSTEM_PROMPT,
63
- },
64
- };
65
-
66
- /** Resolve the config directory, honouring the test override. */
67
- import { getSettings, registerSettings, setSettings } from "@pi-unipi/core";
68
-
69
- // Registered with the unified settings hub — canonical path already matched.
70
- registerSettings({
71
- namespace: "image",
72
- label: "Image",
73
- defaults: DEFAULT_CONFIG as unknown as Record<string, unknown>,
74
- schema: [
75
- {
76
- title: "Generate",
77
- fields: [
78
- { key: "generate.enabled", type: "boolean", label: "Generate enabled", description: "Register the image_generate tool" },
79
- { key: "generate.model", type: "model", label: "Generate model", description: "Image generation model", presets: [DEFAULT_GENERATE_MODEL, "openrouter/openai/gpt-5-image", "openrouter/black-forest-labs/flux.2-pro"] },
80
- { key: "generate.outputDir", type: "string", label: "Output dir", description: "~ expanded" },
81
- { key: "generate.saveToDisk", type: "boolean", label: "Save to disk" },
82
- ],
83
- },
84
- {
85
- title: "Recognize",
86
- fields: [
87
- { key: "recognize.enabled", type: "boolean", label: "Recognize enabled", description: "Register the image_recognize tool" },
88
- { key: "recognize.model", type: "model", label: "Recognize model", emptyLabel: "inherit (session model)", capability: "image-input", emptyOption: "inherit (session model)" },
89
- { key: "recognize.systemPrompt", type: "string", label: "System prompt", description: "Sent with every recognition request", emptyLabel: "built-in analyst prompt" },
90
- ],
91
- },
92
- ],
93
- });
94
-
95
- export function getConfigDir(): string {
96
- const override = process.env.UNIPI_IMAGE_CONFIG_DIR;
97
- if (override && override.trim().length > 0) return override;
98
- return path.join(os.homedir(), ".unipi", "config", "image");
99
- }
100
-
101
- function getConfigPath(): string {
102
- return path.join(getConfigDir(), "config.json");
103
- }
104
-
105
- /** Expand a leading `~` to the user's home directory. */
106
- export function expandHome(target: string): string {
107
- if (target === "~") return os.homedir();
108
- if (target.startsWith("~/") || target.startsWith("~\\")) {
109
- return path.join(os.homedir(), target.slice(2));
110
- }
111
- return target;
112
- }
113
-
114
- function isRecord(value: unknown): value is Record<string, unknown> {
115
- return value !== null && typeof value === "object" && !Array.isArray(value);
116
- }
117
-
118
- /** Merge a loaded section over its defaults, ignoring wrong-typed fields. */
119
- function mergeSection<T extends object>(defaults: T, loaded: unknown): T {
120
- if (!isRecord(loaded)) return { ...defaults };
121
-
122
- const merged: T = { ...defaults };
123
- for (const key of Object.keys(defaults) as Array<keyof T & string>) {
124
- const value = loaded[key];
125
- if (value === undefined || value === null) continue;
126
- // Only accept a value whose type matches the default's.
127
- if (typeof value === typeof defaults[key]) {
128
- merged[key] = value as T[keyof T & string];
129
- }
130
- }
131
- return merged;
132
- }
133
-
134
- /** Load config (engine-layered), falling back to defaults on any problem. */
135
- export function loadConfig(): ImageConfig {
136
- try {
137
- const parsed: unknown = getSettings("image", process.cwd());
138
- if (!isRecord(parsed)) return structuredClone(DEFAULT_CONFIG);
139
- return {
140
- generate: mergeSection(DEFAULT_CONFIG.generate, parsed.generate),
141
- recognize: mergeSection(DEFAULT_CONFIG.recognize, parsed.recognize),
142
- };
143
- } catch {
144
- return structuredClone(DEFAULT_CONFIG);
145
- }
146
- }
147
-
148
- /** Persist config. Returns false instead of throwing when the write fails. */
149
- export function saveConfig(config: ImageConfig): boolean {
150
- try {
151
- setSettings("image", config as unknown as Record<string, unknown>, "global", process.cwd());
152
- return true;
153
- } catch {
154
- return false;
155
- }
156
- }
157
-
158
- /** Apply a partial update, merging one level deep. */
159
- export function updateConfig(partial: Partial<ImageConfig>): ImageConfig {
160
- const current = loadConfig();
161
- const next: ImageConfig = {
162
- generate: { ...current.generate, ...partial.generate },
163
- recognize: { ...current.recognize, ...partial.recognize },
164
- };
165
- saveConfig(next);
166
- return next;
167
- }
168
-
169
- /** Resolved absolute output directory for generated images. */
170
- export function getOutputDir(config: ImageConfig = loadConfig()): string {
171
- const dir = config.generate.outputDir?.trim();
172
- return expandHome(dir && dir.length > 0 ? dir : DEFAULT_CONFIG.generate.outputDir);
173
- }