@hachej/boring-ui-cli 0.1.25 → 0.1.26

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 (152) hide show
  1. package/dist/server/cli.js +445 -38
  2. package/dist/server/pluginDiscovery.js +36 -0
  3. package/dist/server/pluginFrontRuntime.js +1458 -0
  4. package/package.json +7 -7
  5. package/public/assets/DebugDrawer-kRj0QGoh.js +1 -0
  6. package/public/assets/_baseUniq-BaoahSEi.js +1 -0
  7. package/public/assets/angular-ts-BrjP3tb8.js +1 -1
  8. package/public/assets/angular-ts-BwZT4LLn.js +1 -1
  9. package/public/assets/apl-CORt7UWP.js +1 -1
  10. package/public/assets/apl-dKokRX4l.js +1 -1
  11. package/public/assets/arc-BGT9Yoor.js +1 -0
  12. package/public/assets/architectureDiagram-Q4EWVU46-8Fsx8Bw5.js +36 -0
  13. package/public/assets/astro-CbQHKStN.js +1 -1
  14. package/public/assets/astro-HNnZUWAn.js +1 -1
  15. package/public/assets/blade-BjGOyj-B.js +1 -1
  16. package/public/assets/blade-D4QpJJKB.js +1 -1
  17. package/public/assets/{blockDiagram-DXYQGD6D-DEkkj6Fh.js → blockDiagram-DXYQGD6D-CSD_8P6t.js} +5 -5
  18. package/public/assets/{c4Diagram-AHTNJAMY-CEZo72O7.js → c4Diagram-AHTNJAMY-BANFpiWc.js} +1 -1
  19. package/public/assets/channel-BdMajkgC.js +1 -0
  20. package/public/assets/{chunk-4BX2VUAB-kct9rMc4.js → chunk-4BX2VUAB-DwXbCZ7y.js} +1 -1
  21. package/public/assets/{chunk-4TB4RGXK-przR2bht.js → chunk-4TB4RGXK-Dk2hp0kY.js} +3 -3
  22. package/public/assets/{chunk-55IACEB6-BtFWJCbx.js → chunk-55IACEB6-CFeiGe8t.js} +1 -1
  23. package/public/assets/{chunk-EDXVE4YY-wnitHF3B.js → chunk-EDXVE4YY-B0SJv4ej.js} +1 -1
  24. package/public/assets/{chunk-FMBD7UC4-8PDhdiie.js → chunk-FMBD7UC4-BakGwea6.js} +1 -1
  25. package/public/assets/{chunk-OYMX7WX6-TDftW5K8.js → chunk-OYMX7WX6-Dq0T_fTM.js} +2 -2
  26. package/public/assets/{chunk-QZHKN3VN-B5vnQXaM.js → chunk-QZHKN3VN-B5OglTTC.js} +1 -1
  27. package/public/assets/{chunk-YZCP3GAM-CGg2izn8.js → chunk-YZCP3GAM-NDaB-kfw.js} +1 -1
  28. package/public/assets/classDiagram-6PBFFD2Q-B-lcy3NJ.js +1 -0
  29. package/public/assets/classDiagram-v2-HSJHXN6E-B-lcy3NJ.js +1 -0
  30. package/public/assets/clone-CQaScT6j.js +1 -0
  31. package/public/assets/cobol-nBiQ_Alo.js +1 -1
  32. package/public/assets/cobol-nwyudZeR.js +1 -1
  33. package/public/assets/{cose-bilkent-S5V4N54A-CjXKbjHV.js → cose-bilkent-S5V4N54A-C_XiHCxn.js} +1 -1
  34. package/public/assets/crystal-DNxU26gB.js +1 -1
  35. package/public/assets/crystal-tKQVLTB8.js +1 -1
  36. package/public/assets/{dagre-KV5264BT-D-wBcwT7.js → dagre-KV5264BT-DeKC0ZTS.js} +2 -2
  37. package/public/assets/diagram-5BDNPKRD-BI21qA6c.js +10 -0
  38. package/public/assets/diagram-G4DWMVQ6-DXlN6VCm.js +24 -0
  39. package/public/assets/diagram-MMDJMWI5-BCJkUUZU.js +43 -0
  40. package/public/assets/diagram-TYMM5635-C-KOEBZV.js +24 -0
  41. package/public/assets/edge-BkV0erSs.js +1 -1
  42. package/public/assets/edge-FbVlp4U3.js +1 -1
  43. package/public/assets/elixir-CDX3lj18.js +1 -1
  44. package/public/assets/elixir-CkH2-t6x.js +1 -1
  45. package/public/assets/{erDiagram-SMLLAGMA-CUWacc8b.js → erDiagram-SMLLAGMA-_vucZwTa.js} +2 -2
  46. package/public/assets/erb-B12qg9BL.js +1 -1
  47. package/public/assets/erb-BYCe7drp.js +1 -1
  48. package/public/assets/{flowDiagram-DWJPFMVM-DVIhUX7Z.js → flowDiagram-DWJPFMVM-CWye_WgN.js} +4 -4
  49. package/public/assets/{ganttDiagram-T4ZO3ILL-g3ncLabH.js → ganttDiagram-T4ZO3ILL-Cg6T_N_V.js} +3 -3
  50. package/public/assets/gitGraphDiagram-UUTBAWPF-CI6tYFw1.js +106 -0
  51. package/public/assets/glimmer-js-ByusRIyA.js +1 -1
  52. package/public/assets/glimmer-js-Rg0-pVw9.js +1 -1
  53. package/public/assets/glimmer-ts-BfAWNZQY.js +1 -1
  54. package/public/assets/glimmer-ts-U6CK756n.js +1 -1
  55. package/public/assets/{graph-CDl50skT.js → graph-Ci4pDE3A.js} +1 -1
  56. package/public/assets/hack-CaT9iCJl.js +1 -1
  57. package/public/assets/hack-i7_Ulhet.js +1 -1
  58. package/public/assets/haml-B8DHNrY2.js +1 -1
  59. package/public/assets/haml-D5jkg6IW.js +1 -1
  60. package/public/assets/handlebars-BL8al0AC.js +1 -1
  61. package/public/assets/handlebars-BpdQsYii.js +1 -1
  62. package/public/assets/highlighted-body-OFNGDK62-xGLlGy3l.js +1 -0
  63. package/public/assets/html-GMplVEZG.js +1 -1
  64. package/public/assets/html-derivative-BFtXZ54Q.js +1 -1
  65. package/public/assets/html-derivative-DlHx6ybY.js +1 -1
  66. package/public/assets/html-pp8916En.js +1 -1
  67. package/public/assets/index-BNz332Xu.js +1669 -0
  68. package/public/assets/index-D5qFagsx.css +1 -0
  69. package/public/assets/infoDiagram-42DDH7IO-CigcPMhk.js +2 -0
  70. package/public/assets/{ishikawaDiagram-UXIWVN3A-BbjR1GdS.js → ishikawaDiagram-UXIWVN3A-CDZTRajH.js} +4 -4
  71. package/public/assets/jinja-4LBKfQ-Z.js +1 -1
  72. package/public/assets/jinja-f2NsQr07.js +1 -1
  73. package/public/assets/{journeyDiagram-VCZTEJTY-DJHw1u4a.js → journeyDiagram-VCZTEJTY-C9MHFDJ6.js} +4 -4
  74. package/public/assets/just-Cw27pwNe.js +1 -1
  75. package/public/assets/just-VxiPbLrw.js +1 -1
  76. package/public/assets/{kanban-definition-6JOO6SKY-agbEFS20.js → kanban-definition-6JOO6SKY-Hpr02Gir.js} +7 -7
  77. package/public/assets/{layout-BtrYpvUW.js → layout-uDkQb6eL.js} +1 -1
  78. package/public/assets/{linear-C4IQ6Sya.js → linear-D9bVI_aq.js} +1 -1
  79. package/public/assets/liquid-C0sCDyMI.js +1 -1
  80. package/public/assets/liquid-DYVedYrR.js +1 -1
  81. package/public/assets/marko-CnJfTvn9.js +1 -1
  82. package/public/assets/marko-DjSrsDqO.js +1 -1
  83. package/public/assets/mdc-BMNejdWA.js +1 -1
  84. package/public/assets/mdc-DTYItulj.js +1 -1
  85. package/public/assets/{min-DDWmdqiX.js → min-T0CPelcS.js} +1 -1
  86. package/public/assets/{mindmap-definition-QFDTVHPH-By9_uC_s.js → mindmap-definition-QFDTVHPH-BxDJYpMb.js} +8 -8
  87. package/public/assets/nim-BIad80T-.js +1 -1
  88. package/public/assets/nim-CVrawwO9.js +1 -1
  89. package/public/assets/perl-C0TMdlhV.js +1 -1
  90. package/public/assets/perl-NvoQZIq0.js +1 -1
  91. package/public/assets/php-Dhbhpdrm.js +1 -1
  92. package/public/assets/php-R6g_5hLQ.js +1 -1
  93. package/public/assets/pieDiagram-DEJITSTG-C214NdS2.js +30 -0
  94. package/public/assets/pug-CGlum2m_.js +1 -1
  95. package/public/assets/pug-DKIMFp6K.js +1 -1
  96. package/public/assets/{quadrantDiagram-34T5L4WZ-BszNW2Gc.js → quadrantDiagram-34T5L4WZ-B4hldTdV.js} +3 -3
  97. package/public/assets/razor-BDqjjVU7.js +1 -1
  98. package/public/assets/razor-Uh8Bk_45.js +1 -1
  99. package/public/assets/{requirementDiagram-MS252O5E-fdyCkRN5.js → requirementDiagram-MS252O5E-Cv-IZSGz.js} +1 -1
  100. package/public/assets/rst-BrH8l1NY.js +1 -1
  101. package/public/assets/rst-CRjBmOyv.js +1 -1
  102. package/public/assets/ruby-Dw2BHqvy.js +1 -1
  103. package/public/assets/ruby-Wjq7vjNf.js +1 -1
  104. package/public/assets/{sankeyDiagram-XADWPNL6-tg2HOfl2.js → sankeyDiagram-XADWPNL6-C52Wx9t_.js} +2 -2
  105. package/public/assets/{sequenceDiagram-FGHM5R23-GF78cQ5y.js → sequenceDiagram-FGHM5R23-C9gRx_I2.js} +5 -5
  106. package/public/assets/soy-8wufbnw4.js +1 -1
  107. package/public/assets/soy-Brmx7dQM.js +1 -1
  108. package/public/assets/{stateDiagram-FHFEXIEX-C709lyuE.js → stateDiagram-FHFEXIEX-BVweWFAE.js} +1 -1
  109. package/public/assets/stateDiagram-v2-QKLJ7IA2-CftkpTOS.js +1 -0
  110. package/public/assets/svelte-C_ipcX3V.js +1 -1
  111. package/public/assets/svelte-Cy7k_4gC.js +1 -1
  112. package/public/assets/templ-DhtptRzy.js +1 -1
  113. package/public/assets/templ-P3uqSqPl.js +1 -1
  114. package/public/assets/{timeline-definition-GMOUNBTQ-D2y-83yP.js → timeline-definition-GMOUNBTQ-Boc8Q03t.js} +3 -3
  115. package/public/assets/ts-tags-DQrlYJgV.js +1 -1
  116. package/public/assets/ts-tags-zn1MmPIZ.js +1 -1
  117. package/public/assets/twig-DNn4PbVi.js +1 -1
  118. package/public/assets/twig-xg9kU7Mw.js +1 -1
  119. package/public/assets/vennDiagram-DHZGUBPP-BE8XjSYZ.js +34 -0
  120. package/public/assets/vue-D2xRrEX4.js +1 -1
  121. package/public/assets/vue-DN_0RTcg.js +1 -1
  122. package/public/assets/vue-vine-BoDAl6tE.js +1 -1
  123. package/public/assets/vue-vine-CQOfvN7w.js +1 -1
  124. package/public/assets/{wardley-RL74JXVD-NEg4mPyC.js → wardley-RL74JXVD-MQjS4K_T.js} +1 -1
  125. package/public/assets/{wardleyDiagram-NUSXRM2D-BbXAj99t.js → wardleyDiagram-NUSXRM2D-CKUw97nD.js} +2 -2
  126. package/public/assets/{xychartDiagram-5P7HB3ND-C8Y3cC5f.js → xychartDiagram-5P7HB3ND-OkP2T3uc.js} +3 -3
  127. package/public/index.html +2 -2
  128. package/templates/front-canonical.tsx +4 -2
  129. package/public/assets/CodeEditor-DQqOn4xz-BnYIpEQ0.js +0 -31
  130. package/public/assets/FileTree-DjPzfDMq-jOFMfLV-.js +0 -21
  131. package/public/assets/MarkdownEditor-BbSy0bLV-_8qFqqna.js +0 -254
  132. package/public/assets/_baseUniq-DysFpiFw.js +0 -1
  133. package/public/assets/arc-D0sqkACA.js +0 -1
  134. package/public/assets/architectureDiagram-Q4EWVU46-DnyELkA2.js +0 -36
  135. package/public/assets/channel-Ci4DEwaO.js +0 -1
  136. package/public/assets/classDiagram-6PBFFD2Q-0PAZNzgC.js +0 -1
  137. package/public/assets/classDiagram-v2-HSJHXN6E-0PAZNzgC.js +0 -1
  138. package/public/assets/clone-_lPYOTLf.js +0 -1
  139. package/public/assets/diagram-5BDNPKRD-Ch4e4G6h.js +0 -10
  140. package/public/assets/diagram-G4DWMVQ6-a_PPD1uY.js +0 -24
  141. package/public/assets/diagram-MMDJMWI5-D9Lkv8IB.js +0 -43
  142. package/public/assets/diagram-TYMM5635-BlfaVidf.js +0 -24
  143. package/public/assets/gitGraphDiagram-UUTBAWPF-DpPqglKx.js +0 -106
  144. package/public/assets/highlighted-body-OFNGDK62-BcVhfY0-.js +0 -1
  145. package/public/assets/index-C9S_GD0T.js +0 -1330
  146. package/public/assets/index-CrFvzn5a.js +0 -9
  147. package/public/assets/index-Css1pwyY.css +0 -1
  148. package/public/assets/index-Vcq4gwWv.js +0 -1
  149. package/public/assets/infoDiagram-42DDH7IO-COfGVlDB.js +0 -2
  150. package/public/assets/pieDiagram-DEJITSTG-DfzJSvMR.js +0 -30
  151. package/public/assets/stateDiagram-v2-QKLJ7IA2-BbgcVrKo.js +0 -1
  152. package/public/assets/vennDiagram-DHZGUBPP-BOsWwU4c.js +0 -34
