your-attention 0.1.0 → 0.1.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.
Files changed (97) hide show
  1. package/README.md +2 -0
  2. package/bin/your-attention.js +6 -2
  3. package/dist/src/adapters/files.js +66 -0
  4. package/dist/src/adapters/mdns.js +53 -0
  5. package/dist/src/adapters/mermaid.js +50 -0
  6. package/dist/src/adapters/openui-body.js +40 -0
  7. package/dist/src/adapters/openui-library.js +3 -0
  8. package/dist/src/adapters/sqlite.js +68 -0
  9. package/dist/src/cli.js +814 -0
  10. package/dist/src/client.js +459 -0
  11. package/dist/src/cloudflare/collector.js +53 -0
  12. package/dist/src/cloudflare/inbox-object.js +338 -0
  13. package/dist/src/cloudflare/storage.js +151 -0
  14. package/dist/src/cloudflare/worker.js +189 -0
  15. package/dist/src/core/crypto.js +23 -0
  16. package/dist/src/core/diagrams.js +225 -0
  17. package/dist/src/core/expr.js +125 -0
  18. package/dist/src/core/file-links.js +48 -0
  19. package/dist/src/core/hooks.js +74 -0
  20. package/dist/src/core/http.js +651 -0
  21. package/dist/src/core/inbox.js +2463 -0
  22. package/dist/src/core/pin.js +234 -0
  23. package/dist/src/core/sealed.js +167 -0
  24. package/dist/src/core/session-link.js +13 -0
  25. package/dist/src/core/socket.js +88 -0
  26. package/dist/src/core/storage.js +102 -0
  27. package/dist/src/core/summary.js +149 -0
  28. package/dist/src/core/telemetry.js +221 -0
  29. package/dist/src/core/types.js +470 -0
  30. package/dist/src/core/version.js +2 -0
  31. package/dist/src/core/widgets.js +128 -0
  32. package/dist/src/extensions/triage.js +232 -0
  33. package/dist/src/integrations/claude/ask-hook.js +84 -0
  34. package/dist/src/integrations/claude/channel-flag.js +102 -0
  35. package/dist/src/integrations/claude/channel.js +276 -0
  36. package/dist/src/integrations/claude/session-name.js +32 -0
  37. package/dist/src/integrations/claude/turn-end-hook.js +84 -0
  38. package/dist/src/integrations/herdr/approve.js +71 -0
  39. package/dist/src/integrations/herdr/blocked.js +210 -0
  40. package/dist/src/integrations/herdr/dialog.js +64 -0
  41. package/dist/src/integrations/herdr/focus.js +31 -0
  42. package/dist/src/integrations/herdr/place.js +22 -0
  43. package/dist/src/integrations/images.js +43 -0
  44. package/dist/src/integrations/opencode/install.js +24 -0
  45. package/dist/src/integrations/opencode/plugin.js +157 -0
  46. package/dist/src/integrations/pi/extension.js +166 -0
  47. package/dist/src/integrations/run-tool.js +157 -0
  48. package/dist/src/integrations/secrets.js +63 -0
  49. package/dist/src/integrations/tags.js +27 -0
  50. package/dist/src/integrations/tools.js +147 -0
  51. package/dist/src/node/home.js +20 -0
  52. package/dist/src/node/install.js +225 -0
  53. package/dist/src/node/links.js +249 -0
  54. package/dist/src/node/pin-prompt.js +125 -0
  55. package/dist/src/node/server.js +320 -0
  56. package/dist/src/node/service.js +74 -0
  57. package/dist/src/node/telemetry.js +127 -0
  58. package/dist/src/owner-watch.js +49 -0
  59. package/dist/src/tui/api.js +203 -0
  60. package/dist/src/tui/app.js +586 -0
  61. package/dist/src/tui/body.js +727 -0
  62. package/dist/src/tui/cardview.js +453 -0
  63. package/dist/src/tui/doc.js +157 -0
  64. package/dist/src/tui/edit.js +119 -0
  65. package/dist/src/tui/input.js +192 -0
  66. package/dist/src/tui/md.js +196 -0
  67. package/dist/src/tui/screen.js +225 -0
  68. package/dist/src/tui/terminal.js +130 -0
  69. package/dist/src/tui/text.js +103 -0
  70. package/dist/src/tui/theme.js +105 -0
  71. package/dist/src/tui/view.js +314 -0
  72. package/dist/src/watch.js +104 -0
  73. package/dist/web/age.js +16 -0
  74. package/dist/web/answers.js +485 -0
  75. package/dist/web/app.js +739 -0
  76. package/dist/web/body-faults.js +69 -0
  77. package/dist/web/components.js +923 -0
  78. package/dist/web/copy.js +118 -0
  79. package/dist/web/details.js +59 -0
  80. package/dist/web/env.js +97 -0
  81. package/dist/web/favicon-waiting.svg +1 -0
  82. package/dist/web/favicon.svg +1 -0
  83. package/dist/web/index.html +16 -0
  84. package/dist/web/library-spec.js +33 -0
  85. package/dist/web/markdown.js +142 -0
  86. package/dist/web/paste-images.js +193 -0
  87. package/dist/web/pin-key.js +125 -0
  88. package/dist/web/pin.js +126 -0
  89. package/dist/web/queue.js +97 -0
  90. package/dist/web/sealed.js +13 -0
  91. package/dist/web/search.js +70 -0
  92. package/dist/web/stories/cards.stories.js +144 -0
  93. package/dist/web/stories/page.stories.js +54 -0
  94. package/dist/web/stories/render.js +16 -0
  95. package/dist/web/style.css +417 -0
  96. package/package.json +4 -2
  97. package/src/core/version.ts +1 -1
package/README.md CHANGED
@@ -54,6 +54,8 @@ your-attention --help
54
54
 
55
55
  `npm link` puts `your-attention` on your path. To update later, run `git pull && npm ci` in the same folder.
56
56
 
57
+ The package is also on npm, and `npm install -g your-attention` works. The published tarball contains the same sources compiled into `dist/`, because Node refuses to strip TypeScript types in anything under `node_modules`. A checkout runs the sources directly, with no build step.
58
+
57
59
  ### 4. Start or open your inbox
58
60
 
59
61
  **On your laptop:**
@@ -1,3 +1,7 @@
1
1
  #!/usr/bin/env -S node --disable-warning=ExperimentalWarning
