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.
- package/README.md +9 -0
- package/dist/index.d.ts +47 -2
- package/dist/index.js +2017 -32
- package/dist/index.js.map +1 -1
- package/docs/framework/agents/index.md +1 -1
- package/docs/framework/getting-started/desktop.md +23 -4
- package/docs/framework/guides/create-acrylblends.md +5 -3
- package/docs/framework/workshop/README.md +139 -153
- package/docs/framework/workshop/facilitator.md +69 -101
- package/legacy/README.md +9 -0
- package/legacy/agent/157ce3db1112/src/app.ts +98 -0
- package/legacy/agent/157ce3db1112/src/routes.ts +63 -0
- package/legacy/agent/24c681a00fca/src/app.ts +160 -0
- package/legacy/agent/24c681a00fca/src/routes.ts +83 -0
- package/legacy/agent/2e790da977b2/src/app.ts +98 -0
- package/legacy/agent/2e790da977b2/src/routes.ts +63 -0
- package/legacy/agent/6307045545dc/src/app.ts +168 -0
- package/legacy/agent/6307045545dc/src/routes.ts +83 -0
- package/legacy/agent/6a5fb9c15c53/src/app.ts +158 -0
- package/legacy/agent/6a5fb9c15c53/src/routes.ts +83 -0
- package/legacy/agent/71fb54a5a942/src/app.ts +111 -0
- package/legacy/agent/71fb54a5a942/src/routes.ts +63 -0
- package/legacy/agent/74ece148269c/src/app.ts +168 -0
- package/legacy/agent/74ece148269c/src/routes.ts +93 -0
- package/legacy/agent/855359304707/src/app.ts +168 -0
- package/legacy/agent/855359304707/src/routes.ts +126 -0
- package/legacy/agent/b5ef3c786ae4/src/app.ts +127 -0
- package/legacy/agent/b5ef3c786ae4/src/routes.ts +76 -0
- package/legacy/agent/c2f30958043b/src/app.ts +98 -0
- package/legacy/agent/c2f30958043b/src/routes.ts +53 -0
- package/legacy/blank/5a91c558af22/src/routes.ts +18 -0
- package/legacy/blank/9c650cf52bf3/src/routes.ts +14 -0
- package/legacy/blank/aea589283445/src/routes.ts +16 -0
- package/legacy/blank/b2cbb759c889/src/routes.ts +16 -0
- package/legacy/blank/d6d371c9a65e/src/routes.ts +18 -0
- package/legacy/chat/7076cc821e2c/src/app.ts +72 -0
- package/legacy/chat/7076cc821e2c/src/routes.ts +13 -0
- package/legacy/chat/7dbe6d5170df/src/app.ts +107 -0
- package/legacy/chat/7dbe6d5170df/src/routes.ts +19 -0
- package/legacy/chat/a9a9ab44c58e/src/app.ts +107 -0
- package/legacy/chat/a9a9ab44c58e/src/routes.ts +15 -0
- package/legacy/chat/f20ad9a51b06/src/app.ts +106 -0
- package/legacy/chat/f20ad9a51b06/src/routes.ts +15 -0
- package/legacy/layouts.json +524 -0
- package/legacy/nodejs/1d477f37aaf4/src/app.ts +92 -0
- package/legacy/nodejs/1d477f37aaf4/src/routes.ts +40 -0
- package/legacy/nodejs/b1bd9462496c/src/app.ts +61 -0
- package/legacy/nodejs/b1bd9462496c/src/routes.ts +37 -0
- package/legacy/nodejs/d5bab75076ef/src/app.ts +95 -0
- package/legacy/nodejs/d5bab75076ef/src/routes.ts +40 -0
- package/legacy/notes/4edc36949c15/src/app.ts +107 -0
- package/legacy/notes/4edc36949c15/src/routes.ts +14 -0
- package/legacy/notes/6cab2d09747c/src/app.ts +72 -0
- package/legacy/notes/6cab2d09747c/src/routes.ts +12 -0
- package/legacy/notes/7ca2dfed623a/src/app.ts +106 -0
- package/legacy/notes/7ca2dfed623a/src/routes.ts +14 -0
- package/legacy/todo/02e613b3625c/src/app.ts +71 -0
- package/legacy/todo/02e613b3625c/src/todo.ts +123 -0
- package/legacy/todo/45694a8d9e15/src/app.ts +71 -0
- package/legacy/todo/45694a8d9e15/src/todo.ts +96 -0
- package/legacy/todo/5923d3a6099f/src/app.ts +71 -0
- package/legacy/todo/5923d3a6099f/src/todo.ts +125 -0
- package/legacy/todo/849a18d66dc1/src/app.ts +91 -0
- package/legacy/todo/849a18d66dc1/src/todo.ts +125 -0
- package/legacy/todo/8e64211312f3/src/app.ts +71 -0
- package/legacy/todo/8e64211312f3/src/todo.ts +125 -0
- package/legacy/todo/b79078a87565/src/app.ts +71 -0
- package/legacy/todo/b79078a87565/src/todo.ts +125 -0
- package/lib/{audit-BFpULBCj.d.ts → audit-Bok_UNCn.d.ts} +26 -3
- package/lib/{client-modules-drj4mjhj.js → client-modules-Bkdr83jO.js} +1 -1
- package/lib/cordis/door.d.ts +87 -9
- package/lib/cordis/door.js +324 -60
- package/lib/cordis/inspect.js +1 -1
- package/lib/cordis/node-server.d.ts +101 -3
- package/lib/cordis/node-server.js +3 -3
- package/lib/cordis/router-http.d.ts +7 -1
- package/lib/cordis/router-http.js +44 -3
- package/lib/cordis/router.d.ts +1 -1
- package/lib/cordis/router.js +1 -1
- package/lib/cordis/state.js +1 -1
- package/lib/door/host.d.ts +2 -2
- package/lib/door/host.js +1 -1
- package/lib/door/page.d.ts +10 -1
- package/lib/door/page.js +46 -7
- package/lib/door/protocol.d.ts +1 -1
- package/lib/drivers.d.ts +10 -1
- package/lib/{index-DMOtA-NJ.d.ts → index-DGq7H0lb.d.ts} +11 -1
- package/lib/node-server.d.ts +7 -1
- package/lib/node-server.js +2 -2
- package/lib/{protocol-a2aqrp2X.d.ts → protocol-BeIYCgTo.d.ts} +4 -0
- package/lib/{snapshot-text-BwM6T2nG.js → snapshot-text-CXTnjxGx.js} +87 -7
- package/lib/src-CtkIX_Gg.js +427 -0
- package/lib/{src-BTBm4OPD.js → src-iBJ2ysV9.js} +77 -14
- package/lib/{test-websocket-BLeEvybj.js → test-websocket-CQOSvBth.js} +17 -7
- package/lib/{test-websocket-DyeS50Tr.d.ts → test-websocket-GUTAkFQT.d.ts} +3 -1
- package/package.json +2 -1
- package/plugins/acryl-door/plugin.json +3 -0
- package/plugins/acryl-door/src/cli/acryl.ts +63 -6
- package/plugins/acryl-door/src/host/channel.ts +86 -9
- package/plugins/acryl-door/src/host/chat-tools.ts +8 -6
- package/plugins/acryl-door/src/host/door-file.ts +7 -2
- package/plugins/acryl-door/src/host/door.ts +128 -24
- package/plugins/acryl-door/src/host/operator-routes.ts +21 -1
- package/plugins/acryl-door/src/host/page-routes.ts +3 -1
- package/plugins/acryl-door/src/host/page-socket.ts +18 -8
- package/plugins/acryl-door/src/host/verbs.ts +14 -1
- package/plugins/acryl-door/src/index.ts +168 -30
- package/plugins/acryl-door/src/page/base.ts +32 -0
- package/plugins/acryl-door/src/page/channel.ts +3 -1
- package/plugins/acryl-door/src/page/driver/accessibility.ts +11 -0
- package/plugins/acryl-door/src/page/driver/actions.ts +4 -0
- package/plugins/acryl-door/src/page/driver/driver.ts +32 -4
- package/plugins/acryl-door/src/page/driver/snapshot.ts +5 -2
- package/plugins/acryl-door/src/page/index.ts +41 -2
- package/plugins/acryl-door/src/page/outline.ts +56 -0
- package/plugins/acryl-door/src/page/protocol.ts +4 -0
- package/plugins/acryl-door/src/page/snapshot-text.ts +2 -1
- package/plugins/acryl-node-server/src/adapter/listener.ts +4 -0
- package/plugins/acryl-node-server/src/adapter/raw.ts +18 -0
- package/plugins/acryl-node-server/src/adapter/websocket.ts +15 -9
- package/plugins/acryl-node-server/src/client-modules.ts +7 -1
- package/plugins/acryl-node-server/src/index.ts +304 -16
- package/plugins/acryl-node-server/src/upgrade-hub.ts +2 -2
- package/plugins/acryl-page/plugin.json +12 -0
- package/plugins/acryl-page/src/index.ts +163 -0
- package/plugins/acryl-router/src/service.ts +77 -14
- package/plugins/acryl-router-http/src/index.ts +15 -1
- package/plugins/acryl-router-http/src/serve.ts +31 -0
- package/plugins/acryl-web-server/plugin.json +14 -0
- package/plugins/acryl-web-server/src/index.ts +164 -0
- package/plugins/agent/agent.ts +38 -7
- package/plugins/agent/index.ts +1 -1
- package/plugins/agent/model.ts +10 -2
- package/plugins/agent/tools.ts +9 -3
- package/plugins/agent-panel/client/describe.ts +57 -0
- package/plugins/agent-panel/client/driver-pick.ts +22 -0
- package/plugins/agent-panel/client/frame.ts +51 -0
- package/plugins/agent-panel/client/main.ts +238 -26
- package/plugins/agent-panel/drivers.ts +7 -2
- package/plugins/agent-panel/headless.ts +5 -2
- package/plugins/agent-panel/index.ts +146 -57
- package/plugins/agent-panel/mount.ts +27 -0
- package/plugins/agent-panel/visitors.ts +231 -0
- package/plugins/builder/index.ts +13 -35
- package/plugins/builder/package.ts +186 -0
- package/plugins/builder/plugin.json +3 -1
- package/plugins/desktop/frame.ts +231 -0
- package/plugins/desktop/launch.ts +44 -9
- package/plugins/desktop/main.ts +100 -32
- package/plugins/desktop/window.ts +21 -4
- package/plugins/driver-pty/client/terminal.ts +4 -1
- package/plugins/driver-pty/index.ts +38 -11
- package/plugins/driver-pty/spawn.ts +38 -0
- package/plugins/driver-pty/terminal.ts +2 -0
- package/plugins/extensions/index.ts +47 -18
- package/plugins/extensions/plugin-store.ts +27 -0
- package/plugins/extensions/plugin.json +1 -1
- package/plugins/extensions/resolve.ts +13 -0
- package/plugins/extensions/stage.ts +15 -5
- package/plugins/feedback/client.d.ts +22 -0
- package/plugins/feedback/client.js +471 -0
- package/plugins/feedback/index.ts +143 -0
- package/plugins/feedback/plugin.json +11 -0
- package/plugins/feedback/store.ts +307 -0
- package/plugins/home/index.ts +122 -0
- package/plugins/home/plugin.json +11 -0
- package/plugins/instance/index.ts +6 -0
- package/plugins/manifest/app-plugins.ts +86 -0
- package/plugins/manifest/index.ts +12 -2
- package/plugins/manifest/plugin.json +1 -1
- package/plugins/manifest/snapshot.ts +275 -0
- package/plugins/openai/index.ts +64 -5
- package/plugins/openai/prompt.ts +23 -0
- package/plugins/plugin-share/README.md +51 -0
- package/plugins/plugin-share/index.ts +467 -0
- package/plugins/plugin-share/plugin.json +15 -0
- package/plugins/plugin-share/publish.ts +65 -0
- package/plugins/plugin-share/pull-check.ts +129 -0
- package/plugins/plugin-share/pulled-records.ts +70 -0
- package/shared/AGENTS.core.md +36 -16
- package/shared/skills/acryl-add-ui/SKILL.md +19 -0
- package/shared/skills/acryl-app/SKILL.md +13 -0
- package/shared/skills/acryl-build-plugin/SKILL.md +22 -0
- package/shared/skills/acryl-change-plugin/SKILL.md +14 -0
- package/shared/skills/acryl-diagnose-plugin/SKILL.md +13 -0
- package/shared/skills/acryl-fix-plugin/SKILL.md +13 -0
- package/shared/skills/acryl-remove-plugin/SKILL.md +12 -0
- package/shared/skills/acryl-share-plugin/SKILL.md +16 -0
- package/shared/skills/cordis-plugin/SKILL.md +26 -0
- package/templates/agent/.gitattributes +2 -0
- package/templates/agent/AGENTS.md +1 -1
- package/templates/agent/blend.yaml +6 -0
- package/templates/agent/gitignore +1 -0
- package/templates/agent/package.json +1 -0
- package/templates/agent/src/app.ts +154 -33
- package/templates/agent/src/frame.ts +17 -0
- package/templates/agent/src/main.ts +5 -3
- package/templates/agent/src/routes.ts +38 -19
- package/templates/agent/template.json +3 -0
- package/templates/agent/test/app.test.ts +45 -3
- package/templates/blank/.gitattributes +2 -0
- package/templates/blank/AGENTS.md +3 -2
- package/templates/blank/blend.yaml +11 -0
- package/templates/blank/gitignore +1 -0
- package/templates/blank/package.json +1 -0
- package/templates/blank/src/app.ts +100 -29
- package/templates/blank/src/door.ts +7 -0
- package/templates/blank/src/first-page.ts +5 -0
- package/templates/blank/src/frame.ts +17 -0
- package/templates/blank/src/routes.ts +65 -8
- package/templates/blank/template.json +14 -1
- package/templates/blank/test/app.test.ts +103 -16
- package/templates/chat/.gitattributes +2 -0
- package/templates/chat/blend.yaml +2 -1
- package/templates/chat/gitignore +1 -0
- package/templates/chat/package.json +1 -0
- package/templates/chat/src/app.ts +72 -22
- package/templates/chat/src/chat-app.ts +11 -2
- package/templates/chat/src/client/chat.tsx +4 -2
- package/templates/chat/src/frame.ts +17 -0
- package/templates/chat/src/page.tsx +6 -4
- package/templates/chat/src/routes.ts +14 -4
- package/templates/chat/src/runtime.ts +9 -3
- package/templates/chat/template.json +3 -1
- package/templates/chat/test/chat.test.ts +46 -1
- package/templates/chat/test/server.test.ts +17 -3
- package/templates/nodejs/.gitattributes +2 -0
- package/templates/nodejs/blend.yaml +2 -1
- package/templates/nodejs/gitignore +1 -0
- package/templates/nodejs/package.json +1 -0
- package/templates/nodejs/src/app.ts +76 -21
- package/templates/nodejs/src/frame.ts +17 -0
- package/templates/nodejs/src/page.tsx +3 -3
- package/templates/nodejs/src/routes.ts +32 -13
- package/templates/nodejs/template.json +3 -1
- package/templates/nodejs/test/app.test.ts +38 -3
- package/templates/nodejs/test/server.test.ts +17 -3
- package/templates/notes/.gitattributes +2 -0
- package/templates/notes/blend.yaml +2 -1
- package/templates/notes/gitignore +1 -0
- package/templates/notes/package.json +1 -0
- package/templates/notes/src/app.ts +72 -22
- package/templates/notes/src/frame.ts +17 -0
- package/templates/notes/src/notes-app.ts +24 -17
- package/templates/notes/src/routes.ts +11 -4
- package/templates/notes/src/runtime.ts +9 -3
- package/templates/notes/src/views.tsx +26 -17
- package/templates/notes/template.json +3 -1
- package/templates/notes/test/notes.test.ts +39 -1
- package/templates/notes/test/server.test.ts +17 -3
- package/templates/todo/.gitattributes +2 -0
- package/templates/todo/blend.yaml +3 -0
- package/templates/todo/gitignore +1 -0
- package/templates/todo/package.json +1 -0
- package/templates/todo/src/app.ts +81 -27
- package/templates/todo/src/door.ts +7 -0
- package/templates/todo/src/frame.ts +17 -0
- package/templates/todo/src/page.ts +13 -6
- package/templates/todo/src/todo.ts +18 -6
- package/templates/todo/template.json +11 -1
- package/templates/todo/test/todo.test.ts +67 -9
- package/lib/src-LX0E4ec4.js +0 -117
- package/shared/SKILL.md +0 -14
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an app remembers about the plugins it pulled: their source and digests, in the `plugin-share` row's config of `blend.yaml`. A row's config is
|
|
3
|
+
* resolved into `blend.lock.json` too, so the lock records them with no new format, and a copy of the app can fetch exactly the same bytes. Pure text
|
|
4
|
+
* to text; comments and the person's formatting stay.
|
|
5
|
+
*/
|
|
6
|
+
import { isMap, isSeq, parseDocument } from 'yaml'
|
|
7
|
+
|
|
8
|
+
export const ROW = 'plugin-share'
|
|
9
|
+
|
|
10
|
+
export interface PulledRecord {
|
|
11
|
+
/** `github:<owner>/<repo>@<version>` or an https address: something another machine can fetch. */
|
|
12
|
+
readonly source: string
|
|
13
|
+
/** The sha512 of the release file. */
|
|
14
|
+
readonly integrity: string
|
|
15
|
+
/** The sha256 of the plugin's content. */
|
|
16
|
+
readonly digest: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isRecord = (value: unknown): value is PulledRecord => {
|
|
20
|
+
const record = value as Partial<Record<keyof PulledRecord, unknown>> | null
|
|
21
|
+
return (
|
|
22
|
+
typeof record?.source === 'string' &&
|
|
23
|
+
typeof record.integrity === 'string' &&
|
|
24
|
+
typeof record.digest === 'string'
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The plugins this definition remembers pulling. */
|
|
29
|
+
export function pulledRecords(text: string): Readonly<Record<string, PulledRecord>> {
|
|
30
|
+
const rows = (parseDocument(text).toJS() as { spec?: { rows?: unknown } } | null)?.spec?.rows
|
|
31
|
+
if (!Array.isArray(rows)) return {}
|
|
32
|
+
const pulled = (
|
|
33
|
+
rows.find((row: { name?: unknown }) => row?.name === ROW) as
|
|
34
|
+
| { config?: { pulled?: unknown } }
|
|
35
|
+
| undefined
|
|
36
|
+
)?.config?.pulled
|
|
37
|
+
if (typeof pulled !== 'object' || pulled === null) return {}
|
|
38
|
+
return Object.fromEntries(
|
|
39
|
+
Object.entries(pulled).filter(([, record]) => isRecord(record)),
|
|
40
|
+
) as Record<string, PulledRecord>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The definition with one plugin's record written, or taken out when `record` is undefined. Unchanged when it has no `plugin-share` row: an app
|
|
45
|
+
* composed without the row has nowhere to remember.
|
|
46
|
+
*/
|
|
47
|
+
export function withPulledRecord(
|
|
48
|
+
text: string,
|
|
49
|
+
name: string,
|
|
50
|
+
record: PulledRecord | undefined,
|
|
51
|
+
): string {
|
|
52
|
+
const document = parseDocument(text)
|
|
53
|
+
const rows = document.getIn(['spec', 'rows'])
|
|
54
|
+
if (!isSeq(rows)) return text
|
|
55
|
+
const row = rows.items.find((item) => isMap(item) && item.get('name') === ROW)
|
|
56
|
+
if (!isMap(row)) return text
|
|
57
|
+
if (record === undefined) {
|
|
58
|
+
if (!row.hasIn(['config', 'pulled', name])) return text
|
|
59
|
+
row.deleteIn(['config', 'pulled', name])
|
|
60
|
+
const left = row.getIn(['config', 'pulled'])
|
|
61
|
+
if (isMap(left) && left.items.length === 0) row.deleteIn(['config', 'pulled'])
|
|
62
|
+
const config = row.get('config')
|
|
63
|
+
if (isMap(config) && config.items.length === 0) row.delete('config')
|
|
64
|
+
} else {
|
|
65
|
+
row.setIn(['config'], row.get('config') ?? document.createNode({}))
|
|
66
|
+
if (!row.hasIn(['config', 'pulled'])) row.setIn(['config', 'pulled'], document.createNode({}))
|
|
67
|
+
row.setIn(['config', 'pulled', name], document.createNode(record))
|
|
68
|
+
}
|
|
69
|
+
return String(document)
|
|
70
|
+
}
|
package/shared/AGENTS.core.md
CHANGED
|
@@ -19,35 +19,52 @@ The docs write `import { Acryl } from 'acrylblends'` and `from 'acrylblends/<sub
|
|
|
19
19
|
|
|
20
20
|
**JSX: write `.tsx`.** The scripts in `package.json` run Node.js with `--import ./plugins/acryl-node-server/src/register.ts`, which compiles `.tsx` files with this app's own `typescript`; `tsconfig.json` sets `jsx: react-jsx` and `jsxImportSource: #acryl-jsx`, which `package.json`'s `imports` maps to the vendored runtime in `plugins/acryl-router`. See `src/about.tsx` (blank template) for a page and `test/app.test.ts` for how it is tested. Render a component with `.toString()` and return it with `c.html(...)`; import `jsx`, `Fragment` and the types from `../plugins/acryl-router/src/core/jsx/index.ts` when you need them. A command that starts the app without those scripts (a bare `node src/main.ts`) cannot load a `.tsx` file. Without a build step you can write the call JSX compiles to instead: `jsx('h1', { class: 'title' }, 'Hello')`, children after the props.
|
|
21
21
|
|
|
22
|
-
**A plugin
|
|
22
|
+
**A plugin imports the framework by its package name**: `import { Acryl } from 'acrylblends'` (and `acrylblends/<subpath>`, for example
|
|
23
|
+
`acrylblends/helpers`), which resolves to the same files as the relative paths above. Only files in `src/` use the relative paths. `App` is the
|
|
23
24
|
deprecated name of `Acryl`.
|
|
24
25
|
|
|
25
|
-
## How a feature is built
|
|
26
|
+
## How a feature is built: a plugin of its own
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
**Every feature is a Cordis plugin: a folder `plugins/<name>/` with an `index.ts`, plus its row in `blend.yaml`.** Never wire a feature into
|
|
29
|
+
`src/app.ts`, `src/routes.ts` or `src/frame.ts`, and never import it there: the row is what starts it. Settings > Plugins lists it, switches it off
|
|
30
|
+
and on (`disabled: true` on its row) and shares it, and every version of the app carries it.
|
|
31
|
+
|
|
32
|
+
When your prompt names this app's builder docs (`acryl_plugin_docs`), read the doc for your task and its verified example before you write the
|
|
33
|
+
plugin; they are this section in full.
|
|
28
34
|
|
|
29
35
|
```ts
|
|
36
|
+
// plugins/notes/index.ts
|
|
30
37
|
import type { Context } from '@deepseek-ai/cordis'
|
|
31
|
-
import { Acryl
|
|
32
|
-
import { schemaValidator, type StandardSchema } from '../plugins/acryl-router/src/helpers/index.ts'
|
|
38
|
+
import { Acryl } from 'acrylblends'
|
|
33
39
|
|
|
34
|
-
export const name = 'notes' //
|
|
35
|
-
export const inject = ['router'] // services it needs
|
|
40
|
+
export const name = 'notes' // the folder's name; blend.yaml's row names it
|
|
41
|
+
export const inject = ['router'] // services it needs (a name nobody provides leaves it waiting, silently)
|
|
36
42
|
export function apply(ctx: Context) {
|
|
37
43
|
const app = new Acryl()
|
|
38
44
|
.get('/', (c) => c.json({ ok: true }))
|
|
39
|
-
.
|
|
40
|
-
.post('/', schemaValidator('json', NewNote), (c) => c.json(c.req.valid('json'), 201)) // NewNote is a Standard Schema
|
|
45
|
+
.post('/', async (c) => c.json(await c.req.json(), 201))
|
|
41
46
|
ctx.router.route('/notes', app) // build the whole app first: routes added after route() are not served
|
|
42
47
|
}
|
|
43
48
|
```
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
```yaml
|
|
51
|
+
# blend.yaml, before the `acryl-node-server` row
|
|
52
|
+
- id: notes
|
|
53
|
+
name: notes
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- Routes are an ordinary `Acryl` app that the plugin mounts once with `ctx.router.route('/notes', app)`; the mount belongs to the plugin and goes
|
|
57
|
+
away when it unloads. The frame that hosts the app in development adds its canvas prefix to every route a plugin mounts: the same plugin
|
|
58
|
+
answers `/notes` under `npm start` and `/app/notes` in the canvas. Write the app's own path, and fetch it from a page by that path
|
|
59
|
+
(`fetch('/notes')`); never put the frame's prefix in a route or a URL.
|
|
60
|
+
- A plugin's name is its folder's name. It imports only `@deepseek-ai/cordis` (types), `acrylblends`, `node:*`, its own files, and type-only
|
|
61
|
+
`import type {} from '../home/index.ts'` / `'../instance/index.ts'` for the services those give `ctx`. Never guess another path.
|
|
62
|
+
- Something on the first screen (blank template): add `'home'` to `inject` and register a widget in an effect,
|
|
63
|
+
`ctx.effect(() => ctx.home.widget({ id: 'notes', title: 'Notes', render: () => '<ul>...</ul>' }))`; it leaves the page with the plugin.
|
|
64
|
+
- Data that must survive a restart goes in a file in `ctx.appInstance.home` (add `'appInstance'` to `inject`), written whole through a temporary
|
|
65
|
+
file and a rename; a variable starts again at every reload. With `plugins/acryl-state`: `ctx.state.cell<T>('notes.list', initial)` (add
|
|
66
|
+
`'state'` to `inject`), served as JSON at `/api/state/<key>` and streamed at `/api/state/<key>/events`.
|
|
67
|
+
- Test it in `test/<name>.test.ts` without a socket: boot the app as `test/app.test.ts` does and use `app.root.router.request('/notes')`.
|
|
51
68
|
|
|
52
69
|
## Things that surprise people
|
|
53
70
|
|
|
@@ -58,7 +75,10 @@ export function apply(ctx: Context) {
|
|
|
58
75
|
- Handlers get the request as `c.req`: `c.req.param('id')`, `c.req.query('q')`, `await c.req.json()`, `c.req.raw` for the Web `Request`.
|
|
59
76
|
- A server-sent stream is `streamSSE(c, async (stream) => { await stream.writeSSE({ data: JSON.stringify(x) }) ... })`; call `stream.onAbort(cleanup)`.
|
|
60
77
|
- `npm run dev` restarts the app whenever a file changes.
|
|
78
|
+
- **In ACRYL's chat, the running app is the process that runs you: never stop, kill or restart it.** A saved change in `blend.yaml`, `src/` or `plugins/` reloads the app by itself and the page beside you shows it; a new plugin starts with that reload once its folder and its row are there. If a change does not show, the reload failed: read the error line in the frame's bar or `look` again, don't restart. `.acryl/bin/acryl plugin ...` is not available there, and is not needed.
|
|
61
79
|
|
|
62
80
|
## Before you say you are done
|
|
63
81
|
|
|
64
|
-
`npm test` and `npm run check` (type-check) both pass. Put tests in `test/`, written
|
|
82
|
+
`npm test` and `npm run check` (type-check) both pass, and `.acryl/bin/acryl look` shows the feature on the page. Put tests in `test/`, written
|
|
83
|
+
with `node:test` like `test/app.test.ts`. A failure names the file and the line: fix the cause in the plugin, never the test, and never move the
|
|
84
|
+
feature into `src/` to get around it.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: acryl-add-ui
|
|
3
|
+
description: Use when a feature of __APP_NAME__ needs something the person sees - a widget on the first screen, a page of its own, a button or a form.
|
|
4
|
+
---
|
|
5
|
+
# Add UI to the app
|
|
6
|
+
|
|
7
|
+
`<docs>` is the builder docs folder that the `acryl_plugin_docs` section of your prompt names (paths below are under it).
|
|
8
|
+
|
|
9
|
+
The app's UI is HTML its plugins serve. Two places:
|
|
10
|
+
|
|
11
|
+
- **On the first screen (the canvas)**: a widget. Read `<docs>/docs/extending/home-widget.md` completely and the example
|
|
12
|
+
`<docs>/example-plugins/packages/home-widget-basic/`. Add `'home'` to `inject`, register the widget inside `ctx.effect`, escape every value
|
|
13
|
+
you put into its HTML, prefix its element ids with the plugin's name, and let its `<script type="module">` fetch the plugin's own routes by the
|
|
14
|
+
app's path (`fetch('/<name>/items')`, never `/app/...` or `./...`).
|
|
15
|
+
- **A page of its own**: read `<docs>/docs/extending/routes-and-pages.md` and the example `route-page-basic`; link to it from the widget.
|
|
16
|
+
|
|
17
|
+
Decide where the data lives first (`<docs>/docs/extending/state.md`): what the person types must survive a reload, so it goes in a file in
|
|
18
|
+
`ctx.appInstance.home`, never only in a variable or in `localStorage`. Then build it with the acryl-build-plugin workflow and check the page with
|
|
19
|
+
`.acryl/bin/acryl look` and `act`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: acryl-app
|
|
3
|
+
description: How the __APP_NAME__ app is put together - its blend.yaml rows, its plugins/ folder and src/ - and how to change it, run it and check it through the door. Use before adding a feature, a route, a plugin or a dependency to this app, and before saying the work is done.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Working in __APP_NAME__
|
|
7
|
+
|
|
8
|
+
1. **Read first.** `AGENTS.md` (the same text as `CLAUDE.md`) is the app's guide. The framework's API is in `docs/framework/`: start at `docs/framework/README.md`, and `docs/framework/api/summary.md` is the one-page summary. Parts the docs mark "not yet available" are not in this app.
|
|
9
|
+
2. **The app is data plus code.** `blend.yaml` is the blueprint: which plugins (rows) the app mounts, in what order, with what config. `src/app.ts` is the runtime: a row's `name` is a library plugin its `registry` imports or a folder `plugins/<name>/` of the app's own, and `compose` starts them in row order. `src/routes.ts` is the app's own `Acryl` app (its page and API). A plugin the app owns is a folder `plugins/<name>/`.
|
|
10
|
+
3. **Make the change small.** A route is a handler on the app's `Acryl` app mounted with `ctx.router.route('/', app)`; test it with `await app.request('/path')`. Write the app's own paths: the frame that hosts the app adds its canvas prefix to every plugin route (`/board` answers `/board` under `npm start` and `/app/board` in the canvas), so never put the prefix in a route. Every feature is a Cordis plugin: a `plugins/<name>/` folder plus its row in `blend.yaml` (see the acryl-build-plugin skill); `src/app.ts` and `src/routes.ts` are never touched for a feature. `npx acrylblends add <name>` copies a library plugin there.
|
|
11
|
+
4. **Defaults are swappable.** The database, the key-value store, the server and the window are plugins behind ports; the table "Defaults you can swap" in `AGENTS.md` names each default and where it is swapped. Never reach around a port to a driver: `npx acrylblends check-deps` fails the check.
|
|
12
|
+
5. **Look at the running app.** In development the app writes `.acryl/bin/acryl`: `acryl brief` says what this folder runs, `acryl look` shows the open page as text with refs, and `acryl act click|type|select|press|scroll|wait|navigate|reload` acts on it. Look again before each act, because the refs change. This is the app's control surface: use it instead of guessing what the page shows.
|
|
13
|
+
6. **Done means checked.** `npm test` passes, and the change shows in the running app. No secret goes in a file or in `git status`: keys come from environment variables.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: acryl-build-plugin
|
|
3
|
+
description: Use when the person asks for a new feature, widget, page, tool, list or integration in __APP_NAME__ (build a plugin). The workflow from reading the docs to a feature that is on the page and checked.
|
|
4
|
+
---
|
|
5
|
+
# Build a feature as a plugin
|
|
6
|
+
|
|
7
|
+
`<docs>` is the builder docs folder that the `acryl_plugin_docs` section of your prompt names (paths below are under it).
|
|
8
|
+
|
|
9
|
+
1. Read `<docs>/docs/start-here/this-app.md` completely: what the app is made of, the rule (a feature is `plugins/<name>/` plus its row in
|
|
10
|
+
`blend.yaml`, never code in `src/`) and the imports a plugin may use.
|
|
11
|
+
2. Pick the docs for the feature from `<docs>/docs/README.md` (read-it-when column): routes and pages, a home widget, state. Read each one
|
|
12
|
+
completely and follow its links.
|
|
13
|
+
3. Open the closest verified example in `<docs>/example-plugins/README.md` and read every file of it. Copy it to `plugins/<name>/` and change
|
|
14
|
+
it: `name` equals the folder's name, `inject` names only services that exist (`router`, `home`, `appInstance`, or a row's service).
|
|
15
|
+
Never guess a plugin's shape or an import path from memory.
|
|
16
|
+
4. Add its row to `blend.yaml` before the `acryl-node-server` row (`- id: <name>` / `name: <name>`). Do not touch `src/app.ts`,
|
|
17
|
+
`src/routes.ts` or `src/frame.ts`.
|
|
18
|
+
5. Write `test/<name>.test.ts` like `test/app.test.ts`: the widget is on the first screen, the routes answer, the data survives a remount.
|
|
19
|
+
6. Verify (`<docs>/docs/start-here/verify-before-done.md`): `npm test`, `npm run check`, then `.acryl/bin/acryl look` shows it. Fix the
|
|
20
|
+
cause of the first failure and run again.
|
|
21
|
+
7. Tell the person what you built, where it is on the page, and that it is the plugin `<name>` in Settings > Plugins. Never claim it works
|
|
22
|
+
before the three checks agree.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: acryl-change-plugin
|
|
3
|
+
description: Use when the person asks you to change, improve, extend or restyle a feature or plugin that already exists in __APP_NAME__.
|
|
4
|
+
---
|
|
5
|
+
# Change a plugin
|
|
6
|
+
|
|
7
|
+
`<docs>` is the builder docs folder that the `acryl_plugin_docs` section of your prompt names (paths below are under it).
|
|
8
|
+
|
|
9
|
+
1. Find it: the feature's folder in `plugins/` and its row in `blend.yaml` (Settings > Plugins lists them too). If no plugin has it, it may be
|
|
10
|
+
part of the app's own page in `src/`; say so instead of guessing.
|
|
11
|
+
2. Read every file of the plugin before editing, and the doc for what you change (`<docs>/docs/README.md`).
|
|
12
|
+
3. Edit its folder. The frame reloads the app 300 ms after the change; a change to `inject` or the row's config reloads it the same way.
|
|
13
|
+
Keep its name: a new name is a new plugin with a new row.
|
|
14
|
+
4. Update its test, then verify (`<docs>/docs/start-here/verify-before-done.md`) and say what changed and that it is on the page.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: acryl-diagnose-plugin
|
|
3
|
+
description: Use when a feature of __APP_NAME__ misbehaves and the cause is unclear - nothing shows, buttons do nothing, data is lost, the top bar says Not reloaded, or a check fails in a way you do not understand.
|
|
4
|
+
---
|
|
5
|
+
# Diagnose a plugin methodically
|
|
6
|
+
|
|
7
|
+
`<docs>` is the builder docs folder that the `acryl_plugin_docs` section of your prompt names (paths below are under it).
|
|
8
|
+
|
|
9
|
+
Read `<docs>/docs/start-here/troubleshooting.md`. Then, in order: (1) `npm test` and `npm run check`, and read the first error completely
|
|
10
|
+
(it names the file and the line); (2) `.acryl/bin/acryl look` to see what the page shows now; (3) check the plugin's row in `blend.yaml`
|
|
11
|
+
(present, not `disabled: true`, the folder's name) and that every name in `inject` is provided (a missing service leaves the plugin waiting
|
|
12
|
+
without an error); (4) change ONE thing, run the checks again, look again. Never guess a state you have not read. If the same cause survives two
|
|
13
|
+
fixes, stop and tell the person what you observed.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: acryl-fix-plugin
|
|
3
|
+
description: Use when a plugin you wrote in __APP_NAME__ fails - npm test or npm run check fails, the top bar says "Not reloaded", or the feature is not on the page.
|
|
4
|
+
---
|
|
5
|
+
# Fix a plugin that does not work
|
|
6
|
+
|
|
7
|
+
`<docs>` is the builder docs folder that the `acryl_plugin_docs` section of your prompt names (paths below are under it).
|
|
8
|
+
|
|
9
|
+
Read `<docs>/docs/start-here/troubleshooting.md` and `<docs>/docs/start-here/verify-before-done.md`. The error is the real cause: read it
|
|
10
|
+
first. `Cannot find module` is a guessed import (a plugin imports only `@deepseek-ai/cordis`, `acrylblends`, `node:*` and its own files);
|
|
11
|
+
`exports the plugin name` means `name` differs from the folder; `no plugin '<name>' in the app's registry` means the row names a folder that is
|
|
12
|
+
not there; a plugin with no error and nothing on the page waits for a service its `inject` names. Fix the cause in the plugin (never the test,
|
|
13
|
+
never by moving the feature into `src/`), run the checks again, and only then tell the person it works.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: acryl-remove-plugin
|
|
3
|
+
description: Use when the person asks to remove, delete, switch off, hide or undo a feature or plugin of __APP_NAME__.
|
|
4
|
+
---
|
|
5
|
+
# Switch off or remove a plugin
|
|
6
|
+
|
|
7
|
+
`<docs>` is the builder docs folder that the `acryl_plugin_docs` section of your prompt names (paths below are under it). Read `<docs>/docs/delivery/switch-and-remove.md`.
|
|
8
|
+
|
|
9
|
+
Find the exact plugin in `plugins/` and its row in `blend.yaml` (never guess the name). To hide it for now, switch it off: `disabled: true` on
|
|
10
|
+
its row (the switch in Settings > Plugins does the same); everything stays and it comes back when switched on. To remove it, and only when the
|
|
11
|
+
person clearly asked: delete its row, its folder and its test; its data in `.acrylblends/` stays unless they ask. Check with `npm test` and
|
|
12
|
+
`.acryl/bin/acryl look` that it is gone before you say so. Only touch the plugin the person named.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: acryl-share-plugin
|
|
3
|
+
description: Use when the person wants to share, publish or version a plugin of __APP_NAME__, save or restore a version of the app, or make a blueprint of it.
|
|
4
|
+
---
|
|
5
|
+
# Share a plugin, versions and blueprints
|
|
6
|
+
|
|
7
|
+
`<docs>` is the builder docs folder that the `acryl_plugin_docs` section of your prompt names (paths below are under it). Read `<docs>/docs/delivery/share-and-snapshot.md`.
|
|
8
|
+
|
|
9
|
+
The app is `blend.yaml` + `plugins/` + `src/`, and every way it is kept or passed on reads exactly that. A version is a commit the frame
|
|
10
|
+
makes after a turn that changed files; the left panel lists and restores them. A plugin is shared from Settings > Plugins > Share on its row: it is
|
|
11
|
+
packaged for the CordisPlugins registry and published to a GitHub repository of the person's own account, with their own `gh` login.
|
|
12
|
+
|
|
13
|
+
1. Make sure it works first (`<docs>/docs/start-here/verify-before-done.md`) and give it a doc comment whose first sentence says what it does.
|
|
14
|
+
2. Sharing sends the person's work out under their name: never share, push or publish yourself. Tell them to press **Share** on the plugin's row.
|
|
15
|
+
Never ask for or handle a token.
|
|
16
|
+
3. A fix to a plugin that was already shared needs a new version: Share again offers the next patch version and asks first.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cordis-plugin
|
|
3
|
+
description: Add a Cordis plugin to __APP_NAME__ - a plugins/<name>/ folder with name, inject, apply and an optional check, its routes as an Acryl app, how its blend.yaml row mounts it, and how to verify it. Use when a feature should be its own plugin, or when asked to extend the app.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adding a Cordis plugin
|
|
7
|
+
|
|
8
|
+
A plugin is a folder `plugins/<name>/` with an `index.ts` exporting `name` (its id), `inject` (the services it needs) and `apply(ctx)`. Everything it acquires belongs to one `ctx.effect(...)`, so unloading the plugin or stopping the app releases it.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
12
|
+
import { Acryl } from 'acrylblends'
|
|
13
|
+
|
|
14
|
+
export const name = 'due-dates'
|
|
15
|
+
export const inject = ['router']
|
|
16
|
+
|
|
17
|
+
export function apply(ctx: Context): void {
|
|
18
|
+
const app = new Acryl().get('/due-dates', (c) => c.json({ ok: true }))
|
|
19
|
+
ctx.router.route('/due-dates', app)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **Routes** are an `Acryl` app mounted with `ctx.router.route('/due-dates', app)`; removing the plugin removes them. Write the app's own path: the frame that hosts the app in development adds its canvas prefix, so the same route answers `/due-dates` under `npm start` and `/app/due-dates` in the canvas. Never put the frame's prefix in a plugin's route. Import the framework by name (`acrylblends`, `acrylblends/body-limit`); never guess a relative path into another plugin.
|
|
24
|
+
- **State** lives in cells: `ctx.state.cell('due-dates.tasks', [])`. A page follows one with `watch` from `/api/state/client.js`, never with an `EventSource`, and never by polling what `watch` already pushes.
|
|
25
|
+
- **Its row starts it.** Add a row to `blend.yaml` (`- id: <name>` and `name: <name>`, before the `acryl-node-server` row); `src/app.ts` finds the folder by that name, so it is never imported there. `disabled: true` on the row switches it off (Settings > Plugins does the same). `.acryl/bin/acryl plugin add plugins/<name>` installs a folder without a row live, for trying it; a folder with a row is the app's.
|
|
26
|
+
- **Check it.** `npm test` and `npm run check` must pass. With the app running, use the door: `.acryl/bin/acryl look` reads the page as text with refs and `acryl act ...` acts on it; an install or reload shows the new feature in the page.
|
|
@@ -5,7 +5,7 @@ Besides `src/routes.ts`, the app runs an agent (`POST /ask`) that can write plug
|
|
|
5
5
|
rollback; `GET /extensions` and `GET /tasks` show what it did. A plugin there is a folder with an `index.ts` exporting `name`, `inject`, `apply(ctx)`
|
|
6
6
|
and an optional `check(ctx)` that receives the plugin's own context (so `ctx.router` reads as in `apply`). It may import `acryl-router`.
|
|
7
7
|
|
|
8
|
-
**Write an extension, not a row.** A feature you build while the app runs is a folder `plugins/<name>/` installed with `.acryl/bin/acryl plugin add plugins/<name>` (the `extensions` plugin loads it again at every start). Add a row to `blend.yaml`
|
|
8
|
+
**Write an extension, not a row.** A feature you build while the app runs is a folder `plugins/<name>/` installed with `.acryl/bin/acryl plugin add plugins/<name>` (the `extensions` plugin loads it again at every start). Add a row to `blend.yaml` (`- id: <name>` / `name: <name>`; `src/app.ts` finds the folder by that name) only when the person asks for a plugin that is part of the app itself. Both kinds may import the framework by name (`acrylblends`, `acrylblends/body-limit`, `acryl-router`); a row's relative imports of its sibling plugins also work.
|
|
9
9
|
|
|
10
10
|
**Drive the app you build.** `.acryl/bin/acryl look` reads the open page as text with refs, and `acryl act click|type|press|scroll|wait --ref <ref>` presses its buttons, opens its sidebars and fills its forms (look again before each act: the refs change). `act navigate --path /settings` goes to another page of the app; a path the app does not serve answers `404 at /nope` and the window shows a 404 page, so navigate on from there. In the desktop app (`npm run desktop`) the app's windows and menu are verbs too: `acryl window list`, `acryl window open --path /settings`, `acryl look --window <id>` (and `act ... --window <id>`) to drive that window's page, `acryl window resize|focus|close --id <id>`, `acryl menu list` and `acryl menu click --path "File>New Note"`. Closing the last window and Quit end the desktop app, so they need `--force`: do not use it unless the person asks. The app has a `/settings` page already (the appearance: system, light or dark, kept in the browser) and the desktop app's menu has a Settings item that opens it; add a person's other settings to that page, not to a new one, so `acryl menu click --path "File>Settings..."` and `acryl window open --path /settings` keep opening them (on macOS the item is in the application menu). Without a desktop app these answer "no desktop window is attached; run npm run desktop".
|
|
11
11
|
|
|
@@ -24,10 +24,13 @@ spec:
|
|
|
24
24
|
name: tasks
|
|
25
25
|
config:
|
|
26
26
|
store: "{{parameters.taskStore}}"
|
|
27
|
+
# The model the agent asks at each step: a scripted offline one until a provider key is set.
|
|
27
28
|
- id: model
|
|
28
29
|
name: model
|
|
30
|
+
# The tools the agent can call, each with what it does and the input it takes.
|
|
29
31
|
- id: tools
|
|
30
32
|
name: tools
|
|
33
|
+
# Long-term memory for the agent.
|
|
31
34
|
- id: memory
|
|
32
35
|
name: memory
|
|
33
36
|
- id: agent
|
|
@@ -46,11 +49,14 @@ spec:
|
|
|
46
49
|
approval: "{{parameters.approval}}"
|
|
47
50
|
- id: builder
|
|
48
51
|
name: builder
|
|
52
|
+
- id: plugin-share
|
|
53
|
+
name: plugin-share
|
|
49
54
|
- id: save
|
|
50
55
|
name: save
|
|
51
56
|
# The panel: a chat button on every page in development (the app's own agent, or Codex, Claude or pi started in this folder). It starts nothing in production.
|
|
52
57
|
- id: agent-panel
|
|
53
58
|
name: agent-panel
|
|
59
|
+
# The app's own pages and API.
|
|
54
60
|
- id: routes
|
|
55
61
|
name: routes
|
|
56
62
|
- id: server
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"test": "node --import ./plugins/acryl-node-server/src/register.ts --test \"test/**/*.test.{ts,tsx}\"",
|
|
17
17
|
"check": "tsc --noEmit",
|
|
18
18
|
"lock": "node plugins/manifest/lock.ts",
|
|
19
|
+
"snapshot": "node plugins/manifest/snapshot.ts",
|
|
19
20
|
"save": "node plugins/save/cli.ts",
|
|
20
21
|
"upgrade": "node plugins/manifest/upgrade-cli.ts"
|
|
21
22
|
},
|