diff --git a/SKALD.md b/SKALD.md index ac7bf4e..a96b133 100644 --- a/SKALD.md +++ b/SKALD.md @@ -525,7 +525,8 @@ connectors/ ## Fragment.json -Vedi [connector.manifest_guide.md](docs/connector.manifest_guide.md) per i dettagli. +Vedi [docs/connector.manifest_guide.md](docs/connector.manifest_guide.md) per la guida completa +alla creazione. `fragment.json` contiene tutti i campi dell'entry di `connectors.json` **tranne** `files[]`. Questi sono i campi obbligatori: diff --git a/docs/connector.manifest_guide.md b/docs/connector.manifest_guide.md index 0b36d9b..927a7a9 100644 --- a/docs/connector.manifest_guide.md +++ b/docs/connector.manifest_guide.md @@ -11,13 +11,16 @@ every file against a SHA-256 pinned in the index, then either runs it on the hos --- -## 1. The two documents +## 1. The two documents (+ one build script) -### 1a. The root index — `connectors.json` +### 1a. The compiled root index — `connectors.json` -One array of entries, each pointing at a connector folder. **The index is the -signable root: it is the only place that lists a connector's files and their -SHA-256 digests.** Skald refuses any file whose bytes do not match. +**This file is auto-generated.** Do not edit it by hand. It is produced by +[`scripts/compile.py`](../scripts/compile.py), which reads `index.json` + the +`fragment.json` in each connector folder, scans the physical files for SHA-256 +digests, and writes the final index. + +The output schema looks like this: ```jsonc { @@ -37,9 +40,6 @@ SHA-256 digests.** Skald refuses any file whose bytes do not match. "requires": ["NODE"], // human hint: NODE | PYTHON | OAUTH | API_KEY "tags": ["messaging", "mcp", "local", "whatsapp", "qr"], "auth": { "type": "qr" }, // may be repeated here and in the manifest - "tools": [ // OPTIONAL — friendly UI names per tool (§2a) - { "name": "send_message", "display_name": "Send Message" } - ], "folder": "whatsapp", // defaults to id "files": [ { "path": "index.js", "sha256": "…", "size": 21258 }, @@ -58,14 +58,28 @@ SHA-256 digests.** Skald refuses any file whose bytes do not match. - `files[].path` is relative to the connector folder. List **every** file the connector ships (server code, `package.json`/`requirements.txt`, icons, and the `connector.json` itself). A missing or mismatched digest fails the install. -- Compute `sha256` over the exact bytes served: `sha256sum `. +- SHA-256 digests and file sizes are **computed automatically** by `compile.py`. + Never write them by hand. +- The excluded files (`fragment.json`, `connectors.json`, `index.json`, + `.DS_Store`) are handled by `compile.py` — you don't need to think about them. - Do **not** list `node_modules/` or any generated deps — those are installed on the box, not shipped (see §5). -- `size` is optional but recommended. -- `tools[]` is optional but, when present in the manifest, should also be listed here - so that the index is self-contained (the index is the only document Skald downloads - eagerly — metadata UIs that need friendly tool names look here first, §2a). - Structure: `{ "name": "", "display_name": "" }`. +- `size` is optional but the compiler includes it. + +#### How the index is built + +Instead of editing `connectors.json` directly, you maintain **two lightweight +source files** and run one command: + +1. **`connectors/index.json`** — a flat JSON array of folder ids in display + order: `["gmail", "gcal", "myconn", …]` +2. **`connectors//fragment.json`** — the connector's index entry with + **every field except `files[]`** (same schema as above minus that array). +3. **`python3 scripts/compile.py`** — reads `index.json`, loads each + `fragment.json`, scans the folder for real files, computes SHA-256, and + writes `connectors.json`. + +The `tools[]` block (friendly UI names, §2a) goes into `fragment.json`. ### 1b. The per-connector manifest — `/connector.json` @@ -107,6 +121,12 @@ learns which file to run. At activation Skald rewrites it to the file's path inside the user's container (`/root/.skald/mcp//`), so keep it a plain relative filename (`index.js`, `server.py`, `pkg/server.py`). +**`llm_short_description` is model-facing:** it's the one-liner injected into +the LLM's system prompt so the model knows what this connector does. Keep it +short and functional ("Weather — current conditions, 16-day forecast, and AQI +data for any location"), **not** a list of tools (the model discovers tools +after `activate_tools`). + --- ## 2. Server contract (MCP over stdio) @@ -308,14 +328,31 @@ Three fields, in **both** the index entry and the `connector.json`, kept identic - `version` is a **number, not a string** (`1`, not `"1"` or `"2.0.1"`). Start at `1` for the first release under this scheme; **`+1` on every change** to any - shipped file. Never reuse or decrement. + shipped file **or to any manifest metadata** (description, icons, `version_string`). + Never reuse or decrement. - Skald stores the installed `version` and compares it to the feed's: a strictly greater feed `version` shows **"update available"** in the marketplace, and the - Install button becomes **Update**. Clicking it re-downloads the files and users - pick up the new code + deps on their next login. -- **The integer is the UI "is there an update?" signal — the actual re-install - trigger is the content-hash (§5).** So an update still propagates even if the - number is not bumped; but always bump it, or the admin never sees the update. + Install button becomes **Update**. Clicking it re-downloads the files and rewrites + the catalog row. +- **The integer is the *only* "is there an update?" signal** — it is compared + strictly (`feed > installed`). `version_string` (semver), icons and + `llm_short_description` are **never** compared, so a change to any of them that + does not also bump the integer is **invisible**: no "update available" badge + appears. This is the common trap — a "content-only" edit (e.g. a better + `llm_short_description`) that forgets the integer. +- **Two propagation paths, do not conflate them:** + - *Per-user code + deps* (the scripts, `package.json`/`requirements.txt`) reconcile + on a **content-hash** of the source files (§5), so new code lands at each user's + next login even without a reinstall. + - *Catalog metadata* (`llm_short_description` → the model's prompt, icons, friendly + name) is **not** in that hash — it lives in the catalog row and is rewritten only + by an explicit **reinstall/Update**. On reinstall Skald re-pulls the current feed + (never the browse cache) and pushes the new description live: enabled global + servers restart with it, and every logged-in user who activated the connector has + it restarted with the fresh `llm_short_description` — no re-login needed. +- So: to ship a new `llm_short_description`, **bump the integer** (so the admin sees + "update available") and the admin clicks **Update**. Nothing auto-propagates a + description change. - `version_string` and `version_release_date` are display metadata only — never compared. (Migration note: replace any legacy string `"version": "2.0.1"` with the integer `version` + `version_string`.) @@ -327,12 +364,17 @@ Three fields, in **both** the index entry and the `connector.json`, kept identic 1. Folder `myconn/` with: entry file, `connector.json`, deps file (`package.json`/`requirements.txt`), `icon_sm.svg`, `icon_lg.svg`, optional `verify.*`. -2. Server speaks MCP over stdio (§2); **stdout = JSON-RPC only**. -3. `mcp_config.args[0]` names the entry file. -4. Correct `type` + `scope` (§3) and `auth.type` (§4). -5. For `qr`: implement `login_status` (+ `logout`), persist the session under the +2. Create `fragment.json` inside the folder (same schema as the index entry + without `files[]` — include `tools[]` if needed). +3. Add `"myconn"` to `connectors/index.json`. +4. Run `python3 scripts/compile.py` (auto-generates `connectors.json` with + fresh SHA-256 digests). +5. Server speaks MCP over stdio (§2); **stdout = JSON-RPC only**. +6. `mcp_config.args[0]` names the entry file. +7. Correct `type` + `scope` (§3) and `auth.type` (§4). +8. For `qr`: implement `login_status` (+ `logout`), persist the session under the connector dir (§4d). -6. Deps declared as a file, **not** vendored (§5). -7. Add the entry to `connectors.json` with a correct `sha256` for **every** file. -8. Bump `version`. -``` +9. Deps declared as a file, **not** vendored (§5). +10. Bump `version`. +11. Run `python3 scripts/compile.py --verify` to confirm the index is fresh, + then commit.