@tavora/module-schema 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ What changed in `@tavora/module-schema`, newest first.
4
+
5
+ ## 0.1.0
6
+
7
+ - First release on npm: `validateManifest`, `needsNewerTavora` and the JSON Schema for `tavora.module.json`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wellbrained
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.
package/README.md CHANGED
@@ -1,3 +1,20 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @tavora/module-schema
2
+
3
+ The rules for `tavora.module.json`, the manifest every [Tavora](https://mytavora.app) module has: a validator (the same one Tavora runs when it installs a module) and a JSON Schema for editors.
4
+
5
+ ```ts
6
+ import { validateManifest } from "@tavora/module-schema";
7
+
8
+ const result = validateManifest(JSON.parse(text));
9
+ if (!result.ok) console.error(result.errors);
10
+ ```
11
+
12
+ For completion and errors in your editor, point the manifest at the schema:
13
+
14
+ ```json
15
+ {
16
+ "$schema": "https://docs.mytavora.app/schema/tavora.module.schema.json"
17
+ }
18
+ ```
19
+
20
+ What each field means is in the [manifest reference](https://docs.mytavora.app/reference/manifest). You rarely need this package directly: `@tavora/pack` checks the manifest before it packs a module.
@@ -0,0 +1,111 @@
1
+ export interface ModuleViewport {
2
+ minWidth: number;
3
+ minHeight: number;
4
+ preferredWidth?: number;
5
+ preferredHeight?: number;
6
+ }
7
+ /**
8
+ * A named panel type (the decision log, named panels rows of 2026-10-17): one tool a module offers,
9
+ * such as Inventory or Build planner. Players add each type on its own and may open several copies;
10
+ * every type shares the module's saved content and permissions.
11
+ */
12
+ export interface ModulePanelType {
13
+ /** Stable identity, never shown. Saved layouts keep it, so renaming `name` changes nothing for players. */
14
+ id: string;
15
+ /** Short name on the panel's tab, such as `Build planner`. */
16
+ name: string;
17
+ /** The page for this type; the module's own `entry` when left out, so one page may serve every type. */
18
+ entry?: string;
19
+ /** The tab icon for this type; the module's own `icon` when left out. */
20
+ icon?: string;
21
+ /** This type's sizes; the module's own `viewport` when left out. */
22
+ viewport?: ModuleViewport;
23
+ /** Panels opened before the module had panel types become this type. At most one type says so; without one, the first type. */
24
+ continues?: boolean;
25
+ }
26
+ export interface ModuleManifest {
27
+ $schema?: string;
28
+ id: string;
29
+ name: string;
30
+ description?: string;
31
+ version: string;
32
+ apiVersion: "1";
33
+ /** The oldest Tavora the module works with, such as `0.18.0`. An older Tavora refuses to install or load it, and says why. */
34
+ minTavora?: string;
35
+ entry: string;
36
+ icon?: string;
37
+ games: string[];
38
+ viewport: ModuleViewport;
39
+ /** Named panel types. Without them the module has one panel, made from `entry`, `icon` and `viewport`. */
40
+ panels?: ModulePanelType[];
41
+ permissions: {
42
+ /** Hosts the module asks to reach through network.fetch, such as `api.example.com`. Each needs the player's grant. */
43
+ network: string[];
44
+ /** Parts of Tavora that hold a sign-in, which the module asks to use (see CONNECTORS). Each needs the player's grant. */
45
+ connectors?: Connector[];
46
+ /** Lets the page compile WebAssembly (Excalidraw's picture resizing needs it). Only Tavora's own modules may ask for it. */
47
+ webAssembly?: boolean;
48
+ };
49
+ }
50
+ export type ManifestResult = {
51
+ ok: true;
52
+ manifest: ModuleManifest;
53
+ } | {
54
+ ok: false;
55
+ errors: string[];
56
+ };
57
+ /** Most panel types one module may offer, and the longest short name a tab shows. */
58
+ export declare const MAX_PANEL_TYPES = 16;
59
+ export declare const MAX_PANEL_NAME = 24;
60
+ export declare function isPanelTypeId(value: unknown): value is string;
61
+ /** A panel type with the module's own entry, icon and viewport filled in where the type has none. */
62
+ export interface ResolvedPanelType {
63
+ id: string;
64
+ name: string;
65
+ entry: string;
66
+ icon: string | undefined;
67
+ viewport: ModuleViewport;
68
+ }
69
+ /** The module's panel types, resolved; empty for a module with one panel and no types. */
70
+ export declare function panelTypes(manifest: ModuleManifest): ResolvedPanelType[];
71
+ /** The type a panel opened before the module had types becomes: the one marked `continues`, else the first. Null without types. */
72
+ export declare function continuingPanelType(manifest: ModuleManifest): string | null;
73
+ /** Every page and icon the manifest names, its panel types' included, each once: the files a package must hold. */
74
+ export declare function manifestFiles(manifest: ModuleManifest): {
75
+ entries: string[];
76
+ icons: string[];
77
+ };
78
+ export declare function isModuleId(value: unknown): value is string;
79
+ /**
80
+ * Who owns a module's data (the decision log, publisher row of 2026-10-16):
81
+ * Tavora's own modules, unsigned local ones, or a publisher's signing key,
82
+ * named by the hex fingerprint of the publisher's first key. Owners end up in
83
+ * paths and session partitions, so no dots or other separators.
84
+ */
85
+ export declare const OFFICIAL_OWNER = "official";
86
+ export declare const LOCAL_OWNER = "local";
87
+ export declare function isOwnerId(value: unknown): value is string;
88
+ export declare function isGameId(value: unknown): value is string;
89
+ /**
90
+ * Connectors (glossary): parts of Tavora that hold a sign-in and let a granted module use it without
91
+ * seeing the token. `ai-usage` reads the usage of the player's Claude and Codex accounts; `discord` talks to the
92
+ * Discord app on this PC with Tavora's Discord sign-in (voice channels, who is talking, joining and muting).
93
+ */
94
+ export declare const CONNECTORS: readonly ["ai-usage", "discord"];
95
+ export type Connector = (typeof CONNECTORS)[number];
96
+ export declare function isConnector(value: unknown): value is Connector;
97
+ /** Most hosts one manifest may ask for. */
98
+ export declare const MAX_NETWORK_HOSTS = 32;
99
+ /**
100
+ * A host a module may ask to reach (the decision log, module grants row of 2026-10-16): an exact,
101
+ * lowercase public name such as `api.example.com`. No wildcards, ports, schemes, IP addresses or
102
+ * names of this PC or the local network (`localhost`, `.local`), so a grant always names one site.
103
+ */
104
+ export declare function isNetworkHost(value: unknown): value is string;
105
+ /**
106
+ * Whether this Tavora is older than the module's `minTavora`. Compares major, minor and patch as
107
+ * numbers; anything after the patch number of Tavora's own version (a pre-release) is ignored.
108
+ */
109
+ export declare function needsNewerTavora(manifest: Pick<ModuleManifest, "minTavora">, tavoraVersion: string): boolean;
110
+ export declare function validateManifest(input: unknown): ManifestResult;
111
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,261 @@
1
+ // Manifest v1 for tavora.module.json (apps/docs/reference/manifest.md). The JSON Schema
2
+ // in schema/ describes the same rules for editors; tests keep the two in step.
3
+ // Module and game ids end up in session partitions, origins and file paths,
4
+ // so they are restricted to a small, path-safe alphabet.
5
+ const MODULE_ID = /^[a-z][a-z0-9]*(\.[a-z0-9][a-z0-9-]*){1,4}$/;
6
+ const GAME_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
7
+ const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z.-]+)?$/;
8
+ const MAX_VIEWPORT = 10_000;
9
+ const FIELDS = ["$schema", "id", "name", "description", "version", "apiVersion", "minTavora", "entry", "icon", "games", "viewport", "panels", "permissions"];
10
+ const VIEWPORT_FIELDS = ["minWidth", "minHeight", "preferredWidth", "preferredHeight"];
11
+ /** Most panel types one module may offer, and the longest short name a tab shows. */
12
+ export const MAX_PANEL_TYPES = 16;
13
+ export const MAX_PANEL_NAME = 24;
14
+ const PANEL_TYPE_ID = /^[a-z][a-z0-9-]{0,31}$/;
15
+ const PANEL_FIELDS = ["id", "name", "entry", "icon", "viewport", "continues"];
16
+ export function isPanelTypeId(value) {
17
+ return typeof value === "string" && PANEL_TYPE_ID.test(value);
18
+ }
19
+ /** The module's panel types, resolved; empty for a module with one panel and no types. */
20
+ export function panelTypes(manifest) {
21
+ return (manifest.panels ?? []).map((type) => ({
22
+ id: type.id,
23
+ name: type.name,
24
+ entry: type.entry ?? manifest.entry,
25
+ icon: type.icon ?? manifest.icon,
26
+ viewport: type.viewport ?? manifest.viewport,
27
+ }));
28
+ }
29
+ /** The type a panel opened before the module had types becomes: the one marked `continues`, else the first. Null without types. */
30
+ export function continuingPanelType(manifest) {
31
+ const panels = manifest.panels ?? [];
32
+ return (panels.find((type) => type.continues) ?? panels[0])?.id ?? null;
33
+ }
34
+ /** Every page and icon the manifest names, its panel types' included, each once: the files a package must hold. */
35
+ export function manifestFiles(manifest) {
36
+ const entries = new Set([manifest.entry]);
37
+ const icons = new Set(manifest.icon === undefined ? [] : [manifest.icon]);
38
+ for (const type of manifest.panels ?? []) {
39
+ if (type.entry !== undefined)
40
+ entries.add(type.entry);
41
+ if (type.icon !== undefined)
42
+ icons.add(type.icon);
43
+ }
44
+ return { entries: [...entries], icons: [...icons] };
45
+ }
46
+ export function isModuleId(value) {
47
+ return typeof value === "string" && value.length <= 128 && MODULE_ID.test(value);
48
+ }
49
+ /**
50
+ * Who owns a module's data (the decision log, publisher row of 2026-10-16):
51
+ * Tavora's own modules, unsigned local ones, or a publisher's signing key,
52
+ * named by the hex fingerprint of the publisher's first key. Owners end up in
53
+ * paths and session partitions, so no dots or other separators.
54
+ */
55
+ export const OFFICIAL_OWNER = "official";
56
+ export const LOCAL_OWNER = "local";
57
+ const KEY_FINGERPRINT = /^[0-9a-f]{32}$/;
58
+ export function isOwnerId(value) {
59
+ return value === OFFICIAL_OWNER || value === LOCAL_OWNER || (typeof value === "string" && KEY_FINGERPRINT.test(value));
60
+ }
61
+ export function isGameId(value) {
62
+ return typeof value === "string" && GAME_ID.test(value);
63
+ }
64
+ /**
65
+ * Connectors (glossary): parts of Tavora that hold a sign-in and let a granted module use it without
66
+ * seeing the token. `ai-usage` reads the usage of the player's Claude and Codex accounts; `discord` talks to the
67
+ * Discord app on this PC with Tavora's Discord sign-in (voice channels, who is talking, joining and muting).
68
+ */
69
+ export const CONNECTORS = ["ai-usage", "discord"];
70
+ export function isConnector(value) {
71
+ return CONNECTORS.includes(value);
72
+ }
73
+ /** Most hosts one manifest may ask for. */
74
+ export const MAX_NETWORK_HOSTS = 32;
75
+ const HOST_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
76
+ /**
77
+ * A host a module may ask to reach (the decision log, module grants row of 2026-10-16): an exact,
78
+ * lowercase public name such as `api.example.com`. No wildcards, ports, schemes, IP addresses or
79
+ * names of this PC or the local network (`localhost`, `.local`), so a grant always names one site.
80
+ */
81
+ export function isNetworkHost(value) {
82
+ if (typeof value !== "string" || value.length > 253)
83
+ return false;
84
+ const labels = value.split(".");
85
+ if (labels.length < 2 || !labels.every((label) => HOST_LABEL.test(label)))
86
+ return false;
87
+ const last = labels.at(-1);
88
+ // A top-level label of digits only is an IP address (or not a name at all).
89
+ return !/^\d+$/.test(last) && !["localhost", "local", "internal", "arpa"].includes(last);
90
+ }
91
+ /** A Tavora release version: major.minor.patch, numbers only. */
92
+ const TAVORA_VERSION = /^(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})$/;
93
+ /**
94
+ * Whether this Tavora is older than the module's `minTavora`. Compares major, minor and patch as
95
+ * numbers; anything after the patch number of Tavora's own version (a pre-release) is ignored.
96
+ */
97
+ export function needsNewerTavora(manifest, tavoraVersion) {
98
+ if (!manifest.minTavora)
99
+ return false;
100
+ const parts = (version) => version.split(/[-+]/)[0].split(".").map((part) => Number(part) || 0);
101
+ const need = parts(manifest.minTavora);
102
+ const have = parts(tavoraVersion);
103
+ for (let i = 0; i < 3; i++) {
104
+ if ((have[i] ?? 0) !== (need[i] ?? 0))
105
+ return (have[i] ?? 0) < (need[i] ?? 0);
106
+ }
107
+ return false;
108
+ }
109
+ function isRecord(value) {
110
+ return typeof value === "object" && value !== null && !Array.isArray(value);
111
+ }
112
+ function isText(value, maxLength) {
113
+ return typeof value === "string" && value.trim() !== "" && value.length <= maxLength;
114
+ }
115
+ function isDimension(value) {
116
+ return Number.isInteger(value) && value > 0 && value <= MAX_VIEWPORT;
117
+ }
118
+ // Paths must be plain and relative inside the package: no "..", no
119
+ // absolute paths, no backslashes, no URL schemes.
120
+ function isPackagePath(value) {
121
+ if (typeof value !== "string" || value.length === 0 || value.length > 256)
122
+ return false;
123
+ if (value.startsWith("/") || value.includes("\\") || value.includes(":") || value.includes("\0"))
124
+ return false;
125
+ return value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
126
+ }
127
+ function checkViewport(viewport, at, errors) {
128
+ if (!isRecord(viewport)) {
129
+ errors.push(`${at} must be an object`);
130
+ return;
131
+ }
132
+ if (!isDimension(viewport.minWidth) || !isDimension(viewport.minHeight)) {
133
+ errors.push(`${at}.minWidth and ${at}.minHeight must be positive integers`);
134
+ }
135
+ for (const key of ["preferredWidth", "preferredHeight"]) {
136
+ if (viewport[key] !== undefined && !isDimension(viewport[key]))
137
+ errors.push(`${at}.${key} must be a positive integer`);
138
+ }
139
+ const unknown = Object.keys(viewport).filter((key) => !VIEWPORT_FIELDS.includes(key));
140
+ if (unknown.length > 0)
141
+ errors.push(`unknown ${at} fields: ${unknown.join(", ")}`);
142
+ }
143
+ function checkPanels(panels, errors) {
144
+ if (!Array.isArray(panels) || panels.length === 0 || panels.length > MAX_PANEL_TYPES) {
145
+ errors.push(`panels must be an array of 1 to ${MAX_PANEL_TYPES} panel types`);
146
+ return;
147
+ }
148
+ const ids = new Set();
149
+ let continuing = 0;
150
+ panels.forEach((type, index) => {
151
+ const at = `panels[${index}]`;
152
+ if (!isRecord(type)) {
153
+ errors.push(`${at} must be an object`);
154
+ return;
155
+ }
156
+ if (!isPanelTypeId(type.id))
157
+ errors.push(`${at}.id must be lowercase letters, digits and -, starting with a letter, at most 32 characters`);
158
+ else if (ids.has(type.id))
159
+ errors.push(`panels lists the id ${type.id} twice`);
160
+ else
161
+ ids.add(type.id);
162
+ if (!isText(type.name, MAX_PANEL_NAME))
163
+ errors.push(`${at}.name must be a non-empty string of at most ${MAX_PANEL_NAME} characters`);
164
+ if (type.entry !== undefined) {
165
+ if (!isPackagePath(type.entry))
166
+ errors.push(`${at}.entry must be a relative path inside the package`);
167
+ else if (!type.entry.endsWith(".html"))
168
+ errors.push(`${at}.entry must be an .html file`);
169
+ }
170
+ if (type.icon !== undefined) {
171
+ if (!isPackagePath(type.icon))
172
+ errors.push(`${at}.icon must be a relative path inside the package`);
173
+ else if (!/\.(png|webp)$/.test(type.icon))
174
+ errors.push(`${at}.icon must be a .png or .webp file`);
175
+ }
176
+ if (type.viewport !== undefined)
177
+ checkViewport(type.viewport, `${at}.viewport`, errors);
178
+ if (type.continues !== undefined && typeof type.continues !== "boolean")
179
+ errors.push(`${at}.continues must be true or false`);
180
+ if (type.continues === true)
181
+ continuing++;
182
+ const unknown = Object.keys(type).filter((key) => !PANEL_FIELDS.includes(key));
183
+ if (unknown.length > 0)
184
+ errors.push(`unknown ${at} fields: ${unknown.join(", ")}`);
185
+ });
186
+ if (continuing > 1)
187
+ errors.push("only one panel type may set continues");
188
+ }
189
+ export function validateManifest(input) {
190
+ const errors = [];
191
+ if (!isRecord(input))
192
+ return { ok: false, errors: ["manifest must be a JSON object"] };
193
+ if (!isModuleId(input.id))
194
+ errors.push("id must look like publisher.module-name (lowercase, dot-separated)");
195
+ if (input.$schema !== undefined && typeof input.$schema !== "string")
196
+ errors.push("$schema must be a string");
197
+ if (!isText(input.name, 80))
198
+ errors.push("name must be a non-empty string of at most 80 characters");
199
+ if (input.description !== undefined && !isText(input.description, 300)) {
200
+ errors.push("description must be a non-empty string of at most 300 characters");
201
+ }
202
+ if (typeof input.version !== "string" || !SEMVER.test(input.version))
203
+ errors.push("version must be semver, e.g. 1.0.0");
204
+ if (input.apiVersion !== "1")
205
+ errors.push('apiVersion must be "1"');
206
+ if (input.minTavora !== undefined && (typeof input.minTavora !== "string" || !TAVORA_VERSION.test(input.minTavora)))
207
+ errors.push("minTavora must be a Tavora version such as 0.18.0");
208
+ if (!isPackagePath(input.entry))
209
+ errors.push("entry must be a relative path inside the package");
210
+ else if (!input.entry.endsWith(".html"))
211
+ errors.push("entry must be an .html file");
212
+ if (input.icon !== undefined) {
213
+ if (!isPackagePath(input.icon))
214
+ errors.push("icon must be a relative path inside the package");
215
+ else if (!/\.(png|webp)$/.test(input.icon))
216
+ errors.push("icon must be a .png or .webp file");
217
+ }
218
+ if (!Array.isArray(input.games) || !input.games.every(isGameId)) {
219
+ errors.push("games must be an array of game ids (empty means any game)");
220
+ }
221
+ checkViewport(input.viewport, "viewport", errors);
222
+ if (input.panels !== undefined)
223
+ checkPanels(input.panels, errors);
224
+ const permissions = input.permissions;
225
+ if (!isRecord(permissions) || !Array.isArray(permissions.network)) {
226
+ errors.push("permissions.network must be an array");
227
+ }
228
+ else {
229
+ const hosts = permissions.network;
230
+ if (hosts.length > MAX_NETWORK_HOSTS)
231
+ errors.push(`permissions.network may name at most ${MAX_NETWORK_HOSTS} hosts`);
232
+ const bad = hosts.filter((host) => !isNetworkHost(host));
233
+ if (bad.length > 0) {
234
+ errors.push(`permissions.network must list exact public host names such as api.example.com (lowercase, no scheme, port or wildcard): ${bad.slice(0, 3).map(String).join(", ")}`);
235
+ }
236
+ else if (new Set(hosts).size !== hosts.length) {
237
+ errors.push("permissions.network lists a host twice");
238
+ }
239
+ }
240
+ if (isRecord(permissions)) {
241
+ const unsupported = Object.keys(permissions).filter((key) => key !== "network" && key !== "connectors" && key !== "webAssembly");
242
+ if (unsupported.length > 0)
243
+ errors.push(`unsupported permissions: ${unsupported.join(", ")}`);
244
+ if ("connectors" in permissions) {
245
+ const connectors = permissions.connectors;
246
+ if (!Array.isArray(connectors) || !connectors.every(isConnector))
247
+ errors.push(`permissions.connectors must list connectors Tavora has: ${CONNECTORS.join(", ")}`);
248
+ else if (new Set(connectors).size !== connectors.length)
249
+ errors.push("permissions.connectors lists a connector twice");
250
+ }
251
+ if ("webAssembly" in permissions && typeof permissions.webAssembly !== "boolean")
252
+ errors.push("permissions.webAssembly must be true or false");
253
+ }
254
+ const extra = Object.keys(input).filter((key) => !FIELDS.includes(key));
255
+ if (extra.length > 0)
256
+ errors.push(`unknown fields: ${extra.join(", ")}`);
257
+ if (errors.length > 0)
258
+ return { ok: false, errors };
259
+ return { ok: true, manifest: input };
260
+ }
261
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,45 @@
1
1
  {
2
2
  "name": "@tavora/module-schema",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Validator and JSON Schema for Tavora module manifests (tavora.module.json).",
5
+ "type": "module",
6
+ "keywords": [
7
+ "tavora",
8
+ "module",
9
+ "manifest",
10
+ "json-schema"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/wellbrained/tavora.git",
15
+ "directory": "packages/module-schema"
16
+ },
17
+ "homepage": "https://docs.mytavora.app/",
18
+ "bugs": "https://github.com/wellbrained/tavora/issues",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./schema.json": "./schema/tavora.module.schema.json",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "schema",
33
+ "README.md",
34
+ "CHANGELOG.md"
35
+ ],
36
+ "devDependencies": {
37
+ "ajv": "8.20.0",
38
+ "@types/node": "^22.20.5"
39
+ },
40
+ "license": "MIT",
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.build.json",
43
+ "typecheck": "tsc --noEmit -p ."
44
+ }
6
45
  }
