acrylblends 1.1.0 → 1.3.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 (263) hide show
  1. package/README.md +9 -0
  2. package/dist/index.d.ts +47 -2
  3. package/dist/index.js +2017 -32
  4. package/dist/index.js.map +1 -1
  5. package/docs/framework/agents/index.md +1 -1
  6. package/docs/framework/getting-started/desktop.md +23 -4
  7. package/docs/framework/guides/create-acrylblends.md +5 -3
  8. package/docs/framework/workshop/README.md +139 -153
  9. package/docs/framework/workshop/facilitator.md +69 -101
  10. package/legacy/README.md +9 -0
  11. package/legacy/agent/157ce3db1112/src/app.ts +98 -0
  12. package/legacy/agent/157ce3db1112/src/routes.ts +63 -0
  13. package/legacy/agent/24c681a00fca/src/app.ts +160 -0
  14. package/legacy/agent/24c681a00fca/src/routes.ts +83 -0
  15. package/legacy/agent/2e790da977b2/src/app.ts +98 -0
  16. package/legacy/agent/2e790da977b2/src/routes.ts +63 -0
  17. package/legacy/agent/6307045545dc/src/app.ts +168 -0
  18. package/legacy/agent/6307045545dc/src/routes.ts +83 -0
  19. package/legacy/agent/6a5fb9c15c53/src/app.ts +158 -0
  20. package/legacy/agent/6a5fb9c15c53/src/routes.ts +83 -0
  21. package/legacy/agent/71fb54a5a942/src/app.ts +111 -0
  22. package/legacy/agent/71fb54a5a942/src/routes.ts +63 -0
  23. package/legacy/agent/74ece148269c/src/app.ts +168 -0
  24. package/legacy/agent/74ece148269c/src/routes.ts +93 -0
  25. package/legacy/agent/855359304707/src/app.ts +168 -0
  26. package/legacy/agent/855359304707/src/routes.ts +126 -0
  27. package/legacy/agent/b5ef3c786ae4/src/app.ts +127 -0
  28. package/legacy/agent/b5ef3c786ae4/src/routes.ts +76 -0
  29. package/legacy/agent/c2f30958043b/src/app.ts +98 -0
  30. package/legacy/agent/c2f30958043b/src/routes.ts +53 -0
  31. package/legacy/blank/5a91c558af22/src/routes.ts +18 -0
  32. package/legacy/blank/9c650cf52bf3/src/routes.ts +14 -0
  33. package/legacy/blank/aea589283445/src/routes.ts +16 -0
  34. package/legacy/blank/b2cbb759c889/src/routes.ts +16 -0
  35. package/legacy/blank/d6d371c9a65e/src/routes.ts +18 -0
  36. package/legacy/chat/7076cc821e2c/src/app.ts +72 -0
  37. package/legacy/chat/7076cc821e2c/src/routes.ts +13 -0
  38. package/legacy/chat/7dbe6d5170df/src/app.ts +107 -0
  39. package/legacy/chat/7dbe6d5170df/src/routes.ts +19 -0
  40. package/legacy/chat/a9a9ab44c58e/src/app.ts +107 -0
  41. package/legacy/chat/a9a9ab44c58e/src/routes.ts +15 -0
  42. package/legacy/chat/f20ad9a51b06/src/app.ts +106 -0
  43. package/legacy/chat/f20ad9a51b06/src/routes.ts +15 -0
  44. package/legacy/layouts.json +524 -0
  45. package/legacy/nodejs/1d477f37aaf4/src/app.ts +92 -0
  46. package/legacy/nodejs/1d477f37aaf4/src/routes.ts +40 -0
  47. package/legacy/nodejs/b1bd9462496c/src/app.ts +61 -0
  48. package/legacy/nodejs/b1bd9462496c/src/routes.ts +37 -0
  49. package/legacy/nodejs/d5bab75076ef/src/app.ts +95 -0
  50. package/legacy/nodejs/d5bab75076ef/src/routes.ts +40 -0
  51. package/legacy/notes/4edc36949c15/src/app.ts +107 -0
  52. package/legacy/notes/4edc36949c15/src/routes.ts +14 -0
  53. package/legacy/notes/6cab2d09747c/src/app.ts +72 -0
  54. package/legacy/notes/6cab2d09747c/src/routes.ts +12 -0
  55. package/legacy/notes/7ca2dfed623a/src/app.ts +106 -0
  56. package/legacy/notes/7ca2dfed623a/src/routes.ts +14 -0
  57. package/legacy/todo/02e613b3625c/src/app.ts +71 -0
  58. package/legacy/todo/02e613b3625c/src/todo.ts +123 -0
  59. package/legacy/todo/45694a8d9e15/src/app.ts +71 -0
  60. package/legacy/todo/45694a8d9e15/src/todo.ts +96 -0
  61. package/legacy/todo/5923d3a6099f/src/app.ts +71 -0
  62. package/legacy/todo/5923d3a6099f/src/todo.ts +125 -0
  63. package/legacy/todo/849a18d66dc1/src/app.ts +91 -0
  64. package/legacy/todo/849a18d66dc1/src/todo.ts +125 -0
  65. package/legacy/todo/8e64211312f3/src/app.ts +71 -0
  66. package/legacy/todo/8e64211312f3/src/todo.ts +125 -0
  67. package/legacy/todo/b79078a87565/src/app.ts +71 -0
  68. package/legacy/todo/b79078a87565/src/todo.ts +125 -0
  69. package/lib/{audit-BFpULBCj.d.ts → audit-Bok_UNCn.d.ts} +26 -3
  70. package/lib/{client-modules-drj4mjhj.js → client-modules-Bkdr83jO.js} +1 -1
  71. package/lib/cordis/door.d.ts +87 -9
  72. package/lib/cordis/door.js +324 -60
  73. package/lib/cordis/inspect.js +1 -1
  74. package/lib/cordis/node-server.d.ts +101 -3
  75. package/lib/cordis/node-server.js +3 -3
  76. package/lib/cordis/router-http.d.ts +7 -1
  77. package/lib/cordis/router-http.js +44 -3
  78. package/lib/cordis/router.d.ts +1 -1
  79. package/lib/cordis/router.js +1 -1
  80. package/lib/cordis/state.js +1 -1
  81. package/lib/door/host.d.ts +2 -2
  82. package/lib/door/host.js +1 -1
  83. package/lib/door/page.d.ts +10 -1
  84. package/lib/door/page.js +46 -7
  85. package/lib/door/protocol.d.ts +1 -1
  86. package/lib/drivers.d.ts +10 -1
  87. package/lib/{index-DMOtA-NJ.d.ts → index-DGq7H0lb.d.ts} +11 -1
  88. package/lib/node-server.d.ts +7 -1
  89. package/lib/node-server.js +2 -2
  90. package/lib/{protocol-a2aqrp2X.d.ts → protocol-BeIYCgTo.d.ts} +4 -0
  91. package/lib/{snapshot-text-BwM6T2nG.js → snapshot-text-CXTnjxGx.js} +87 -7
  92. package/lib/src-CtkIX_Gg.js +427 -0
  93. package/lib/{src-BTBm4OPD.js → src-iBJ2ysV9.js} +77 -14
  94. package/lib/{test-websocket-BLeEvybj.js → test-websocket-CQOSvBth.js} +17 -7
  95. package/lib/{test-websocket-DyeS50Tr.d.ts → test-websocket-GUTAkFQT.d.ts} +3 -1
  96. package/package.json +2 -1
  97. package/plugins/acryl-door/plugin.json +3 -0
  98. package/plugins/acryl-door/src/cli/acryl.ts +63 -6
  99. package/plugins/acryl-door/src/host/channel.ts +86 -9
  100. package/plugins/acryl-door/src/host/chat-tools.ts +8 -6
  101. package/plugins/acryl-door/src/host/door-file.ts +7 -2
  102. package/plugins/acryl-door/src/host/door.ts +128 -24
  103. package/plugins/acryl-door/src/host/operator-routes.ts +21 -1
  104. package/plugins/acryl-door/src/host/page-routes.ts +3 -1
  105. package/plugins/acryl-door/src/host/page-socket.ts +18 -8
  106. package/plugins/acryl-door/src/host/verbs.ts +14 -1
  107. package/plugins/acryl-door/src/index.ts +168 -30
  108. package/plugins/acryl-door/src/page/base.ts +32 -0
  109. package/plugins/acryl-door/src/page/channel.ts +3 -1
  110. package/plugins/acryl-door/src/page/driver/accessibility.ts +11 -0
  111. package/plugins/acryl-door/src/page/driver/actions.ts +4 -0
  112. package/plugins/acryl-door/src/page/driver/driver.ts +32 -4
  113. package/plugins/acryl-door/src/page/driver/snapshot.ts +5 -2
  114. package/plugins/acryl-door/src/page/index.ts +41 -2
  115. package/plugins/acryl-door/src/page/outline.ts +56 -0
  116. package/plugins/acryl-door/src/page/protocol.ts +4 -0
  117. package/plugins/acryl-door/src/page/snapshot-text.ts +2 -1
  118. package/plugins/acryl-node-server/src/adapter/listener.ts +4 -0
  119. package/plugins/acryl-node-server/src/adapter/raw.ts +18 -0
  120. package/plugins/acryl-node-server/src/adapter/websocket.ts +15 -9
  121. package/plugins/acryl-node-server/src/client-modules.ts +7 -1
  122. package/plugins/acryl-node-server/src/index.ts +304 -16
  123. package/plugins/acryl-node-server/src/upgrade-hub.ts +2 -2
  124. package/plugins/acryl-page/plugin.json +12 -0
  125. package/plugins/acryl-page/src/index.ts +163 -0
  126. package/plugins/acryl-router/src/service.ts +77 -14
  127. package/plugins/acryl-router-http/src/index.ts +15 -1
  128. package/plugins/acryl-router-http/src/serve.ts +31 -0
  129. package/plugins/acryl-web-server/plugin.json +14 -0
  130. package/plugins/acryl-web-server/src/index.ts +164 -0
  131. package/plugins/agent/agent.ts +38 -7
  132. package/plugins/agent/index.ts +1 -1
  133. package/plugins/agent/model.ts +10 -2
  134. package/plugins/agent/tools.ts +9 -3
  135. package/plugins/agent-panel/client/describe.ts +57 -0
  136. package/plugins/agent-panel/client/driver-pick.ts +22 -0
  137. package/plugins/agent-panel/client/frame.ts +51 -0
  138. package/plugins/agent-panel/client/main.ts +238 -26
  139. package/plugins/agent-panel/drivers.ts +7 -2
  140. package/plugins/agent-panel/headless.ts +5 -2
  141. package/plugins/agent-panel/index.ts +146 -57
  142. package/plugins/agent-panel/mount.ts +27 -0
  143. package/plugins/agent-panel/visitors.ts +231 -0
  144. package/plugins/builder/index.ts +13 -35
  145. package/plugins/builder/package.ts +186 -0
  146. package/plugins/builder/plugin.json +3 -1
  147. package/plugins/desktop/frame.ts +231 -0
  148. package/plugins/desktop/launch.ts +44 -9
  149. package/plugins/desktop/main.ts +100 -32
  150. package/plugins/desktop/window.ts +21 -4
  151. package/plugins/driver-pty/client/terminal.ts +4 -1
  152. package/plugins/driver-pty/index.ts +38 -11
  153. package/plugins/driver-pty/spawn.ts +38 -0
  154. package/plugins/driver-pty/terminal.ts +2 -0
  155. package/plugins/extensions/index.ts +47 -18
  156. package/plugins/extensions/plugin-store.ts +27 -0
  157. package/plugins/extensions/plugin.json +1 -1
  158. package/plugins/extensions/resolve.ts +13 -0
  159. package/plugins/extensions/stage.ts +15 -5
  160. package/plugins/feedback/client.d.ts +22 -0
  161. package/plugins/feedback/client.js +471 -0
  162. package/plugins/feedback/index.ts +143 -0
  163. package/plugins/feedback/plugin.json +11 -0
  164. package/plugins/feedback/store.ts +307 -0
  165. package/plugins/home/index.ts +122 -0
  166. package/plugins/home/plugin.json +11 -0
  167. package/plugins/instance/index.ts +6 -0
  168. package/plugins/manifest/app-plugins.ts +86 -0
  169. package/plugins/manifest/index.ts +12 -2
  170. package/plugins/manifest/plugin.json +1 -1
  171. package/plugins/manifest/snapshot.ts +275 -0
  172. package/plugins/openai/index.ts +64 -5
  173. package/plugins/openai/prompt.ts +23 -0
  174. package/plugins/plugin-share/README.md +51 -0
  175. package/plugins/plugin-share/index.ts +467 -0
  176. package/plugins/plugin-share/plugin.json +15 -0
  177. package/plugins/plugin-share/publish.ts +65 -0
  178. package/plugins/plugin-share/pull-check.ts +129 -0
  179. package/plugins/plugin-share/pulled-records.ts +70 -0
  180. package/shared/AGENTS.core.md +36 -16
  181. package/shared/skills/acryl-add-ui/SKILL.md +19 -0
  182. package/shared/skills/acryl-app/SKILL.md +13 -0
  183. package/shared/skills/acryl-build-plugin/SKILL.md +22 -0
  184. package/shared/skills/acryl-change-plugin/SKILL.md +14 -0
  185. package/shared/skills/acryl-diagnose-plugin/SKILL.md +13 -0
  186. package/shared/skills/acryl-fix-plugin/SKILL.md +13 -0
  187. package/shared/skills/acryl-remove-plugin/SKILL.md +12 -0
  188. package/shared/skills/acryl-share-plugin/SKILL.md +16 -0
  189. package/shared/skills/cordis-plugin/SKILL.md +26 -0
  190. package/templates/agent/.gitattributes +2 -0
  191. package/templates/agent/AGENTS.md +1 -1
  192. package/templates/agent/blend.yaml +6 -0
  193. package/templates/agent/gitignore +1 -0
  194. package/templates/agent/package.json +1 -0
  195. package/templates/agent/src/app.ts +154 -33
  196. package/templates/agent/src/frame.ts +17 -0
  197. package/templates/agent/src/main.ts +5 -3
  198. package/templates/agent/src/routes.ts +38 -19
  199. package/templates/agent/template.json +3 -0
  200. package/templates/agent/test/app.test.ts +45 -3
  201. package/templates/blank/.gitattributes +2 -0
  202. package/templates/blank/AGENTS.md +3 -2
  203. package/templates/blank/blend.yaml +11 -0
  204. package/templates/blank/gitignore +1 -0
  205. package/templates/blank/package.json +1 -0
  206. package/templates/blank/src/app.ts +100 -29
  207. package/templates/blank/src/door.ts +7 -0
  208. package/templates/blank/src/first-page.ts +5 -0
  209. package/templates/blank/src/frame.ts +17 -0
  210. package/templates/blank/src/routes.ts +65 -8
  211. package/templates/blank/template.json +14 -1
  212. package/templates/blank/test/app.test.ts +103 -16
  213. package/templates/chat/.gitattributes +2 -0
  214. package/templates/chat/blend.yaml +2 -1
  215. package/templates/chat/gitignore +1 -0
  216. package/templates/chat/package.json +1 -0
  217. package/templates/chat/src/app.ts +72 -22
  218. package/templates/chat/src/chat-app.ts +11 -2
  219. package/templates/chat/src/client/chat.tsx +4 -2
  220. package/templates/chat/src/frame.ts +17 -0
  221. package/templates/chat/src/page.tsx +6 -4
  222. package/templates/chat/src/routes.ts +14 -4
  223. package/templates/chat/src/runtime.ts +9 -3
  224. package/templates/chat/template.json +3 -1
  225. package/templates/chat/test/chat.test.ts +46 -1
  226. package/templates/chat/test/server.test.ts +17 -3
  227. package/templates/nodejs/.gitattributes +2 -0
  228. package/templates/nodejs/blend.yaml +2 -1
  229. package/templates/nodejs/gitignore +1 -0
  230. package/templates/nodejs/package.json +1 -0
  231. package/templates/nodejs/src/app.ts +76 -21
  232. package/templates/nodejs/src/frame.ts +17 -0
  233. package/templates/nodejs/src/page.tsx +3 -3
  234. package/templates/nodejs/src/routes.ts +32 -13
  235. package/templates/nodejs/template.json +3 -1
  236. package/templates/nodejs/test/app.test.ts +38 -3
  237. package/templates/nodejs/test/server.test.ts +17 -3
  238. package/templates/notes/.gitattributes +2 -0
  239. package/templates/notes/blend.yaml +2 -1
  240. package/templates/notes/gitignore +1 -0
  241. package/templates/notes/package.json +1 -0
  242. package/templates/notes/src/app.ts +72 -22
  243. package/templates/notes/src/frame.ts +17 -0
  244. package/templates/notes/src/notes-app.ts +24 -17
  245. package/templates/notes/src/routes.ts +11 -4
  246. package/templates/notes/src/runtime.ts +9 -3
  247. package/templates/notes/src/views.tsx +26 -17
  248. package/templates/notes/template.json +3 -1
  249. package/templates/notes/test/notes.test.ts +39 -1
  250. package/templates/notes/test/server.test.ts +17 -3
  251. package/templates/todo/.gitattributes +2 -0
  252. package/templates/todo/blend.yaml +3 -0
  253. package/templates/todo/gitignore +1 -0
  254. package/templates/todo/package.json +1 -0
  255. package/templates/todo/src/app.ts +81 -27
  256. package/templates/todo/src/door.ts +7 -0
  257. package/templates/todo/src/frame.ts +17 -0
  258. package/templates/todo/src/page.ts +13 -6
  259. package/templates/todo/src/todo.ts +18 -6
  260. package/templates/todo/template.json +11 -1
  261. package/templates/todo/test/todo.test.ts +67 -9
  262. package/lib/src-LX0E4ec4.js +0 -117
  263. package/shared/SKILL.md +0 -14