2
- // Node runs the TypeScript sources directly, so there is no build step.
3
- await import('../src/cli.ts');
2
+ // Node runs the TypeScript sources directly, so a checkout has no build step. A package
3
+ // installed from npm is different: Node refuses to strip types under `node_modules`, so the
4
+ // published tarball carries `dist/`, written by `scripts/pack-sources.js` at pack time.
5
+ const compiled = new URL('../dist/src/cli.js', import.meta.url);
6
+ const hasCompiled = await import('node:fs').then((fs) => fs.existsSync(compiled));
7
+ await import(hasCompiled ? compiled.href : new URL('../src/cli.ts', import.meta.url).href);
@@ -0,0 +1,66 @@
1
+ // File bytes on disk for the laptop build: one file per attachment, plus its type.
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+
6
+ export class FileBlobs {
7
+ dir ;
8
+
9
+ constructor(dir ) {
10
+ this.dir = dir;
11
+ }
12
+
13
+ // encodeURIComponent turns ":" into "%3A", which every file system accepts.
14
+ file(key ) {
15
+ return path.join(this.dir, encodeURIComponent(key));
16
+ }
17
+
18
+ async put(key , bytes , type ) {
19
+ await fs.mkdir(this.dir, { recursive: true, mode: 0o700 });
20
+ await fs.writeFile(this.file(key), bytes, { mode: 0o600 });
21
+ await fs.writeFile(`${this.file(key)}.type`, type, { mode: 0o600 });
22
+ }
23
+
24
+ async get(key ) {
25
+ try {
26
+ const [buf, type] = await Promise.all([fs.readFile(this.file(key)), fs.readFile(`${this.file(key)}.type`, 'utf8')]);
27
+ return { bytes: new Uint8Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)) , type };
28
+ } catch {
29
+ return undefined;
30
+ }
31
+ }
32
+
33
+ async delete(key ) {
34
+ await Promise.all([fs.rm(this.file(key), { force: true }), fs.rm(`${this.file(key)}.type`, { force: true })]);
35
+ }
36
+ }
37
+
38
+ /** A file on this machine, by the decoded path of its file: address, for the laptop inbox's file links. A directory, a
39
+ * missing file or anything else that is not a regular file is undefined. With `folder`, so is a file whose real path, after
40
+ * every symlink, is not inside the real path of that folder. The caller reads the bytes after it checks the size. */
41
+ export async function localFile(name , folder ) {
42
+ // A file: address on Windows has a path like /C:/Users/me, with a slash before the drive.
43
+ const native = (p ) => (process.platform === 'win32' ? path.normalize(p.replace(/^\/(?=[A-Za-z]:)/, '')) : p);
44
+ let file = native(name);
45
+ if (!path.isAbsolute(file)) return undefined;
46
+ try {
47
+ if (folder !== undefined) {
48
+ const [real, root] = await Promise.all([fs.realpath(file), fs.realpath(native(folder))]);
49
+ const inside = path.relative(root, real);
50
+ if (!inside || inside.split(path.sep)[0] === '..' || path.isAbsolute(inside)) return undefined;
51
+ // Read the path that was checked, so a symlink swapped in after the check is not followed.
52
+ file = real;
53
+ }
54
+ const stat = await fs.stat(file);
55
+ if (!stat.isFile()) return undefined;
56
+ return {
57
+ size: stat.size,
58
+ async read() {
59
+ const buf = await fs.readFile(file);
60
+ return new Uint8Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)) ;
61
+ },
62
+ };
63
+ } catch {
64
+ return undefined;
65
+ }
66
+ }
@@ -0,0 +1,53 @@
1
+ // Discovery on a LAN with DNS-SD over mDNS, the protocol Bonjour and Avahi use.
2
+ // The announcement says only that an inbox exists. A client still has to pair.
3
+ import { Bonjour } from 'bonjour-service';
4
+ import os from 'node:os';
5
+
6
+ export const SERVICE_TYPE = 'cardinbox';
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+ export function advertise(opts ) {
21
+ const bonjour = new Bonjour({}, (err ) => console.error('[mdns]', err.message));
22
+ bonjour.publish({
23
+ name: opts.name ?? `your-attention on ${os.hostname().replace(/\.local$/, '')}`,
24
+ type: SERVICE_TYPE,
25
+ port: opts.port,
26
+ txt: { v: '1', id: opts.inboxId, pair: '/v1/pair' },
27
+ });
28
+ return {
29
+ stop: () => new Promise((resolve) => bonjour.unpublishAll(() => { bonjour.destroy(); resolve(); })),
30
+ };
31
+ }
32
+
33
+ /** Listen for inboxes for `ms` milliseconds. */
34
+ export function discover(ms ) {
35
+ const bonjour = new Bonjour({}, (err ) => console.error('[mdns]', err.message));
36
+ const found = new Map ();
37
+ const browser = bonjour.find({ type: SERVICE_TYPE }, (s) => {
38
+ found.set(s.name, {
39
+ name: s.name,
40
+ host: s.host,
41
+ addresses: s.addresses ?? [],
42
+ port: s.port,
43
+ txt: Object.fromEntries(Object.entries((s.txt ?? {}) ).map(([k, v]) => [k, String(v)])),
44
+ });
45
+ });
46
+ return new Promise((resolve) => {
47
+ setTimeout(() => {
48
+ browser.stop();
49
+ bonjour.destroy();
50
+ resolve([...found.values()]);
51
+ }, ms);
52
+ });
53
+ }
@@ -0,0 +1,50 @@
1
+ // Checks a Mermaid diagram with mermaid's own parser, the version the page draws with.
2
+ // Both builds use it: the laptop inbox in Node, and the hosted inbox in its Durable Object.
3
+ // It loads mermaid on the first diagram, so an inbox that never sees one never pays for it.
4
+
5
+
6
+ /** Pinned exactly in package.json. web/components.js loads the same version. */
7
+ export const MERMAID_VERSION = '11.17.2';
8
+ /** The settings the page draws with. The theme changes only colors, so the check leaves it out. */
9
+ export const MERMAID_CONFIG = { startOnLoad: false, securityLevel: 'strict' } ;
10
+
11
+
12
+ let loading ;
13
+ // mermaid keeps the diagram it parses in module state, so one parse runs at a time.
14
+ let queue = Promise.resolve();
15
+ // A patch checks the card's other diagrams again, so recent results are kept.
16
+ const recent = new Map ();
17
+ const RECENT = 200;
18
+
19
+ async function load() {
20
+ // Without a DOM, DOMPurify has neither sanitize nor addHook, and mermaid's parser calls both on labels.
21
+ // A DOMPurify that finds no usable DOM returns the text it gets, and these do the same. They change no global.
22
+ const { default: DOMPurify } = await import('dompurify');
23
+ if (!DOMPurify.isSupported) Object.assign(DOMPurify, { sanitize: (dirty ) => dirty, addHook: () => {} });
24
+ const { default: mermaid } = await import('mermaid');
25
+ mermaid.initialize(MERMAID_CONFIG);
26
+ return mermaid;
27
+ }
28
+
29
+ async function parse(source ) {
30
+ const mermaid = await (loading ??= load());
31
+ // The page refuses to draw a diagram over maxTextSize, and the parser does not check it.
32
+ const max = mermaid.mermaidAPI.getConfig().maxTextSize ?? 50_000;
33
+ if (source.length > max) return `A diagram takes at most ${max} characters, and this one has ${source.length}.`;
34
+ try {
35
+ await mermaid.parse(source);
36
+ return undefined;
37
+ } catch (e) {
38
+ return String((e )?.message ?? e).trim();
39
+ }
40
+ }
41
+
42
+ export const checkMermaid = Object.assign(async (source ) => {
43
+ if (recent.has(source)) return recent.get(source);
44
+ const next = queue.then(() => parse(source));
45
+ queue = next.catch(() => undefined);
46
+ const result = await next;
47
+ recent.set(source, result);
48
+ if (recent.size > RECENT) recent.delete(recent.keys().next().value );
49
+ return result;
50
+ }, { version: MERMAID_VERSION });
@@ -0,0 +1,40 @@
1
+ // A card body is OpenUI Lang, and the page draws what the OpenUI parser reads of it. The laptop inbox and the posting client
2
+ // use this check. The parser drops a statement it cannot read, and everything after a string that never closes, without an
3
+ // error. So the inbox reads each body with the same parser and the page's component list, and refuses one the page would
4
+ // draw only in part. web/body-faults.js reads the parser's result, so the page finds the same faults on an inbox that does not check.
5
+ import { createParser, } from '@openuidev/lang-core';
6
+ import { bodyFaults, fallbackSources, } from '../../web/body-faults.js';
7
+ import { LIBRARY } from './openui-library.js';
8
+
9
+ let parser ;
10
+ const read = (body ) => (parser ??= createParser(LIBRARY , 'root')).parse(body) ;
11
+
12
+ /** What keeps the page from drawing all of a body, one sentence each. Empty when the page draws it whole. */
13
+ export function bodyErrors(body ) {
14
+ return bodyFaults(body, read(body));
15
+ }
16
+
17
+ /** What the page draws for a body it can't draw whole, which an inbox stored before it checked bodies. */
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+ const fallbacks = new Map ();
26
+
27
+ /** The fallback for a body the page can't draw whole, or undefined for one it draws whole. */
28
+ export function fallbackBody(body ) {
29
+ if (fallbacks.has(body)) return fallbacks.get(body);
30
+ const parsed = read(body);
31
+ const faults = bodyFaults(body, parsed);
32
+ let out ;
33
+ if (faults.length) {
34
+ const { partial, whole } = fallbackSources(body, parsed);
35
+ out = { body: partial !== undefined && !bodyErrors(partial).length ? partial : whole, faults };
36
+ }
37
+ if (fallbacks.size > 500) fallbacks.clear();
38
+ fallbacks.set(body, out);
39
+ return out;
40
+ }
@@ -0,0 +1,3 @@
1
+ // Written by scripts/openui-library.ts from the page library. Do not edit it by hand.
2
+ // The components a card body can use, as the OpenUI parser reads them.
3
+ export const LIBRARY = {"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"Card":{"$ref":"#/$defs/Card"},"CardHeader":{"$ref":"#/$defs/CardHeader"},"TextContent":{"$ref":"#/$defs/TextContent"},"MarkDownRenderer":{"$ref":"#/$defs/MarkDownRenderer"},"Callout":{"$ref":"#/$defs/Callout"},"TextCallout":{"$ref":"#/$defs/TextCallout"},"Image":{"$ref":"#/$defs/Image"},"ImageBlock":{"$ref":"#/$defs/ImageBlock"},"ImageGallery":{"$ref":"#/$defs/ImageGallery"},"CodeBlock":{"$ref":"#/$defs/CodeBlock"},"Separator":{"$ref":"#/$defs/Separator"},"Table":{"$ref":"#/$defs/Table"},"Col":{"$ref":"#/$defs/Col"},"BarChart":{"$ref":"#/$defs/BarChart"},"LineChart":{"$ref":"#/$defs/LineChart"},"AreaChart":{"$ref":"#/$defs/AreaChart"},"RadarChart":{"$ref":"#/$defs/RadarChart"},"HorizontalBarChart":{"$ref":"#/$defs/HorizontalBarChart"},"Series":{"$ref":"#/$defs/Series"},"PieChart":{"$ref":"#/$defs/PieChart"},"RadialChart":{"$ref":"#/$defs/RadialChart"},"SingleStackedBarChart":{"$ref":"#/$defs/SingleStackedBarChart"},"Slice":{"$ref":"#/$defs/Slice"},"ScatterChart":{"$ref":"#/$defs/ScatterChart"},"ScatterSeries":{"$ref":"#/$defs/ScatterSeries"},"Point":{"$ref":"#/$defs/Point"},"Form":{"$ref":"#/$defs/Form"},"FormControl":{"$ref":"#/$defs/FormControl"},"Label":{"$ref":"#/$defs/Label"},"Input":{"$ref":"#/$defs/Input"},"TextArea":{"$ref":"#/$defs/TextArea"},"Select":{"$ref":"#/$defs/Select"},"SelectItem":{"$ref":"#/$defs/SelectItem"},"DatePicker":{"$ref":"#/$defs/DatePicker"},"Slider":{"$ref":"#/$defs/Slider"},"CheckBoxGroup":{"$ref":"#/$defs/CheckBoxGroup"},"CheckBoxItem":{"$ref":"#/$defs/CheckBoxItem"},"RadioGroup":{"$ref":"#/$defs/RadioGroup"},"RadioItem":{"$ref":"#/$defs/RadioItem"},"SwitchGroup":{"$ref":"#/$defs/SwitchGroup"},"SwitchItem":{"$ref":"#/$defs/SwitchItem"},"Button":{"$ref":"#/$defs/Button"},"Buttons":{"$ref":"#/$defs/Buttons"},"ListBlock":{"$ref":"#/$defs/ListBlock"},"ListItem":{"$ref":"#/$defs/ListItem"},"FollowUpBlock":{"$ref":"#/$defs/FollowUpBlock"},"FollowUpItem":{"$ref":"#/$defs/FollowUpItem"},"SectionBlock":{"$ref":"#/$defs/SectionBlock"},"SectionItem":{"$ref":"#/$defs/SectionItem"},"Tabs":{"$ref":"#/$defs/Tabs"},"TabItem":{"$ref":"#/$defs/TabItem"},"Accordion":{"$ref":"#/$defs/Accordion"},"AccordionItem":{"$ref":"#/$defs/AccordionItem"},"Steps":{"$ref":"#/$defs/Steps"},"StepsItem":{"$ref":"#/$defs/StepsItem"},"Carousel":{"$ref":"#/$defs/Carousel"},"TagBlock":{"$ref":"#/$defs/TagBlock"},"Tag":{"$ref":"#/$defs/Tag"},"EntityList":{"$ref":"#/$defs/EntityList"},"InlineHeader":{"$ref":"#/$defs/InlineHeader"},"Icon":{"$ref":"#/$defs/Icon"},"IconButton":{"$ref":"#/$defs/IconButton"},"EditableTable":{"$ref":"#/$defs/EditableTable"},"ChipItem":{"$ref":"#/$defs/ChipItem"},"Chips":{"$ref":"#/$defs/Chips"},"OptionCard":{"$ref":"#/$defs/OptionCard"},"OptionCards":{"$ref":"#/$defs/OptionCards"},"Text":{"$ref":"#/$defs/Text"},"BoldText":{"$ref":"#/$defs/BoldText"},"IconText":{"$ref":"#/$defs/IconText"},"ImageText":{"$ref":"#/$defs/ImageText"},"ImageTextLarge":{"$ref":"#/$defs/ImageTextLarge"},"MetricIndicatorInline":{"$ref":"#/$defs/MetricIndicatorInline"},"MetricIndicatorWithStrikethrough":{"$ref":"#/$defs/MetricIndicatorWithStrikethrough"},"SnippetCardItem":{"$ref":"#/$defs/SnippetCardItem"},"SnippetCardBlock":{"$ref":"#/$defs/SnippetCardBlock"},"OverviewCardItem":{"$ref":"#/$defs/OverviewCardItem"},"OverviewCardBlock":{"$ref":"#/$defs/OverviewCardBlock"},"ContextCardItem":{"$ref":"#/$defs/ContextCardItem"},"ContextCardBlock":{"$ref":"#/$defs/ContextCardBlock"},"CompositeCardItem":{"$ref":"#/$defs/CompositeCardItem"},"CompositeCardBlock":{"$ref":"#/$defs/CompositeCardBlock"},"VisualCardItem":{"$ref":"#/$defs/VisualCardItem"},"VisualCardBlock":{"$ref":"#/$defs/VisualCardBlock"},"CheckWithText":{"$ref":"#/$defs/CheckWithText"},"Mermaid":{"$ref":"#/$defs/Mermaid"},"SecretInput":{"$ref":"#/$defs/SecretInput"}},"required":["Card","CardHeader","TextContent","MarkDownRenderer","Callout","TextCallout","Image","ImageBlock","ImageGallery","CodeBlock","Separator","Table","Col","BarChart","LineChart","AreaChart","RadarChart","HorizontalBarChart","Series","PieChart","RadialChart","SingleStackedBarChart","Slice","ScatterChart","ScatterSeries","Point","Form","FormControl","Label","Input","TextArea","Select","SelectItem","DatePicker","Slider","CheckBoxGroup","CheckBoxItem","RadioGroup","RadioItem","SwitchGroup","SwitchItem","Button","Buttons","ListBlock","ListItem","FollowUpBlock","FollowUpItem","SectionBlock","SectionItem","Tabs","TabItem","Accordion","AccordionItem","Steps","StepsItem","Carousel","TagBlock","Tag","EntityList","InlineHeader","Icon","IconButton","EditableTable","ChipItem","Chips","OptionCard","OptionCards","Text","BoldText","IconText","ImageText","ImageTextLarge","MetricIndicatorInline","MetricIndicatorWithStrikethrough","SnippetCardItem","SnippetCardBlock","OverviewCardItem","OverviewCardBlock","ContextCardItem","ContextCardBlock","CompositeCardItem","CompositeCardBlock","VisualCardItem","VisualCardBlock","CheckWithText","Mermaid","SecretInput"],"additionalProperties":false,"$defs":{"Card":{"type":"object","properties":{"children":{"type":"array","items":{"anyOf":[{"$ref":"#/$defs/TextContent"},{"$ref":"#/$defs/MarkDownRenderer"},{"$ref":"#/$defs/CardHeader"},{"$ref":"#/$defs/Callout"},{"$ref":"#/$defs/TextCallout"},{"$ref":"#/$defs/CodeBlock"},{"$ref":"#/$defs/Image"},{"$ref":"#/$defs/ImageBlock"},{"$ref":"#/$defs/ImageGallery"},{"$ref":"#/$defs/Separator"},{"$ref":"#/$defs/HorizontalBarChart"},{"$ref":"#/$defs/RadarChart"},{"$ref":"#/$defs/PieChart"},{"$ref":"#/$defs/RadialChart"},{"$ref":"#/$defs/SingleStackedBarChart"},{"$ref":"#/$defs/ScatterChart"},{"$ref":"#/$defs/AreaChart"},{"$ref":"#/$defs/BarChart"},{"$ref":"#/$defs/LineChart"},{"$ref":"#/$defs/Table"},{"$ref":"#/$defs/TagBlock"},{"$ref":"#/$defs/Form"},{"$ref":"#/$defs/Buttons"},{"$ref":"#/$defs/IconButton"},{"$ref":"#/$defs/Steps"},{"$ref":"#/$defs/InlineHeader"},{"$ref":"#/$defs/EntityList"},{"$ref":"#/$defs/EditableTable"},{"$ref":"#/$defs/SnippetCardBlock"},{"$ref":"#/$defs/OverviewCardBlock"},{"$ref":"#/$defs/ContextCardBlock"},{"$ref":"#/$defs/CompositeCardBlock"},{"$ref":"#/$defs/VisualCardBlock"},{"$ref":"#/$defs/ListBlock"},{"$ref":"#/$defs/FollowUpBlock"},{"$ref":"#/$defs/SectionBlock"},{"$ref":"#/$defs/Tabs"},{"$ref":"#/$defs/Carousel"},{"$ref":"#/$defs/CheckWithText"},{"$ref":"#/$defs/Mermaid"}]}},"sources":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"sourceName":{"type":"string"}},"required":["title","sourceName"],"additionalProperties":false}}},"required":["children"],"additionalProperties":false,"description":"Vertical container for all content in a chat response. Children stack top to bottom automatically. Optional sources ([{ title, sourceName, url }]) render as a Sources strip at the bottom and back inline [n] citations in TextContent."},"TextContent":{"type":"object","properties":{"text":{"type":"string"},"size":{"type":"string","enum":["small","default","large","small-heavy","large-heavy"]}},"required":["text"],"additionalProperties":false,"description":"Text block. Supports markdown. Optional size: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\"."},"MarkDownRenderer":{"type":"object","properties":{"textMarkdown":{"type":"string"},"variant":{"type":"string","enum":["clear","card","sunk"]}},"required":["textMarkdown"],"additionalProperties":false,"description":"Renders markdown text with optional container variant"},"CardHeader":{"type":"object","properties":{"title":{"type":"string"},"subtitle":{"type":"string"}},"additionalProperties":false,"description":"Header with optional title and subtitle"},"Callout":{"type":"object","properties":{"variant":{"type":"string","enum":["info","warning","error","success","neutral"]},"title":{"type":"string"},"description":{"type":"string"},"visible":{"type":"boolean"}},"required":["variant","title","description"],"additionalProperties":false,"description":"Callout banner. Optional visible is a reactive $boolean — auto-dismisses after 3s by setting $visible to false."},"TextCallout":{"type":"object","properties":{"variant":{"type":"string","enum":["neutral","info","warning","success","danger"]},"title":{"type":"string"},"description":{"type":"string"}},"additionalProperties":false,"description":"Text callout with variant, title, and description"},"CodeBlock":{"type":"object","properties":{"language":{"type":"string"},"codeString":{"type":"string"}},"required":["language","codeString"],"additionalProperties":false,"description":"Syntax-highlighted code block"},"Image":{"type":"object","properties":{"alt":{"type":"string"},"src":{"type":"string"}},"required":["alt"],"additionalProperties":false,"description":"Image with alt text and optional URL"},"ImageBlock":{"type":"object","properties":{"src":{"type":"string"},"alt":{"type":"string"}},"required":["src"],"additionalProperties":false,"description":"Image block with loading state"},"ImageGallery":{"type":"object","properties":{"images":{"type":"array","items":{"type":"object","properties":{"src":{"type":"string"},"alt":{"type":"string"},"details":{"type":"string"}},"required":["src"],"additionalProperties":false}}},"required":["images"],"additionalProperties":false,"description":"Gallery grid of images with modal preview"},"Separator":{"type":"object","properties":{"orientation":{"type":"string","enum":["horizontal","vertical"]},"decorative":{"type":"boolean"}},"additionalProperties":false,"description":"Visual divider between content sections"},"HorizontalBarChart":{"type":"object","properties":{"labels":{"type":"array","items":{"type":"string"}},"series":{"type":"array","items":{"$ref":"#/$defs/Series"}},"variant":{"type":"string","enum":["grouped","stacked"]},"xLabel":{"type":"string"},"yLabel":{"type":"string"}},"required":["labels","series"],"additionalProperties":false,"description":"Horizontal bars; prefer when category labels are long or for ranked lists"},"Series":{"type":"object","properties":{"category":{"type":"string"},"values":{"type":"array","items":{"type":"number"}}},"required":["category","values"],"additionalProperties":false,"description":"One data series"},"RadarChart":{"type":"object","properties":{"labels":{"type":"array","items":{"type":"string"}},"series":{"type":"array","items":{"$ref":"#/$defs/Series"}}},"required":["labels","series"],"additionalProperties":false,"description":"Spider/web chart; use for comparing multiple variables across one or more entities"},"PieChart":{"type":"object","properties":{"labels":{"type":"array","items":{"type":"string"}},"values":{"type":"array","items":{"type":"number"}},"variant":{"type":"string","enum":["pie","donut"]},"appearance":{"type":"string","enum":["circular","semiCircular"]}},"required":["labels","values"],"additionalProperties":false,"description":"Circular slices; use plucked arrays: PieChart(data.categories, data.values)"},"RadialChart":{"type":"object","properties":{"labels":{"type":"array","items":{"type":"string"}},"values":{"type":"array","items":{"type":"number"}}},"required":["labels","values"],"additionalProperties":false,"description":"Radial bars; use plucked arrays: RadialChart(data.categories, data.values)"},"SingleStackedBarChart":{"type":"object","properties":{"labels":{"type":"array","items":{"type":"string"}},"values":{"type":"array","items":{"type":"number"}}},"required":["labels","values"],"additionalProperties":false,"description":"Single horizontal stacked bar; use plucked arrays: SingleStackedBarChart(data.categories, data.values)"},"ScatterChart":{"type":"object","properties":{"datasets":{"type":"array","items":{"$ref":"#/$defs/ScatterSeries"}},"xLabel":{"type":"string"},"yLabel":{"type":"string"}},"required":["datasets"],"additionalProperties":false,"description":"X/Y scatter plot; use for correlations, distributions, and clustering"},"ScatterSeries":{"type":"object","properties":{"name":{"type":"string"},"points":{"type":"array","items":{"$ref":"#/$defs/Point"}}},"required":["name","points"],"additionalProperties":false,"description":"Named dataset"},"Point":{"type":"object","properties":{"x":{"type":"number"},"y":{"type":"number"},"z":{"type":"number"}},"required":["x","y"],"additionalProperties":false,"description":"Data point with numeric coordinates"},"AreaChart":{"type":"object","properties":{"labels":{"type":"array","items":{"type":"string"}},"series":{"type":"array","items":{"$ref":"#/$defs/Series"}},"variant":{"type":"string","enum":["linear","natural","step"]},"xLabel":{"type":"string"},"yLabel":{"type":"string"},"height":{"type":"number"}},"required":["labels","series"],"additionalProperties":false,"description":"Filled area under lines; use for cumulative totals or volume trends over time"},"BarChart":{"type":"object","properties":{"labels":{"type":"array","items":{"type":"string"}},"series":{"type":"array","items":{"$ref":"#/$defs/Series"}},"variant":{"type":"string","enum":["grouped","stacked"]},"xLabel":{"type":"string"},"yLabel":{"type":"string"},"height":{"type":"number"}},"required":["labels","series"],"additionalProperties":false,"description":"Vertical bars; use for comparing values across categories with one or more series"},"LineChart":{"type":"object","properties":{"labels":{"type":"array","items":{"type":"string"}},"series":{"type":"array","items":{"$ref":"#/$defs/Series"}},"variant":{"type":"string","enum":["linear","natural","step"]},"xLabel":{"type":"string"},"yLabel":{"type":"string"},"height":{"type":"number"}},"required":["labels","series"],"additionalProperties":false,"description":"Lines over categories; use for trends and continuous data over time"},"Table":{"type":"object","properties":{"columns":{"type":"array","items":{"$ref":"#/$defs/Col"}}},"required":["columns"],"additionalProperties":false,"description":"Data table — column-oriented. Each Col holds its own data array."},"Col":{"type":"object","properties":{"label":{"type":"string"},"data":{},"type":{"type":"string","enum":["string","number","action"]}},"required":["label","data"],"additionalProperties":false,"description":"Column definition — holds label + data array"},"TagBlock":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}},"size":{"type":"string","enum":["sm","md","lg"]}},"required":["tags"],"additionalProperties":false,"description":"tags is an array of strings; optional size sm | md | lg"},"Form":{"type":"object","properties":{"name":{"type":"string"},"buttons":{"$ref":"#/$defs/Buttons"},"fields":{"default":[],"type":"array","items":{"$ref":"#/$defs/FormControl"}}},"required":["name","buttons","fields"],"additionalProperties":false,"description":"Form container with fields and explicit action buttons"},"Buttons":{"type":"object","properties":{"buttons":{"type":"array","items":{"$ref":"#/$defs/Button"}},"direction":{"type":"string","enum":["row","column"]}},"required":["buttons"],"additionalProperties":false,"description":"Group of Button components. direction: \"row\" (default) | \"column\"."},"Button":{"type":"object","properties":{"label":{"type":"string"},"action":{"$ref":"#/$defs/ActionExpression"},"variant":{"type":"string","enum":["primary","secondary","tertiary"]},"type":{"type":"string","enum":["normal","destructive"]},"size":{"type":"string","enum":["extra-small","small","medium","large"]}},"required":["label"],"additionalProperties":false,"description":"Clickable button"},"ActionExpression":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"open_url"},"url":{"type":"string"}},"required":["type","url"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"continue_conversation"},"context":{"type":"string"}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string"},"params":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["type"],"additionalProperties":false}]},"FormControl":{"type":"object","properties":{"label":{"type":"string"},"input":{"anyOf":[{"$ref":"#/$defs/Input"},{"$ref":"#/$defs/TextArea"},{"$ref":"#/$defs/Select"},{"$ref":"#/$defs/DatePicker"},{"$ref":"#/$defs/Slider"},{"$ref":"#/$defs/CheckBoxGroup"},{"$ref":"#/$defs/RadioGroup"},{"$ref":"#/$defs/Chips"},{"$ref":"#/$defs/OptionCards"},{"$ref":"#/$defs/CheckWithText"},{"$ref":"#/$defs/SecretInput"}]},"hint":{"type":"string"}},"required":["label","input"],"additionalProperties":false,"description":"Field with label, input component, and optional hint text"},"Input":{"type":"object","properties":{"name":{"type":"string"},"placeholder":{"type":"string"},"type":{"type":"string","enum":["text","email","password","number","url"]},"rules":{"type":"object","properties":{"required":{"type":"boolean"},"email":{"type":"boolean"},"url":{"type":"boolean"},"numeric":{"type":"boolean"},"min":{"type":"number"},"max":{"type":"number"},"minLength":{"type":"number"},"maxLength":{"type":"number"},"pattern":{"type":"string"}},"additionalProperties":false},"value":{"type":"string"}},"required":["name"],"additionalProperties":false},"TextArea":{"type":"object","properties":{"name":{"type":"string"},"placeholder":{"type":"string"},"rows":{"type":"number"},"rules":{"type":"object","properties":{"required":{"type":"boolean"},"email":{"type":"boolean"},"url":{"type":"boolean"},"numeric":{"type":"boolean"},"min":{"type":"number"},"max":{"type":"number"},"minLength":{"type":"number"},"maxLength":{"type":"number"},"pattern":{"type":"string"}},"additionalProperties":false},"value":{"type":"string"}},"required":["name"],"additionalProperties":false},"Select":{"type":"object","properties":{"name":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/$defs/SelectItem"}},"placeholder":{"type":"string"},"rules":{"type":"object","properties":{"required":{"type":"boolean"},"email":{"type":"boolean"},"url":{"type":"boolean"},"numeric":{"type":"boolean"},"min":{"type":"number"},"max":{"type":"number"},"minLength":{"type":"number"},"maxLength":{"type":"number"},"pattern":{"type":"string"}},"additionalProperties":false},"value":{"type":"string"},"size":{"type":"string","enum":["small","medium","large"]}},"required":["name","items"],"additionalProperties":false},"SelectItem":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}},"required":["value","label"],"additionalProperties":false,"description":"Option for Select"},"DatePicker":{"type":"object","properties":{"name":{"type":"string"},"mode":{"type":"string","enum":["single","range"]},"rules":{"type":"object","properties":{"required":{"type":"boolean"},"email":{"type":"boolean"},"url":{"type":"boolean"},"numeric":{"type":"boolean"},"min":{"type":"number"},"max":{"type":"number"},"minLength":{"type":"number"},"maxLength":{"type":"number"},"pattern":{"type":"string"}},"additionalProperties":false},"value":{}},"required":["name"],"additionalProperties":false},"Slider":{"type":"object","properties":{"name":{"type":"string"},"variant":{"type":"string","enum":["continuous","discrete"]},"min":{"type":"number"},"max":{"type":"number"},"step":{"type":"number"},"defaultValue":{"type":"array","items":{"type":"number"}},"label":{"type":"string"},"rules":{"type":"object","properties":{"required":{"type":"boolean"},"email":{"type":"boolean"},"url":{"type":"boolean"},"numeric":{"type":"boolean"},"min":{"type":"number"},"max":{"type":"number"},"minLength":{"type":"number"},"maxLength":{"type":"number"},"pattern":{"type":"string"}},"additionalProperties":false},"value":{"type":"array","items":{"type":"number"}}},"required":["name","variant","min","max"],"additionalProperties":false,"description":"Numeric slider input; supports continuous and discrete (stepped) variants"},"CheckBoxGroup":{"type":"object","properties":{"name":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/$defs/CheckBoxItem"}},"rules":{"type":"object","properties":{"required":{"type":"boolean"},"email":{"type":"boolean"},"url":{"type":"boolean"},"numeric":{"type":"boolean"},"min":{"type":"number"},"max":{"type":"number"},"minLength":{"type":"number"},"maxLength":{"type":"number"},"pattern":{"type":"string"}},"additionalProperties":false},"value":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["name","items"],"additionalProperties":false},"CheckBoxItem":{"type":"object","properties":{"label":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"},"defaultChecked":{"type":"boolean"}},"required":["label","description","name"],"additionalProperties":false},"RadioGroup":{"type":"object","properties":{"name":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/$defs/RadioItem"}},"defaultValue":{"type":"string"},"rules":{"type":"object","properties":{"required":{"type":"boolean"},"email":{"type":"boolean"},"url":{"type":"boolean"},"numeric":{"type":"boolean"},"min":{"type":"number"},"max":{"type":"number"},"minLength":{"type":"number"},"maxLength":{"type":"number"},"pattern":{"type":"string"}},"additionalProperties":false},"value":{"type":"string"}},"required":["name","items"],"additionalProperties":false},"RadioItem":{"type":"object","properties":{"label":{"type":"string"},"description":{"type":"string"},"value":{"type":"string"}},"required":["label","description","value"],"additionalProperties":false},"Chips":{"type":"object","properties":{"name":{"type":"string"},"type":{"default":"multiple","type":"string","enum":["single","multiple"]},"items":{"default":[],"type":"array","items":{"$ref":"#/$defs/ChipItem"}},"rules":{"type":"object","properties":{"required":{"type":"boolean"},"email":{"type":"boolean"},"url":{"type":"boolean"},"numeric":{"type":"boolean"},"min":{"type":"number"},"max":{"type":"number"},"minLength":{"type":"number"},"maxLength":{"type":"number"},"pattern":{"type":"string"}},"additionalProperties":false},"defaultValue":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}},"required":["name","type","items"],"additionalProperties":false,"description":"A form field of compact selectable chips for choosing one or many short options; the selection is stored under `name`."},"ChipItem":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"},"icon":{"$ref":"#/$defs/Icon"},"disabled":{"type":"boolean"}},"required":["value","label"],"additionalProperties":false,"description":"A single selectable chip inside a Chips group, with a value, label and optional icon."},"Icon":{"type":"object","properties":{"name":{"type":"string"},"category":{"type":"string"}},"required":["name"],"additionalProperties":false,"description":"A lucide icon by kebab-case name (e.g. 'circle-check'). Optional category picks a topical fallback when the name doesn't resolve."},"OptionCards":{"type":"object","properties":{"name":{"type":"string"},"type":{"default":"single","type":"string","enum":["single","multiple"]},"items":{"default":[],"type":"array","items":{"$ref":"#/$defs/OptionCard"}},"rules":{"type":"object","properties":{"required":{"type":"boolean"},"email":{"type":"boolean"},"url":{"type":"boolean"},"numeric":{"type":"boolean"},"min":{"type":"number"},"max":{"type":"number"},"minLength":{"type":"number"},"maxLength":{"type":"number"},"pattern":{"type":"string"}},"additionalProperties":false},"defaultValue":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}},"required":["name","type","items"],"additionalProperties":false,"description":"A form field of selectable cards (title, optional subtitle, optional icon or image) laid out in a responsive grid for choosing one or many options; the selection is stored under `name`."},"OptionCard":{"type":"object","properties":{"value":{"type":"string"},"title":{"type":"string"},"subtitle":{"type":"string"},"topContent":{"anyOf":[{"$ref":"#/$defs/Icon"},{"$ref":"#/$defs/Image"}]},"disabled":{"type":"boolean"}},"required":["value","title"],"additionalProperties":false,"description":"A single selectable card inside an OptionCards group, with a value, title, optional subtitle and an optional Icon or Image on top."},"CheckWithText":{"type":"object","properties":{"label":{"type":"string"},"check":{"anyOf":[{"$ref":"#/$defs/Input"},{"$ref":"#/$defs/TextArea"},{"$ref":"#/$defs/Select"},{"$ref":"#/$defs/DatePicker"},{"$ref":"#/$defs/Slider"},{"$ref":"#/$defs/CheckBoxGroup"},{"$ref":"#/$defs/RadioGroup"},{"$ref":"#/$defs/Chips"},{"$ref":"#/$defs/OptionCards"}]},"content":{"type":"array","items":{"anyOf":[{"$ref":"#/$defs/TextContent"},{"$ref":"#/$defs/MarkDownRenderer"},{"$ref":"#/$defs/CardHeader"},{"$ref":"#/$defs/Callout"},{"$ref":"#/$defs/TextCallout"},{"$ref":"#/$defs/CodeBlock"},{"$ref":"#/$defs/Image"},{"$ref":"#/$defs/ImageBlock"},{"$ref":"#/$defs/ImageGallery"},{"$ref":"#/$defs/Separator"},{"$ref":"#/$defs/HorizontalBarChart"},{"$ref":"#/$defs/RadarChart"},{"$ref":"#/$defs/PieChart"},{"$ref":"#/$defs/RadialChart"},{"$ref":"#/$defs/SingleStackedBarChart"},{"$ref":"#/$defs/ScatterChart"},{"$ref":"#/$defs/AreaChart"},{"$ref":"#/$defs/BarChart"},{"$ref":"#/$defs/LineChart"},{"$ref":"#/$defs/Table"},{"$ref":"#/$defs/TagBlock"},{"$ref":"#/$defs/Form"},{"$ref":"#/$defs/Buttons"},{"$ref":"#/$defs/IconButton"},{"$ref":"#/$defs/Steps"},{"$ref":"#/$defs/InlineHeader"},{"$ref":"#/$defs/EntityList"},{"$ref":"#/$defs/EditableTable"},{"$ref":"#/$defs/SnippetCardBlock"},{"$ref":"#/$defs/OverviewCardBlock"},{"$ref":"#/$defs/ContextCardBlock"},{"$ref":"#/$defs/CompositeCardBlock"},{"$ref":"#/$defs/VisualCardBlock"},{"$ref":"#/$defs/ListBlock"},{"$ref":"#/$defs/FollowUpBlock"}]}}},"required":["label","check","content"],"additionalProperties":false,"description":"A check box with a label that shows editable content when clicked."},"IconButton":{"type":"object","properties":{"name":{"type":"string"},"icon":{"$ref":"#/$defs/Icon"},"action":{"$ref":"#/$defs/ActionExpression"},"variant":{"type":"string","enum":["primary","secondary","tertiary"]},"size":{"type":"string","enum":["extra-small","small","medium","large"]},"shape":{"type":"string","enum":["square","circle"]}},"required":["name","icon"],"additionalProperties":false,"description":"Icon-only button. name is the accessible label and the action label; icon is an Icon; action fires on click."},"Steps":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/$defs/StepsItem"}}},"required":["items"],"additionalProperties":false,"description":"Step-by-step guide"},"StepsItem":{"type":"object","properties":{"title":{"type":"string"},"details":{"type":"string"}},"required":["title","details"],"additionalProperties":false,"description":"title and details text for one step"},"InlineHeader":{"type":"object","properties":{"heading":{"type":"string"},"description":{"type":"string"}},"required":["heading"],"additionalProperties":false,"description":"Compact section heading with an optional one-line description, for use inside cards."},"EntityList":{"type":"object","properties":{"rows":{"default":[],"type":"array","items":{"type":"object","properties":{"left":{"type":"string"},"right":{"type":"string"},"rightVariant":{"default":"text","type":"string","enum":["text","number"]}},"required":["left","right","rightVariant"],"additionalProperties":false}},"size":{"default":"default","type":"string","enum":["small","default"]},"header":{"type":"object","properties":{"left":{"type":"string"},"right":{"type":"string"},"rightVariant":{"default":"text","type":"string","enum":["text","number"]}},"required":["left","right","rightVariant"],"additionalProperties":false},"footer":{"type":"object","properties":{"left":{"type":"string"},"right":{"type":"string"},"rightVariant":{"default":"text","type":"string","enum":["text","number"]}},"required":["left","right","rightVariant"],"additionalProperties":false}},"required":["rows","size"],"additionalProperties":false,"description":"Two-column key/value rows (left label, right value). size 'default' supports optional header and footer rows; rightVariant 'number' uses tabular numbers."},"EditableTable":{"type":"object","properties":{"name":{"default":"","type":"string"},"columns":{"default":[],"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["text","number","date-single","select","url"]},"key":{"default":"default","type":"string"},"header":{"default":"","type":"string"},"width":{"type":"number"},"options":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}},"required":["value","label"],"additionalProperties":false}}},"required":["type","key","header"],"additionalProperties":false}},"data":{"default":[],"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"values":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"required":["id","values"],"additionalProperties":false}}},"required":["name","columns","data"],"additionalProperties":false,"description":"Spreadsheet-like table whose cells the user can edit inline (text, number, url, date, select columns); edits are saved back as a form field"},"SnippetCardBlock":{"type":"object","properties":{"items":{"minItems":2,"type":"array","items":{"$ref":"#/$defs/SnippetCardItem"}},"layout":{"default":"grid","type":"string","enum":["grid"]},"responsive":{"default":true,"type":"boolean"},"action":{"$ref":"#/$defs/ActionExpression"},"gap":{"anyOf":[{"type":"number"},{"type":"string"}]}},"required":["items","layout","responsive"],"additionalProperties":false,"description":"A responsive grid of compact label/value cards (2 per row) for showing several short facts side by side; optionally clickable with a shared action."},"SnippetCardItem":{"type":"object","properties":{"id":{"type":"string"},"lhs":{"anyOf":[{"$ref":"#/$defs/IconText"},{"$ref":"#/$defs/ImageText"}]},"rhs":{"anyOf":[{"$ref":"#/$defs/Text"},{"$ref":"#/$defs/BoldText"}]}},"required":["lhs"],"additionalProperties":false,"description":"One row-style snippet card: a label on the left (IconText or ImageText) and an optional value on the right (Text or BoldText)."},"IconText":{"type":"object","properties":{"icon":{"$ref":"#/$defs/Icon"},"iconVariant":{"default":"neutral","type":"string","enum":["neutral","info","success","warning","danger","inverted","filled","soft"]},"iconSize":{"default":"m","type":"string","enum":["xs","s","m","l","xl","sm","md","lg"]},"title":{"type":"string"},"subtitle":{"type":"string"},"bold":{"default":false,"type":"boolean"},"layout":{"default":"horizontal","type":"string","enum":["horizontal","vertical"]}},"required":["icon","iconVariant","iconSize","title","bold","layout"],"additionalProperties":false,"description":"An icon badge with a title and optional subtitle, laid out horizontally or vertically. iconVariant sets the badge color."},"ImageText":{"type":"object","properties":{"src":{"type":"string"},"alt":{"type":"string"},"title":{"type":"string"},"subtitle":{"type":"string"},"bold":{"default":false,"type":"boolean"},"layout":{"default":"horizontal","type":"string","enum":["horizontal","vertical"]},"imageSize":{"type":"number"}},"required":["src","title","bold","layout"],"additionalProperties":false,"description":"A small square image (thumbnail/avatar) with a title and optional subtitle. src must be a real image URL."},"Text":{"type":"object","properties":{"variant":{"default":"text","type":"string","enum":["text","number"]},"value":{"type":"string"},"subtext":{"type":"string"},"subtextVariant":{"default":"text","type":"string","enum":["text","number","metric"]},"size":{"default":"sm","type":"string","enum":["xs","sm","md","lg"]}},"required":["variant","value","subtextVariant","size"],"additionalProperties":false,"description":"Plain text line with optional subtext. variant 'number' uses tabular number styling; subtextVariant 'metric' colors a leading +/- subtext green/red."},"BoldText":{"type":"object","properties":{"variant":{"default":"text","type":"string","enum":["text","number"]},"value":{"type":"string"},"subtext":{"type":"string"},"subtextVariant":{"default":"text","type":"string","enum":["text","number","metric"]},"size":{"default":"sm","type":"string","enum":["xs","sm","md","lg"]}},"required":["variant","value","subtextVariant","size"],"additionalProperties":false,"description":"Emphasized (bold) text line with optional subtext. variant 'number' uses tabular number styling; subtextVariant 'metric' colors a leading +/- subtext green/red."},"OverviewCardBlock":{"type":"object","properties":{"items":{"minItems":2,"type":"array","items":{"$ref":"#/$defs/OverviewCardItem"}},"layout":{"default":"grid","type":"string","enum":["grid","carousel"]},"responsive":{"default":true,"type":"boolean"},"action":{"$ref":"#/$defs/ActionExpression"},"gap":{"anyOf":[{"type":"number"},{"type":"string"}]}},"required":["items","layout","responsive"],"additionalProperties":false,"description":"A grid or horizontal carousel of compact overview cards, each with a heading (icon/image/text) on top and an inline metric below; optionally clickable with a shared action."},"OverviewCardItem":{"type":"object","properties":{"id":{"type":"string"},"top":{"anyOf":[{"$ref":"#/$defs/IconText"},{"$ref":"#/$defs/ImageText"},{"$ref":"#/$defs/Text"}]},"bottom":{"$ref":"#/$defs/MetricIndicatorInline"}},"required":["top"],"additionalProperties":false,"description":"One overview card: a heading slot at the top (IconText, ImageText or Text) and an optional MetricIndicatorInline at the bottom."},"MetricIndicatorInline":{"type":"object","properties":{"value":{"type":"string"},"subtext":{"type":"string"},"trend":{"type":"object","properties":{"direction":{"type":"string","enum":["up","down"]},"value":{"type":"number"}},"required":["direction","value"],"additionalProperties":false}},"required":["value"],"additionalProperties":false,"description":"Headline metric value with an optional +/- percentage trend and subtext, all on one line."},"ContextCardBlock":{"type":"object","properties":{"items":{"minItems":2,"type":"array","items":{"$ref":"#/$defs/ContextCardItem"}},"layout":{"default":"grid","type":"string","enum":["grid","carousel"]},"responsive":{"default":true,"type":"boolean"},"action":{"$ref":"#/$defs/ActionExpression"},"gap":{"anyOf":[{"type":"number"},{"type":"string"}]}},"required":["items","layout","responsive"],"additionalProperties":false,"description":"A grid or carousel of compact tinted context cards (title or tag plus a short bold body); an optional action makes every card clickable."},"ContextCardItem":{"type":"object","properties":{"id":{"type":"string"},"title":{"anyOf":[{"type":"string"},{"$ref":"#/$defs/Tag"}]},"body":{"type":"string"},"bgColor":{"type":"string","enum":["gray"]},"bgImageSrc":{"type":"string"},"bgImageAlt":{"type":"string"}},"required":["title"],"additionalProperties":false,"description":"A single card inside a ContextCardBlock: a title (plain string or Tag), an optional markdown body, and an optional gray tint or background image."},"Tag":{"type":"object","properties":{"text":{"type":"string"},"icon":{"$ref":"#/$defs/Icon"},"size":{"type":"string","enum":["sm","md","lg"]},"variant":{"type":"string","enum":["neutral","info","success","warning","danger"]}},"required":["text"],"additionalProperties":false,"description":"Styled tag/badge with optional Icon and variant"},"CompositeCardBlock":{"type":"object","properties":{"items":{"minItems":2,"type":"array","items":{"$ref":"#/$defs/CompositeCardItem"}},"layout":{"default":"grid","type":"string","enum":["grid","carousel"]},"responsive":{"default":true,"type":"boolean"},"action":{"$ref":"#/$defs/ActionExpression"},"gap":{"anyOf":[{"type":"number"},{"type":"string"}]}},"required":["items","layout","responsive"],"additionalProperties":false,"description":"A two-per-row grid or carousel of rich cards, each with an optional header, stacked body content (text, metrics, charts, lists, tags) and a price/button footer; an optional action makes every card clickable."},"CompositeCardItem":{"type":"object","properties":{"id":{"type":"string"},"header":{"anyOf":[{"$ref":"#/$defs/IconText"},{"$ref":"#/$defs/ImageText"},{"$ref":"#/$defs/ImageTextLarge"},{"$ref":"#/$defs/Text"},{"$ref":"#/$defs/Image"}]},"body":{"default":[],"type":"array","items":{"anyOf":[{"$ref":"#/$defs/Text"},{"$ref":"#/$defs/BoldText"},{"$ref":"#/$defs/MetricIndicatorInline"},{"$ref":"#/$defs/IconText"},{"$ref":"#/$defs/Image"},{"$ref":"#/$defs/AreaChart"},{"$ref":"#/$defs/BarChart"},{"$ref":"#/$defs/LineChart"},{"$ref":"#/$defs/ListBlock"},{"$ref":"#/$defs/TagBlock"},{"$ref":"#/$defs/EntityList"}]}},"footer":{"type":"object","properties":{"price":{"anyOf":[{"$ref":"#/$defs/BoldText"},{"$ref":"#/$defs/MetricIndicatorWithStrikethrough"}]},"button":{"$ref":"#/$defs/Button"}},"additionalProperties":false}},"required":["body"],"additionalProperties":false,"description":"A single card inside a CompositeCardBlock: an optional header (icon/image/text), a stack of body elements (text, metrics, charts, lists, tags), and an optional price/button footer."},"ImageTextLarge":{"type":"object","properties":{"src":{"type":"string"},"alt":{"type":"string"},"title":{"type":"string"},"subtitle":{"type":"string"},"bold":{"default":false,"type":"boolean"}},"required":["src","title","bold"],"additionalProperties":false,"description":"A full-width banner image above a bold title and optional subtitle. src must be a real image URL."},"ListBlock":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/$defs/ListItem"}},"variant":{"type":"string","enum":["number","image"]},"size":{"type":"string","enum":["default","small"]}},"required":["items"],"additionalProperties":false,"description":"A list of items with number or image indicators. Each item can optionally have an action. size small renders a compact list."},"ListItem":{"type":"object","properties":{"title":{"type":"string"},"subtitle":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"},"alt":{"type":"string"}},"required":["src","alt"],"additionalProperties":false},"actionLabel":{"type":"string"},"action":{"$ref":"#/$defs/ActionExpression"}},"required":["title"],"additionalProperties":false,"description":"Item in a ListBlock — displays a title with an optional subtitle and image. When action is provided, the item becomes clickable."},"MetricIndicatorWithStrikethrough":{"type":"object","properties":{"value":{"type":"string"},"subtext":{"type":"string"},"previousValue":{"type":"string"},"trend":{"type":"object","properties":{"direction":{"type":"string","enum":["up","down"]},"value":{"type":"number"}},"required":["direction","value"],"additionalProperties":false}},"required":["value"],"additionalProperties":false,"description":"Headline metric value with an optional struck-through previousValue, a +/- percentage trend, and subtext below."},"VisualCardBlock":{"type":"object","properties":{"items":{"minItems":2,"type":"array","items":{"$ref":"#/$defs/VisualCardItem"}},"layout":{"default":"grid","type":"string","enum":["grid","carousel"]},"responsive":{"default":true,"type":"boolean"},"action":{"$ref":"#/$defs/ActionExpression"},"gap":{"anyOf":[{"type":"number"},{"type":"string"}]}},"required":["items","layout","responsive"],"additionalProperties":false,"description":"A grid or carousel of photo-first cards: a full-bleed background image with a tag on top and a bold text panel at the bottom; an optional action makes every card clickable."},"VisualCardItem":{"type":"object","properties":{"body":{"$ref":"#/$defs/BoldText"},"id":{"type":"string"},"bgImageSrc":{"type":"string"},"tag":{"$ref":"#/$defs/Tag"},"bgImageAlt":{"type":"string"}},"required":["body"],"additionalProperties":false,"description":"A single photo-first card inside a VisualCardBlock: a BoldText body panel, an optional Tag, and a background image (bgImageSrc must be a real URL; bgImageAlt is its alt text)."},"FollowUpBlock":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/$defs/FollowUpItem"}}},"required":["items"],"additionalProperties":false,"description":"List of clickable follow-up suggestions placed at the end of a response"},"FollowUpItem":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"],"additionalProperties":false,"description":"Clickable follow-up suggestion — when clicked, sends text as user message"},"SecretInput":{"type":"object","properties":{"name":{"type":"string"},"publicKey":{"type":"string"}},"required":["name","publicKey"],"additionalProperties":false,"description":"A secret field: the page seals the value with the public key, a JSON Web Key as text."},"SectionBlock":{"type":"object","properties":{"sections":{"type":"array","items":{"$ref":"#/$defs/SectionItem"}},"isFoldable":{"type":"boolean"}},"required":["sections"],"additionalProperties":false,"description":"Collapsible accordion sections. Auto-opens sections as they stream in. Use SectionItem for each section."},"SectionItem":{"type":"object","properties":{"value":{"type":"string"},"trigger":{"type":"string"},"content":{"type":"array","items":{"anyOf":[{"$ref":"#/$defs/TextContent"},{"$ref":"#/$defs/MarkDownRenderer"},{"$ref":"#/$defs/CardHeader"},{"$ref":"#/$defs/Callout"},{"$ref":"#/$defs/TextCallout"},{"$ref":"#/$defs/CodeBlock"},{"$ref":"#/$defs/Image"},{"$ref":"#/$defs/ImageBlock"},{"$ref":"#/$defs/ImageGallery"},{"$ref":"#/$defs/Separator"},{"$ref":"#/$defs/HorizontalBarChart"},{"$ref":"#/$defs/RadarChart"},{"$ref":"#/$defs/PieChart"},{"$ref":"#/$defs/RadialChart"},{"$ref":"#/$defs/SingleStackedBarChart"},{"$ref":"#/$defs/ScatterChart"},{"$ref":"#/$defs/AreaChart"},{"$ref":"#/$defs/BarChart"},{"$ref":"#/$defs/LineChart"},{"$ref":"#/$defs/Table"},{"$ref":"#/$defs/TagBlock"},{"$ref":"#/$defs/Form"},{"$ref":"#/$defs/Buttons"},{"$ref":"#/$defs/IconButton"},{"$ref":"#/$defs/Steps"},{"$ref":"#/$defs/InlineHeader"},{"$ref":"#/$defs/EntityList"},{"$ref":"#/$defs/EditableTable"},{"$ref":"#/$defs/SnippetCardBlock"},{"$ref":"#/$defs/OverviewCardBlock"},{"$ref":"#/$defs/ContextCardBlock"},{"$ref":"#/$defs/CompositeCardBlock"},{"$ref":"#/$defs/VisualCardBlock"},{"$ref":"#/$defs/ListBlock"},{"$ref":"#/$defs/FollowUpBlock"},{"$ref":"#/$defs/Tabs"},{"$ref":"#/$defs/Accordion"},{"$ref":"#/$defs/Mermaid"}]}}},"required":["value","trigger","content"],"additionalProperties":false,"description":"Section with a label and collapsible content — used inside SectionBlock"},"Tabs":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/$defs/TabItem"}}},"required":["items"],"additionalProperties":false,"description":"Tabbed container"},"TabItem":{"type":"object","properties":{"value":{"type":"string"},"trigger":{"type":"string"},"content":{"type":"array","items":{"anyOf":[{"$ref":"#/$defs/TextContent"},{"$ref":"#/$defs/MarkDownRenderer"},{"$ref":"#/$defs/CardHeader"},{"$ref":"#/$defs/Callout"},{"$ref":"#/$defs/TextCallout"},{"$ref":"#/$defs/CodeBlock"},{"$ref":"#/$defs/Image"},{"$ref":"#/$defs/ImageBlock"},{"$ref":"#/$defs/ImageGallery"},{"$ref":"#/$defs/Separator"},{"$ref":"#/$defs/HorizontalBarChart"},{"$ref":"#/$defs/RadarChart"},{"$ref":"#/$defs/PieChart"},{"$ref":"#/$defs/RadialChart"},{"$ref":"#/$defs/SingleStackedBarChart"},{"$ref":"#/$defs/ScatterChart"},{"$ref":"#/$defs/AreaChart"},{"$ref":"#/$defs/BarChart"},{"$ref":"#/$defs/LineChart"},{"$ref":"#/$defs/Table"},{"$ref":"#/$defs/TagBlock"},{"$ref":"#/$defs/Form"},{"$ref":"#/$defs/Buttons"},{"$ref":"#/$defs/IconButton"},{"$ref":"#/$defs/Steps"},{"$ref":"#/$defs/InlineHeader"},{"$ref":"#/$defs/EntityList"},{"$ref":"#/$defs/EditableTable"},{"$ref":"#/$defs/SnippetCardBlock"},{"$ref":"#/$defs/OverviewCardBlock"},{"$ref":"#/$defs/ContextCardBlock"},{"$ref":"#/$defs/CompositeCardBlock"},{"$ref":"#/$defs/VisualCardBlock"},{"$ref":"#/$defs/ListBlock"},{"$ref":"#/$defs/FollowUpBlock"},{"$ref":"#/$defs/Accordion"},{"$ref":"#/$defs/Mermaid"}]}}},"required":["value","trigger","content"],"additionalProperties":false,"description":"value is unique id, trigger is tab label, content is array of components"},"Accordion":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/$defs/AccordionItem"}}},"required":["items"],"additionalProperties":false,"description":"Collapsible sections"},"AccordionItem":{"type":"object","properties":{"value":{"type":"string"},"trigger":{"type":"string"},"content":{"type":"array","items":{"anyOf":[{"$ref":"#/$defs/TextContent"},{"$ref":"#/$defs/MarkDownRenderer"},{"$ref":"#/$defs/CardHeader"},{"$ref":"#/$defs/Callout"},{"$ref":"#/$defs/TextCallout"},{"$ref":"#/$defs/CodeBlock"},{"$ref":"#/$defs/Image"},{"$ref":"#/$defs/ImageBlock"},{"$ref":"#/$defs/ImageGallery"},{"$ref":"#/$defs/Separator"},{"$ref":"#/$defs/HorizontalBarChart"},{"$ref":"#/$defs/RadarChart"},{"$ref":"#/$defs/PieChart"},{"$ref":"#/$defs/RadialChart"},{"$ref":"#/$defs/SingleStackedBarChart"},{"$ref":"#/$defs/ScatterChart"},{"$ref":"#/$defs/AreaChart"},{"$ref":"#/$defs/BarChart"},{"$ref":"#/$defs/LineChart"},{"$ref":"#/$defs/Table"},{"$ref":"#/$defs/TagBlock"},{"$ref":"#/$defs/Form"},{"$ref":"#/$defs/Buttons"},{"$ref":"#/$defs/IconButton"},{"$ref":"#/$defs/Steps"},{"$ref":"#/$defs/InlineHeader"},{"$ref":"#/$defs/EntityList"},{"$ref":"#/$defs/EditableTable"},{"$ref":"#/$defs/SnippetCardBlock"},{"$ref":"#/$defs/OverviewCardBlock"},{"$ref":"#/$defs/ContextCardBlock"},{"$ref":"#/$defs/CompositeCardBlock"},{"$ref":"#/$defs/VisualCardBlock"},{"$ref":"#/$defs/ListBlock"},{"$ref":"#/$defs/FollowUpBlock"},{"$ref":"#/$defs/Mermaid"}]}}},"required":["value","trigger","content"],"additionalProperties":false,"description":"value is unique id, trigger is section title"},"Mermaid":{"type":"object","properties":{"source":{"type":"string"}},"required":["source"],"additionalProperties":false,"description":"A diagram drawn from Mermaid source."},"Carousel":{"type":"object","properties":{"children":{"type":"array","items":{"type":"array","items":{"anyOf":[{"$ref":"#/$defs/TextContent"},{"$ref":"#/$defs/MarkDownRenderer"},{"$ref":"#/$defs/CardHeader"},{"$ref":"#/$defs/Callout"},{"$ref":"#/$defs/TextCallout"},{"$ref":"#/$defs/CodeBlock"},{"$ref":"#/$defs/Image"},{"$ref":"#/$defs/ImageBlock"},{"$ref":"#/$defs/ImageGallery"},{"$ref":"#/$defs/Separator"},{"$ref":"#/$defs/HorizontalBarChart"},{"$ref":"#/$defs/RadarChart"},{"$ref":"#/$defs/PieChart"},{"$ref":"#/$defs/RadialChart"},{"$ref":"#/$defs/SingleStackedBarChart"},{"$ref":"#/$defs/ScatterChart"},{"$ref":"#/$defs/AreaChart"},{"$ref":"#/$defs/BarChart"},{"$ref":"#/$defs/LineChart"},{"$ref":"#/$defs/Table"},{"$ref":"#/$defs/TagBlock"},{"$ref":"#/$defs/Form"},{"$ref":"#/$defs/Buttons"},{"$ref":"#/$defs/IconButton"},{"$ref":"#/$defs/Steps"},{"$ref":"#/$defs/InlineHeader"},{"$ref":"#/$defs/EntityList"},{"$ref":"#/$defs/EditableTable"},{"$ref":"#/$defs/SnippetCardBlock"},{"$ref":"#/$defs/OverviewCardBlock"},{"$ref":"#/$defs/ContextCardBlock"},{"$ref":"#/$defs/CompositeCardBlock"},{"$ref":"#/$defs/VisualCardBlock"},{"$ref":"#/$defs/ListBlock"},{"$ref":"#/$defs/FollowUpBlock"},{"$ref":"#/$defs/Mermaid"}]}}},"variant":{"type":"string","enum":["card","sunk"]}},"required":["children"],"additionalProperties":false,"description":"Horizontal scrollable carousel"},"Slice":{"type":"object","properties":{"category":{"type":"string"},"value":{"type":"number"}},"required":["category","value"],"additionalProperties":false,"description":"One slice with label and numeric value"},"Label":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"],"additionalProperties":false,"description":"Text label"},"SwitchGroup":{"type":"object","properties":{"name":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/$defs/SwitchItem"}},"variant":{"type":"string","enum":["clear","card","sunk"]},"value":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["name","items"],"additionalProperties":false,"description":"Group of switch toggles"},"SwitchItem":{"type":"object","properties":{"label":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"},"defaultChecked":{"type":"boolean"}},"required":["name"],"additionalProperties":false,"description":"Individual switch toggle"}}};
@@ -0,0 +1,68 @@
1
+ // Storage in one SQLite file, with full-text search over cards and answers.
2
+ import { DatabaseSync } from 'node:sqlite';
3
+
4
+
5
+ export class SqliteStorage {
6
+ db ;
7
+
8
+ constructor(file ) {
9
+ this.db = new DatabaseSync(file);
10
+ this.db.exec(`
11
+ PRAGMA journal_mode = WAL;
12
+ PRAGMA synchronous = NORMAL;
13
+ CREATE TABLE IF NOT EXISTS docs (
14
+ collection TEXT NOT NULL,
15
+ id TEXT NOT NULL,
16
+ value TEXT NOT NULL,
17
+ PRIMARY KEY (collection, id)
18
+ );
19
+ CREATE INDEX IF NOT EXISTS docs_order ON docs (collection);
20
+ CREATE VIRTUAL TABLE IF NOT EXISTS search USING fts5(card UNINDEXED, text);
21
+ `);
22
+ }
23
+
24
+ get (collection , id ) {
25
+ const row = this.db.prepare('SELECT value FROM docs WHERE collection = ? AND id = ?').get(collection, id) ;
26
+ return row ? (JSON.parse(row.value) ) : undefined;
27
+ }
28
+
29
+ put (collection , id , value ) {
30
+ this.db.prepare('INSERT INTO docs (collection, id, value) VALUES (?, ?, ?) ON CONFLICT (collection, id) DO UPDATE SET value = excluded.value')
31
+ .run(collection, id, JSON.stringify(value));
32
+ }
33
+
34
+ delete(collection , id ) {
35
+ this.db.prepare('DELETE FROM docs WHERE collection = ? AND id = ?').run(collection, id);
36
+ }
37
+
38
+ list (collection ) {
39
+ const rows = this.db.prepare('SELECT value FROM docs WHERE collection = ? ORDER BY rowid').all(collection) ;
40
+ return rows.map((r) => JSON.parse(r.value) );
41
+ }
42
+
43
+ first (collection , limit ) {
44
+ const rows = this.db.prepare('SELECT value FROM docs WHERE collection = ? ORDER BY rowid LIMIT ?').all(collection, limit) ;
45
+ return rows.map((r) => JSON.parse(r.value) );
46
+ }
47
+
48
+ count(collection ) {
49
+ return Number((this.db.prepare('SELECT COUNT(*) AS n FROM docs WHERE collection = ?').get(collection) )?.n ?? 0);
50
+ }
51
+
52
+ index(card , text ) {
53
+ this.db.prepare('DELETE FROM search WHERE card = ?').run(card);
54
+ if (text) this.db.prepare('INSERT INTO search (card, text) VALUES (?, ?)').run(card, text);
55
+ }
56
+
57
+ search(query , limit ) {
58
+ // Each word becomes a quoted prefix term, so user input never reaches FTS5 syntax.
59
+ const terms = query.split(/\s+/).map((w) => w.replace(/"/g, '')).filter(Boolean).map((w) => `"${w}"*`);
60
+ if (!terms.length) return [];
61
+ const rows = this.db.prepare('SELECT card, rank FROM search WHERE search MATCH ? ORDER BY rank LIMIT ?').all(terms.join(' '), limit) ;
62
+ return rows.map((r) => ({ card: r.card, rank: r.rank }));
63
+ }
64
+
65
+ close() {
66
+ this.db.close();
67
+ }
68
+ }