acrylblends 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +14 -5
  3. package/dist/index.js.map +1 -1
  4. package/legacy/README.md +4 -1
  5. package/legacy/agent/858de7a1ad0b/src/app.ts +289 -0
  6. package/legacy/agent/858de7a1ad0b/src/routes.ts +145 -0
  7. package/legacy/agent/b96ba6e3598a/src/app.ts +285 -0
  8. package/legacy/agent/b96ba6e3598a/src/routes.ts +145 -0
  9. package/legacy/agent/ecbc84f1c1e5/src/app.ts +291 -0
  10. package/legacy/agent/ecbc84f1c1e5/src/routes.ts +145 -0
  11. package/legacy/blank/2447ac6d5d24/src/app.ts +61 -0
  12. package/legacy/blank/387f7e25aaeb/src/app.ts +81 -0
  13. package/legacy/blank/5b9f10e1ba5e/src/app.ts +61 -0
  14. package/legacy/blank/5f6dd8910c50/src/app.ts +152 -0
  15. package/legacy/blank/5f6dd8910c50/src/routes.ts +75 -0
  16. package/legacy/blank/a7e8f539ffee/src/app.ts +160 -0
  17. package/legacy/blank/a7e8f539ffee/src/routes.ts +75 -0
  18. package/legacy/blank/bdb9735e013b/src/app.ts +61 -0
  19. package/legacy/blank/c3be1d735b1a/src/app.ts +61 -0
  20. package/legacy/blank/c3be1d735b1a/src/routes.ts +18 -0
  21. package/legacy/blank/e04303a7f8dd/src/app.ts +61 -0
  22. package/legacy/chat/5120c4a9fa61/src/app.ts +157 -0
  23. package/legacy/chat/5120c4a9fa61/src/routes.ts +29 -0
  24. package/legacy/chat/d4464dfb3b04/src/app.ts +165 -0
  25. package/legacy/chat/d4464dfb3b04/src/routes.ts +29 -0
  26. package/legacy/frozen.json +7 -0
  27. package/legacy/layouts.json +146 -45
  28. package/legacy/nodejs/9d59115118c7/src/app.ts +150 -0
  29. package/legacy/nodejs/9d59115118c7/src/routes.ts +59 -0
  30. package/legacy/nodejs/a6a340b4d3a6/src/app.ts +158 -0
  31. package/legacy/nodejs/a6a340b4d3a6/src/routes.ts +59 -0
  32. package/legacy/notes/fb43a1c5f3b4/src/app.ts +165 -0
  33. package/legacy/notes/fb43a1c5f3b4/src/routes.ts +21 -0
  34. package/legacy/notes/fc3fe7c749d2/src/app.ts +157 -0
  35. package/legacy/notes/fc3fe7c749d2/src/routes.ts +21 -0
  36. package/legacy/todo/164274ffcf7f/src/app.ts +145 -0
  37. package/legacy/todo/164274ffcf7f/src/todo.ts +137 -0
  38. package/lib/cordis/door.d.ts +27 -6
  39. package/lib/cordis/door.js +40 -9
  40. package/package.json +1 -1
  41. package/plugins/acryl-door/src/cli/acryl.ts +7 -2
  42. package/plugins/acryl-door/src/host/door.ts +41 -3
  43. package/plugins/acryl-door/src/host/verbs.ts +3 -3
  44. package/plugins/extensions/index.ts +12 -6
  45. package/plugins/plugin-share/index.ts +99 -1
  46. package/plugins/plugin-share/publish.ts +17 -3
  47. package/plugins/plugin-share/pull-check.ts +2 -0
  48. package/plugins/plugin-share/pull-list.ts +71 -0
  49. package/plugins/showcase/cards.ts +162 -0
  50. package/plugins/showcase/index.ts +116 -0
  51. package/plugins/showcase/plugin.json +11 -0
  52. package/templates/agent/AGENTS.md +4 -0
  53. package/templates/agent/blend.yaml +3 -0
  54. package/templates/agent/src/app.ts +2 -0
  55. package/templates/agent/src/routes.ts +23 -5
  56. package/templates/agent/template.json +1 -0
  57. package/templates/agent/test/app.test.ts +26 -0
  58. /package/legacy/blank/{d6d371c9a65e → 2447ac6d5d24}/src/routes.ts +0 -0
  59. /package/legacy/blank/{5a91c558af22 → 387f7e25aaeb}/src/routes.ts +0 -0
  60. /package/legacy/blank/{b2cbb759c889 → 5b9f10e1ba5e}/src/routes.ts +0 -0
  61. /package/legacy/blank/{9c650cf52bf3 → bdb9735e013b}/src/routes.ts +0 -0
  62. /package/legacy/blank/{aea589283445 → e04303a7f8dd}/src/routes.ts +0 -0
