andesine 0.4.0 → 0.5.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.
@@ -0,0 +1,5 @@
1
+ # The private key from `andesine extensions keys generate` (a JSON Web Key on one line)
2
+ ANDESINE_EXTENSION_KEY=
3
+ # API URLs of the Andesine instances that the backend works with, separated by commas
4
+ ANDESINE_INSTANCES=https://api.andesine.app
5
+ PORT=3000
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vrite, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,33 @@
1
+ # Andesine extension
2
+
3
+ An Andesine extension with a Solid frontend and a Node backend.
4
+
5
+ - `andesine.config.ts` — the manifest: name, permissions, backend, webhooks, configuration, and views.
6
+ - `andesine.keys.json` — the backend's public keys; `andesine extensions keys` maintains it.
7
+ - `src/frontend` — the views, rendered by Andesine in a sandbox; `index.tsx` exports every view that the manifest names.
8
+ - `src/backend` — the backend; it receives webhooks and verifies frontend sessions.
9
+
10
+ ## Samples
11
+
12
+ Each frontend module shows one kind of contribution; remove the ones you do not need from the manifest and `index.tsx`.
13
+
14
+ - `note-view.tsx` — element views: `<Note>` with its editable content in `ContentSlot`, a tone switch that edits the element's props, and a `<NoteTitle>` descendant view.
15
+ - `shout-action.tsx` — a block action for paragraphs and headings that replaces the selection or inserts a paragraph after it.
16
+ - `bookmarks-panel.tsx` — a left panel with a list kept in the extension storage, and a link to a declared URL.
17
+ - `hello-panel.tsx` — a right panel for the open entry: configuration, an API call with the member's granted permissions, and a call to the backend.
18
+ - `about-section.tsx` — a settings section with the granted permissions and host actions (copy, download, open a URL).
19
+ - `src/backend/server.ts` — lifecycle and `entry.created` webhooks, session verification, and the secret `apiToken` field and storage, read with the extension's credentials.
20
+
21
+ ## Commands
22
+
23
+ - `npm run build` — validate the manifest and build the frontend, its CSS, and the artifact metadata into `dist/`.
24
+ - `npm run backend` — start the backend with the variables from `.env` (see `.env.example`).
25
+ - `npm run dev` — run the extension on a local Andesine instance for you, reloading it on each change; Ctrl+C stops it and keeps its state. It needs `andesine auth login` against the local instance, points the extension at the local backend (`--backend`, port 3000 as in `.env.example`), and writes a development key to `.env`.
26
+ - `npx andesine extensions test-event <webhook> <type>` — send a sample event to a webhook of the running development extension.
27
+ - `npx andesine extensions keys generate` — create a key pair; the private key never leaves your machine.
28
+ - `npx andesine extensions keys check` — compare the backend's private key with the manifest keys.
29
+ - `npx andesine extensions keys revoke <kid>` — stop listing a key; publish a new version afterwards.
30
+
31
+ Private keys stay in `.andesine/keys` and `.env`, which Git ignores.
32
+
33
+ Publish a version through a pull request to the extension registry.
@@ -0,0 +1,79 @@
1
+ import { defineBuild, defineExtension } from "@andesine/extensions";
2
+ import * as z from "zod";
3
+ // `andesine extensions keys generate` and `keys revoke` maintain this file.
4
+ import backendKeys from "./andesine.keys.json" with { type: "json" };
5
+
6
+ export default defineExtension({
7
+ // Your registry name, e.g. `acme/publish`.
8
+ name: "example/hello",
9
+ version: "0.1.0",
10
+ displayName: "Hello",
11
+ description:
12
+ "Samples of every extension feature: views, panels, actions, settings, and a backend.",
13
+ icon: "i-lucide:hand",
14
+ apiVersion: 1,
15
+ // The API permissions that members approve; the frontend gets them only where members have them.
16
+ permissions: ["read:entries"],
17
+ // Other URLs that the frontend can request, link to, and open without confirmation.
18
+ requests: ["https://andesine.app/"],
19
+ backend: {
20
+ // Where the backend in src/backend is deployed; it must use HTTPS. `npm run dev` uses the
21
+ // local backend instead (`--backend` in package.json).
22
+ url: "https://hello.example.com",
23
+ keys: backendKeys.keys
24
+ },
25
+ webhooks: {
26
+ lifecycle: { events: ["extension.installed", "extension.uninstalled"], path: "/webhooks" },
27
+ entries: { events: ["entry.created"], path: "/webhooks" }
28
+ },
29
+ // Managers fill it in Settings → Extensions; secret fields reach only the backend.
30
+ configuration: z.object({
31
+ greeting: z.string().min(1).max(100).default("Hello").meta({ title: "Greeting" }),
32
+ apiToken: z
33
+ .string()
34
+ .max(200)
35
+ .optional()
36
+ .meta({ title: "API token", description: "Only the backend reads it", secret: true })
37
+ }),
38
+ // Renders `<Note>` elements; `<NoteTitle>` inside a note uses its own view.
39
+ elementViews: [
40
+ {
41
+ id: "note",
42
+ name: "Note",
43
+ element: "Note",
44
+ entry: "NoteView",
45
+ descendants: [{ element: "NoteTitle", entry: "NoteTitleView" }]
46
+ }
47
+ ],
48
+ // Shown in the block menu of the listed block types.
49
+ blockActions: [
50
+ {
51
+ id: "shout",
52
+ label: "Shout",
53
+ icon: "i-lucide:megaphone",
54
+ blocks: ["paragraph", "heading"],
55
+ entry: "ShoutAction"
56
+ }
57
+ ],
58
+ panels: [
59
+ {
60
+ id: "bookmarks",
61
+ side: "left",
62
+ name: "Bookmarks",
63
+ icon: "i-lucide:bookmark",
64
+ entry: "BookmarksPanel"
65
+ },
66
+ {
67
+ id: "hello",
68
+ side: "right",
69
+ name: "Hello",
70
+ icon: "i-lucide:hand",
71
+ context: "entry",
72
+ entry: "HelloPanel"
73
+ }
74
+ ],
75
+ // Shown in the extension's settings, below the configuration form.
76
+ settingsSections: [{ id: "about", heading: "About", entry: "AboutSection" }]
77
+ });
78
+
79
+ export const build = defineBuild({ frontend: "src/frontend/index.tsx" });
@@ -0,0 +1,4 @@
1
+ {
2
+ "keys": [],
3
+ "revokedKeys": []
4
+ }
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ .andesine
4
+ .env
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "andesine-extension",
3
+ "version": "0.5.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "scripts": {
7
+ "build": "andesine extensions build",
8
+ "dev": "andesine extensions dev --backend http://localhost:3000",
9
+ "backend": "node --env-file-if-exists=.env --import tsx src/backend/server.ts",
10
+ "check": "tsc --noEmit"
11
+ },
12
+ "engines": {
13
+ "node": ">=22.12.0"
14
+ },
15
+ "dependencies": {
16
+ "@andesine/extensions": "0.5.0",
17
+ "@andesine/sdk": "0.5.0",
18
+ "solid-js": "^1.9.14",
19
+ "zod": "4.4.3"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "26.1.2",
23
+ "andesine": "0.5.0",
24
+ "tsx": "^4.23.5",
25
+ "typescript": "6.0.3"
26
+ }
27
+ }
@@ -0,0 +1,85 @@
1
+ import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
2
+ import {
3
+ createExtensionBackend,
4
+ ExtensionNotificationError,
5
+ ExtensionSessionError
6
+ } from "@andesine/sdk/extensions";
7
+
8
+ const extension = createExtensionBackend({
9
+ name: "example/hello",
10
+ // The private key from `andesine extensions keys generate`, as a JSON Web Key.
11
+ privateKey: process.env.ANDESINE_EXTENSION_KEY ?? "",
12
+ // API URLs of the Andesine instances that this backend works with.
13
+ trustedInstances: (process.env.ANDESINE_INSTANCES ?? "https://api.andesine.app").split(",")
14
+ });
15
+
16
+ const readBody = async (request: IncomingMessage): Promise<string> => {
17
+ const chunks: Buffer[] = [];
18
+
19
+ for await (const chunk of request) chunks.push(chunk as Buffer);
20
+
21
+ return Buffer.concat(chunks).toString("utf8");
22
+ };
23
+ const send = (response: ServerResponse, status: number, body?: unknown): void => {
24
+ response.writeHead(status, body === undefined ? {} : { "content-type": "application/json" });
25
+ response.end(body === undefined ? undefined : JSON.stringify(body));
26
+ };
27
+ // The extension frontend calls the backend from the browser, so it needs CORS.
28
+ const allowFrontend = (response: ServerResponse): void => {
29
+ response.setHeader("access-control-allow-origin", "*");
30
+ response.setHeader("access-control-allow-methods", "POST, OPTIONS");
31
+ response.setHeader(
32
+ "access-control-allow-headers",
33
+ "authorization, content-type, andesine-extension, andesine-instance"
34
+ );
35
+ };
36
+ const server = createServer(async (request, response) => {
37
+ allowFrontend(response);
38
+
39
+ try {
40
+ if (request.method === "OPTIONS") {
41
+ send(response, 204);
42
+ } else if (request.method === "POST" && request.url === "/webhooks") {
43
+ // Fetching the delivery with the extension's key confirms the notification.
44
+ const { event, client } = await extension.receive(await readBody(request));
45
+
46
+ if (event.type === "entry.created") {
47
+ // The client acts for the installation, with the extension's permissions.
48
+ const entry = await client.entries.get({ id: event.subject.id });
49
+
50
+ console.log(`Entry created: ${entry.name}`);
51
+ } else {
52
+ console.log(`Received ${event.type}`);
53
+ }
54
+
55
+ send(response, 204);
56
+ } else if (request.method === "POST" && request.url === "/hello") {
57
+ const session = await extension.verifySession(request.headers);
58
+ // Secret fields are readable only here, with the extension's own credentials.
59
+ const { values } = await session.client.extensions.getSelfConfiguration();
60
+ // The frontend's storage entries, e.g. the bookmarks panel.
61
+ const bookmarks = await session.client.extensions
62
+ .getSelfStorageEntry({ key: "bookmarks" })
63
+ .catch(() => null);
64
+
65
+ console.log(`API token ${values.apiToken ? "set" : "not set"}`);
66
+ console.log(`Bookmarks: ${JSON.stringify(bookmarks?.value ?? [])}`);
67
+ send(response, 200, {
68
+ name: session.member.profile.name ?? session.member.profile.email ?? "member"
69
+ });
70
+ } else {
71
+ send(response, 404);
72
+ }
73
+ } catch (error) {
74
+ const isRejected =
75
+ error instanceof ExtensionSessionError || error instanceof ExtensionNotificationError;
76
+
77
+ if (!isRejected) console.error(error);
78
+
79
+ send(response, isRejected ? 401 : 500);
80
+ }
81
+ });
82
+
83
+ server.listen(Number(process.env.PORT ?? 3000), () => {
84
+ console.log(`Backend listening on port ${process.env.PORT ?? 3000}`);
85
+ });
@@ -0,0 +1,47 @@
1
+ import {
2
+ Badge,
3
+ Button,
4
+ copyText,
5
+ downloadFile,
6
+ openURL,
7
+ Setting,
8
+ Stack,
9
+ storage,
10
+ usePermissions
11
+ } from "@andesine/extensions/solid";
12
+ import { For } from "solid-js";
13
+
14
+ /** A settings section: the granted permissions and the host actions. */
15
+ export const AboutSection = () => {
16
+ const permissions = usePermissions();
17
+ const exportStorage = async () => {
18
+ const { data } = await storage.list();
19
+
20
+ await downloadFile("hello-storage.json", JSON.stringify(data, null, 2), "application/json");
21
+ };
22
+
23
+ return (
24
+ <Stack gap="small">
25
+ <Setting label="Permissions" description="What the extension can do for you">
26
+ <Stack direction="row" gap="small" wrap>
27
+ <For each={permissions()}>{(permission) => <Badge>{permission}</Badge>}</For>
28
+ </Stack>
29
+ </Setting>
30
+ <Setting label="Storage" description="Download the extension's stored data">
31
+ <Button variant="outlined" size="small" onClick={exportStorage}>
32
+ Download
33
+ </Button>
34
+ </Setting>
35
+ <Setting label="Share" description="Host actions need a click in the view">
36
+ <Stack direction="row" gap="small">
37
+ <Button variant="text" size="small" onClick={() => copyText("example/hello")}>
38
+ Copy name
39
+ </Button>
40
+ <Button variant="text" size="small" onClick={() => openURL("https://andesine.app/")}>
41
+ Open site
42
+ </Button>
43
+ </Stack>
44
+ </Setting>
45
+ </Stack>
46
+ );
47
+ };
@@ -0,0 +1,67 @@
1
+ import {
2
+ Button,
3
+ Icon,
4
+ IconButton,
5
+ Input,
6
+ Link,
7
+ List,
8
+ Stack,
9
+ storage,
10
+ Text,
11
+ TimeAgo
12
+ } from "@andesine/extensions/solid";
13
+ import { createResource, createSignal, For, Show } from "solid-js";
14
+
15
+ const KEY = "bookmarks";
16
+
17
+ /** The left panel: a workspace list kept in the extension storage, which its backend shares. */
18
+ export const BookmarksPanel = () => {
19
+ const [title, setTitle] = createSignal("");
20
+ const [entry, { mutate }] = createResource(() => {
21
+ return storage.get<string[]>(KEY).catch(() => null);
22
+ });
23
+ const bookmarks = () => entry()?.value ?? [];
24
+ const save = async (next: string[]) => {
25
+ const { updatedAt } = await storage.set(KEY, next);
26
+
27
+ mutate({ key: KEY, value: next, updatedAt });
28
+ };
29
+ const add = () => {
30
+ if (!title().trim()) return;
31
+
32
+ void save([...bookmarks(), title().trim()]);
33
+ setTitle("");
34
+ };
35
+
36
+ return (
37
+ <Stack gap="small">
38
+ <Stack direction="row" gap="small" align="center">
39
+ <Icon name="i-lucide:bookmark" size="small" tone="muted" />
40
+ <Text weight="semibold">Bookmarks</Text>
41
+ </Stack>
42
+ <List
43
+ emptyLabel="No bookmarks yet"
44
+ onRemove={(index) => save(bookmarks().filter((_, item) => item !== index))}
45
+ >
46
+ <For each={bookmarks()}>{(bookmark) => <Text>{bookmark}</Text>}</For>
47
+ </List>
48
+ <Stack direction="row" gap="small">
49
+ <Input value={title()} placeholder="Title" onInput={setTitle} onEnter={add} />
50
+ <IconButton icon="i-lucide:plus" label="Add bookmark" onClick={add} />
51
+ </Stack>
52
+ <Show when={entry()}>
53
+ {(saved) => (
54
+ <Text size="xs" tone="muted">
55
+ Saved <TimeAgo date={saved().updatedAt} />
56
+ </Text>
57
+ )}
58
+ </Show>
59
+ <Stack direction="row" justify="between" align="center">
60
+ <Link href="https://andesine.app/">Andesine</Link>
61
+ <Button variant="text" size="small" onClick={() => save([])}>
62
+ Clear
63
+ </Button>
64
+ </Stack>
65
+ </Stack>
66
+ );
67
+ };
@@ -0,0 +1,61 @@
1
+ import { createClient } from "@andesine/sdk";
2
+ import {
3
+ apiFetch,
4
+ backend,
5
+ Button,
6
+ EXTENSION_API_URL,
7
+ hasPermission,
8
+ Stack,
9
+ Text,
10
+ useConfiguration
11
+ } from "@andesine/extensions/solid";
12
+ import { createResource, createSignal, Show } from "solid-js";
13
+
14
+ interface HelloPanelProps {
15
+ /** Right panels with the `entry` context get the open entry's ID. */
16
+ entryID?: string;
17
+ }
18
+
19
+ // API calls go through Andesine with the member's session, limited to the extension's grant.
20
+ const client = createClient({ baseURL: EXTENSION_API_URL, fetch: apiFetch });
21
+
22
+ /** The right panel: reads the open entry through the API, then asks the backend who you are. */
23
+ export const HelloPanel = (props: HelloPanelProps) => {
24
+ const configuration = useConfiguration<{ greeting: string }>();
25
+ const [name, setName] = createSignal<string | null>(null);
26
+ const [failed, setFailed] = createSignal(false);
27
+ const [entry] = createResource(
28
+ () => (hasPermission("read:entries") ? props.entryID : undefined),
29
+ // A failed request would end the view, so the entry line stays hidden instead.
30
+ (id) => client.entries.get({ id }).catch(() => null)
31
+ );
32
+ const askBackend = async () => {
33
+ try {
34
+ const response = await backend.fetch("/hello", { method: "POST" });
35
+ const result = (await response.json()) as { name: string };
36
+
37
+ setName(result.name);
38
+ } catch {
39
+ setFailed(true);
40
+ }
41
+ };
42
+
43
+ return (
44
+ <Stack gap="small">
45
+ <Text>
46
+ {configuration().greeting ?? "Hello"}, {name() ?? "member"}!
47
+ </Text>
48
+ <Show when={entry()}>
49
+ {(loaded) => (
50
+ <Text size="sm" tone="muted">
51
+ Entry: {loaded().name}
52
+ </Text>
53
+ )}
54
+ </Show>
55
+ <Button onClick={askBackend}>Ask the backend</Button>
56
+ <Show when={failed()}>
57
+ <Text tone="danger">The backend did not answer.</Text>
58
+ </Show>
59
+ </Stack>
60
+ );
61
+ };
@@ -0,0 +1,6 @@
1
+ // Every view entry of the manifest is a named export of this module.
2
+ export { AboutSection } from "./about-section";
3
+ export { BookmarksPanel } from "./bookmarks-panel";
4
+ export { HelloPanel } from "./hello-panel";
5
+ export { NoteTitleView, NoteView } from "./note-view";
6
+ export { ShoutAction } from "./shout-action";
@@ -0,0 +1,54 @@
1
+ import {
2
+ Box,
3
+ ContentSlot,
4
+ Icon,
5
+ Stack,
6
+ Text,
7
+ ToggleGroup,
8
+ useEditor
9
+ } from "@andesine/extensions/solid";
10
+
11
+ interface ElementViewProps {
12
+ /** The element name, e.g. `Note`. */
13
+ element: string;
14
+ /** The element's props from the document. */
15
+ props: { tone?: string };
16
+ }
17
+
18
+ const tones = [
19
+ { value: "info", label: "Info" },
20
+ { value: "warning", label: "Warning" }
21
+ ];
22
+
23
+ /**
24
+ * The view of `<Note>` elements. `ContentSlot` shows the element's editable content, which
25
+ * Andesine keeps; the toggle edits the element's props as the member's own edit.
26
+ */
27
+ export const NoteView = (props: ElementViewProps) => {
28
+ const editor = useEditor();
29
+ const tone = () => props.props.tone ?? "info";
30
+
31
+ return (
32
+ <Box padding="medium" background="soft" border class="rounded-lg">
33
+ <Stack gap="small">
34
+ <Stack direction="row" justify="between" align="center">
35
+ <Icon name={tone() === "warning" ? "i-lucide:triangle-alert" : "i-lucide:info"} />
36
+ <ToggleGroup
37
+ value={tone()}
38
+ options={tones}
39
+ onChange={(value) => editor.setElementProps({ tone: value })}
40
+ />
41
+ </Stack>
42
+ <ContentSlot />
43
+ </Stack>
44
+ </Box>
45
+ );
46
+ };
47
+ /** The view of `<NoteTitle>` elements inside a note, from the note view's descendants. */
48
+ export const NoteTitleView = () => {
49
+ return (
50
+ <Text weight="semibold" class="uppercase tracking-wide">
51
+ <ContentSlot />
52
+ </Text>
53
+ );
54
+ };
@@ -0,0 +1,57 @@
1
+ import { Button, type ContentNode, Stack, Text, useEditor } from "@andesine/extensions/solid";
2
+ import { createSignal, Show } from "solid-js";
3
+
4
+ interface BlockActionProps {
5
+ /** The selected blocks as editor JSON. */
6
+ blocks: ContentNode[];
7
+ }
8
+
9
+ const shout = (node: ContentNode): ContentNode => ({
10
+ ...node,
11
+ ...(node.text !== undefined && { text: node.text.toUpperCase() }),
12
+ ...(node.content && { content: node.content.map(shout) })
13
+ });
14
+ const countWords = (node: ContentNode): number => {
15
+ const own = node.text?.split(/\s+/).filter(Boolean).length ?? 0;
16
+
17
+ return own + (node.content ?? []).reduce((total, child) => total + countWords(child), 0);
18
+ };
19
+
20
+ /** The block action: replaces the selected blocks or inserts a paragraph after them. */
21
+ export const ShoutAction = (props: BlockActionProps) => {
22
+ const editor = useEditor();
23
+ const [error, setError] = createSignal<string | null>(null);
24
+ const words = () => props.blocks.reduce((total, block) => total + countWords(block), 0);
25
+ const run = async (edit: () => Promise<void>) => {
26
+ try {
27
+ await edit();
28
+ await editor.close();
29
+ } catch (failure) {
30
+ setError(failure instanceof Error ? failure.message : "The edit failed");
31
+ }
32
+ };
33
+
34
+ return (
35
+ <Stack gap="small">
36
+ <Text size="sm">{words()} words selected.</Text>
37
+ <Stack direction="row" gap="small">
38
+ <Button color="primary" onClick={() => run(() => editor.replace(props.blocks.map(shout)))}>
39
+ Shout
40
+ </Button>
41
+ <Button
42
+ variant="outlined"
43
+ onClick={() => {
44
+ return run(() => {
45
+ return editor.insertAfter([
46
+ { type: "paragraph", content: [{ type: "text", text: `${words()} words.` }] }
47
+ ]);
48
+ });
49
+ }}
50
+ >
51
+ Count below
52
+ </Button>
53
+ </Stack>
54
+ <Show when={error()}>{(message) => <Text tone="danger">{message()}</Text>}</Show>
55
+ </Stack>
56
+ );
57
+ };
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2022", "DOM"],
7
+ "jsx": "preserve",
8
+ "jsxImportSource": "solid-js",
9
+ "strict": true,
10
+ "noEmit": true,
11
+ "skipLibCheck": true,
12
+ "resolveJsonModule": true,
13
+ "types": ["node"]
14
+ },
15
+ "include": ["andesine.config.ts", "src"]
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "andesine",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Command-line tools for Andesine",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,22 +24,33 @@
24
24
  "node": ">=22.12.0"
