@alashi/cli 0.8.0 → 0.9.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/assets/v1/chat.js +42 -2
- package/assets/v1/system.css +16 -18
- package/package.json +3 -3
- package/templates/basic-app/AGENTS.md +17 -3
- package/templates/basic-app/README.md +7 -1
- package/templates/basic-app/index.mjs +8 -7
- package/templates/basic-app/migrations/001-init.sql +7 -0
- package/templates/basic-app/package.json +1 -1
package/assets/v1/chat.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* alashi chat
|
|
1
|
+
/* alashi chat + markdown components v1
|
|
2
2
|
*
|
|
3
3
|
* The system chat UI, served by the host. Apps drop in:
|
|
4
4
|
*
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* and cost display. Renders into light DOM using system.css classes, so it
|
|
10
10
|
* inherits the app's theme tokens.
|
|
11
11
|
*
|
|
12
|
+
* The same script also registers <alashi-markdown>, the markdown renderer on
|
|
13
|
+
* its own for apps that display prose outside a conversation (see the bottom
|
|
14
|
+
* of this file). One renderer, one place formatting bugs get fixed.
|
|
15
|
+
*
|
|
12
16
|
* Attributes:
|
|
13
17
|
* app app name (default: derived from /app/<name>/ in the URL)
|
|
14
18
|
* placeholder input placeholder text
|
|
@@ -31,7 +35,8 @@
|
|
|
31
35
|
* Breaking changes ship as /assets/v2/chat.js. */
|
|
32
36
|
|
|
33
37
|
(() => {
|
|
34
|
-
|
|
38
|
+
// Loading the script twice is a no-op for every element it defines.
|
|
39
|
+
if (customElements.get('alashi-chat') || customElements.get('alashi-markdown')) return;
|
|
35
40
|
|
|
36
41
|
const escapeHtml = (s) =>
|
|
37
42
|
s
|
|
@@ -357,5 +362,40 @@
|
|
|
357
362
|
}
|
|
358
363
|
}
|
|
359
364
|
|
|
365
|
+
/* <alashi-markdown> — the same renderMarkdown() as chat bubbles, as a
|
|
366
|
+
* standalone display primitive. No fetching, no SSE, no conversations.
|
|
367
|
+
*
|
|
368
|
+
* <alashi-markdown text="**hi**"></alashi-markdown> // static markup
|
|
369
|
+
* node.text = card.description; // re-renders
|
|
370
|
+
*
|
|
371
|
+
* The `text` property is the primary path: apps pass model-derived,
|
|
372
|
+
* multi-line strings that do not belong in an attribute. Renders into light
|
|
373
|
+
* DOM (like _setMsgText) so it inherits the app's theme tokens, and carries
|
|
374
|
+
* the `markdown` class so system.css can style its children. */
|
|
375
|
+
class AlashiMarkdown extends HTMLElement {
|
|
376
|
+
connectedCallback() {
|
|
377
|
+
this.classList.add('markdown');
|
|
378
|
+
// A property set before connect wins; the attribute is the static path.
|
|
379
|
+
if (this._text === undefined) this._text = this.getAttribute('text') ?? '';
|
|
380
|
+
this._render();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
get text() {
|
|
384
|
+
return this._text ?? '';
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
set text(value) {
|
|
388
|
+
this._text = String(value ?? '');
|
|
389
|
+
this._render();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
_render() {
|
|
393
|
+
// Escape-first pipeline: renderMarkdown escapes before rebuilding a
|
|
394
|
+
// known-safe subset of tags, so untrusted markdown stays inert.
|
|
395
|
+
this.innerHTML = renderMarkdown(String(this._text ?? ''));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
360
399
|
customElements.define('alashi-chat', AlashiChat);
|
|
400
|
+
customElements.define('alashi-markdown', AlashiMarkdown);
|
|
361
401
|
})();
|
package/assets/v1/system.css
CHANGED
|
@@ -578,41 +578,39 @@ input[type="checkbox"] {
|
|
|
578
578
|
/* assistant bubbles hold rendered markdown (chat.js), not preformatted text */
|
|
579
579
|
white-space: normal;
|
|
580
580
|
}
|
|
581
|
-
.
|
|
582
|
-
.
|
|
583
|
-
|
|
584
|
-
.
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
581
|
+
/* Rendered-markdown prose. Shared by assistant chat bubbles and the
|
|
582
|
+
* <alashi-markdown> element (or any `.markdown` container) — chat.js renders
|
|
583
|
+
* both with the same renderMarkdown(), so the styling lives in one place too.
|
|
584
|
+
* Headings stay h3..h6 because that is what renderMarkdown emits. */
|
|
585
|
+
alashi-markdown {
|
|
586
|
+
/* unknown element to CSS, same as <alashi-chat> in consumer apps */
|
|
587
|
+
display: block;
|
|
588
|
+
}
|
|
589
|
+
:is(.msg.assistant, alashi-markdown, .markdown) :is(p, ul, ol, pre, h3, h4, h5, h6) {
|
|
589
590
|
margin: 0 0 0.6rem;
|
|
590
591
|
}
|
|
591
|
-
.msg.assistant > :last-child {
|
|
592
|
+
:is(.msg.assistant, alashi-markdown, .markdown) > :last-child {
|
|
592
593
|
margin-bottom: 0;
|
|
593
594
|
}
|
|
594
|
-
.msg.assistant ul,
|
|
595
|
-
.msg.assistant ol {
|
|
595
|
+
:is(.msg.assistant, alashi-markdown, .markdown) :is(ul, ol) {
|
|
596
596
|
padding-left: 1.3rem;
|
|
597
597
|
}
|
|
598
|
-
.msg.assistant li {
|
|
598
|
+
:is(.msg.assistant, alashi-markdown, .markdown) li {
|
|
599
599
|
margin: 0.15rem 0;
|
|
600
600
|
}
|
|
601
|
-
.msg.assistant img {
|
|
601
|
+
:is(.msg.assistant, alashi-markdown, .markdown) img {
|
|
602
602
|
max-width: 100%;
|
|
603
603
|
border-radius: var(--radius-sm);
|
|
604
604
|
border: 1px solid var(--border);
|
|
605
605
|
}
|
|
606
|
-
.msg.assistant pre {
|
|
606
|
+
:is(.msg.assistant, alashi-markdown, .markdown) pre {
|
|
607
607
|
background: var(--panel);
|
|
608
608
|
padding: 0.6rem 0.75rem;
|
|
609
609
|
}
|
|
610
|
-
.msg.assistant h3 {
|
|
610
|
+
:is(.msg.assistant, alashi-markdown, .markdown) h3 {
|
|
611
611
|
font-size: 1rem;
|
|
612
612
|
}
|
|
613
|
-
.msg.assistant h4,
|
|
614
|
-
.msg.assistant h5,
|
|
615
|
-
.msg.assistant h6 {
|
|
613
|
+
:is(.msg.assistant, alashi-markdown, .markdown) :is(h4, h5, h6) {
|
|
616
614
|
font-size: 0.92rem;
|
|
617
615
|
}
|
|
618
616
|
.msg.assistant.pending {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alashi/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "alashi host: install, manage, and run Claude Code-powered apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
"templates"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@alashi/apps-sdk": "^0.
|
|
32
|
+
"@alashi/apps-sdk": "^0.6.0",
|
|
33
33
|
"@hono/node-server": "^2.1.1",
|
|
34
34
|
"commander": "^14.0.0",
|
|
35
35
|
"croner": "^10.0.1",
|
|
36
36
|
"hono": "^4.13.2",
|
|
37
37
|
"semver": "^7.8.5",
|
|
38
38
|
"zod": "^4.4.3",
|
|
39
|
-
"@alashi/provider-claude": "^0.2.
|
|
39
|
+
"@alashi/provider-claude": "^0.2.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^26.2.0",
|
|
@@ -29,13 +29,22 @@ Beside the manifest: `routes` (fetch-style `(Request, ctx) => Response`) and
|
|
|
29
29
|
`jobHandlers` (`{ [name]: (ctx) => Promise<void> }`).
|
|
30
30
|
|
|
31
31
|
Known gap: `onInstall`/`onUpdate` exist in the SDK but the host never calls
|
|
32
|
-
them —
|
|
32
|
+
them — so nothing runs at install time and the app owns its own schema. Schema
|
|
33
|
+
lives in ordered `.sql` files in `migrations/` (`001-init.sql`,
|
|
34
|
+
`002-add-....sql`), applied by `runMigrations(db, join(import.meta.dirname,
|
|
35
|
+
'migrations'))` every time the DB is opened. It is forward-only and tracked in
|
|
36
|
+
`PRAGMA user_version`: a schema change is **a new numbered file**, never an
|
|
37
|
+
edit to one that has already been applied, and there are no down-migrations.
|
|
38
|
+
Statements that cannot run inside a transaction — notably
|
|
39
|
+
`PRAGMA journal_mode = WAL` — must not live in a migration file; set those on
|
|
40
|
+
the connection before calling `runMigrations`.
|
|
33
41
|
|
|
34
42
|
## Runtime surface (`ctx`)
|
|
35
43
|
|
|
36
44
|
- `ctx.dataDir` — the **only** writable directory. SQLite DB and files go
|
|
37
45
|
here (`node:sqlite` `DatabaseSync`). Treat the repo itself as read-only at
|
|
38
|
-
runtime.
|
|
46
|
+
runtime — the DB lives in `ctx.dataDir`, but the `migrations/` files that
|
|
47
|
+
shape it are code and are read from the app repo (`import.meta.dirname`).
|
|
39
48
|
- `ctx.config` — merged config (readonly).
|
|
40
49
|
- `ctx.state` — small key/value store; `ctx.logger` — log here, not console.
|
|
41
50
|
- `ctx.ai.createSession()` — an agent session, pre-scoped by the host
|
|
@@ -64,6 +73,10 @@ them — create DB schema lazily (`CREATE TABLE IF NOT EXISTS` on first open).
|
|
|
64
73
|
with `openConversation`/`adoptSession`). Methods: `adoptSession(sessionId, title?)`,
|
|
65
74
|
`openConversation(id)`, `newChat()`. Bubbling events: `alashi-chat:turn`
|
|
66
75
|
(`{ok, costUsd, error, conversationId}`), `alashi-chat:conversation`.
|
|
76
|
+
The same script also registers `<alashi-markdown>` — the host's markdown
|
|
77
|
+
renderer as a standalone display primitive, for prose outside a conversation
|
|
78
|
+
(`node.text = card.description` re-renders on assignment; `text="**hi**"` is
|
|
79
|
+
honoured on connect for static markup). No fetching, no conversation logic.
|
|
67
80
|
Never vendor or fork system UI; breaking changes ship as `/assets/v2/`.
|
|
68
81
|
|
|
69
82
|
## App routes
|
|
@@ -75,7 +88,8 @@ them — create DB schema lazily (`CREATE TABLE IF NOT EXISTS` on first open).
|
|
|
75
88
|
|
|
76
89
|
- Never `innerHTML` external or model-derived data in the UI — build DOM via
|
|
77
90
|
the `el()` helper / `textContent`. Markdown rendering belongs to
|
|
78
|
-
`<alashi-chat>`,
|
|
91
|
+
`<alashi-chat>` / `<alashi-markdown>`, the sanctioned exception because the
|
|
92
|
+
host's renderer is escape-first. Never bundle a markdown library.
|
|
79
93
|
- Validate at boundaries (webhooks, user input): JSON only, cap body sizes,
|
|
80
94
|
4xx on garbage. Model output is a boundary too — parse defensively.
|
|
81
95
|
- Secrets come from declared `config` keys (`secret: true`), referenced as
|
|
@@ -18,6 +18,7 @@ your changes are live.
|
|
|
18
18
|
| File | What it is |
|
|
19
19
|
|---|---|
|
|
20
20
|
| `index.mjs` | The app: manifest, API routes, job handlers |
|
|
21
|
+
| `migrations/` | Ordered `.sql` files — the DB schema, applied at DB open |
|
|
21
22
|
| `ui/index.html` | The web UI, served at `/app/__APP_NAME__/` |
|
|
22
23
|
| `AGENTS.md` | The platform contract, written for coding agents working in this repo |
|
|
23
24
|
|
|
@@ -27,7 +28,12 @@ your `routes` handler, so you can also server-render pages.
|
|
|
27
28
|
## What the app gets
|
|
28
29
|
|
|
29
30
|
- **`ctx.dataDir`** — the only writable directory; put your SQLite DB and
|
|
30
|
-
files here (the template
|
|
31
|
+
files here (the template opens `app.db` there on first request and applies
|
|
32
|
+
`migrations/` to it)
|
|
33
|
+
- **`migrations/`** — the schema, as ordered `.sql` files applied by
|
|
34
|
+
`runMigrations` at DB open: forward-only, tracked in `PRAGMA user_version`.
|
|
35
|
+
To change the schema, add the next numbered file (`002-...sql`) — never edit
|
|
36
|
+
one that has already run
|
|
31
37
|
- **`ctx.state`** — small key/value store (`state.json`)
|
|
32
38
|
- **`ctx.config`** — `configDefaults` merged with the user's per-app config
|
|
33
39
|
- **`ctx.ai`** — AI sessions through the host; the Chat page uses the host's
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { DatabaseSync } from 'node:sqlite';
|
|
3
|
-
import { defineApp } from '@alashi/apps-sdk';
|
|
3
|
+
import { defineApp, runMigrations } from '@alashi/apps-sdk';
|
|
4
4
|
|
|
5
5
|
export default defineApp({
|
|
6
6
|
name: '__APP_NAME__',
|
|
7
7
|
version: '0.1.0',
|
|
8
8
|
description: 'An alashi app',
|
|
9
|
-
sdkVersion: '^0.
|
|
9
|
+
sdkVersion: '^0.6.0',
|
|
10
10
|
ui: 'ui',
|
|
11
11
|
configDefaults: {
|
|
12
12
|
greeting: 'What should we work on?',
|
|
@@ -27,11 +27,12 @@ export default defineApp({
|
|
|
27
27
|
routes: (ctx) => {
|
|
28
28
|
// The app's own SQLite database, living in its data dir.
|
|
29
29
|
const db = new DatabaseSync(join(ctx.dataDir, 'app.db'));
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
// Schema comes from the ordered .sql files in this repo's migrations/
|
|
31
|
+
// (not ctx.dataDir), applied forward-only at DB open. A schema change
|
|
32
|
+
// ships as a new numbered file — never as an edit to an applied one.
|
|
33
|
+
// Pragmas that cannot run inside a transaction (journal_mode) go here,
|
|
34
|
+
// before the call, not into a migration file.
|
|
35
|
+
runMigrations(db, join(import.meta.dirname, 'migrations'), { logger: ctx.logger });
|
|
35
36
|
|
|
36
37
|
return async (req) => {
|
|
37
38
|
const url = new URL(req.url);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Applied once, then never touched again. Schema changes ship as a new
|
|
2
|
+
-- numbered file (002-..., 003-...), never as an edit to this one.
|
|
3
|
+
CREATE TABLE notes (
|
|
4
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5
|
+
text TEXT NOT NULL,
|
|
6
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
7
|
+
);
|