From 88ecf5bf5c276f0925b9dfde95d972b01a1dfb8b Mon Sep 17 00:00:00 2001 From: seb Date: Mon, 12 Oct 2026 14:07:40 +0200 Subject: [PATCH] u --- src/defaultSkills/applet.SKILL | 126 +++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/defaultSkills/applet.SKILL diff --git a/src/defaultSkills/applet.SKILL b/src/defaultSkills/applet.SKILL new file mode 100644 index 0000000..381245c --- /dev/null +++ b/src/defaultSkills/applet.SKILL @@ -0,0 +1,126 @@ +Applet Guide: Sandboxed Execution, Typed CSV Output + +# Overview +An applet is a flat, reusable `.applet.js` script stored in the VFS that queries Dashback REST endpoints and computes tabular data. +Applets return structured, semicolon-separated (`;`) typed CSV conforming to output metadata headers. + +Applets can be executed by: +1. `javascript_execute` tool using `{ "appletvfsfile": ".applet.js", "applet_input": { ... } }`. +2. REST API endpoint `POST /vfs/.applet.js` with `{ "input": { ... } }`. + +--- + +# Every `.applet.js` file MUST start with three metadata lines: + +``` +// @applet-input-schema {"type":"object","properties":{...},"required":[...]} +// @applet-output-schema name:type;name:type +// @applet-description "Lorem ipsum" +``` + +`@applet-input-schema` +- JSON Schema object (`"type": "object"`). +- Parameter descriptions in the schema are optional + +`@applet-output-schema` +- Semicolon-separated (`col1:type;col2:type;...`) column declarations. +- Bare column names without `:type` default to `string`. +- Allowed data types: + - `string`: plain text or serialized object. + - `integer`: truncated integer number. + - `number`: floating-point number. + - `boolean`: boolean value (`true`, `false`, `1`, `0`, `"true"`, `"false"`). + - `date`: Date object or ISO string. + +--- + +# Execution Environment & Globals + +Applets run in an isolated worker thread pool sandbox. + +- **Entry point**: Export an async function taking `applet_input`: + ```javascript + module.exports = async function (applet_input) { ... }; + ``` +- **No `require()` or `import`**: Attempting to import modules throws an error. +- **Pre-injected Globals**: + - `fetch(path, options)`: Authenticated `fetch` pre-configured with a JWT scoped to the current user and selected company. Use root-relative API routes, e.g. `fetch('/article/42000009')`. + - `fs` / `vfs`: Sandboxed VFS filesystem interface for reading/writing local company files. + - `ExcelJS`: Pre-injected spreadsheet library. + - `PDFDocument`: Pre-injected PDFKit library. + - `sharp`: Pre-injected image manipulation library. + - `crypto`: Node / WebCrypto API. + - `console.log(...)`: returned in the tool response + +--- + +# Return Value & Typed CSV Mapping + +The exported function must return: +- An **Array of row objects**: `[ { col1: 'val1', col2: 10 }, { col1: 'val2', col2: 20 } ]` +- Or a **Single row object**: `{ col1: 'val1', col2: 10 }` (treated as a 1-row table) + +--- + +# Standard Applet Template + +```javascript +// @applet-input-schema {"type":"object","properties":{"sku":{"type":"string","title":"Article SKU","description":"SKU to lookup"}},"required":["sku"]} +// @applet-output-schema sku:string;name:string;netPrice:number;tracked:boolean;brand:string +// @applet-description "Lorem ipsum" + +module.exports = async function (applet_input) { + const sku = String(applet_input?.sku || '').trim(); + if (!sku) { + throw new Error('Missing required parameter: sku'); + } + + const res = await fetch(`/article/${encodeURIComponent(sku)}`); + if (res.status === 404) throw new Error(`Article "${sku}" not found.`); + if (!res.ok) throw new Error(`GET /article/${sku} failed with status ${res.status}`); + + const { article } = await res.json(); + const price = (article.prices || [])[0]; + + console.log(`Found article ${article.sku}: ${article.name}`); + + return [{ + sku: article.sku, + name: article.name || '', + netPrice: price?.net ?? '', + tracked: Boolean(article.track_stock), + brand: article.brand?.name || '' + }]; +}; +``` + +--- + +# Common Implementation Patterns + +### 1. Paging List Endpoints +Page using `offset` and `limit` until all items are loaded or `limit` is reached: +```javascript +const items = []; +let offset = 0; +while (true) { + const res = await fetch(`/order?offset=${offset}&limit=100`); + if (!res.ok) throw new Error(`Fetch failed: ${res.status}`); + const data = await res.json(); + const page = data.orders || []; + items.push(...page); + offset += page.length; + if (page.length === 0 || offset >= (data.total ?? items.length)) break; +} +``` + +--- + +# Shipped Reference Applets + +Pre-installed examples in the VFS root provide working reference code: +- `v1-article-lookup.applet.js`: Single entity lookup and key/value sheet generation. +- `v1-article-catalogue.applet.js`: Walking category trees and joining category article lists. +- `v1-category-inventory.applet.js`: Recursive tree flattening and depth calculation. +- `v1-delivery-register.applet.js`: Paged list fetching with batched detail requests. +- `v1-order-revenue-by-month.applet.js`: Paged order retrieval with calendar month aggregation.