acrylblends 0.7.7 → 0.7.8
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/dist/index.d.ts +1 -1
- package/dist/index.js +59 -19
- package/dist/index.js.map +1 -1
- package/docs/framework/README.md +1 -1
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/agents/index.md +18 -3
- package/docs/framework/getting-started/desktop.md +2 -2
- package/docs/framework/guides/client-modules.md +4 -3
- package/docs/framework/guides/data.md +6 -6
- package/docs/framework/helpers/streaming.md +12 -27
- package/docs/framework/upstream.json +2 -2
- package/lib/browser/read-sse.js +84 -0
- package/lib/db/sqlite.d.ts +1 -1
- package/lib/db.d.ts +1 -84
- package/lib/index-BLTYsyc1.d.ts +93 -0
- package/lib/index-D4FAsBZu.d.ts +85 -0
- package/lib/kv/db.d.ts +2 -2
- package/lib/kv/json.d.ts +1 -1
- package/lib/kv/json.js +87 -24
- package/lib/kv.d.ts +1 -92
- package/lib/node-server.d.ts +3 -3
- package/lib/node-server.js +13 -8
- package/lib/read-sse.d.ts +33 -0
- package/lib/read-sse.js +84 -0
- package/package.json +5 -1
- package/plugins/acryl-db/plugin.json +1 -1
- package/plugins/acryl-db/upstream.json +2 -2
- package/plugins/acryl-db-sqlite/upstream.json +1 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-kv/plugin.json +1 -1
- package/plugins/acryl-kv/upstream.json +1 -1
- package/plugins/acryl-kv-db/upstream.json +1 -1
- package/plugins/acryl-kv-json/src/open.ts +86 -22
- package/plugins/acryl-kv-json/upstream.json +2 -2
- package/plugins/acryl-node-server/src/client-modules.ts +10 -9
- package/plugins/acryl-node-server/upstream.json +2 -2
- package/plugins/acryl-router/src/browser/read-sse.ts +3 -0
- package/plugins/acryl-router/src/helpers/read-sse.ts +111 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/builder/index.ts +2 -2
- package/plugins/builder/plugin.json +1 -1
- package/plugins/desktop/main.ts +3 -3
- package/plugins/desktop/plugin.json +2 -2
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/main.ts +2 -2
- package/plugins/desktop-web/plugin.json +2 -2
- package/plugins/desktop-web/upstream.json +1 -1
- package/plugins/extensions/index.ts +19 -1
- package/plugins/extensions/plugin.json +1 -1
- package/plugins/extensions/resolve.ts +1 -1
- package/plugins/manifest/lock.ts +7 -3
- package/plugins/manifest/upgrade-cli.ts +1 -1
- package/plugins/save/cli.ts +1 -1
- package/plugins/save/plugin.json +1 -1
- package/shared/AGENTS.core.md +10 -10
- package/shared/DEFAULTS.md +40 -0
- package/shared/SKILL.md +14 -0
- package/templates/agent/AGENTS.md +1 -1
- package/templates/agent/README.md +6 -6
- package/templates/agent/gitignore +1 -0
- package/templates/agent/package.json +8 -8
- package/templates/agent/src/app.ts +28 -15
- package/templates/agent/src/main.ts +2 -2
- package/templates/agent/src/routes.ts +5 -5
- package/templates/agent/test/agent.test.ts +1 -1
- package/templates/agent/test/app.test.ts +1 -1
- package/templates/agent/test/builder.test.ts +1 -1
- package/templates/agent/test/model.test.ts +1 -1
- package/templates/agent/tsconfig.json +48 -50
- package/templates/blank/AGENTS.md +1 -1
- package/templates/blank/README.md +2 -2
- package/templates/blank/gitignore +1 -0
- package/templates/blank/package.json +8 -8
- package/templates/blank/src/about.tsx +2 -2
- package/templates/blank/src/app.ts +4 -4
- package/templates/blank/src/main.ts +1 -1
- package/templates/blank/src/routes.ts +1 -1
- package/templates/blank/test/app.test.ts +1 -1
- package/templates/blank/tsconfig.json +1 -1
- package/templates/todo/README.md +1 -1
- package/templates/todo/gitignore +1 -0
- package/templates/todo/package.json +8 -8
- package/templates/todo/src/app.ts +5 -5
- package/templates/todo/src/main.ts +1 -1
- package/templates/todo/src/todo.ts +4 -4
- package/templates/todo/test/todo.test.ts +1 -1
- package/templates/todo/tsconfig.json +1 -1
- package/templates/todo/upstream.json +2 -2
- package/lib/database-D_UN6We8.d.ts +0 -44
- package/lib/kv-DWDm0Lja.d.ts +0 -39
package/plugins/save/cli.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
4
4
|
import { saveFolder } from './index.ts'
|
|
5
5
|
|
|
6
6
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
7
|
-
const appRoot = fileURLToPath(new URL('
|
|
7
|
+
const appRoot = fileURLToPath(new URL('../..', import.meta.url))
|
|
8
8
|
const result = saveFolder(appRoot, process.argv.slice(2).join(' ').trim() || 'save the app')
|
|
9
9
|
if (result.status === 'refused') {
|
|
10
10
|
console.error(`not saved: ${result.reason}`)
|
package/plugins/save/plugin.json
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
"@deepseek-ai/cordis": "4.0.4",
|
|
9
9
|
"@webboxes/app-persistence": "0.1.1"
|
|
10
10
|
},
|
|
11
|
-
"mount": "add a script \"save\": \"node
|
|
11
|
+
"mount": "add a script \"save\": \"node plugins/save/cli.ts\" to package.json; mount the plugin to give the agent a save_app tool"
|
|
12
12
|
}
|
package/shared/AGENTS.core.md
CHANGED
|
@@ -9,17 +9,17 @@ Start at `docs/framework/README.md`; `api/summary.md` is the one-page summary. R
|
|
|
9
9
|
`api/context.md` (`c`), `cordis/validation.md` and `guides/validation.md`, `guides/helpers.md` (`streamSSE`), `concepts/middleware.md`, `cordis/plugin-lifecycle.md` (what a plugin
|
|
10
10
|
owns and when it is unloaded), `cordis/mounting.md` and `cordis/node-server.md`. Pages marked "not yet available" describe parts that are not in this app.
|
|
11
11
|
|
|
12
|
-
The docs write `import { Acryl } from 'acrylblends'` and `from 'acrylblends/<subpath>'`. In this app the framework is the vendored plugin source in `
|
|
12
|
+
The docs write `import { Acryl } from 'acrylblends'` and `from 'acrylblends/<subpath>'`. In this app the framework is the vendored plugin source in `plugins/acryl-router/src`
|
|
13
13
|
(read it, do not edit it: a change belongs in ACRYL, upstream), so from a file in `src/` import:
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
15
|
+
- `../plugins/acryl-router/src/index.ts` for `Acryl`, `Context`, `HTTPException`, `connectionOf`, `getConnInfo` and the types;
|
|
16
|
+
- `../plugins/acryl-router/src/middleware/index.ts` for every middleware (`cors`, `bodyLimit`, `trimTrailingSlash`, `loopbackOnly`, ...);
|
|
17
|
+
- `../plugins/acryl-router/src/helpers/index.ts` for the helpers and validators (`streamSSE`, `schemaValidator`, `validator`, cookies, `testClient`, ...).
|
|
18
|
+
- `../plugins/acryl-router/src/core/jsx/index.ts` for `acrylblends/jsx` (`jsx`, `Fragment`, the types), and `.../core/jsx/streaming.ts` for `acrylblends/jsx/streaming`.
|
|
19
19
|
|
|
20
|
-
**JSX: write `.tsx`.** The scripts in `package.json` run Node.js with `--import ./
|
|
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 written into `
|
|
22
|
+
A plugin written into `plugins/` while the app runs may use the docs' own names instead: `acrylblends` and `acrylblends/<subpath>` resolve to the same files. `App` is the
|
|
23
23
|
deprecated name of `Acryl`.
|
|
24
24
|
|
|
25
25
|
## How a feature is built
|
|
@@ -28,8 +28,8 @@ Routes are an ordinary `Acryl` app that a plugin mounts once with `ctx.router.ro
|
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
30
|
import type { Context } from '@deepseek-ai/cordis'
|
|
31
|
-
import { Acryl, HTTPException } from '
|
|
32
|
-
import { schemaValidator, type StandardSchema } from '
|
|
31
|
+
import { Acryl, HTTPException } from '../plugins/acryl-router/src/index.ts'
|
|
32
|
+
import { schemaValidator, type StandardSchema } from '../plugins/acryl-router/src/helpers/index.ts'
|
|
33
33
|
|
|
34
34
|
export const name = 'notes' // a plugin's name; blend.yaml refers to it
|
|
35
35
|
export const inject = ['router'] // services it needs
|
|
@@ -44,7 +44,7 @@ export function apply(ctx: Context) {
|
|
|
44
44
|
|
|
45
45
|
- A small route goes on the `App` in `src/routes.ts`. A new feature is a new plugin file: add it to `registry` in `src/app.ts`, add a row
|
|
46
46
|
(`- id: notes` / `name: notes`) to `blend.yaml` before the `acryl-node-server` row, and pass runtime values (a data file) as its config in `createApp`.
|
|
47
|
-
- State, when `
|
|
47
|
+
- State, when `plugins/acryl-state` exists: `const cell = ctx.state.cell<T>('notes.list', initial)` (add `'state'` to `inject`), then
|
|
48
48
|
`cell.get()`, `cell.set(value)`, `cell.update(fn)` and `cell.subscribe(fn)` (returns the unsubscribe). Every cell is also served as JSON at `/api/state/<key>`
|
|
49
49
|
and streamed at `/api/state/<key>/events`.
|
|
50
50
|
- Test a route without a socket: `await ctx.router.request('/notes')`, or boot the app as `test/app.test.ts` does and use `fetch`.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
## Defaults you can swap
|
|
3
|
+
|
|
4
|
+
The framework is not rigid. Every choice is a plugin behind a port, and what the app starts with is only a fallback. Name the default before you change it,
|
|
5
|
+
and swap it at the one place that mounts it.
|
|
6
|
+
|
|
7
|
+
| Choice | Default | To swap it |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Records | none until you add one: `npx acrylblends add db` mounts the `db` port on SQLite | mount another provider of `db` in `src/app.ts`; plugins use `ctx.db` and do not change |
|
|
10
|
+
| Small state | none until you add one: `npx acrylblends add kv` mounts the `kv` port on one JSON file | mount `acryl-kv-db` (the same port over `db`) or a provider of your own |
|
|
11
|
+
| Server | Node (`acryl-node-server`) | mount the adapter of another runtime in `src/app.ts` |
|
|
12
|
+
| Window | the browser | `npx acrylblends add desktop` adds an Electron window on the same server |
|
|
13
|
+
|
|
14
|
+
- **An outside tool** (a database server, a bundler, a router such as Vike) joins the app as a provider: a plugin in `plugins/<name>/` that implements a port and
|
|
15
|
+
passes that port's conformance tests, then `src/app.ts` mounts it. Postgres is such a provider and is not part of this package; add it when the app needs it.
|
|
16
|
+
- **The dependency rule:** only the file that mounts a provider imports it; everything else takes the port as an argument or injects it. `npx acrylblends check-deps` enforces it.
|
|
17
|
+
|
|
18
|
+
## Plugins
|
|
19
|
+
|
|
20
|
+
There is one plugin folder: `plugins/` at the root of the app. A plugin is a folder `plugins/<name>/` with an `index.ts`. `npx acrylblends add <name>` copies a library
|
|
21
|
+
plugin there; a plugin you or an agent write goes there too (the `extensions` plugin installs it into the running app, and `plugins/library.json` lists the copies it must not
|
|
22
|
+
install a second time). Do not create `src/plugins/` or `extensions/`.
|
|
23
|
+
|
|
24
|
+
## The door
|
|
25
|
+
|
|
26
|
+
The door is a command in this folder, `.acryl/bin/acryl`, that lets you look at and change the running app; it reaches only the app running from this folder. Its state (a secret and an audit log) lives under `.acryl/`, which git ignores: never commit it. If `.acryl/bin/acryl` is not here, this app has no door yet: run it with its scripts and check it with `npm test`.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
.acryl/bin/acryl brief what this app is: its plugins, where the docs are, the verbs
|
|
30
|
+
.acryl/bin/acryl look a snapshot of the page, with refs (r1, r2, ...)
|
|
31
|
+
.acryl/bin/acryl act click --ref <r>
|
|
32
|
+
.acryl/bin/acryl act type --ref <r> --text <t> [--submit] [--no-clear]
|
|
33
|
+
.acryl/bin/acryl act select --ref <r> --option <o>
|
|
34
|
+
.acryl/bin/acryl act press --key <Enter|Escape|...>
|
|
35
|
+
.acryl/bin/acryl act scroll [--ref <r>] --direction up|down [--amount <n>]
|
|
36
|
+
.acryl/bin/acryl act wait (--text <t> | --role <role> [--name <n>]) [--gone] [--timeout-ms <ms>]
|
|
37
|
+
.acryl/bin/acryl plugin list | add <folder> | remove <name> | reload
|
|
38
|
+
.acryl/bin/acryl tool list | call --tool <name> --args '<json>'
|
|
39
|
+
```
|
|
40
|
+
Every verb takes `--json`. `plugin add plugins/<name>` checks the plugin, installs it and makes it live without a restart. `tool call` reaches only the tools on the door's allowlist (empty by default). On Windows the command is `.acryl\bin\acryl.cmd`.
|
package/shared/SKILL.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: app-guide
|
|
3
|
+
description: How to change, run and check the __APP_NAME__ app. Use it 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` in this folder says how the app is laid out. The framework's API is in `docs/framework/`: start at `docs/framework/README.md`, and
|
|
9
|
+
`docs/framework/api/summary.md` is the one-page summary. Parts the docs mark "not yet available" are not in this app.
|
|
10
|
+
2. **Make the change small.** Your own routes are a plugin (`src/routes.ts`) that builds an `Acryl` app and mounts it; test a route with `await app.request('/path')`.
|
|
11
|
+
3. **One plugin folder.** A plugin lives in `plugins/<name>/` (`npx acrylblends add <name>` copies a library plugin there). There is no `src/plugins/` and no `extensions/`.
|
|
12
|
+
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.
|
|
13
|
+
5. **Look at the running app.** The door is a command in this folder, `.acryl/bin/acryl`, that lets you look at and change the running app; it reaches only the app running from this folder. Its state (a secret and an audit log) lives under `.acryl/`, which git ignores: never commit it. If `.acryl/bin/acryl` is not here, this app has no door yet: run it with its scripts and check it with `npm test`. Start with `.acryl/bin/acryl brief`, then `look` for the page and `act` to click or type on it; the full list is in `AGENTS.md`.
|
|
14
|
+
6. **Done means checked.** `npm test` passes, and the change shows in the running app after a restart. No secret goes in a file or in `git status`: keys come from environment variables.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
## This app (agent)
|
|
3
3
|
|
|
4
|
-
Besides `src/routes.ts`, the app runs an agent (`POST /ask`) that can write plugins into `
|
|
4
|
+
Besides `src/routes.ts`, the app runs an agent (`POST /ask`) that can write plugins into `plugins/` while the app runs and install them with
|
|
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`.
|
|
@@ -13,14 +13,14 @@ curl -X POST <url>/ask -H 'content-type: application/json' -d '{"question":"add
|
|
|
13
13
|
# {"status":"completed","output":"The answer is 5.","steps":2}
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
- `src/main.ts` chooses where the app lives (`
|
|
16
|
+
- `src/main.ts` chooses where the app lives (`plugins/instance`) and starts it.
|
|
17
17
|
- `blend.yaml` is the app as data: which plugins it mounts, in what order, with what config and parameters. Switch
|
|
18
18
|
one off with `disabled: true`. `npm run lock` writes `blend.lock.json`: the manifest's digest and a digest of
|
|
19
19
|
every plugin folder, so a diff of the lock shows what changed.
|
|
20
20
|
- `src/app.ts` holds the registry of plugins the code provides, and mounts the manifest's rows.
|
|
21
|
-
- `
|
|
22
|
-
- `
|
|
23
|
-
version running. `
|
|
21
|
+
- `plugins/agent` is the offline agent: a scripted model, a tool registry, memory, and one child fiber per run.
|
|
22
|
+
- `plugins/extensions` installs, updates and removes plugins while the app runs; a failed update keeps the previous
|
|
23
|
+
version running. `plugins/builder` gives the agent tools to do that itself, writing into `plugins/`.
|
|
24
24
|
|
|
25
25
|
Give it a real model and it builds any Cordis plugin you describe (any OpenAI-compatible API; nothing is installed):
|
|
26
26
|
|
|
@@ -40,9 +40,9 @@ curl <url>/tasks # every run as a durable task, with the tool calls
|
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Other commands: `update route <name> <path> <text>`, `break plugin <name>` (shows the rollback), `remove plugin <name>`,
|
|
43
|
-
`list plugins`. What the agent writes lands in `
|
|
43
|
+
`list plugins`. What the agent writes lands in `plugins/`, is part of your app, and starts again at the next start.
|
|
44
44
|
- `src/routes.ts` is your own plugin: it builds an `App` (`import { App } from './plugins/acryl-router/src/index.ts'`) and mounts it with `ctx.router.route(prefix, app)`. Routes added to the app after it is mounted are not served. A page mounted at `/x` answers `/x`, not `/x/` (trailing slashes are strict). The router API is documented at https://acrylblends.github.io/docs/framework/the-acryl-framework and in `docs/framework` of the acrylblends repository.
|
|
45
|
-
- `
|
|
45
|
+
- `plugins/` holds the plugins copied from the ACRYLBLENDS library. They are yours now; edit them freely.
|
|
46
46
|
Add more with `acrylblends add <plugin>`.
|
|
47
47
|
|
|
48
48
|
`npm run save -- "message"` commits the app with git (and pushes when it has a remote). It refuses a file that looks like
|
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"imports": {
|
|
7
|
-
"#acryl-jsx/jsx-runtime": "./
|
|
8
|
-
"#acryl-jsx/jsx-dev-runtime": "./
|
|
7
|
+
"#acryl-jsx/jsx-runtime": "./plugins/acryl-router/src/core/jsx/jsx-runtime.ts",
|
|
8
|
+
"#acryl-jsx/jsx-dev-runtime": "./plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts"
|
|
9
9
|
},
|
|
10
10
|
"engines": {
|
|
11
11
|
"node": ">=22.19.0"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
|
-
"start": "node --import ./
|
|
15
|
-
"dev": "node --watch --import ./
|
|
16
|
-
"test": "node --import ./
|
|
14
|
+
"start": "node --import ./plugins/acryl-node-server/src/register.ts src/main.ts",
|
|
15
|
+
"dev": "node --watch --import ./plugins/acryl-node-server/src/register.ts src/main.ts",
|
|
16
|
+
"test": "node --import ./plugins/acryl-node-server/src/register.ts --test \"test/**/*.test.{ts,tsx}\"",
|
|
17
17
|
"check": "tsc --noEmit",
|
|
18
|
-
"lock": "node
|
|
19
|
-
"save": "node
|
|
20
|
-
"upgrade": "node
|
|
18
|
+
"lock": "node plugins/manifest/lock.ts",
|
|
19
|
+
"save": "node plugins/save/cli.ts",
|
|
20
|
+
"upgrade": "node plugins/manifest/upgrade-cli.ts"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"typescript": "6.0.3"
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
1
2
|
import { join } from 'node:path'
|
|
2
3
|
import { fileURLToPath } from 'node:url'
|
|
3
4
|
import { Context, type Fiber } from '@deepseek-ai/cordis'
|
|
4
|
-
import * as nodeServer from '
|
|
5
|
-
import * as router from '
|
|
6
|
-
import * as state from '
|
|
7
|
-
import { agent, memory, model, tools } from '
|
|
8
|
-
import { builder } from '
|
|
9
|
-
import { extensions } from '
|
|
10
|
-
import { type AppInstance, instance } from '
|
|
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 * as state from '../plugins/acryl-state/src/index.ts'
|
|
8
|
+
import { agent, memory, model, tools } from '../plugins/agent/index.ts'
|
|
9
|
+
import { builder } from '../plugins/builder/index.ts'
|
|
10
|
+
import { extensions } from '../plugins/extensions/index.ts'
|
|
11
|
+
import { type AppInstance, instance } from '../plugins/instance/index.ts'
|
|
11
12
|
import {
|
|
12
13
|
blueprintsIn,
|
|
13
14
|
compose,
|
|
14
15
|
loadManifest,
|
|
15
16
|
type ParameterValue,
|
|
16
|
-
} from '
|
|
17
|
-
import { type OpenAIConfig, openai } from '
|
|
18
|
-
import { sandboxPlugin } from '
|
|
19
|
-
import { save } from '
|
|
20
|
-
import { tasks } from '
|
|
17
|
+
} from '../plugins/manifest/index.ts'
|
|
18
|
+
import { type OpenAIConfig, openai } from '../plugins/openai/index.ts'
|
|
19
|
+
import { sandboxPlugin } from '../plugins/sandbox/index.ts'
|
|
20
|
+
import { save } from '../plugins/save/index.ts'
|
|
21
|
+
import { tasks } from '../plugins/tasks/index.ts'
|
|
21
22
|
import { routes } from './routes.ts'
|
|
22
23
|
|
|
23
24
|
/** The plugins this app's code provides. `blend.yaml` chooses, orders and configures them. */
|
|
@@ -59,7 +60,7 @@ export interface App {
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
|
-
* The app: `instance` first (where it lives, chosen at run time), then the manifest's rows in order. The app's
|
|
63
|
+
* The app: `instance` first (where it lives, chosen at run time), then the manifest's rows in order. The app's `plugins/`
|
|
63
64
|
* folder is trusted to install at start, and is where the builder writes.
|
|
64
65
|
*/
|
|
65
66
|
export async function createApp(
|
|
@@ -71,7 +72,14 @@ export async function createApp(
|
|
|
71
72
|
} = {},
|
|
72
73
|
): Promise<App> {
|
|
73
74
|
const manifest = readManifest(options.values)
|
|
74
|
-
const extensionsDir = join(chosen.root, '
|
|
75
|
+
const extensionsDir = join(chosen.root, 'plugins')
|
|
76
|
+
// One release of grace: an app made before 0.7.8 kept what the agent wrote in `extensions/`. It is still installed, with a warning.
|
|
77
|
+
const legacyDir = join(chosen.root, 'extensions')
|
|
78
|
+
const legacy = existsSync(legacyDir)
|
|
79
|
+
if (legacy)
|
|
80
|
+
console.warn(
|
|
81
|
+
'warning: extensions/ is the old folder for plugins the agent writes; move them to plugins/ ("git mv extensions/<name> plugins/<name>"). extensions/ is read for this release only.',
|
|
82
|
+
)
|
|
75
83
|
const root = new Context()
|
|
76
84
|
const first = root.plugin(instance, chosen)
|
|
77
85
|
await first.await()
|
|
@@ -83,7 +91,12 @@ export async function createApp(
|
|
|
83
91
|
// Runtime values the manifest cannot hold: the manifest's own digest (every task records it), and who may install.
|
|
84
92
|
tasks: { manifest: `sha256:${manifest.digest}` },
|
|
85
93
|
// The `approval` parameter in blend.yaml (the extensions row) turns this source into one that asks.
|
|
86
|
-
extensions: {
|
|
94
|
+
extensions: {
|
|
95
|
+
sources: [
|
|
96
|
+
{ dir: extensionsDir, trust: 'install' },
|
|
97
|
+
...(legacy ? [{ dir: legacyDir, trust: 'install' as const }] : []),
|
|
98
|
+
],
|
|
99
|
+
},
|
|
87
100
|
builder: { dir: extensionsDir },
|
|
88
101
|
server: { port: chosen.port.start, scan: chosen.port.scan ? 20 : 0, hostname: chosen.host },
|
|
89
102
|
})),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import { selectInstance } from '../plugins/instance/index.ts'
|
|
3
|
+
import { modelConfigFrom } from '../plugins/openai/index.ts'
|
|
2
4
|
import { createApp } from './app.ts'
|
|
3
|
-
import { selectInstance } from './plugins/instance/index.ts'
|
|
4
|
-
import { modelConfigFrom } from './plugins/openai/index.ts'
|
|
5
5
|
import { APP_NAME } from './routes.ts'
|
|
6
6
|
|
|
7
7
|
// The composition root: the one place that reads the environment. selectInstance chooses where the app lives;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis'
|
|
2
|
-
import { Acryl } from '
|
|
3
|
-
import { bodyLimit } from '
|
|
4
|
-
import type {} from '
|
|
5
|
-
import type {} from '
|
|
6
|
-
import type {} from '
|
|
2
|
+
import { Acryl } from '../plugins/acryl-router/src/index.ts'
|
|
3
|
+
import { bodyLimit } from '../plugins/acryl-router/src/middleware/index.ts'
|
|
4
|
+
import type {} from '../plugins/agent/index.ts'
|
|
5
|
+
import type {} from '../plugins/extensions/index.ts'
|
|
6
|
+
import type {} from '../plugins/tasks/index.ts'
|
|
7
7
|
|
|
8
8
|
export const APP_NAME = '__APP_NAME__'
|
|
9
9
|
|
|
@@ -4,8 +4,8 @@ import { tmpdir } from 'node:os'
|
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { test } from 'node:test'
|
|
6
6
|
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { pinnedInstance, withPort } from '../plugins/instance/index.ts'
|
|
7
8
|
import { type App, createApp } from '../src/app.ts'
|
|
8
|
-
import { pinnedInstance, withPort } from '../src/plugins/instance/index.ts'
|
|
9
9
|
|
|
10
10
|
const projectRoot = fileURLToPath(new URL('..', import.meta.url))
|
|
11
11
|
|
|
@@ -4,8 +4,8 @@ import { tmpdir } from 'node:os'
|
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { test } from 'node:test'
|
|
6
6
|
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { pinnedInstance, withPort } from '../plugins/instance/index.ts'
|
|
7
8
|
import { type App, createApp } from '../src/app.ts'
|
|
8
|
-
import { pinnedInstance, withPort } from '../src/plugins/instance/index.ts'
|
|
9
9
|
import { APP_NAME } from '../src/routes.ts'
|
|
10
10
|
|
|
11
11
|
const projectRoot = fileURLToPath(new URL('..', import.meta.url))
|
|
@@ -3,8 +3,8 @@ import { mkdtemp, rm } from 'node:fs/promises'
|
|
|
3
3
|
import { tmpdir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { test } from 'node:test'
|
|
6
|
+
import { pinnedInstance, withPort } from '../plugins/instance/index.ts'
|
|
6
7
|
import { createApp } from '../src/app.ts'
|
|
7
|
-
import { pinnedInstance, withPort } from '../src/plugins/instance/index.ts'
|
|
8
8
|
|
|
9
9
|
/** The agent extends the running app through POST /ask; a restart keeps what it built. */
|
|
10
10
|
test('the agent builds, updates, protects and removes a plugin in the running app, and a restart keeps it', async () => {
|
|
@@ -5,8 +5,8 @@ import type { AddressInfo } from 'node:net'
|
|
|
5
5
|
import { tmpdir } from 'node:os'
|
|
6
6
|
import { join } from 'node:path'
|
|
7
7
|
import { test } from 'node:test'
|
|
8
|
+
import { pinnedInstance, withPort } from '../plugins/instance/index.ts'
|
|
8
9
|
import { createApp } from '../src/app.ts'
|
|
9
|
-
import { pinnedInstance, withPort } from '../src/plugins/instance/index.ts'
|
|
10
10
|
|
|
11
11
|
/** A plugin no scripted command knows: the model writes it from scratch. */
|
|
12
12
|
const COUNTER = `import type { Context } from '@deepseek-ai/cordis'
|
|
@@ -13,58 +13,56 @@
|
|
|
13
13
|
"skipLibCheck": true,
|
|
14
14
|
"types": ["node"],
|
|
15
15
|
"paths": {
|
|
16
|
-
"acrylblends": ["./
|
|
17
|
-
"acrylblends/conninfo": ["./
|
|
18
|
-
"acrylblends/http-exception": ["./
|
|
19
|
-
"acrylblends/types": ["./
|
|
20
|
-
"acrylblends/client": ["./
|
|
21
|
-
"acrylblends/basic-auth": ["./
|
|
22
|
-
"acrylblends/bearer-auth": ["./
|
|
23
|
-
"acrylblends/body-limit": ["./
|
|
24
|
-
"acrylblends/combine": ["./
|
|
25
|
-
"acrylblends/compress": ["./
|
|
26
|
-
"acrylblends/context-storage": ["./
|
|
27
|
-
"acrylblends/cors": ["./
|
|
28
|
-
"acrylblends/csrf": ["./
|
|
29
|
-
"acrylblends/etag": ["./
|
|
30
|
-
"acrylblends/ip-restriction": ["./
|
|
31
|
-
"acrylblends/jwk": ["./
|
|
32
|
-
"acrylblends/jwt": ["./
|
|
33
|
-
"acrylblends/language": ["./
|
|
34
|
-
"acrylblends/logger": ["./
|
|
35
|
-
"acrylblends/loopback-only": ["./
|
|
36
|
-
"acrylblends/method-not-allowed": ["./
|
|
37
|
-
"acrylblends/method-override": ["./
|
|
38
|
-
"acrylblends/powered-by": ["./
|
|
39
|
-
"acrylblends/pretty-json": ["./
|
|
40
|
-
"acrylblends/request-id": ["./
|
|
41
|
-
"acrylblends/secure-headers": ["./
|
|
42
|
-
"acrylblends/serve-static": ["./
|
|
43
|
-
"acrylblends/timeout": ["./
|
|
44
|
-
"acrylblends/timing": ["./
|
|
45
|
-
"acrylblends/trailing-slash": ["./
|
|
46
|
-
"acrylblends/accepts": ["./
|
|
47
|
-
"acrylblends/cookie": ["./
|
|
48
|
-
"acrylblends/factory": ["./
|
|
49
|
-
"acrylblends/html": ["./
|
|
50
|
-
"acrylblends/route": ["./
|
|
51
|
-
"acrylblends/schema-validator": ["./
|
|
52
|
-
"acrylblends/streaming": ["./
|
|
53
|
-
"acrylblends/testing": ["./
|
|
54
|
-
"acrylblends/validator": ["./
|
|
55
|
-
"acrylblends/ws": ["./
|
|
56
|
-
"acryl-router": ["./
|
|
57
|
-
"acryl-router/middleware": ["./
|
|
58
|
-
"acryl-router/helpers": ["./
|
|
59
|
-
"acrylblends/jsx": ["./
|
|
60
|
-
"acrylblends/jsx/jsx-runtime": ["./
|
|
61
|
-
"acrylblends/jsx/jsx-dev-runtime": [
|
|
62
|
-
|
|
63
|
-
],
|
|
64
|
-
"acrylblends/jsx/streaming": ["./src/plugins/acryl-router/src/core/jsx/streaming.ts"]
|
|
16
|
+
"acrylblends": ["./plugins/acryl-router/src/index.ts"],
|
|
17
|
+
"acrylblends/conninfo": ["./plugins/acryl-router/src/index.ts"],
|
|
18
|
+
"acrylblends/http-exception": ["./plugins/acryl-router/src/index.ts"],
|
|
19
|
+
"acrylblends/types": ["./plugins/acryl-router/src/index.ts"],
|
|
20
|
+
"acrylblends/client": ["./plugins/acryl-router/src/client.ts"],
|
|
21
|
+
"acrylblends/basic-auth": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
22
|
+
"acrylblends/bearer-auth": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
23
|
+
"acrylblends/body-limit": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
24
|
+
"acrylblends/combine": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
25
|
+
"acrylblends/compress": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
26
|
+
"acrylblends/context-storage": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
27
|
+
"acrylblends/cors": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
28
|
+
"acrylblends/csrf": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
29
|
+
"acrylblends/etag": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
30
|
+
"acrylblends/ip-restriction": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
31
|
+
"acrylblends/jwk": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
32
|
+
"acrylblends/jwt": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
33
|
+
"acrylblends/language": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
34
|
+
"acrylblends/logger": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
35
|
+
"acrylblends/loopback-only": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
36
|
+
"acrylblends/method-not-allowed": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
37
|
+
"acrylblends/method-override": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
38
|
+
"acrylblends/powered-by": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
39
|
+
"acrylblends/pretty-json": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
40
|
+
"acrylblends/request-id": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
41
|
+
"acrylblends/secure-headers": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
42
|
+
"acrylblends/serve-static": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
43
|
+
"acrylblends/timeout": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
44
|
+
"acrylblends/timing": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
45
|
+
"acrylblends/trailing-slash": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
46
|
+
"acrylblends/accepts": ["./plugins/acryl-router/src/helpers/index.ts"],
|
|
47
|
+
"acrylblends/cookie": ["./plugins/acryl-router/src/helpers/index.ts"],
|
|
48
|
+
"acrylblends/factory": ["./plugins/acryl-router/src/helpers/index.ts"],
|
|
49
|
+
"acrylblends/html": ["./plugins/acryl-router/src/helpers/index.ts"],
|
|
50
|
+
"acrylblends/route": ["./plugins/acryl-router/src/helpers/index.ts"],
|
|
51
|
+
"acrylblends/schema-validator": ["./plugins/acryl-router/src/helpers/index.ts"],
|
|
52
|
+
"acrylblends/streaming": ["./plugins/acryl-router/src/helpers/index.ts"],
|
|
53
|
+
"acrylblends/testing": ["./plugins/acryl-router/src/helpers/index.ts"],
|
|
54
|
+
"acrylblends/validator": ["./plugins/acryl-router/src/helpers/index.ts"],
|
|
55
|
+
"acrylblends/ws": ["./plugins/acryl-router/src/helpers/index.ts"],
|
|
56
|
+
"acryl-router": ["./plugins/acryl-router/src/index.ts"],
|
|
57
|
+
"acryl-router/middleware": ["./plugins/acryl-router/src/middleware/index.ts"],
|
|
58
|
+
"acryl-router/helpers": ["./plugins/acryl-router/src/helpers/index.ts"],
|
|
59
|
+
"acrylblends/jsx": ["./plugins/acryl-router/src/core/jsx/index.ts"],
|
|
60
|
+
"acrylblends/jsx/jsx-runtime": ["./plugins/acryl-router/src/core/jsx/jsx-runtime.ts"],
|
|
61
|
+
"acrylblends/jsx/jsx-dev-runtime": ["./plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts"],
|
|
62
|
+
"acrylblends/jsx/streaming": ["./plugins/acryl-router/src/core/jsx/streaming.ts"]
|
|
65
63
|
},
|
|
66
64
|
"jsx": "react-jsx",
|
|
67
65
|
"jsxImportSource": "#acryl-jsx"
|
|
68
66
|
},
|
|
69
|
-
"include": ["src", "test", "
|
|
67
|
+
"include": ["src", "test", "plugins"]
|
|
70
68
|
}
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
## This app (blank)
|
|
3
3
|
|
|
4
4
|
`src/routes.ts` is the app's own plugin: add routes to its `App`. `blend.yaml` lists the plugins that are mounted. `acrylblends add <plugin>` copies
|
|
5
|
-
another library plugin (for example `acryl-state`) into `
|
|
5
|
+
another library plugin (for example `acryl-state`) into `plugins/`.
|
|
@@ -10,13 +10,13 @@ npm test
|
|
|
10
10
|
npm run check # type-check
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
- `src/main.ts` chooses where the app lives (`
|
|
13
|
+
- `src/main.ts` chooses where the app lives (`plugins/instance`) and starts it.
|
|
14
14
|
- `blend.yaml` is the app as data: which plugins it mounts, in what order, with what config and parameters. Switch
|
|
15
15
|
one off with `disabled: true`. `npm run lock` writes `blend.lock.json`: the manifest's digest and a digest of
|
|
16
16
|
every plugin folder, so a diff of the lock shows what changed.
|
|
17
17
|
- `src/app.ts` holds the registry of plugins the code provides, and mounts the manifest's rows.
|
|
18
18
|
- `src/routes.ts` is your own plugin: it builds an `App` (`import { App } from './plugins/acryl-router/src/index.ts'`) and mounts it with `ctx.router.route(prefix, app)`. Routes added to the app after it is mounted are not served. A page mounted at `/x` answers `/x`, not `/x/` (trailing slashes are strict). The router API is documented at https://acrylblends.github.io/docs/framework/the-acryl-framework and in `docs/framework` of the acrylblends repository.
|
|
19
|
-
- `
|
|
19
|
+
- `plugins/` holds the plugins copied from the ACRYLBLENDS library. They are yours now; edit them freely.
|
|
20
20
|
Add more with `acrylblends add <plugin>`.
|
|
21
21
|
|
|
22
22
|
`npm run save -- "message"` commits the app with git (and pushes when it has a remote). It refuses a file that looks like
|
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"imports": {
|
|
7
|
-
"#acryl-jsx/jsx-runtime": "./
|
|
8
|
-
"#acryl-jsx/jsx-dev-runtime": "./
|
|
7
|
+
"#acryl-jsx/jsx-runtime": "./plugins/acryl-router/src/core/jsx/jsx-runtime.ts",
|
|
8
|
+
"#acryl-jsx/jsx-dev-runtime": "./plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts"
|
|
9
9
|
},
|
|
10
10
|
"engines": {
|
|
11
11
|
"node": ">=22.19.0"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
|
-
"start": "node --import ./
|
|
15
|
-
"dev": "node --watch --import ./
|
|
16
|
-
"test": "node --import ./
|
|
14
|
+
"start": "node --import ./plugins/acryl-node-server/src/register.ts src/main.ts",
|
|
15
|
+
"dev": "node --watch --import ./plugins/acryl-node-server/src/register.ts src/main.ts",
|
|
16
|
+
"test": "node --import ./plugins/acryl-node-server/src/register.ts --test \"test/**/*.test.{ts,tsx}\"",
|
|
17
17
|
"check": "tsc --noEmit",
|
|
18
|
-
"lock": "node
|
|
19
|
-
"save": "node
|
|
20
|
-
"upgrade": "node
|
|
18
|
+
"lock": "node plugins/manifest/lock.ts",
|
|
19
|
+
"save": "node plugins/save/cli.ts",
|
|
20
|
+
"upgrade": "node plugins/manifest/upgrade-cli.ts"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"typescript": "6.0.3"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/** A page written in JSX: the scripts in package.json run Node.js with the vendored register hook (
|
|
1
|
+
/** A page written in JSX: the scripts in package.json run Node.js with the vendored register hook (plugins/acryl-node-server/src/register.ts), which compiles .tsx with this app's own typescript. */
|
|
2
2
|
const About = () => (
|
|
3
3
|
<main>
|
|
4
4
|
<h1>About</h1>
|
|
5
|
-
<p>Rendered on the server with the JSX runtime in
|
|
5
|
+
<p>Rendered on the server with the JSX runtime in plugins/acryl-router.</p>
|
|
6
6
|
</main>
|
|
7
7
|
)
|
|
8
8
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { join } from 'node:path'
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { Context, type Fiber } from '@deepseek-ai/cordis'
|
|
4
|
-
import * as nodeServer from '
|
|
5
|
-
import * as router from '
|
|
6
|
-
import { type AppInstance, instance } from '
|
|
4
|
+
import * as nodeServer from '../plugins/acryl-node-server/src/index.ts'
|
|
5
|
+
import * as router from '../plugins/acryl-router/src/index.ts'
|
|
6
|
+
import { type AppInstance, instance } from '../plugins/instance/index.ts'
|
|
7
7
|
import {
|
|
8
8
|
blueprintsIn,
|
|
9
9
|
compose,
|
|
10
10
|
loadManifest,
|
|
11
11
|
type ParameterValue,
|
|
12
|
-
} from '
|
|
12
|
+
} from '../plugins/manifest/index.ts'
|
|
13
13
|
import { routes } from './routes.ts'
|
|
14
14
|
|
|
15
15
|
/** The plugins this app's code provides. `blend.yaml` chooses, orders and configures them. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import { selectInstance } from '../plugins/instance/index.ts'
|
|
2
3
|
import { createApp } from './app.ts'
|
|
3
|
-
import { selectInstance } from './plugins/instance/index.ts'
|
|
4
4
|
import { APP_NAME } from './routes.ts'
|
|
5
5
|
|
|
6
6
|
// The composition root: the one place that reads the environment (through selectInstance) and chooses where the app lives.
|
|
@@ -4,8 +4,8 @@ import { tmpdir } from 'node:os'
|
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { test } from 'node:test'
|
|
6
6
|
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { pinnedInstance, withPort } from '../plugins/instance/index.ts'
|
|
7
8
|
import { type App, createApp } from '../src/app.ts'
|
|
8
|
-
import { pinnedInstance, withPort } from '../src/plugins/instance/index.ts'
|
|
9
9
|
import { APP_NAME } from '../src/routes.ts'
|
|
10
10
|
|
|
11
11
|
const projectRoot = fileURLToPath(new URL('..', import.meta.url))
|