@@ -3,6 +3,7 @@ import { Acryl } from '../plugins/acryl-router/src/index.ts'
3
3
  import { bodyLimit } from '../plugins/acryl-router/src/middleware/index.ts'
4
4
  import type {} from '../plugins/agent/index.ts'
5
5
  import type {} from '../plugins/extensions/index.ts'
6
+ import { type Home, renderWidgets } from '../plugins/home/index.ts'
6
7
  import type {} from '../plugins/tasks/index.ts'
7
8
 
8
9
  export const APP_NAME = '__APP_NAME__'
@@ -59,20 +60,24 @@ const appearanceScript = (prefix: string) => `<script src="${prefix}/appearance.
59
60
  const settingsPage = (prefix: string) =>
60
61
  `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Settings - ${APP_NAME}</title>${APPEARANCE_STYLE}${appearanceScript(prefix)}</head><body><h1>Settings</h1><p><label for="theme">Appearance</label> <select id="theme" name="theme"><option value="system">Match the system</option><option value="light">Light</option><option value="dark">Dark</option></select></p><p id="saved" role="status"></p><p><a href="${prefix}/">Back to ${APP_NAME}</a></p><script>(function(){var select=document.getElementById('theme'),saved=document.getElementById('saved');try{select.value=localStorage.getItem('theme')||'system'}catch(e){}select.addEventListener('change',function(){var value=select.value;try{if(value==='system'){localStorage.removeItem('theme');delete document.documentElement.dataset.theme}else{localStorage.setItem('theme',value);document.documentElement.dataset.theme=value}saved.textContent='Saved: '+select.options[select.selectedIndex].text}catch(e){saved.textContent='This browser would not keep the setting.'}})})()</script></body></html>`
61
62
 
63
+ const widgetsOf = (home: Home | undefined): Promise<string> =>
64
+ home === undefined ? Promise.resolve('') : renderWidgets(home)
65
+
62
66
  /**
63
67
  * The app's own pages and routes, mountable at any prefix: the standalone server mounts it at the root, the frame row at {@link FRAME_MOUNT_PREFIX}.
64
- * Every absolute URL a page writes is derived from the prefix it is mounted at.
68
+ * Every absolute URL a page writes is derived from the prefix it is mounted at. The home page shows the widgets of the app's plugins (`ctx.home`), read
69
+ * at each request through `home()`, so a plugin pulled, switched off or removed shows at the next load; without a home service the page has none.
65
70
  */
66
- export function makeApp(prefix = ''): Acryl {
71
+ export function makeApp(prefix = '', home: () => Home | undefined = () => undefined): Acryl {
67
72
  const limit = bodyLimit({ maxSize: 64 * 1024 })
68
73
  return (
69
74
  new Acryl()
70
75
  .use(limit)
71
76
  // A browser gets a page (the door's and the agent panel's script tags are added where they are mounted); everything else gets the JSON.
72
- .get('/', (c) =>
77
+ .get('/', async (c) =>
73
78
  (c.req.header('accept') ?? '').includes('text/html')
74
79
  ? c.html(
75
- `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${APP_NAME}</title>${APPEARANCE_STYLE}${appearanceScript(prefix)}</head><body><h1>${APP_NAME}</h1><p>This app runs an agent unless it is switched off (production switches it off, see ACRYL_PRODUCTION_KEEP in AGENTS.md). In ACRYL, ask it for a change in the chat beside the app (or with <code>POST /ask</code>); published with the assistant on, it helps visitors use the app without changing it.</p>${DOOR_TAG}</body></html>`,
80
+ `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${APP_NAME}</title>${APPEARANCE_STYLE}${appearanceScript(prefix)}</head><body><h1>${APP_NAME}</h1><p>This app runs an agent unless it is switched off (production switches it off, see ACRYL_PRODUCTION_KEEP in AGENTS.md). In ACRYL, ask it for a change in the chat beside the app (or with <code>POST /ask</code>); published with the assistant on, it helps visitors use the app without changing it.</p>${await widgetsOf(home())}${DOOR_TAG}</body></html>`,
76
81
  )
77
82
  : c.json({ app: APP_NAME, ok: true }),
78
83
  )
@@ -102,7 +107,20 @@ export const routes = {
102
107
  }
103
108
  const limit = bodyLimit({ maxSize: 64 * 1024 })
104
109
  // The app's own page and routes at the one prefix; the agent routes below mount at the same prefix.
105
- ctx.router.route(prefix === '' ? '/' : prefix, makeApp(prefix))
110
+ // The home page's widgets while a `home` row runs: an app made before the agent template mounted `home` (its blend.yaml has no such row) keeps its pages.
111
+ let home: Home | undefined
112
+ ctx.inject(['home'], (inner) => {
113
+ inner.effect(() => {
114
+ home = inner.home
115
+ return () => {
116
+ home = undefined
117
+ }
118
+ }, 'routes: the home page widgets')
119
+ })
120
+ ctx.router.route(
121
+ prefix === '' ? '/' : prefix,
122
+ makeApp(prefix, () => home),
123
+ )
106
124
  // The routes that give a visitor the agent: mounted only while the agent, the extensions and the tasks run (see the header).
107
125
  // `agentRoutes: false` (the production Assistant) keeps them unmounted although the agent runs.
108
126
  if (options.agentRoutes === false) return
@@ -2,6 +2,7 @@
2
2
  "plugins": [
3
3
  "instance",
4
4
  "acryl-router",
5
+ "home",
5
6
  "acryl-state",
6
7
  "acryl-node-server",
7
8
  "acryl-page",
@@ -44,6 +44,32 @@ test('answers health checks and 404 for unknown paths', () =>
44
44
  await missing.body?.cancel()
45
45
  }))
46
46
 
47
+ test('a plugin puts its widget on the home page, and the widget leaves with the plugin', () =>
48
+ withApp(async (app) => {
49
+ const page = async () =>
50
+ (await app.root.router.request('/', { headers: { accept: 'text/html' } })).text()
51
+ const feature = app.root.plugin({
52
+ name: 'hello-widget',
53
+ inject: ['home'],
54
+ apply(ctx: typeof app.root) {
55
+ ctx.effect(() =>
56
+ ctx.home.widget({
57
+ id: 'hello-widget',
58
+ title: 'Hello',
59
+ render: () => '<p id="hello">hi</p>',
60
+ }),
61
+ )
62
+ },
63
+ })
64
+ await feature.await()
65
+ assert.match(
66
+ await page(),
67
+ /<section class="widget" data-widget="hello-widget">[\s\S]*<p id="hello">hi<\/p>/,
68
+ )
69
+ await feature.dispose()
70
+ assert.doesNotMatch(await page(), /data-widget="hello-widget"/)
71
+ }))
72
+
47
73
  /** A sandbox may forbid listening on a port (Codex's workspace-write does); the one test that starts the real server is skipped there, and says so. */
48
74
  /**
49
75
  * Whether this environment forbids listening at all: only a sandbox's refusal (EPERM/EACCES) means the real-server test must skip. A loaded