25
25
  },
26
26
  "dependencies": {
27
+ "@babel/core": "^7.29.7",
27
28
  "@clack/prompts": "1.7.0",
28
29
  "@commander-js/extra-typings": "15.0.0",
30
+ "@iconify-json/lucide": "^1.2.140",
31
+ "@iconify-json/tabler": "^1.2.41",
32
+ "@iconify/utils": "^3.1.4",
33
+ "@orpc/zod": "^1.14.14",
34
+ "babel-preset-solid": "^1.9.12",
35
+ "base-x": "^5.0.1",
29
36
  "commander": "15.0.0",
30
- "zod": "4.5.4",
31
37
  "open": "11.0.0",
32
38
  "proper-lockfile": "4.1.2",
33
- "@andesine/sdk": "0.4.0"
39
+ "rolldown": "^1.2.2",
40
+ "unocss": "^66.7.5",
41
+ "zod": "4.5.4",
42
+ "@andesine/sdk": "0.5.0"
34
43
  },
35
44
  "devDependencies": {
45
+ "@types/babel__core": "^7.20.5",
36
46
  "@types/node": "^26.1.2",
37
- "publint": "0.3.24",
38
- "rolldown": "^1.2.2",
39
- "typescript": "6.0.3",
40
47
  "@types/proper-lockfile": "4.1.4",
48
+ "prettier": "^3.9.6",
49
+ "publint": "0.3.24",
41
50
  "tsx": "^4.23.5",
42
- "prettier": "^3.9.6"
51
+ "typescript": "6.0.3",
52
+ "@andesine/contracts": "0.0.0",
53
+ "@andesine/uno-preset": "0.0.0"
43
54
  },
44
55
  "repository": {
45
56
  "type": "git",