@@ -0,0 +1,249 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "tavora.module.schema.json",
4
+ "title": "Tavora module manifest (tavora.module.json), apiVersion 1",
5
+ "description": "Describes a Tavora module package. Tavora Desktop rejects a manifest with unknown fields instead of ignoring them. The TypeScript validator in @tavora/module-schema is the reference; this schema is checked against it in tests.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "id",
10
+ "name",
11
+ "version",
12
+ "apiVersion",
13
+ "entry",
14
+ "games",
15
+ "viewport",
16
+ "permissions"
17
+ ],
18
+ "properties": {
19
+ "$schema": {
20
+ "type": "string",
21
+ "description": "Lets editors find this schema. Ignored by Tavora."
22
+ },
23
+ "id": {
24
+ "type": "string",
25
+ "maxLength": 128,
26
+ "pattern": "^[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*){1,4}$",
27
+ "description": "Unique module id: publisher.module-name, lowercase and dot-separated. Never change it after release: data and permissions are keyed on it.",
28
+ "examples": [
29
+ "community.example.campaign-companion"
30
+ ]
31
+ },
32
+ "name": {
33
+ "type": "string",
34
+ "minLength": 1,
35
+ "maxLength": 80,
36
+ "pattern": "\\S",
37
+ "description": "Name shown to players."
38
+ },
39
+ "description": {
40
+ "type": "string",
41
+ "minLength": 1,
42
+ "maxLength": 300,
43
+ "pattern": "\\S",
44
+ "description": "One or two sentences shown in the module picker."
45
+ },
46
+ "version": {
47
+ "type": "string",
48
+ "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(-[0-9A-Za-z.-]+)?$",
49
+ "description": "Module version, semver.",
50
+ "examples": [
51
+ "1.0.0"
52
+ ]
53
+ },
54
+ "apiVersion": {
55
+ "const": "1",
56
+ "description": "Manifest and bridge contract version. Only \"1\" exists."
57
+ },
58
+ "minTavora": {
59
+ "type": "string",
60
+ "pattern": "^(0|[1-9]\\d{0,5})\\.(0|[1-9]\\d{0,5})\\.(0|[1-9]\\d{0,5})$",
61
+ "description": "The oldest Tavora the module works with. An older Tavora refuses to install or load it and says why.",
62
+ "examples": [
63
+ "0.18.0"
64
+ ]
65
+ },
66
+ "entry": {
67
+ "type": "string",
68
+ "allOf": [
69
+ {
70
+ "$ref": "#/definitions/packagePath"
71
+ },
72
+ {
73
+ "pattern": "\\.html$"
74
+ }
75
+ ],
76
+ "description": "HTML page Tavora loads, relative to the package root.",
77
+ "examples": [
78
+ "dist/index.html"
79
+ ]
80
+ },
81
+ "icon": {
82
+ "type": "string",
83
+ "allOf": [
84
+ {
85
+ "$ref": "#/definitions/packagePath"
86
+ },
87
+ {
88
+ "pattern": "\\.(png|webp)$"
89
+ }
90
+ ],
91
+ "description": "Square PNG or WebP icon inside the package, at least 128 × 128 pixels.",
92
+ "examples": [
93
+ "assets/icon.png"
94
+ ]
95
+ },
96
+ "games": {
97
+ "type": "array",
98
+ "items": {
99
+ "type": "string",
100
+ "pattern": "^[a-z0-9][a-z0-9-]{0,63}$"
101
+ },
102
+ "description": "Game ids this module is made for. Empty means any game.",
103
+ "examples": [
104
+ [
105
+ "poe2"
106
+ ]
107
+ ]
108
+ },
109
+ "viewport": {
110
+ "$ref": "#/definitions/viewport",
111
+ "description": "Panel sizes in CSS pixels. Tavora never makes the panel smaller than the minimum."
112
+ },
113
+ "panels": {
114
+ "type": "array",
115
+ "minItems": 1,
116
+ "maxItems": 16,
117
+ "description": "Named panel types the module offers, such as Inventory and Build planner. Players add each on its own and may open several copies; all share the module's saved content and permissions. Ids must be unique and at most one type sets continues.",
118
+ "items": {
119
+ "type": "object",
120
+ "additionalProperties": false,
121
+ "required": [
122
+ "id",
123
+ "name"
124
+ ],
125
+ "properties": {
126
+ "id": {
127
+ "type": "string",
128
+ "pattern": "^[a-z][a-z0-9-]{0,31}$",
129
+ "description": "Stable identity of the panel type, never shown. Saved layouts keep it, so never change it after release.",
130
+ "examples": [
131
+ "build-planner"
132
+ ]
133
+ },
134
+ "name": {
135
+ "type": "string",
136
+ "minLength": 1,
137
+ "maxLength": 24,
138
+ "pattern": "\\S",
139
+ "description": "Short name on the panel's tab.",
140
+ "examples": [
141
+ "Build planner"
142
+ ]
143
+ },
144
+ "entry": {
145
+ "type": "string",
146
+ "allOf": [
147
+ {
148
+ "$ref": "#/definitions/packagePath"
149
+ },
150
+ {
151
+ "pattern": "\\.html$"
152
+ }
153
+ ],
154
+ "description": "The page for this type. The module's entry when left out."
155
+ },
156
+ "icon": {
157
+ "type": "string",
158
+ "allOf": [
159
+ {
160
+ "$ref": "#/definitions/packagePath"
161
+ },
162
+ {
163
+ "pattern": "\\.(png|webp)$"
164
+ }
165
+ ],
166
+ "description": "Tab icon for this type. The module's icon when left out."
167
+ },
168
+ "viewport": {
169
+ "$ref": "#/definitions/viewport",
170
+ "description": "Sizes for this type. The module's viewport when left out."
171
+ },
172
+ "continues": {
173
+ "type": "boolean",
174
+ "description": "Panels opened before the module had panel types become this type. Without it, the first type."
175
+ }
176
+ }
177
+ }
178
+ },
179
+ "permissions": {
180
+ "type": "object",
181
+ "additionalProperties": false,
182
+ "required": [
183
+ "network"
184
+ ],
185
+ "description": "What the module asks for. Each permission needs the player's grant before it works; the player is asked when they first add the module.",
186
+ "properties": {
187
+ "network": {
188
+ "type": "array",
189
+ "maxItems": 32,
190
+ "uniqueItems": true,
191
+ "description": "Exact public host names the module may reach through network.fetch, such as api.example.com. HTTPS only; no scheme, port, wildcard, IP address or local name.",
192
+ "items": {
193
+ "type": "string",
194
+ "maxLength": 253,
195
+ "pattern": "^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+(?!(?:[0-9]+|localhost|local|internal|arpa)$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"
196
+ }
197
+ },
198
+ "connectors": {
199
+ "type": "array",
200
+ "uniqueItems": true,
201
+ "description": "Parts of Tavora that hold a sign-in, which the module asks to use without seeing the token. ai-usage reads the usage of the player's Claude and Codex accounts; discord talks to the Discord app on this PC (voice channels, who is talking, joining and muting).",
202
+ "items": {
203
+ "enum": ["ai-usage", "discord"]
204
+ }
205
+ },
206
+ "webAssembly": {
207
+ "type": "boolean",
208
+ "description": "Lets the page compile WebAssembly. Only Tavora's own modules may ask for it; any other module that does is not loaded."
209
+ }
210
+ }
211
+ }
212
+ },
213
+ "definitions": {
214
+ "dimension": {
215
+ "type": "integer",
216
+ "minimum": 1,
217
+ "maximum": 10000
218
+ },
219
+ "packagePath": {
220
+ "type": "string",
221
+ "minLength": 1,
222
+ "maxLength": 256,
223
+ "pattern": "^(?!/)(?!.*//)(?!(.*/)?\\.\\.?(/|$))[^\\\\:\\u0000]+$",
224
+ "description": "Relative path inside the package: no leading slash, no '.' or '..' segments, no backslashes or colons."
225
+ },
226
+ "viewport": {
227
+ "type": "object",
228
+ "additionalProperties": false,
229
+ "required": [
230
+ "minWidth",
231
+ "minHeight"
232
+ ],
233
+ "properties": {
234
+ "minWidth": {
235
+ "$ref": "#/definitions/dimension"
236
+ },
237
+ "minHeight": {
238
+ "$ref": "#/definitions/dimension"
239
+ },
240
+ "preferredWidth": {
241
+ "$ref": "#/definitions/dimension"
242
+ },
243
+ "preferredHeight": {
244
+ "$ref": "#/definitions/dimension"
245
+ }
246
+ }
247
+ }
248
+ }
249
+ }