@@ -0,0 +1,18 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import { Acryl } from '../plugins/acryl-router/src/index.ts'
3
+ import { aboutPage } from './about.tsx'
4
+
5
+ export const APP_NAME = '__APP_NAME__'
6
+
7
+ /** The app's own routes: an `App` mounted on the router. The mount is removed when this plugin is disposed. */
8
+ export const routes = {
9
+ name: 'routes',
10
+ inject: ['router'],
11
+ apply(ctx: Context) {
12
+ const app = new Acryl()
13
+ .get('/', (c) => c.json({ app: APP_NAME, ok: true }))
14
+ .get('/health', (c) => c.json({ ok: true }))
15
+ .get('/about', (c) => c.html(aboutPage()))
16
+ ctx.router.route('/', app)
17
+ },
18
+ }
@@ -0,0 +1,14 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import type {} from './plugins/acryl-router/src/index.ts'
3
+
4
+ export const APP_NAME = '__APP_NAME__'
5
+
6
+ /** The app's own routes on the router. A route registered from this plugin's context is removed when the plugin is disposed. */
7
+ export const routes = {
8
+ name: 'routes',
9
+ inject: ['router'],
10
+ apply(ctx: Context) {
11
+ ctx.router.get('/', (c) => c.json({ app: APP_NAME, ok: true }))
12
+ ctx.router.get('/health', (c) => c.json({ ok: true }))
13
+ },
14
+ }
@@ -0,0 +1,16 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import { App } from './plugins/acryl-router/src/index.ts'
3
+
4
+ export const APP_NAME = '__APP_NAME__'
5
+
6
+ /** The app's own routes: an `App` mounted on the router. The mount is removed when this plugin is disposed. */
7
+ export const routes = {
8
+ name: 'routes',
9
+ inject: ['router'],
10
+ apply(ctx: Context) {
11
+ const app = new App()
12
+ .get('/', (c) => c.json({ app: APP_NAME, ok: true }))
13
+ .get('/health', (c) => c.json({ ok: true }))
14
+ ctx.router.route('/', app)
15
+ },
16
+ }
@@ -0,0 +1,16 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import { Acryl } from './plugins/acryl-router/src/index.ts'
3
+
4
+ export const APP_NAME = '__APP_NAME__'
5
+
6
+ /** The app's own routes: an `App` mounted on the router. The mount is removed when this plugin is disposed. */
7
+ export const routes = {
8
+ name: 'routes',
9
+ inject: ['router'],
10
+ apply(ctx: Context) {
11
+ const app = new Acryl()
12
+ .get('/', (c) => c.json({ app: APP_NAME, ok: true }))
13
+ .get('/health', (c) => c.json({ ok: true }))
14
+ ctx.router.route('/', app)
15
+ },
16
+ }
@@ -0,0 +1,18 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import { aboutPage } from './about.tsx'
3
+ import { Acryl } from './plugins/acryl-router/src/index.ts'
4
+
5
+ export const APP_NAME = '__APP_NAME__'
6
+
7
+ /** The app's own routes: an `App` mounted on the router. The mount is removed when this plugin is disposed. */
8
+ export const routes = {
9
+ name: 'routes',
10
+ inject: ['router'],
11
+ apply(ctx: Context) {
12
+ const app = new Acryl()
13
+ .get('/', (c) => c.json({ app: APP_NAME, ok: true }))
14
+ .get('/health', (c) => c.json({ ok: true }))
15
+ .get('/about', (c) => c.html(aboutPage()))
16
+ ctx.router.route('/', app)
17
+ },
18
+ }
@@ -0,0 +1,72 @@
1
+ import { isAbsolute, join, resolve } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { Context, type Fiber } from '@deepseek-ai/cordis'
4
+ import * as db from '../plugins/acryl-db-sqlite/src/index.ts'
5
+ import * as nodeServer from '../plugins/acryl-node-server/src/index.ts'
6
+ import * as router from '../plugins/acryl-router/src/index.ts'
7
+ import { type AppInstance, instance } from '../plugins/instance/index.ts'
8
+ import {
9
+ blueprintsIn,
10
+ compose,
11
+ loadManifest,
12
+ type ParameterValue,
13
+ } from '../plugins/manifest/index.ts'
14
+ import { routes } from './routes.ts'
15
+
16
+ /** The plugins this app's code provides. `blend.yaml` chooses, orders and configures them. */
17
+ export const registry = { 'acryl-router': router, 'acryl-db-sqlite': db, 'acryl-node-server': nodeServer, routes }
18
+
19
+ const projectRoot = fileURLToPath(new URL('..', import.meta.url))
20
+
21
+ /** The manifest next to this app's source. */
22
+ export const MANIFEST = join(projectRoot, 'blend.yaml')
23
+
24
+ /** The manifest, resolved. `createApp` and the tests read it the same way. */
25
+ export function readManifest(values?: Readonly<Record<string, ParameterValue>>) {
26
+ return loadManifest(MANIFEST, {
27
+ known: Object.keys(registry),
28
+ getBlueprint: blueprintsIn(join(projectRoot, 'blueprints')),
29
+ ...(values ? { values } : {}),
30
+ })
31
+ }
32
+
33
+ export interface App {
34
+ readonly root: Context
35
+ readonly url: string
36
+ stop(): Promise<void>
37
+ }
38
+
39
+ export interface AppOptions {
40
+ readonly values?: Readonly<Record<string, ParameterValue>>
41
+ /** The SQLite file, relative to the app folder; `:memory:` for a database that lives and dies with the process (tests). Default: ${'data/chat.sqlite'}. */
42
+ readonly database?: string
43
+ }
44
+
45
+ /**
46
+ * The app: `instance` first (where it lives, chosen at run time), then the manifest's rows in order. The `db` row opens the database and applies
47
+ * `migrations/`; the one place that names the SQLite provider is this file (`acrylblends check-deps`), so a different provider is a change here.
48
+ */
49
+ export async function createApp(chosen: AppInstance, options: AppOptions = {}): Promise<App> {
50
+ const manifest = readManifest(options.values)
51
+ const file = options.database ?? 'data/chat.sqlite'
52
+ const root = new Context()
53
+ const first = root.plugin(instance, chosen)
54
+ await first.await()
55
+ const fibers: Fiber[] = [
56
+ first,
57
+ ...(await compose(root, manifest, registry, {
58
+ db: {
59
+ path: file === ':memory:' || isAbsolute(file) ? file : resolve(projectRoot, file),
60
+ migrations: join(projectRoot, 'migrations'),
61
+ },
62
+ server: { port: chosen.port.start, scan: chosen.port.scan ? 20 : 0 },
63
+ })),
64
+ ]
65
+ return {
66
+ root,
67
+ url: root.nodeServer.url,
68
+ async stop() {
69
+ for (const fiber of [...fibers].reverse()) await fiber.dispose()
70
+ },
71
+ }
72
+ }
@@ -0,0 +1,13 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import type {} from '../plugins/acryl-db/src/cordis.ts'
3
+ import { createApp } from './chat-app.ts'
4
+ import { openAiCompatible } from './llm.ts'
5
+
6
+ /** The app's own plugin: it builds the app on the `db` port and mounts it on the router. The mount is removed when the plugin is disposed. */
7
+ export const routes = {
8
+ name: 'routes',
9
+ inject: ['router', 'db'],
10
+ apply(ctx: Context) {
11
+ ctx.router.route('/', createApp(ctx.db, openAiCompatible))
12
+ },
13
+ }
@@ -0,0 +1,107 @@
1
+ import { isAbsolute, join, resolve } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { Context, type Fiber } from '@deepseek-ai/cordis'
4
+ import * as db from '../plugins/acryl-db-sqlite/src/index.ts'
5
+ import * as door from '../plugins/acryl-door/src/index.ts'
6
+ import * as nodeServer from '../plugins/acryl-node-server/src/index.ts'
7
+ import * as router from '../plugins/acryl-router/src/index.ts'
8
+ import { extensions } from '../plugins/extensions/index.ts'
9
+ import { type AppInstance, instance } from '../plugins/instance/index.ts'
10
+ import {
11
+ blueprintsIn,
12
+ compose,
13
+ loadManifest,
14
+ type ParameterValue,
15
+ } from '../plugins/manifest/index.ts'
16
+ import { doorEnabled } from './door.ts'
17
+ import { routes } from './routes.ts'
18
+
19
+ /** The plugins this app's code provides. `blend.yaml` chooses, orders and configures them. */
20
+ export const registry = {
21
+ 'acryl-router': router,
22
+ 'acryl-db-sqlite': db,
23
+ 'acryl-node-server': nodeServer,
24
+ 'acryl-door': door,
25
+ extensions,
26
+ routes,
27
+ }
28
+
29
+ const projectRoot = fileURLToPath(new URL('..', import.meta.url))
30
+
31
+ /** The manifest next to this app's source. */
32
+ export const MANIFEST = join(projectRoot, 'blend.yaml')
33
+
34
+ /** The manifest, resolved. `createApp` and the tests read it the same way. */
35
+ export function readManifest(values?: Readonly<Record<string, ParameterValue>>) {
36
+ return loadManifest(MANIFEST, {
37
+ known: Object.keys(registry),
38
+ getBlueprint: blueprintsIn(join(projectRoot, 'blueprints')),
39
+ ...(values ? { values } : {}),
40
+ })
41
+ }
42
+
43
+ /** The manifest without some rows: without its server (and so its door) the app is reached in process, through `root.router.request`; the door is also left out in production. */
44
+ function without<M extends { plugins: readonly { use: string }[] }>(
45
+ manifest: M,
46
+ ...uses: string[]
47
+ ): M {
48
+ return {
49
+ ...manifest,
50
+ plugins: manifest.plugins.map((row) =>
51
+ uses.includes(row.use) ? { ...row, disabled: true } : row,
52
+ ),
53
+ }
54
+ }
55
+
56
+ export interface App {
57
+ readonly root: Context
58
+ /** The address the server listens on; empty when the app was made with `listen: false`. */
59
+ readonly url: string
60
+ stop(): Promise<void>
61
+ }
62
+
63
+ export interface AppOptions {
64
+ readonly values?: Readonly<Record<string, ParameterValue>>
65
+ /** The SQLite file, relative to the app folder; an absolute path is used as it is (tests give a file in a throwaway home). Default: `data/chat.sqlite`. */
66
+ readonly database?: string
67
+ /** Start the HTTP server (default). Tests pass `false` and call `app.root.router.request(path)`: it needs no port, so it also runs where a sandbox forbids listening. */
68
+ readonly listen?: boolean
69
+ }
70
+
71
+ /**
72
+ * The app: `instance` first (where it lives, chosen at run time), then the manifest's rows in order. The `db` row opens the database and applies
73
+ * `migrations/`; the one place that names the SQLite provider is this file (`acrylblends check-deps`), so a different provider is a change here.
74
+ */
75
+ export async function createApp(chosen: AppInstance, options: AppOptions = {}): Promise<App> {
76
+ const read = readManifest(options.values)
77
+ const listen = options.listen
78
+ // Rows left out: the server (and the door, which needs it) when the app is reached in process; the door when it is switched off; live plugin installs in production.
79
+ const leaveOut = new Set<string>()
80
+ if (listen === false) leaveOut.add('acryl-node-server').add('acryl-door')
81
+ if (!doorEnabled()) leaveOut.add('acryl-door')
82
+ if (process.env.NODE_ENV === 'production') leaveOut.add('extensions')
83
+ const manifest = without(read, ...leaveOut)
84
+ const file = options.database ?? 'data/chat.sqlite'
85
+ const root = new Context()
86
+ const first = root.plugin(instance, chosen)
87
+ await first.await()
88
+ const fibers: Fiber[] = [
89
+ first,
90
+ ...(await compose(root, manifest, registry, {
91
+ db: {
92
+ path: isAbsolute(file) ? file : resolve(projectRoot, file),
93
+ migrations: join(projectRoot, 'migrations'),
94
+ },
95
+ // What is installed while the app runs lives in plugins/, next to the plugins copied from the library (plugins/library.json lists those, so they are not installed twice).
96
+ extensions: { sources: [{ dir: join(projectRoot, 'plugins'), trust: 'install' }] },
97
+ server: { port: chosen.port.start, scan: chosen.port.scan ? 20 : 0 },
98
+ })),
99
+ ]
100
+ return {
101
+ root,
102
+ url: listen === false ? '' : root.nodeServer.url,
103
+ async stop() {
104
+ for (const fiber of [...fibers].reverse()) await fiber.dispose()
105
+ },
106
+ }
107
+ }
@@ -0,0 +1,19 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import type {} from '../plugins/acryl-db/src/cordis.ts'
3
+ import { chatAccess } from './access.ts'
4
+ import { createApp } from './chat-app.ts'
5
+ import { openAiCompatible } from './llm.ts'
6
+ import { createClient } from './runtime.ts'
7
+
8
+ /** The app's own plugin: it builds the app on the `db` port and mounts it on the router. The mount is removed when the plugin is disposed. */
9
+ export const routes = {
10
+ name: 'routes',
11
+ inject: ['router', 'db'],
12
+ apply(ctx: Context) {
13
+ // In production the chat is closed unless it has a token or is made public on purpose: anyone who reaches it spends the model key.
14
+ const { access, notices } = chatAccess(process.env)
15
+ for (const line of notices) console.warn(line)
16
+ ctx.router.route('/', createApp(ctx.db, openAiCompatible, access))
17
+ ctx.router.route('/client', createClient())
18
+ },
19
+ }
@@ -0,0 +1,107 @@
1
+ import { isAbsolute, join, resolve } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { Context, type Fiber } from '@deepseek-ai/cordis'
4
+ import * as db from '../plugins/acryl-db-sqlite/src/index.ts'
5
+ import * as door from '../plugins/acryl-door/src/index.ts'
6
+ import * as nodeServer from '../plugins/acryl-node-server/src/index.ts'
7
+ import * as router from '../plugins/acryl-router/src/index.ts'
8
+ import { extensions } from '../plugins/extensions/index.ts'
9
+ import { type AppInstance, instance } from '../plugins/instance/index.ts'
10
+ import {
11
+ blueprintsIn,
12
+ compose,
13
+ loadManifest,
14
+ type ParameterValue,
15
+ } from '../plugins/manifest/index.ts'
16
+ import { doorEnabled } from './door.ts'
17
+ import { routes } from './routes.ts'
18
+
19
+ /** The plugins this app's code provides. `blend.yaml` chooses, orders and configures them. */
20
+ export const registry = {
21
+ 'acryl-router': router,
22
+ 'acryl-db-sqlite': db,
23
+ 'acryl-node-server': nodeServer,
24
+ 'acryl-door': door,
25
+ extensions,
26
+ routes,
27
+ }
28
+
29
+ const projectRoot = fileURLToPath(new URL('..', import.meta.url))
30
+
31
+ /** The manifest next to this app's source. */
32
+ export const MANIFEST = join(projectRoot, 'blend.yaml')
33
+
34
+ /** The manifest, resolved. `createApp` and the tests read it the same way. */
35
+ export function readManifest(values?: Readonly<Record<string, ParameterValue>>) {
36
+ return loadManifest(MANIFEST, {
37
+ known: Object.keys(registry),
38
+ getBlueprint: blueprintsIn(join(projectRoot, 'blueprints')),
39
+ ...(values ? { values } : {}),
40
+ })
41
+ }
42
+
43
+ /** The manifest without some rows: without its server (and so its door) the app is reached in process, through `root.router.request`; the door is also left out in production. */
44
+ function without<M extends { plugins: readonly { use: string }[] }>(
45
+ manifest: M,
46
+ ...uses: string[]
47
+ ): M {
48
+ return {
49
+ ...manifest,
50
+ plugins: manifest.plugins.map((row) =>
51
+ uses.includes(row.use) ? { ...row, disabled: true } : row,
52
+ ),
53
+ }
54
+ }
55
+
56
+ export interface App {
57
+ readonly root: Context
58
+ /** The address the server listens on; empty when the app was made with `listen: false`. */
59
+ readonly url: string
60
+ stop(): Promise<void>
61
+ }
62
+
63
+ export interface AppOptions {
64
+ readonly values?: Readonly<Record<string, ParameterValue>>
65
+ /** The SQLite file, relative to the app folder; an absolute path is used as it is (tests give a file in a throwaway home). Default: `data/chat.sqlite`. */
66
+ readonly database?: string
67
+ /** Start the HTTP server (default). Tests pass `false` and call `app.root.router.request(path)`: it needs no port, so it also runs where a sandbox forbids listening. */
68
+ readonly listen?: boolean
69
+ }
70
+
71
+ /**
72
+ * The app: `instance` first (where it lives, chosen at run time), then the manifest's rows in order. The `db` row opens the database and applies
73
+ * `migrations/`; the one place that names the SQLite provider is this file (`acrylblends check-deps`), so a different provider is a change here.
74
+ */
75
+ export async function createApp(chosen: AppInstance, options: AppOptions = {}): Promise<App> {
76
+ const read = readManifest(options.values)
77
+ const listen = options.listen
78
+ // Rows left out: the server (and the door, which needs it) when the app is reached in process; the door when it is switched off; live plugin installs in production.
79
+ const leaveOut = new Set<string>()
80
+ if (listen === false) leaveOut.add('acryl-node-server').add('acryl-door')
81
+ if (!doorEnabled()) leaveOut.add('acryl-door')
82
+ if (process.env.NODE_ENV === 'production') leaveOut.add('extensions')
83
+ const manifest = without(read, ...leaveOut)
84
+ const file = options.database ?? 'data/chat.sqlite'
85
+ const root = new Context()
86
+ const first = root.plugin(instance, chosen)
87
+ await first.await()
88
+ const fibers: Fiber[] = [
89
+ first,
90
+ ...(await compose(root, manifest, registry, {
91
+ db: {
92
+ path: isAbsolute(file) ? file : resolve(projectRoot, file),
93
+ migrations: join(projectRoot, 'migrations'),
94
+ },
95
+ // What is installed while the app runs lives in plugins/, next to the plugins copied from the library (plugins/library.json lists those, so they are not installed twice).
96
+ extensions: { sources: [{ dir: join(projectRoot, 'plugins'), trust: 'install' }] },
97
+ server: { port: chosen.port.start, scan: chosen.port.scan ? 20 : 0 },
98
+ })),
99
+ ]
100
+ return {
101
+ root,
102
+ url: listen === false ? '' : root.nodeServer.url,
103
+ async stop() {
104
+ for (const fiber of [...fibers].reverse()) await fiber.dispose()
105
+ },
106
+ }
107
+ }
@@ -0,0 +1,15 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import type {} from '../plugins/acryl-db/src/cordis.ts'
3
+ import { createApp } from './chat-app.ts'
4
+ import { openAiCompatible } from './llm.ts'
5
+ import { createClient } from './runtime.ts'
6
+
7
+ /** The app's own plugin: it builds the app on the `db` port and mounts it on the router. The mount is removed when the plugin is disposed. */
8
+ export const routes = {
9
+ name: 'routes',
10
+ inject: ['router', 'db'],
11
+ apply(ctx: Context) {
12
+ ctx.router.route('/', createApp(ctx.db, openAiCompatible))
13
+ ctx.router.route('/client', createClient())
14
+ },
15
+ }
@@ -0,0 +1,106 @@
1
+ import { isAbsolute, join, resolve } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { Context, type Fiber } from '@deepseek-ai/cordis'
4
+ import * as db from '../plugins/acryl-db-sqlite/src/index.ts'
5
+ import * as door from '../plugins/acryl-door/src/index.ts'
6
+ import * as nodeServer from '../plugins/acryl-node-server/src/index.ts'
7
+ import * as router from '../plugins/acryl-router/src/index.ts'
8
+ import { type AppInstance, instance } from '../plugins/instance/index.ts'
9
+ import {
10
+ blueprintsIn,
11
+ compose,
12
+ loadManifest,
13
+ type ParameterValue,
14
+ } from '../plugins/manifest/index.ts'
15
+ import { doorEnabled } from './door.ts'
16
+ import { routes } from './routes.ts'
17
+
18
+ /** The plugins this app's code provides. `blend.yaml` chooses, orders and configures them. */
19
+ export const registry = {
20
+ 'acryl-router': router,
21
+ 'acryl-db-sqlite': db,
22
+ 'acryl-node-server': nodeServer,
23
+ 'acryl-door': door,
24
+ routes,
25
+ }
26
+
27
+ const projectRoot = fileURLToPath(new URL('..', import.meta.url))
28
+
29
+ /** The manifest next to this app's source. */
30
+ export const MANIFEST = join(projectRoot, 'blend.yaml')
31
+
32
+ /** The manifest, resolved. `createApp` and the tests read it the same way. */
33
+ export function readManifest(values?: Readonly<Record<string, ParameterValue>>) {
34
+ return loadManifest(MANIFEST, {
35
+ known: Object.keys(registry),
36
+ getBlueprint: blueprintsIn(join(projectRoot, 'blueprints')),
37
+ ...(values ? { values } : {}),
38
+ })
39
+ }
40
+
41
+ /** The manifest without some rows: without its server (and so its door) the app is reached in process, through `root.router.request`; the door is also left out in production. */
42
+ function without<M extends { plugins: readonly { use: string }[] }>(
43
+ manifest: M,
44
+ ...uses: string[]
45
+ ): M {
46
+ return {
47
+ ...manifest,
48
+ plugins: manifest.plugins.map((row) =>
49
+ uses.includes(row.use) ? { ...row, disabled: true } : row,
50
+ ),
51
+ }
52
+ }
53
+
54
+ export interface App {
55
+ readonly root: Context
56
+ /** The address the server listens on; empty when the app was made with `listen: false`. */
57
+ readonly url: string
58
+ stop(): Promise<void>
59
+ }
60
+
61
+ export interface AppOptions {
62
+ readonly values?: Readonly<Record<string, ParameterValue>>
63
+ /** The SQLite file, relative to the app folder; an absolute path is used as it is (tests give a file in a throwaway home). Default: `data/chat.sqlite`. */
64
+ readonly database?: string
65
+ /** Start the HTTP server (default). Tests pass `false` and call `app.root.router.request(path)`: it needs no port, so it also runs where a sandbox forbids listening. */
66
+ readonly listen?: boolean
67
+ }
68
+
69
+ /**
70
+ * The app: `instance` first (where it lives, chosen at run time), then the manifest's rows in order. The `db` row opens the database and applies
71
+ * `migrations/`; the one place that names the SQLite provider is this file (`acrylblends check-deps`), so a different provider is a change here.
72
+ */
73
+ export async function createApp(chosen: AppInstance, options: AppOptions = {}): Promise<App> {
74
+ const manifest = readManifest(options.values)
75
+ const listen = options.listen
76
+ const file = options.database ?? 'data/chat.sqlite'
77
+ const root = new Context()
78
+ const first = root.plugin(instance, chosen)
79
+ await first.await()
80
+ const fibers: Fiber[] = [
81
+ first,
82
+ ...(await compose(
83
+ root,
84
+ listen === false
85
+ ? without(manifest, 'acryl-node-server', 'acryl-door')
86
+ : doorEnabled()
87
+ ? manifest
88
+ : without(manifest, 'acryl-door'),
89
+ registry,
90
+ {
91
+ db: {
92
+ path: isAbsolute(file) ? file : resolve(projectRoot, file),
93
+ migrations: join(projectRoot, 'migrations'),
94
+ },
95
+ server: { port: chosen.port.start, scan: chosen.port.scan ? 20 : 0 },
96
+ },
97
+ )),
98
+ ]
99
+ return {
100
+ root,
101
+ url: listen === false ? '' : root.nodeServer.url,
102
+ async stop() {
103
+ for (const fiber of [...fibers].reverse()) await fiber.dispose()
104
+ },
105
+ }
106
+ }
@@ -0,0 +1,15 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import type {} from '../plugins/acryl-db/src/cordis.ts'
3
+ import { createApp } from './chat-app.ts'
4
+ import { openAiCompatible } from './llm.ts'
5
+ import { createClient } from './runtime.ts'
6
+
7
+ /** The app's own plugin: it builds the app on the `db` port and mounts it on the router. The mount is removed when the plugin is disposed. */
8
+ export const routes = {
9
+ name: 'routes',
10
+ inject: ['router', 'db'],
11
+ apply(ctx: Context) {
12
+ ctx.router.route('/', createApp(ctx.db, openAiCompatible))
13
+ ctx.router.route('/client', createClient())
14
+ },
15
+ }