@@ -15,6 +15,11 @@ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:pat
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { parseArgs } from "node:util";
17
17
  import { createLocalWorkspaceRegistry } from "./localWorkspaces.js";
18
+ import {
19
+ createCliPluginAssetManager,
20
+ getGlobalPiExtensionsRoot,
21
+ resolveCliBoringPluginDirs
22
+ } from "./pluginDiscovery.js";
18
23
  const MODE_MAP = {
19
24
  "local": "direct",
20
25
  // no sandbox, full network access
@@ -98,11 +103,18 @@ async function provisionCliWorkspaceRuntime(opts) {
98
103
  if (!adapter) {
99
104
  throw new Error(`runtime mode ${opts.mode} does not support workspace provisioning`);
100
105
  }
101
- return await agent.provisionWorkspaceRuntime({
106
+ const result = await agent.provisionWorkspaceRuntime({
102
107
  plugins: [createBoringUiCliRuntimePlugin(), ...opts.plugins ?? []],
103
108
  adapter,
104
109
  runtimeLayout
105
110
  });
111
+ return {
112
+ ...result,
113
+ env: {
114
+ ...result.env,
115
+ BORING_AGENT_WORKSPACE_LOCAL_PLUGIN_ROOTS: opts.mode === "direct" || opts.mode === "local" ? "1" : "0"
116
+ }
117
+ };
106
118
  }
107
119
  function ensureFrontendBuilt(publicDir) {
108
120
  if (existsSync(join(publicDir, "index.html"))) return;
@@ -162,10 +174,174 @@ async function checkAuth() {
162
174
  const registry = ModelRegistry.create(authStorage);
163
175
  return registry.getAvailable().length;
164
176
  }
177
+ const FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID = "folder";
178
+ const RUNTIME_PLUGIN_TRUST_LABEL = "Trusted local runtime plugins";
179
+ const RUNTIME_PLUGIN_TRUST_DESCRIPTION = "Loads plugin UI code from trusted local Pi extension roots through the CLI-owned runtime module host.";
180
+ function createRuntimePluginDiagnosticsStore() {
181
+ const byWorkspace = /* @__PURE__ */ new Map();
182
+ function upsert(workspaceId, pluginId) {
183
+ const workspace = byWorkspace.get(workspaceId) ?? /* @__PURE__ */ new Map();
184
+ byWorkspace.set(workspaceId, workspace);
185
+ const existing = workspace.get(pluginId) ?? {
186
+ workspaceId,
187
+ pluginId,
188
+ recent: []
189
+ };
190
+ workspace.set(pluginId, existing);
191
+ return existing;
192
+ }
193
+ return {
194
+ record(diagnostic) {
195
+ if (!diagnostic.workspaceId || !diagnostic.pluginId) return;
196
+ const entry = upsert(diagnostic.workspaceId, diagnostic.pluginId);
197
+ const now = Date.now();
198
+ entry.lastDiagnostic = diagnostic;
199
+ entry.recent = [...entry.recent, diagnostic].slice(-12);
200
+ if (diagnostic.revision !== void 0) entry.revision = diagnostic.revision;
201
+ if (diagnostic.requestedPath) entry.lastRequestedPath = diagnostic.requestedPath;
202
+ if (diagnostic.resolvedPath) entry.lastResolvedPath = diagnostic.resolvedPath;
203
+ const details = diagnostic.details ?? {};
204
+ if (typeof details.rootDir === "string") entry.rootDir = details.rootDir;
205
+ if (typeof details.entryUrl === "string") entry.entryUrl = details.entryUrl;
206
+ if (typeof diagnostic.requestedPath === "string") entry.frontEntrySubpath = diagnostic.requestedPath;
207
+ if (diagnostic.stage === "track" && diagnostic.outcome === "tracked") {
208
+ entry.lastErrorCode = void 0;
209
+ entry.lastErrorMessage = void 0;
210
+ entry.lastErrorStage = void 0;
211
+ }
212
+ if (diagnostic.stage === "cache") entry.lastRequestAt = now;
213
+ if (diagnostic.stage === "transform" && diagnostic.outcome === "served") {
214
+ entry.lastTransformAt = now;
215
+ entry.lastTransformDurationMs = diagnostic.durationMs;
216
+ }
217
+ if (diagnostic.stage === "serve" && diagnostic.outcome === "served") {
218
+ entry.lastServeAt = now;
219
+ entry.lastServeDurationMs = diagnostic.durationMs;
220
+ entry.lastErrorCode = void 0;
221
+ entry.lastErrorMessage = void 0;
222
+ entry.lastErrorStage = void 0;
223
+ }
224
+ if (diagnostic.outcome === "rejected") {
225
+ entry.lastRejectedAt = now;
226
+ entry.lastErrorCode = diagnostic.code;
227
+ entry.lastErrorMessage = diagnostic.msg;
228
+ entry.lastErrorStage = diagnostic.stage;
229
+ }
230
+ if (diagnostic.stage === "cleanup" && diagnostic.outcome === "disposed") {
231
+ entry.lastDisposedAt = now;
232
+ }
233
+ },
234
+ snapshot(workspaceId) {
235
+ return [...byWorkspace.get(workspaceId)?.values() ?? []].map((entry) => ({ ...entry, recent: [...entry.recent] })).sort((a, b) => a.pluginId.localeCompare(b.pluginId));
236
+ },
237
+ disposeWorkspace(workspaceId) {
238
+ byWorkspace.delete(workspaceId);
239
+ }
240
+ };
241
+ }
242
+ function syncRuntimeHostFromPluginEvents(runtimeHost, workspaceId, events) {
243
+ for (const event of events) {
244
+ if (event.type === "boring.plugin.unload" || event.type === "boring.plugin.load" && !event.frontTarget) {
245
+ runtimeHost.untrackPlugin(workspaceId, event.id);
246
+ }
247
+ }
248
+ }
249
+ function buildRuntimePluginDiagnosticsResponse(args) {
250
+ const byPlugin = /* @__PURE__ */ new Map();
251
+ for (const plugin of args.loaded) {
252
+ byPlugin.set(plugin.id, {
253
+ id: plugin.id,
254
+ ...plugin.version ? { version: plugin.version } : {},
255
+ ...plugin.rootDir ? { rootDir: plugin.rootDir } : {},
256
+ ...plugin.frontPath ? { frontPath: plugin.frontPath } : {},
257
+ ...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
258
+ ...plugin.revision !== void 0 ? { serverLoadedRevision: plugin.revision } : {}
259
+ });
260
+ }
261
+ for (const error of args.errors) {
262
+ const current = byPlugin.get(error.id) ?? { id: error.id };
263
+ byPlugin.set(error.id, {
264
+ ...current,
265
+ serverError: error.message
266
+ });
267
+ }
268
+ for (const hostEntry of args.host) {
269
+ const current = byPlugin.get(hostEntry.pluginId) ?? { id: hostEntry.pluginId };
270
+ byPlugin.set(hostEntry.pluginId, {
271
+ ...current,
272
+ ...current.rootDir ? {} : hostEntry.rootDir ? { rootDir: hostEntry.rootDir } : {},
273
+ ...current.frontPath ? {} : hostEntry.frontEntrySubpath ? { frontPath: hostEntry.frontEntrySubpath } : {},
274
+ ...current.frontTarget ? {} : hostEntry.entryUrl ? { frontTarget: { kind: "native", entryUrl: hostEntry.entryUrl, revision: hostEntry.revision ?? 0, trust: "local-trusted-native" } } : {},
275
+ host: hostEntry
276
+ });
277
+ }
278
+ return {
279
+ workspaceId: args.workspaceId,
280
+ plugins: [...byPlugin.values()].sort((a, b) => a.id.localeCompare(b.id))
281
+ };
282
+ }
283
+ async function createFolderModeApp(opts) {
284
+ const workspaceRoot = resolve(opts.workspaceRoot);
285
+ const projectName = opts.projectName ?? (basename(workspaceRoot) || "workspace");
286
+ const [{ createWorkspaceAgentServer, readWorkspacePluginPackageRuntimePlugins }, { createPluginFrontRuntimeHost }] = await Promise.all([
287
+ import("@hachej/boring-workspace/app/server"),
288
+ import("./pluginFrontRuntime.js")
289
+ ]);
290
+ const diagnosticsStore = createRuntimePluginDiagnosticsStore();
291
+ const runtimeHost = await createPluginFrontRuntimeHost({
292
+ onDiagnostic: (diagnostic) => diagnosticsStore.record(diagnostic)
293
+ });
294
+ const runtimeProvisioning = await provisionCliWorkspaceRuntime({
295
+ workspaceRoot,
296
+ mode: opts.mode,
297
+ provisionWorkspace: opts.provisionWorkspace,
298
+ plugins: readWorkspacePluginPackageRuntimePlugins(resolveCliBoringPluginDirs(workspaceRoot))
299
+ });
300
+ const app = await createWorkspaceAgentServer({
301
+ workspaceRoot,
302
+ mode: opts.mode,
303
+ logger: false,
304
+ provisionWorkspace: false,
305
+ runtimeProvisioning,
306
+ additionalBoringPluginDirs: [getGlobalPiExtensionsRoot()],
307
+ boringPluginFrontTargetResolver: runtimeHost.createFrontTargetResolver(FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID),
308
+ boringPluginIncludeLegacyFrontUrl: false
309
+ });
310
+ await runtimeHost.registerRoutes(app);
311
+ const folderAssetManager = app.__boringAssetManager;
312
+ const closeFolderRuntimeCleanup = folderAssetManager?.subscribe((event) => {
313
+ syncRuntimeHostFromPluginEvents(runtimeHost, FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID, [event]);
314
+ });
315
+ if (closeFolderRuntimeCleanup) {
316
+ app.addHook("onClose", async () => {
317
+ closeFolderRuntimeCleanup();
318
+ });
319
+ }
320
+ app.get("/api/v1/runtime-plugin-diagnostics", async () => {
321
+ const manager = app.__boringAssetManager;
322
+ return buildRuntimePluginDiagnosticsResponse({
323
+ workspaceId: FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID,
324
+ loaded: manager?.inspectLoaded() ?? [],
325
+ errors: manager?.getErrors() ?? [],
326
+ host: diagnosticsStore.snapshot(FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID)
327
+ });
328
+ });
329
+ app.get("/api/v1/workspace/meta", async () => ({
330
+ workspaceRoot,
331
+ projectName,
332
+ version: CLI_VERSION,
333
+ runtimePluginFrontLoadingEnabled: true,
334
+ runtimePluginTrustLabel: RUNTIME_PLUGIN_TRUST_LABEL,
335
+ runtimePluginTrustDescription: RUNTIME_PLUGIN_TRUST_DESCRIPTION,
336
+ runtimePluginDiagnosticsEnabled: true
337
+ }));
338
+ return app;
339
+ }
165
340
  async function startFolderMode(opts) {
166
341
  const workspaceRoot = process.env.BORING_AGENT_WORKSPACE_ROOT ?? resolve(opts.folderArg ?? process.cwd());
167
342
  const projectName = basename(resolve(workspaceRoot)) || "workspace";
168
343
  const modelCount = await checkAuth();
344
+ const url = `http://localhost:${opts.port}`;
169
345
  console.log(`
170
346
  ${projectName}`);
171
347
  console.log(` workspace ${workspaceRoot}`);
@@ -173,40 +349,39 @@ ${projectName}`);
173
349
  console.log(` port ${opts.port}`);
174
350
  console.log(` host ${opts.host}`);
175
351
  if (modelCount === 0) console.log(AUTH_GUIDE);
176
- const runtimeProvisioning = await provisionCliWorkspaceRuntime({
177
- workspaceRoot,
178
- mode: opts.mode
179
- });
180
- const { createWorkspaceAgentServer } = await import("@hachej/boring-workspace/app/server");
181
- const app = await createWorkspaceAgentServer({
352
+ console.log(`
353
+ starting ${url} \u2026`);
354
+ const app = await createFolderModeApp({
182
355
  workspaceRoot,
183
356
  mode: opts.mode,
184
- logger: false,
185
- provisionWorkspace: false,
186
- runtimeProvisioning
357
+ projectName
187
358
  });
188
- app.get("/api/v1/workspace/meta", async () => ({
189
- workspaceRoot,
190
- projectName,
191
- version: CLI_VERSION
192
- }));
193
359
  await registerStatic(app, opts.publicDir);
194
360
  await app.listen({ port: opts.port, host: opts.host });
195
- console.log(`
196
- http://localhost:${opts.port}
361
+ console.log(` ${url} ready
197
362
  `);
198
- openBrowser(`http://localhost:${opts.port}`);
363
+ openBrowser(url);
199
364
  }
200
- async function startWorkspacesMode(opts) {
201
- const [workspaceAppServer, workspaceServer, agentServer, fastifyModule] = await Promise.all([
365
+ async function createWorkspacesModeApp(opts) {
366
+ const [workspaceAppServer, workspaceServer, agentServer, fastifyModule, { createPluginFrontRuntimeHost }] = await Promise.all([
202
367
  import("@hachej/boring-workspace/app/server"),
203
368
  import("@hachej/boring-workspace/server"),
204
369
  import("@hachej/boring-agent/server"),
205
- import("fastify")
370
+ import("fastify"),
371
+ import("./pluginFrontRuntime.js")
206
372
  ]);
207
- const registry = createLocalWorkspaceRegistry();
373
+ const registry = createLocalWorkspaceRegistry(opts.registryPath);
208
374
  const app = fastifyModule.default({ logger: false, bodyLimit: 16 * 1024 * 1024 });
375
+ const diagnosticsStore = createRuntimePluginDiagnosticsStore();
376
+ const runtimeHost = await createPluginFrontRuntimeHost({
377
+ onDiagnostic: (diagnostic) => diagnosticsStore.record(diagnostic)
378
+ });
379
+ await runtimeHost.registerRoutes(app);
209
380
  const bridges = /* @__PURE__ */ new Map();
381
+ const workspaceEventClosers = /* @__PURE__ */ new Map();
382
+ const pluginRuntimes = /* @__PURE__ */ new Map();
383
+ const pluginPiSnapshots = /* @__PURE__ */ new Map();
384
+ const runtimeProvisioningByWorkspace = /* @__PURE__ */ new Map();
210
385
  function getBridge(workspaceId) {
211
386
  let bridge = bridges.get(workspaceId);
212
387
  if (!bridge) {
@@ -224,6 +399,65 @@ async function startWorkspacesMode(opts) {
224
399
  async function workspaceFromRequest(request) {
225
400
  return await requireWorkspace(resolveWorkspaceIdFromRequest(request));
226
401
  }
402
+ function pluginRuntimeKey(workspace) {
403
+ return `${workspace.id}:${workspace.path}`;
404
+ }
405
+ function syncLoadedPluginPiSnapshot(workspace, manager) {
406
+ pluginPiSnapshots.set(pluginRuntimeKey(workspace), manager.inspectLoadedPiSnapshot());
407
+ }
408
+ function getOrCreatePluginRuntime(workspace) {
409
+ const key = pluginRuntimeKey(workspace);
410
+ let runtime = pluginRuntimes.get(key);
411
+ if (!runtime) {
412
+ const manager = createCliPluginAssetManager(workspace.path, {
413
+ frontTargetResolver: runtimeHost.createFrontTargetResolver(workspace.id),
414
+ includeLegacyFrontUrl: false
415
+ });
416
+ runtime = {
417
+ manager,
418
+ ensureLoaded: manager.load().then(() => {
419
+ syncLoadedPluginPiSnapshot(workspace, manager);
420
+ })
421
+ };
422
+ pluginRuntimes.set(key, runtime);
423
+ }
424
+ return runtime;
425
+ }
426
+ async function getLoadedPluginRuntime(workspace) {
427
+ const runtime = getOrCreatePluginRuntime(workspace);
428
+ await runtime.ensureLoaded;
429
+ return runtime;
430
+ }
431
+ function getLoadedPluginPiSnapshot(workspace) {
432
+ return pluginPiSnapshots.get(pluginRuntimeKey(workspace)) ?? {
433
+ additionalSkillPaths: [],
434
+ packages: [],
435
+ extensionPaths: []
436
+ };
437
+ }
438
+ async function disposeWorkspaceRuntime(workspace) {
439
+ for (const close of workspaceEventClosers.get(workspace.id) ?? []) {
440
+ try {
441
+ close();
442
+ } catch {
443
+ }
444
+ }
445
+ workspaceEventClosers.delete(workspace.id);
446
+ const runtimeKey = pluginRuntimeKey(workspace);
447
+ pluginRuntimes.delete(runtimeKey);
448
+ pluginPiSnapshots.delete(runtimeKey);
449
+ runtimeProvisioningByWorkspace.delete(workspace.id);
450
+ bridges.delete(workspace.id);
451
+ diagnosticsStore.disposeWorkspace(workspace.id);
452
+ await runtimeHost.disposeWorkspace(workspace.id);
453
+ }
454
+ function reloadDiagnostics(scan) {
455
+ return scan.errors.map((error) => ({
456
+ source: "workspaces-plugin-manager",
457
+ message: error.message,
458
+ pluginId: error.id
459
+ }));
460
+ }
227
461
  app.get("/api/v1/local-workspaces", async () => ({
228
462
  workspaces: await registry.list()
229
463
  }));
@@ -243,10 +477,12 @@ async function startWorkspacesMode(opts) {
243
477
  });
244
478
  }
245
479
  });
246
- app.delete("/api/v1/local-workspaces/:id", async (request) => {
480
+ app.delete("/api/v1/local-workspaces/:id", async (request, reply) => {
247
481
  const { id } = request.params;
482
+ const workspace = await registry.get(id);
248
483
  await registry.remove(id);
249
- return { ok: true };
484
+ if (workspace) await disposeWorkspaceRuntime(workspace);
485
+ return reply.send({ ok: true });
250
486
  });
251
487
  app.get("/api/v1/workspaces", async () => ({
252
488
  workspaces: (await registry.list()).map(toCoreWorkspace)
@@ -260,20 +496,50 @@ async function startWorkspacesMode(opts) {
260
496
  await app.register(agentServer.registerAgentRoutes, {
261
497
  mode: opts.mode,
262
498
  systemPromptAppend: workspaceAppServer.buildWorkspaceContextPrompt(),
499
+ getSystemPromptDynamic: async ({ workspaceId }) => {
500
+ const workspace = await requireWorkspace(workspaceId);
501
+ await getLoadedPluginRuntime(workspace);
502
+ return getLoadedPluginPiSnapshot(workspace).systemPromptAppend;
503
+ },
263
504
  getWorkspaceId: async (request) => (await workspaceFromRequest(request)).id,
264
505
  getWorkspaceRoot: async (workspaceId) => (await requireWorkspace(workspaceId)).path,
265
506
  getSessionNamespace: async ({ workspaceId }) => `local-workspace-${workspaceId}`,
266
- provisionRuntime: async ({ workspaceRoot, runtimeMode, runtimeLayout, provisioningAdapter }) => {
267
- return await provisionCliWorkspaceRuntime({
507
+ provisionRuntime: async ({ workspaceId, workspaceRoot, runtimeMode, runtimeLayout, provisioningAdapter }) => {
508
+ if (runtimeProvisioningByWorkspace.has(workspaceId)) {
509
+ return runtimeProvisioningByWorkspace.get(workspaceId);
510
+ }
511
+ const provisioned = await provisionCliWorkspaceRuntime({
268
512
  workspaceRoot,
269
513
  mode: runtimeMode,
514
+ provisionWorkspace: opts.provisionWorkspace,
270
515
  adapter: provisioningAdapter,
271
- runtimeLayout
516
+ runtimeLayout,
517
+ plugins: workspaceAppServer.readWorkspacePluginPackageRuntimePlugins(resolveCliBoringPluginDirs(workspaceRoot))
272
518
  });
519
+ runtimeProvisioningByWorkspace.set(workspaceId, provisioned);
520
+ return provisioned;
521
+ },
522
+ beforeReload: async ({ workspaceId }) => {
523
+ const workspace = await requireWorkspace(workspaceId);
524
+ const runtime = await getLoadedPluginRuntime(workspace);
525
+ const scan = await runtime.manager.load();
526
+ syncLoadedPluginPiSnapshot(workspace, runtime.manager);
527
+ syncRuntimeHostFromPluginEvents(runtimeHost, workspaceId, scan.events);
528
+ return {
529
+ restart_warnings: workspaceServer.collectRestartWarnings(scan.events),
530
+ diagnostics: reloadDiagnostics(scan)
531
+ };
532
+ },
533
+ getPi: async ({ workspaceId, workspaceRoot }) => {
534
+ const workspace = await requireWorkspace(workspaceId);
535
+ await getLoadedPluginRuntime(workspace);
536
+ return {
537
+ additionalSkillPaths: [join(workspaceRoot, ".agents", "skills")],
538
+ packages: [],
539
+ extensionPaths: [],
540
+ getHotReloadableResources: () => getLoadedPluginPiSnapshot(workspace)
541
+ };
273
542
  },
274
- getPi: async ({ workspaceRoot }) => ({
275
- additionalSkillPaths: [join(workspaceRoot, ".agents", "skills")]
276
- }),
277
543
  getExtraTools: async ({ workspaceId, workspaceRoot, workspaceFsCapability }) => [
278
544
  ...workspaceServer.createWorkspaceUiTools(getBridge(workspaceId), {
279
545
  workspaceRoot: workspaceFsCapability === "strong" ? workspaceRoot : void 0
@@ -283,23 +549,127 @@ async function startWorkspacesMode(opts) {
283
549
  await app.register(workspaceServer.uiRoutes, {
284
550
  getBridge: async (request) => getBridge((await workspaceFromRequest(request)).id)
285
551
  });
552
+ app.get("/api/v1/runtime-plugin-diagnostics", async (request) => {
553
+ const workspace = await workspaceFromRequest(request);
554
+ const runtime = await getLoadedPluginRuntime(workspace);
555
+ return buildRuntimePluginDiagnosticsResponse({
556
+ workspaceId: workspace.id,
557
+ loaded: runtime.manager.inspectLoaded(),
558
+ errors: runtime.manager.getErrors(),
559
+ host: diagnosticsStore.snapshot(workspace.id)
560
+ });
561
+ });
562
+ app.get("/api/v1/agent-plugins", async (request) => {
563
+ const workspace = await workspaceFromRequest(request);
564
+ const runtime = await getLoadedPluginRuntime(workspace);
565
+ return runtime.manager.list();
566
+ });
567
+ app.get("/api/v1/agent-plugins/:id/error", async (request, reply) => {
568
+ const workspace = await workspaceFromRequest(request);
569
+ const runtime = await getLoadedPluginRuntime(workspace);
570
+ const { id } = request.params;
571
+ const error = runtime.manager.getError(id);
572
+ if (error == null) return reply.code(404).send({ error: "not_found" });
573
+ return reply.type("text/plain").send(error);
574
+ });
575
+ app.get("/api/v1/agent-plugins/events", async (request, reply) => {
576
+ const workspace = await workspaceFromRequest(request);
577
+ const runtime = await getLoadedPluginRuntime(workspace);
578
+ const manager = runtime.manager;
579
+ reply.hijack();
580
+ const res = reply.raw;
581
+ res.statusCode = 200;
582
+ res.setHeader("Content-Type", "text/event-stream");
583
+ res.setHeader("Cache-Control", "no-cache, no-transform");
584
+ res.setHeader("Connection", "keep-alive");
585
+ res.setHeader("X-Accel-Buffering", "no");
586
+ res.flushHeaders?.();
587
+ const write = (eventName, payload) => {
588
+ try {
589
+ res.write(`event: ${eventName}
590
+ `);
591
+ res.write(`data: ${JSON.stringify(payload)}
592
+
593
+ `);
594
+ } catch {
595
+ }
596
+ };
597
+ const liveQueue = [];
598
+ let replaying = true;
599
+ const unsubscribe = manager.subscribe((event) => {
600
+ if (event.type === "boring.plugin.unload" || event.type === "boring.plugin.load" && !event.frontTarget) {
601
+ runtimeHost.untrackPlugin(workspace.id, event.id);
602
+ }
603
+ const payload = {
604
+ ...event,
605
+ workspaceId: workspace.id,
606
+ replay: false
607
+ };
608
+ if (replaying) {
609
+ liveQueue.push({ eventName: event.type, payload });
610
+ return;
611
+ }
612
+ write(event.type, payload);
613
+ });
614
+ for (const plugin of manager.list()) {
615
+ write("boring.plugin.load", {
616
+ type: "boring.plugin.load",
617
+ id: plugin.id,
618
+ boring: plugin.boring,
619
+ version: plugin.version,
620
+ revision: plugin.revision,
621
+ ...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
622
+ workspaceId: workspace.id,
623
+ replay: true
624
+ });
625
+ }
626
+ write("boring.plugin.replay-complete", {
627
+ type: "boring.plugin.replay-complete",
628
+ workspaceId: workspace.id,
629
+ replay: true
630
+ });
631
+ replaying = false;
632
+ for (const event of liveQueue) write(event.eventName, event.payload);
633
+ const heartbeat = setInterval(() => {
634
+ try {
635
+ res.write(": heartbeat\n\n");
636
+ } catch {
637
+ }
638
+ }, 25e3);
639
+ const closeStream = () => {
640
+ clearInterval(heartbeat);
641
+ unsubscribe();
642
+ workspaceEventClosers.get(workspace.id)?.delete(closeStream);
643
+ try {
644
+ res.end();
645
+ } catch {
646
+ }
647
+ };
648
+ const closers = workspaceEventClosers.get(workspace.id) ?? /* @__PURE__ */ new Set();
649
+ closers.add(closeStream);
650
+ workspaceEventClosers.set(workspace.id, closers);
651
+ request.raw.on("close", closeStream);
652
+ });
286
653
  app.get("/api/v1/workspace/meta", async () => ({
287
654
  projectName: "Boring UI",
288
655
  workspacesMode: true,
289
- version: CLI_VERSION
656
+ version: CLI_VERSION,
657
+ runtimePluginFrontLoadingEnabled: true,
658
+ runtimePluginTrustLabel: RUNTIME_PLUGIN_TRUST_LABEL,
659
+ runtimePluginTrustDescription: RUNTIME_PLUGIN_TRUST_DESCRIPTION,
660
+ runtimePluginDiagnosticsEnabled: true
290
661
  }));
662
+ return app;
663
+ }
664
+ async function startWorkspacesMode(opts) {
665
+ const app = await createWorkspacesModeApp({ mode: opts.mode });
666
+ const registry = createLocalWorkspaceRegistry();
291
667
  await registerStatic(app, opts.publicDir);
292
668
  await app.listen({ port: opts.port, host: opts.host });
293
669
  const initialWorkspace = (await registry.list()).find((workspace) => workspace.available);
294
670
  const initialUrl = initialWorkspace ? `http://localhost:${opts.port}/workspace/${encodeURIComponent(initialWorkspace.id)}` : `http://localhost:${opts.port}`;
295
- console.log(`
296
- Boring UI`);
297
671
  console.log(` workspaces ${registry.path}`);
298
- console.log(` mode ${opts.cliMode}`);
299
- console.log(` port ${opts.port}`);
300
- console.log(` host ${opts.host}`);
301
- console.log(`
302
- ${initialUrl}
672
+ console.log(` ${initialUrl} ready
303
673
  `);
304
674
  if (await checkAuth() === 0) console.log(AUTH_GUIDE);
305
675
  openBrowser(initialUrl);
@@ -457,6 +827,7 @@ async function runCli(options) {
457
827
  mode: { type: "string", short: "m" },
458
828
  name: { type: "string", short: "n" },
459
829
  path: { type: "string" },
830
+ json: { type: "boolean" },
460
831
  help: { type: "boolean", short: "h" }
461
832
  },
462
833
  allowPositionals: true,
@@ -510,6 +881,10 @@ async function runCli(options) {
510
881
  });
511
882
  return;
512
883
  }
884
+ if (positionals[0] === "plugin-status") {
885
+ handlePluginStatusCommand({ json: args.json === true });
886
+ return;
887
+ }
513
888
  if (positionals[0] === "scaffold-plugin") {
514
889
  await handleScaffoldPluginCommand({ positionals });
515
890
  return;
@@ -526,6 +901,32 @@ async function runCli(options) {
526
901
  function defaultWorkspaceRoot() {
527
902
  return process.env.BORING_AGENT_WORKSPACE_ROOT ?? process.cwd();
528
903
  }
904
+ function workspaceLocalPluginRootsEnabled() {
905
+ const raw = process.env.BORING_AGENT_WORKSPACE_LOCAL_PLUGIN_ROOTS;
906
+ if (raw == null || raw.trim() === "") return true;
907
+ return !["0", "false", "no", "off"].includes(raw.trim().toLowerCase());
908
+ }
909
+ function buildPluginStatus() {
910
+ const workspaceRoot = resolve(defaultWorkspaceRoot());
911
+ const enabled = workspaceLocalPluginRootsEnabled();
912
+ return {
913
+ workspaceLocalPluginRoots: enabled,
914
+ workspaceRoot,
915
+ extensionsDir: join(workspaceRoot, ".pi", "extensions"),
916
+ reloadSupported: enabled,
917
+ ...enabled ? {} : {
918
+ reason: "This runtime writes to a remote sandbox; host-side plugin discovery cannot load .pi/extensions from there."
919
+ }
920
+ };
921
+ }
922
+ function handlePluginStatusCommand(opts) {
923
+ const status = buildPluginStatus();
924
+ if (opts.json) {
925
+ console.log(JSON.stringify(status, null, 2));
926
+ return;
927
+ }
928
+ console.log(status.workspaceLocalPluginRoots ? `workspace-local plugin roots enabled: ${status.extensionsDir}` : `workspace-local plugin roots disabled: ${status.reason}`);
929
+ }
529
930
  async function handleVerifyPluginCommand(opts) {
530
931
  const maybeName = opts.positionals[1];
531
932
  const maybeWorkspace = opts.positionals[2];
@@ -556,6 +957,10 @@ async function handleScaffoldPluginCommand(opts) {
556
957
  if (!name) {
557
958
  throw new Error("usage: boring-ui scaffold-plugin <name> [workspace]");
558
959
  }
960
+ const status = buildPluginStatus();
961
+ if (!status.workspaceLocalPluginRoots) {
962
+ throw new Error(`${status.reason} Do not scaffold into .pi/extensions in this runtime.`);
963
+ }
559
964
  const workspaceRoot = resolve(opts.positionals[2] ?? defaultWorkspaceRoot());
560
965
  const { scaffoldPlugin } = await import("./scaffoldPlugin.js");
561
966
  const result = scaffoldPlugin({ name, workspaceRoot });
@@ -577,6 +982,8 @@ async function handleScaffoldPluginCommand(opts) {
577
982
  }
578
983
  export {
579
984
  createBoringUiCliRuntimePlugin,
985
+ createFolderModeApp,
986
+ createWorkspacesModeApp,
580
987
  provisionCliWorkspaceRuntime,
581
988
  registerStatic,
582
989
  resolveBoringUiCliPackageRoot,
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { homedir } from "node:os";
3
+ import { join, resolve } from "node:path";
4
+ import {
5
+ BoringPluginAssetManager
6
+ } from "@hachej/boring-workspace/server";
7
+ import {
8
+ readWorkspacePluginPackagePiSnapshot
9
+ } from "@hachej/boring-workspace/app/server";
10
+ function getGlobalPiExtensionsRoot(options = {}) {
11
+ return resolve(options.globalRoot ?? join(homedir(), ".pi", "agent", "extensions"));
12
+ }
13
+ function resolveCliBoringPluginDirs(workspaceRoot, options = {}) {
14
+ const roots = [
15
+ getGlobalPiExtensionsRoot(options),
16
+ resolve(workspaceRoot, ".pi", "extensions")
17
+ ];
18
+ return [...new Set(roots)];
19
+ }
20
+ function readCliPluginPiSnapshot(workspaceRoot, options = {}) {
21
+ return readWorkspacePluginPackagePiSnapshot(resolveCliBoringPluginDirs(workspaceRoot, options));
22
+ }
23
+ function createCliPluginAssetManager(workspaceRoot, options = {}) {
24
+ return new BoringPluginAssetManager({
25
+ pluginDirs: resolveCliBoringPluginDirs(workspaceRoot, options),
26
+ errorRoot: resolve(workspaceRoot, ".boring-agent", "plugin-errors"),
27
+ frontTargetResolver: options.frontTargetResolver,
28
+ includeLegacyFrontUrl: options.includeLegacyFrontUrl
29
+ });
30
+ }
31
+ export {
32
+ createCliPluginAssetManager,
33
+ getGlobalPiExtensionsRoot,
34
+ readCliPluginPiSnapshot,
35
+ resolveCliBoringPluginDirs
36
+ };