acrylblends 1.4.0 → 1.5.1
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 +5 -2
- package/dist/index.d.ts +7 -4
- package/dist/index.js +191 -62
- package/dist/index.js.map +1 -1
- package/legacy/README.md +1 -1
- package/legacy/agent/46c6eadf9230/src/app.ts +293 -0
- package/legacy/agent/46c6eadf9230/src/routes.ts +163 -0
- package/legacy/blank/a53d41e6ef42/src/app.ts +160 -0
- package/legacy/blank/a53d41e6ef42/src/routes.ts +79 -0
- package/legacy/layouts.json +30 -0
- package/lib/{client-modules-Bkdr83jO.js → client-modules-C5qiVroz.js} +1 -1
- package/lib/cordis/door.js +1 -1
- package/lib/node-server/register.js +6 -1
- package/lib/node-server.js +1 -1
- package/lib/{transpile-QUK9b3eG.js → transpile-CRRaixyS.js} +19 -4
- package/package.json +1 -1
- package/plugins/acryl-door/src/cli/acryl.ts +6 -2
- package/plugins/acryl-node-server/src/register.ts +10 -1
- package/plugins/acryl-node-server/src/transpile.ts +25 -6
- package/plugins/acryl-page/src/favicon.ts +19 -0
- package/plugins/acryl-page/src/index.ts +18 -13
- package/plugins/agent-panel/client/main.ts +9 -0
- package/plugins/agent-panel/headless.ts +2 -2
- package/plugins/desktop/app-page.ts +151 -0
- package/plugins/desktop/assets/acrylblends-logo.png +0 -0
- package/plugins/desktop/brand.ts +164 -0
- package/plugins/desktop/frame.ts +88 -7
- package/plugins/desktop/git.ts +44 -0
- package/plugins/desktop/host-supervisor.ts +63 -0
- package/plugins/desktop/launch.ts +17 -1
- package/plugins/desktop/main.ts +268 -17
- package/plugins/desktop/preload.cjs +8 -0
- package/plugins/desktop/product.ts +117 -0
- package/plugins/desktop/window.ts +4 -0
- package/plugins/desktop-web/main.ts +3 -2
- package/plugins/desktop-web/plugin.json +1 -1
- package/plugins/extensions/index.ts +52 -7
- package/plugins/instance/archive.ts +395 -0
- package/plugins/instance/bundle-overlay.ts +10 -0
- package/plugins/instance/bundle.ts +201 -0
- package/plugins/instance/home.ts +395 -0
- package/plugins/instance/index.ts +132 -24
- package/plugins/instance/storage.ts +413 -0
- package/shared/skills/acryl-remove-plugin/SKILL.md +1 -1
- package/templates/agent/README.md +1 -1
- package/templates/agent/gitignore +2 -0
- package/templates/blank/README.md +1 -1
- package/templates/blank/gitignore +2 -0
- package/templates/blank/src/routes.ts +9 -5
- package/templates/chat/gitignore +2 -0
- package/templates/chat/src/chat-app.ts +7 -3
- package/templates/chat/src/llm.ts +7 -1
- package/templates/chat/test/chat.test.ts +16 -1
- package/templates/nodejs/gitignore +2 -0
- package/templates/notes/gitignore +2 -0
- package/templates/todo/README.md +1 -1
- package/templates/todo/gitignore +2 -0
|
@@ -9,7 +9,7 @@ export const APP_NAME = '__APP_NAME__'
|
|
|
9
9
|
/**
|
|
10
10
|
* The only place the app's mount prefix is named. On its own server (production, `npm start`) the app mounts at the root and
|
|
11
11
|
* its public URLs are `/`, `/about`, `/health`; when the ACRYL frame hosts it (`npm run dev`) the routes mount here, and the
|
|
12
|
-
* frame opens its tile at `/app
|
|
12
|
+
* frame opens its tile at `/app` (`/app/` answers too). Every page link and asset URL this template writes is derived from the prefix it is
|
|
13
13
|
* mounted at, so the same source serves both.
|
|
14
14
|
*/
|
|
15
15
|
export const FRAME_MOUNT_PREFIX = '/app'
|
|
@@ -50,14 +50,14 @@ const homePage = (name: string, widgets: string): string => `<!doctype html>
|
|
|
50
50
|
* The app's own pages and routes, mountable at any prefix: the standalone server mounts it at the root, the frame host at {@link FRAME_MOUNT_PREFIX}.
|
|
51
51
|
* The first screen shows the widgets of the app's plugins (`ctx.home`), read at each request, so a plugin switched on or off shows at the next load.
|
|
52
52
|
*/
|
|
53
|
-
export function makeApp(home: Home): Acryl {
|
|
53
|
+
export function makeApp(home: Home, name: () => string = () => APP_NAME): Acryl {
|
|
54
54
|
return (
|
|
55
55
|
new Acryl()
|
|
56
56
|
// A browser gets the first screen; an API client gets the JSON hello.
|
|
57
57
|
.get('/', async (c) =>
|
|
58
58
|
(c.req.header('accept') ?? '').includes('text/html')
|
|
59
|
-
? c.html(homePage(
|
|
60
|
-
: c.json({ app:
|
|
59
|
+
? c.html(homePage(name(), await renderWidgets(home)))
|
|
60
|
+
: c.json({ app: name(), ok: true }),
|
|
61
61
|
)
|
|
62
62
|
.get('/health', (c) => c.json({ ok: true }))
|
|
63
63
|
.get('/about', (c) => c.html(aboutPage()))
|
|
@@ -70,6 +70,10 @@ export const routes = {
|
|
|
70
70
|
inject: ['router', 'home'],
|
|
71
71
|
apply(ctx: Context, options: { readonly prefix?: string } = {}) {
|
|
72
72
|
const prefix = options.prefix ?? ''
|
|
73
|
-
|
|
73
|
+
// The name the app has now (Settings > App renames it), else the one the scaffold was made with.
|
|
74
|
+
ctx.router.route(
|
|
75
|
+
prefix === '' ? '/' : prefix,
|
|
76
|
+
makeApp(ctx.home, () => ctx.get('appInstance')?.name ?? APP_NAME),
|
|
77
|
+
)
|
|
74
78
|
},
|
|
75
79
|
}
|
package/templates/chat/gitignore
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { Acryl } from '../plugins/acryl-router/src/index.ts'
|
|
8
8
|
import { type Access, accessGate } from './access.ts'
|
|
9
9
|
import { chatStore } from './db.ts'
|
|
10
|
-
import { type Llm, openAiCompatible } from './llm.ts'
|
|
10
|
+
import { type Llm, MissingKey, openAiCompatible } from './llm.ts'
|
|
11
11
|
import { chatPage } from './page.tsx'
|
|
12
12
|
|
|
13
13
|
interface ChatInput {
|
|
@@ -99,10 +99,14 @@ export function createApp(
|
|
|
99
99
|
} catch (error) {
|
|
100
100
|
if (!signal.aborted) {
|
|
101
101
|
console.error(error)
|
|
102
|
-
// The reason stays in the log; the page gets a plain message.
|
|
102
|
+
// The reason stays in the log; the page gets a plain message, or how to set the missing key.
|
|
103
103
|
await stream.writeSSE({
|
|
104
104
|
event: 'error',
|
|
105
|
-
data: JSON.stringify(
|
|
105
|
+
data: JSON.stringify(
|
|
106
|
+
error instanceof MissingKey
|
|
107
|
+
? error.message
|
|
108
|
+
: 'The model could not answer. Try again.',
|
|
109
|
+
),
|
|
106
110
|
})
|
|
107
111
|
}
|
|
108
112
|
} finally {
|
|
@@ -4,13 +4,19 @@ export type Llm = (
|
|
|
4
4
|
signal: AbortSignal,
|
|
5
5
|
) => AsyncIterable<string>
|
|
6
6
|
|
|
7
|
+
/** The model has no key: the one failure whose message the page may show, since it names the fix and holds no detail from the service. */
|
|
8
|
+
export class MissingKey extends Error {}
|
|
9
|
+
|
|
7
10
|
/**
|
|
8
11
|
* An OpenAI-compatible chat completions API with `stream: true` (DeepSeek by default). The key is read from DEEPSEEK_API_KEY at the moment of the call and
|
|
9
12
|
* is never stored; DEEPSEEK_BASE_URL and DEEPSEEK_MODEL choose the service.
|
|
10
13
|
*/
|
|
11
14
|
export const openAiCompatible: Llm = async function* (messages, signal) {
|
|
12
15
|
const key = process.env.DEEPSEEK_API_KEY
|
|
13
|
-
if (!key)
|
|
16
|
+
if (!key)
|
|
17
|
+
throw new MissingKey(
|
|
18
|
+
'DEEPSEEK_API_KEY is not set: set it in the terminal that runs the app, then start the app again.',
|
|
19
|
+
)
|
|
14
20
|
const base = (process.env.DEEPSEEK_BASE_URL ?? 'https://api.deepseek.com').replace(/\/$/, '')
|
|
15
21
|
const response = await fetch(`${base}/chat/completions`, {
|
|
16
22
|
method: 'POST',
|
|
@@ -12,7 +12,7 @@ import { pinnedInstance, withPort } from '../plugins/instance/index.ts'
|
|
|
12
12
|
import { chatAccess } from '../src/access.ts'
|
|
13
13
|
import { createApp as boot } from '../src/app.ts'
|
|
14
14
|
import { createApp } from '../src/chat-app.ts'
|
|
15
|
-
import type
|
|
15
|
+
import { type Llm, openAiCompatible } from '../src/llm.ts'
|
|
16
16
|
import { FRAME_MOUNT_PREFIX } from '../src/routes.ts'
|
|
17
17
|
|
|
18
18
|
/** Throwaway homes and databases live inside the project (`.acrylblends/` is ignored by git), because a sandboxed agent may not write to the system temp folder. */
|
|
@@ -155,6 +155,21 @@ test('a failing model sends an error event with a plain message and no detail',
|
|
|
155
155
|
assert.match(last?.[1] ?? '', /could not answer/)
|
|
156
156
|
})
|
|
157
157
|
|
|
158
|
+
test('a model without its key tells the page which variable to set', async () => {
|
|
159
|
+
const saved = process.env.DEEPSEEK_API_KEY
|
|
160
|
+
delete process.env.DEEPSEEK_API_KEY
|
|
161
|
+
try {
|
|
162
|
+
const app = createApp(await memoryDb(), openAiCompatible)
|
|
163
|
+
const last = events(await (await app.request('/api/chat', post({ message: 'hi' }))).text()).at(
|
|
164
|
+
-1,
|
|
165
|
+
)
|
|
166
|
+
assert.equal(last?.[0], 'error')
|
|
167
|
+
assert.match(last?.[1] ?? '', /DEEPSEEK_API_KEY is not set/)
|
|
168
|
+
} finally {
|
|
169
|
+
if (saved !== undefined) process.env.DEEPSEEK_API_KEY = saved
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
158
173
|
test('when the client goes away the model call is aborted and what arrived is kept', async () => {
|
|
159
174
|
let sawAbort: (value: boolean) => void = () => {}
|
|
160
175
|
const aborted = new Promise<boolean>((resolve) => {
|
package/templates/todo/README.md
CHANGED
|
@@ -21,7 +21,7 @@ curl -N $URL/todo/events
|
|
|
21
21
|
|
|
22
22
|
The routes only answer a caller on this machine: a request from another site's page (a foreign `Origin`, or `Origin: null`) or addressed to another host name gets `403`.
|
|
23
23
|
|
|
24
|
-
Open `$URL/todo` for the page. Stop the app and start it again: the list is still there (`<home>/todo.json`,
|
|
24
|
+
Open `$URL/todo` for the page. Stop the app and start it again: the list is still there (`<home>/todo.json`, `~/.acryl/apps/<id>/data/` unless `ACRYLBLENDS_HOME` says otherwise).
|
|
25
25
|
|
|
26
26
|
- `blend.yaml` is the app as data. `npm run lock` records what it is built from; `npm run save` commits it, refusing secrets and public remotes for a private app.
|
|
27
27
|
- The framework plugins are in `plugins/acryl-*/src`. Do not edit them: they are ACRYL's, vendored; the text that explains them is in the ACRYLBLENDS docs.
|