@silverbulletmd/silverbullet 2.8.1 → 2.10.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.
Files changed (65) hide show
  1. package/README.md +42 -25
  2. package/client/plugos/hooks/syscall.ts +24 -10
  3. package/client/plugos/syscalls/asset.ts +61 -23
  4. package/client/plugos/syscalls/clientStore.ts +32 -6
  5. package/client/plugos/syscalls/client_code_widget.ts +9 -4
  6. package/client/plugos/syscalls/code_widget.ts +26 -11
  7. package/client/plugos/syscalls/config.ts +146 -30
  8. package/client/plugos/syscalls/datastore.ts +146 -40
  9. package/client/plugos/syscalls/editor.ts +1432 -700
  10. package/client/plugos/syscalls/event.ts +46 -13
  11. package/client/plugos/syscalls/fetch.ts +123 -82
  12. package/client/plugos/syscalls/index.ts +243 -114
  13. package/client/plugos/syscalls/jsonschema.ts +107 -8
  14. package/client/plugos/syscalls/language.ts +38 -12
  15. package/client/plugos/syscalls/markdown.ts +166 -50
  16. package/client/plugos/syscalls/mq.ts +93 -20
  17. package/client/plugos/syscalls/schema_introspection.ts +33 -0
  18. package/client/plugos/syscalls/service_registry.ts +74 -20
  19. package/client/plugos/syscalls/shell.ts +51 -20
  20. package/client/plugos/syscalls/space.ts +169 -113
  21. package/client/plugos/syscalls/sync.ts +50 -19
  22. package/client/plugos/syscalls/system.ts +236 -139
  23. package/client/plugos/system.ts +21 -5
  24. package/client/space_lua/api_documentation.ts +107 -0
  25. package/client/space_lua/ast.ts +13 -0
  26. package/client/space_lua/eval.ts +12 -4
  27. package/client/space_lua/parse.ts +254 -119
  28. package/client/space_lua/pretty_print.ts +465 -0
  29. package/client/space_lua/query_collection.ts +3 -3
  30. package/client/space_lua/render_widget.ts +156 -0
  31. package/client/space_lua/runtime.ts +83 -22
  32. package/client/space_lua/stdlib/crypto.ts +8 -3
  33. package/client/space_lua/stdlib/encoding.ts +27 -9
  34. package/client/space_lua/stdlib/js.ts +137 -22
  35. package/client/space_lua/stdlib/load.ts +26 -17
  36. package/client/space_lua/stdlib/math.ts +323 -118
  37. package/client/space_lua/stdlib/net.ts +57 -9
  38. package/client/space_lua/stdlib/os.ts +111 -44
  39. package/client/space_lua/stdlib/space_lua.ts +397 -13
  40. package/client/space_lua/stdlib/string.ts +282 -104
  41. package/client/space_lua/stdlib/string_pack.ts +58 -19
  42. package/client/space_lua/stdlib/table.ts +195 -40
  43. package/client/space_lua/stdlib.ts +336 -94
  44. package/client/space_lua/syscalls.ts +270 -0
  45. package/dist/plug-compile.js +1 -1
  46. package/package.json +12 -4
  47. package/plug-api/lib/ref.ts +96 -0
  48. package/plug-api/lib/shortcut.ts +2 -1
  49. package/plug-api/syscalls/config.ts +5 -3
  50. package/plug-api/syscalls/editor.ts +23 -2
  51. package/plug-api/syscalls/index.ts +24 -1
  52. package/plug-api/syscalls/jsonschema.ts +9 -0
  53. package/plug-api/syscalls/lua.ts +28 -1
  54. package/plug-api/syscalls/shell.ts +0 -1
  55. package/plug-api/syscalls/system.ts +7 -0
  56. package/plug-api/system_mock.ts +2 -2
  57. package/plug-api/types/config.ts +1 -1
  58. package/plug-api/types/index.ts +52 -1
  59. package/plug-api/types/manifest.ts +22 -4
  60. package/plug-api/ui/cx.ts +4 -0
  61. package/plug-api/ui/index.ts +22 -0
  62. package/plug-api/ui/panel_styles.ts +33 -0
  63. package/plug-api/ui/slugify.ts +44 -0
  64. package/plugs/builtin_plugs.ts +1 -0
  65. package/client/plugos/syscalls/lua.ts +0 -58
package/README.md CHANGED
@@ -5,45 +5,52 @@
5
5
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/silverbulletmd/silverbullet)
6
6
 
7
7
  # SilverBullet
8
- SilverBullet is a Programmable, Private, Browser-based, Open Source, Self Hosted, Personal Knowledge Management Platform.
8
+ SilverBullet is a Programmable, Private, Browser-based, Open Source, Self Hosted, Personal Knowledge Database — a fancy term for a "notes app on steroids".
9
9
 
10
10
  _Yowza!_ That surely is a lot of adjectives to describe a browser-based Markdown editor programmable with Lua.
11
11
 
12
12
  Let’s get more specific.
13
13
 
14
+ SilverBullet combines a clean live-preview editor with wiki-style linking, a built-in database and query language, and a fully integrated Space Lua scripting environment, turning your notes into a programmable system that grows with you.
15
+
14
16
  In SilverBullet you keep your content as a collection of Markdown Pages (called a Space). You navigate your space using the Page Picker like a traditional notes app, or through Links like a wiki (except they are bi-directional).
15
17
 
16
- If you are the **writer** type, you’ll appreciate SilverBullet as a clean Markdown editor with Live Preview. If you have more of an **outliner** personality, SilverBullet has Outlining tools for you. Productivity freak? Have a look at Tasks. More of a **database** person? You will appreciate Objects and Queries.
18
+ If you are the **writer** type, you’ll appreciate SilverBullet as a clean Markdown editor with Live Preview. If you have more of an **outliner** personality, SilverBullet has Outlining tools for you. Productivity freak? Have a look at Tasks. More of a **database** person? You will appreciate Objects and Queries (SLIQ).
17
19
 
18
20
  And if you are comfortable **programming** a little bit — now we’re really talking. You will love _dynamically generating content_ with Space Lua (SilverBullet’s Lua dialect), or to use it to create custom Commands, Page Templates or Widgets.
19
21
 
22
+ You were told there’s no such thing as a silver bullet. You were told wrong.
23
+
20
24
  [Much more detail can be found on silverbullet.md](https://silverbullet.md)
21
25
 
22
26
  ## Installing SilverBullet
23
27
  Check out the [instructions](https://silverbullet.md/Install).
24
28
 
25
29
  ## Developing SilverBullet
26
- SilverBullet's frontend is written in [TypeScript](https://www.typescriptlang.org/) and built on top of the excellent [CodeMirror 6](https://codemirror.net/) editor component. Additional UI is built using [Preact](https://preactjs.com). [ESBuild](https://esbuild.github.io) is used to build the frontend.
30
+ SilverBullet's frontend ("client") is written in [TypeScript](https://www.typescriptlang.org/) and built on top of the excellent [CodeMirror 6](https://codemirror.net/) editor component. Additional UI is built using [Preact](https://preactjs.com). [ESBuild](https://esbuild.github.io) is used to build the frontend.
27
31
 
28
- The server backend is written in Go.
32
+ The server backend is written in [Rust](https://www.rust-lang.org/) (a Cargo workspace). It serves the pre-built client bundle, the file/space HTTP API, authentication, and a headless-Chrome runtime for server-side Lua.
29
33
 
30
34
  ## Code structure
31
- * `client/`: The SilverBullet client, implemented with TypeScript
32
- * `server/`: The SilverBullet server, written in Go
33
- * `plugs`: Set of built-in plugs that are distributed with SilverBullet
34
- * `libraries`: A set of libraries (space scripts, page templates, slash templates) distributed with SilverBullet
35
- * `plug-api/`: Useful APIs for use in plugs
35
+ * `bin/silverbullet/`: The standalone `silverbullet` server binary (Rust) (this is the one you generally run)
36
+ * `bin/sb/`: The `sb` CLI client (Rust)
37
+ * `bin/plug-compile.ts`: the plug compiler
38
+ * `client/`: The SilverBullet client (browser, TypeScript)
39
+ * `server/`: The SilverBullet server library (Rust): HTTP router, handlers, auth, runtime seam
40
+ * `server-common/`: Shared Rust crate (space primitives, shared types)
41
+ * `server-runtime-chrome/`: Headless-Chrome runtime backend (Rust)
42
+ * `plugs`: Set of built-in plugs (TypeScript) that are distributed with SilverBullet
43
+ * `libraries`: A set of libraries (space scripts, page templates, slash templates) distributed with SilverBullet (Space Lua)
44
+ * `plug-api/`: Useful APIs for use in plugs and published to NPM
36
45
  * `lib/`: Useful libraries to be used in plugs
37
46
  * `syscalls/`: TypeScript wrappers around syscalls
38
47
  * `types/`: Various (client) types that can be references from plugs
39
- * `bin`
40
- * `plug_compile.ts` the plug compiler
41
48
  * `scripts/`: Useful scripts
42
- * `website/`: silverbullet.md website content
49
+ * `docs/`: documentation (also serves a silverbullet.md website content)
43
50
 
44
51
  ### Requirements
45
- * [Node.js](https://nodejs.org/) 24+ and npm 10+: Used to build the frontend and plugs
46
- * [Go](https://go.dev/): Used to build the backend
52
+ * [Node.js](https://nodejs.org/) 24+ and npm 10+: Used to build the frontend (client) and plugs
53
+ * [Rust](https://www.rust-lang.org/tools/install) (stable, via `rustup`): Used to build the server
47
54
 
48
55
  The project includes `.nvmrc` and `.node-version` files. If you use [nvm](https://github.com/nvm-sh/nvm) or another Node version manager, it will automatically use the correct Node.js version:
49
56
 
@@ -51,35 +58,45 @@ The project includes `.nvmrc` and `.node-version` files. If you use [nvm](https:
51
58
  nvm use # If using nvm
52
59
  ```
53
60
 
54
- It's convenient to also install [air](https://github.com/air-verse/air) for development, this will automatically rebuild both the frontend and backend when changes are made:
61
+ First, install dependencies:
55
62
 
56
63
  ```shell
57
- go install github.com/air-verse/air@latest
64
+ make setup
58
65
  ```
59
- Make sure your `$GOPATH/bin` is in your $PATH.
60
66
 
61
- First, install dependencies:
67
+ To create a (release) build:
62
68
 
63
69
  ```shell
64
- make setup
70
+ make
65
71
  ```
66
72
 
67
- To build everything and run the server (which automatically restarts upon file changing):
73
+ This will produce `silverbullet` and `cli` binaries in `./target/release/`, run `./target/release/silverbullet` to run the server.
74
+
75
+ ### Development workflow
76
+
77
+ SilverBullet has two halves you rebuild **independently**:
78
+
79
+ * The **client** (TypeScript: `client/`) is built by ESBuild into `client_bundle/`, which the server then serves.
80
+ * The **server** (Rust: `server/`, `server-common/`, `server-runtime-chrome/`, `bin/silverbullet/`) is a compiled binary.
81
+
82
+ Run the server in development with `cargo run`. A **debug** build serves the client bundle **live from `client_bundle/` on disk** (a release build embeds it instead):
68
83
 
69
84
  ```shell
70
- air <PATH-TO-YOUR-SPACE>
85
+ npm run build
86
+ cargo run <PATH-TO-YOUR-SPACE>
71
87
  ```
72
88
 
73
- Alternatively, to build:
89
+ **When you change only the client** (TypeScript in `client/`): you do **not** need to restart the server. Rebuild just the client bundle and reload the page in your browser — the debug server serves the freshly-built bundle straight from disk:
74
90
 
75
91
  ```shell
76
- make
92
+ npm run build
77
93
  ```
78
94
 
79
- To run the resulting server:
95
+ To build a self-contained **release** binary (with the client bundle embedded), and run it:
80
96
 
81
97
  ```shell
82
- ./silverbullet <PATH-TO-YOUR-SPACE>
98
+ make
99
+ ./target/release/silverbullet <PATH-TO-YOUR-SPACE>
83
100
  ```
84
101
 
85
102
  ### Useful development tasks
@@ -1,5 +1,5 @@
1
1
  import type { Hook, Manifest } from "../types.ts";
2
- import type { SysCallMapping, System } from "../system.ts";
2
+ import type { SysCallMapping, SyscallContext, System } from "../system.ts";
3
3
  import type { SyscallHookT } from "@silverbulletmd/silverbullet/type/manifest";
4
4
 
5
5
  export class SyscallHook implements Hook<SyscallHookT> {
@@ -24,16 +24,27 @@ export class SyscallHook implements Hook<SyscallHookT> {
24
24
  continue;
25
25
  }
26
26
 
27
- const syscallName = functionDef.syscall;
27
+ const syscallDefinition = functionDef.syscall;
28
+ const syscallName =
29
+ typeof syscallDefinition === "string"
30
+ ? syscallDefinition
31
+ : syscallDefinition.name;
28
32
 
29
33
  // Add the syscall to our mapping
30
- syscalls[syscallName] = (ctx, ...args) => {
31
- // Delegate to the system to invoke the function
32
- return system.syscall(ctx, "system.invokeFunction", [
34
+ const callback = (ctx: SyscallContext, ...args: any[]) =>
35
+ system.syscall(ctx, "system.invokeFunction", [
33
36
  `${plug.manifest!.name}.${name}`,
34
37
  ...args,
35
38
  ]);
36
- };
39
+ if (typeof syscallDefinition === "string") {
40
+ syscalls[syscallName] = callback;
41
+ } else {
42
+ const { name: _name, ...metadata } = syscallDefinition;
43
+ syscalls[syscallName] = {
44
+ callback,
45
+ ...metadata,
46
+ };
47
+ }
37
48
 
38
49
  // Register the syscalls with no required permissions
39
50
  system.registerSyscalls([], syscalls);
@@ -48,16 +59,19 @@ export class SyscallHook implements Hook<SyscallHookT> {
48
59
  continue;
49
60
  }
50
61
 
51
- // Validate syscall name is provided
52
- if (!functionDef.syscall) {
62
+ const syscallName =
63
+ typeof functionDef.syscall === "string"
64
+ ? functionDef.syscall
65
+ : functionDef.syscall.name;
66
+ if (!syscallName) {
53
67
  errors.push(`Function ${name} has a syscall but no name`);
54
68
  continue;
55
69
  }
56
70
 
57
71
  // Validate syscall name format (should be namespaced)
58
- if (!functionDef.syscall.includes(".")) {
72
+ if (!syscallName.includes(".")) {
59
73
  errors.push(
60
- `Function ${name} has invalid syscall name "${functionDef.syscall}" - must be in format "namespace.name"`,
74
+ `Function ${name} has invalid syscall name "${syscallName}" - must be in format "namespace.name"`,
61
75
  );
62
76
  }
63
77
  }
@@ -3,31 +3,69 @@ import type { FileMeta } from "../../../plug-api/types/index.ts";
3
3
 
4
4
  export default function assetSyscalls(system: System<any>): SysCallMapping {
5
5
  return {
6
- "asset.readAsset": (_ctx, plugName: string, name: string): string => {
7
- return system.loadedPlugs.get(plugName)!.assets!.readFileAsDataUrl(name);
6
+ "asset.readAsset": {
7
+ callback: (_ctx, plugName: string, name: string): string => {
8
+ return system.loadedPlugs
9
+ .get(plugName)!
10
+ .assets!.readFileAsDataUrl(name);
11
+ },
12
+ description: "Reads an asset embedded in a plug as a data URL.",
13
+ parameters: [
14
+ { name: "plugName", type: "string", description: "Plug name." },
15
+ { name: "name", type: "string", description: "Asset path." },
16
+ ],
17
+ returns: [{ type: "string", description: "Asset data URL." }],
18
+ examples: [
19
+ { code: 'local image = asset.readAsset("myplug", "image.png")' },
20
+ ],
8
21
  },
9
- "asset.listFiles": (_ctx, plugName: string): FileMeta[] => {
10
- const assets = system.loadedPlugs.get(plugName)!.assets!;
11
- const fileNames = assets.listFiles();
12
- return fileNames.map((name) => ({
13
- name,
14
- contentType: assets.getMimeType(name),
15
- created: assets.getMtime(name),
16
- lastModified: assets.getMtime(name),
17
- size: -1,
18
- perm: "ro",
19
- }));
22
+ "asset.listFiles": {
23
+ callback: (_ctx, plugName: string): FileMeta[] => {
24
+ const assets = system.loadedPlugs.get(plugName)!.assets!;
25
+ const fileNames = assets.listFiles();
26
+ return fileNames.map((name) => ({
27
+ name,
28
+ contentType: assets.getMimeType(name),
29
+ created: assets.getMtime(name),
30
+ lastModified: assets.getMtime(name),
31
+ size: -1,
32
+ perm: "ro",
33
+ }));
34
+ },
35
+ description: "Lists the assets embedded in a plug.",
36
+ parameters: [
37
+ { name: "plugName", type: "string", description: "Plug name." },
38
+ ],
39
+ returns: [{ type: "table", description: "List of file metadata." }],
40
+ examples: [
41
+ {
42
+ code: 'for _, file in ipairs(asset.listFiles("myplug")) do\n print(file.name)\nend',
43
+ },
44
+ ],
20
45
  },
21
- "asset.getFileMeta": (_ctx, plugName: string, name: string): FileMeta => {
22
- const assets = system.loadedPlugs.get(plugName)!.assets!;
23
- return {
24
- name,
25
- contentType: assets.getMimeType(name),
26
- created: assets.getMtime(name),
27
- lastModified: assets.getMtime(name),
28
- size: -1,
29
- perm: "ro",
30
- };
46
+ "asset.getFileMeta": {
47
+ callback: (_ctx, plugName: string, name: string): FileMeta => {
48
+ const assets = system.loadedPlugs.get(plugName)!.assets!;
49
+ return {
50
+ name,
51
+ contentType: assets.getMimeType(name),
52
+ created: assets.getMtime(name),
53
+ lastModified: assets.getMtime(name),
54
+ size: -1,
55
+ perm: "ro",
56
+ };
57
+ },
58
+ description: "Gets metadata for an asset embedded in a plug.",
59
+ parameters: [
60
+ { name: "plugName", type: "string", description: "Plug name." },
61
+ { name: "name", type: "string", description: "Asset path." },
62
+ ],
63
+ returns: [{ type: "table", description: "File metadata." }],
64
+ examples: [
65
+ {
66
+ code: 'local meta = asset.getFileMeta("myplug", "data.txt")\nprint(meta.lastModified)',
67
+ },
68
+ ],
31
69
  },
32
70
  };
33
71
  }
@@ -8,14 +8,40 @@ export function clientStoreSyscalls(
8
8
  prefix: KvKey = ["client"],
9
9
  ): SysCallMapping {
10
10
  return {
11
- "clientStore.get": (_ctx, key: string): Promise<any> => {
12
- return ds.get([...prefix, key]);
11
+ "clientStore.get": {
12
+ callback: (_ctx, key: string): Promise<any> => {
13
+ return ds.get([...prefix, key]);
14
+ },
15
+ description:
16
+ "Gets a client-specific value from the local key-value store.",
17
+ parameters: [
18
+ { name: "key", type: "string", description: "Key to read." },
19
+ ],
20
+ returns: [{ description: "Stored value, or nil when absent." }],
21
+ examples: [{ code: 'local theme = clientStore.get("theme")' }],
13
22
  },
14
- "clientStore.set": (_ctx, key: string, val: any): Promise<void> => {
15
- return ds.set([...prefix, key], val);
23
+ "clientStore.set": {
24
+ callback: (_ctx, key: string, val: any): Promise<void> => {
25
+ return ds.set([...prefix, key], val);
26
+ },
27
+ description:
28
+ "Stores a client-specific value in the local key-value store.",
29
+ parameters: [
30
+ { name: "key", type: "string", description: "Key to set." },
31
+ { name: "value", description: "Value to store." },
32
+ ],
33
+ examples: [{ code: 'clientStore.set("theme", "dark")' }],
16
34
  },
17
- "clientStore.delete": (_ctx, key: string): Promise<void> => {
18
- return ds.delete([...prefix, key]);
35
+ "clientStore.delete": {
36
+ callback: (_ctx, key: string): Promise<void> => {
37
+ return ds.delete([...prefix, key]);
38
+ },
39
+ description:
40
+ "Deletes a client-specific value from the local key-value store.",
41
+ parameters: [
42
+ { name: "key", type: "string", description: "Key to delete." },
43
+ ],
44
+ examples: [{ code: 'clientStore.delete("theme")' }],
19
45
  },
20
46
  };
21
47
  }
@@ -5,10 +5,15 @@ import { broadcastReload } from "../../components/widget_sandbox_iframe.ts";
5
5
 
6
6
  export function clientCodeWidgetSyscalls(client: Client): SysCallMapping {
7
7
  return {
8
- "codeWidget.refreshAll": () => {
9
- client.widgetCache.clearPrewarm();
10
- broadcastReload();
11
- return reloadAllWidgets();
8
+ "codeWidget.refreshAll": {
9
+ callback: () => {
10
+ client.widgetCache.clearPrewarm();
11
+ broadcastReload();
12
+ return reloadAllWidgets();
13
+ },
14
+ description:
15
+ "Refreshes all code widgets on the current page that support refreshing.",
16
+ examples: [{ code: "codeWidget.refreshAll()" }],
12
17
  },
13
18
  };
14
19
  }
@@ -6,17 +6,32 @@ export function codeWidgetSyscalls(
6
6
  codeWidgetHook: CodeWidgetHook,
7
7
  ): SysCallMapping {
8
8
  return {
9
- "codeWidget.render": (
10
- _ctx,
11
- lang: string,
12
- body: string,
13
- pageName: string,
14
- ): Promise<CodeWidgetContent | null> => {
15
- const langCallback = codeWidgetHook.codeWidgetCallbacks.get(lang);
16
- if (!langCallback) {
17
- throw new Error(`Code widget ${lang} not found`);
18
- }
19
- return langCallback(body, pageName);
9
+ "codeWidget.render": {
10
+ callback: (
11
+ _ctx,
12
+ lang: string,
13
+ body: string,
14
+ pageName: string,
15
+ ): Promise<CodeWidgetContent | null> => {
16
+ const langCallback = codeWidgetHook.codeWidgetCallbacks.get(lang);
17
+ if (!langCallback) {
18
+ throw new Error(`Code widget ${lang} not found`);
19
+ }
20
+ return langCallback(body, pageName);
21
+ },
22
+ description: "Renders code through the widget registered for a language.",
23
+ parameters: [
24
+ { name: "language", type: "string", description: "Widget language." },
25
+ { name: "body", type: "string", description: "Code block body." },
26
+ {
27
+ name: "pageName",
28
+ type: "string",
29
+ description: "Containing page name.",
30
+ },
31
+ ],
32
+ returns: [
33
+ { type: "table", description: "Rendered widget content, or nil." },
34
+ ],
20
35
  },
21
36
  };
22
37
  }
@@ -5,45 +5,161 @@ import { stripFunctions } from "../util.ts";
5
5
 
6
6
  export function configSyscalls(config: Config): SysCallMapping {
7
7
  return {
8
- "config.get": (_ctx, path: string, defaultValue: any) => {
9
- return config.get(path, defaultValue);
8
+ "config.get": {
9
+ callback: (_ctx, path: string, defaultValue: any) => {
10
+ return config.get(path, defaultValue);
11
+ },
12
+ description:
13
+ "Gets a configuration value by path, with dot notation support.",
14
+ parameters: [
15
+ { name: "path", type: "string", description: "Configuration path." },
16
+ {
17
+ name: "defaultValue",
18
+ description: "Value returned when the path is absent.",
19
+ },
20
+ ],
21
+ returns: [{ description: "Configured value or the supplied default." }],
22
+ examples: [{ code: 'local theme = config.get("theme", "light")' }],
10
23
  },
11
- "config.set": (
12
- _ctx,
13
- keyOrValues: string | string[] | Record<string, any>,
14
- value?: any,
15
- ) => {
16
- config.set(keyOrValues as any, value);
24
+ "config.set": {
25
+ callback: (
26
+ _ctx,
27
+ keyOrValues: string | string[] | Record<string, any>,
28
+ value?: any,
29
+ ) => {
30
+ config.set(keyOrValues as any, value);
31
+ },
32
+ description: "Sets one configuration value or multiple values at once.",
33
+ signatures: ["config.set(path, value)", "config.set(values)"],
34
+ parameters: [
35
+ {
36
+ name: "pathOrValues",
37
+ description: "Configuration path or table of values.",
38
+ },
39
+ {
40
+ name: "value",
41
+ description: "Value to set when a path is supplied.",
42
+ optional: true,
43
+ },
44
+ ],
45
+ examples: [
46
+ { code: 'config.set("theme", "dark")' },
47
+ { code: 'config.set({theme = "dark", fontSize = 14})' },
48
+ ],
17
49
  },
18
- "lua:config.setLuaValue": async (
19
- _ctx,
20
- keyOrValues: string | string[] | Record<string, any>,
21
- value?: any,
22
- ) => {
23
- // This is for special cases where we explicitly want to NOT convert a value to a JS value, but maintain its Lua version (mostly for LuaTables) — main use case: metatable
24
- keyOrValues = await luaValueToJS(keyOrValues, LuaStackFrame.lostFrame);
25
- config.set(keyOrValues as any, value);
50
+ "lua:config.setLuaValue": {
51
+ callback: async (
52
+ _ctx,
53
+ keyOrValues: string | string[] | Record<string, any>,
54
+ value?: any,
55
+ ) => {
56
+ // This is for special cases where we explicitly want to NOT convert a value to a JS value, but maintain its Lua version (mostly for LuaTables) — main use case: metatable
57
+ keyOrValues = await luaValueToJS(keyOrValues, LuaStackFrame.lostFrame);
58
+ config.set(keyOrValues as any, value);
59
+ },
60
+ description:
61
+ "Sets configuration while preserving the supplied Lua value representation.",
62
+ signatures: [
63
+ "config.setLuaValue(path, value)",
64
+ "config.setLuaValue(values)",
65
+ ],
66
+ parameters: [
67
+ {
68
+ name: "pathOrValues",
69
+ description: "Configuration path or table of values.",
70
+ },
71
+ {
72
+ name: "value",
73
+ description: "Lua value to preserve.",
74
+ optional: true,
75
+ },
76
+ ],
26
77
  },
27
- "config.insert": (_ctx, key: string | string[], value: any) => {
28
- config.insert(key, value);
78
+ "config.insert": {
79
+ callback: (_ctx, key: string | string[], value: any) => {
80
+ config.insert(key, value);
81
+ },
82
+ description: "Appends a value to the configuration array at a path.",
83
+ parameters: [
84
+ { name: "path", description: "Configuration path." },
85
+ { name: "value", description: "Value to append." },
86
+ ],
29
87
  },
30
- "config.has": (_ctx, path: string) => {
31
- return config.has(path);
88
+ "config.has": {
89
+ callback: (_ctx, path: string) => {
90
+ return config.has(path);
91
+ },
92
+ description: "Checks whether a configuration path exists.",
93
+ parameters: [
94
+ { name: "path", type: "string", description: "Configuration path." },
95
+ ],
96
+ returns: [{ type: "boolean", description: "Whether the path exists." }],
32
97
  },
33
- "config.define": (_ctx, key: string, schema: any) => {
34
- config.define(key, schema);
98
+ "config.define": {
99
+ callback: (_ctx, key: string, schema: any) => {
100
+ config.define(key, schema);
101
+ },
102
+ description: "Defines a JSON schema for a configuration key.",
103
+ parameters: [
104
+ { name: "key", type: "string", description: "Configuration key." },
105
+ {
106
+ name: "schema",
107
+ type: "table",
108
+ description:
109
+ "JSON Schema definition; default applies a missing value and ui annotations expose it in the Configuration Manager.",
110
+ },
111
+ ],
112
+ examples: [
113
+ {
114
+ code: 'config.define("shortWikiLinks", {\n description = "Render short wiki link labels",\n type = "boolean",\n default = true,\n ui = {category = "Editor", label = "Short wiki links", priority = 1},\n})',
115
+ },
116
+ ],
35
117
  },
36
- "config.defineCategory": (_ctx, definition: any) => {
37
- config.defineCategory(definition);
118
+ "config.defineCategory": {
119
+ callback: (_ctx, definition: any) => {
120
+ config.defineCategory(definition);
121
+ },
122
+ description: "Defines or updates a Configuration Manager UI category.",
123
+ parameters: [
124
+ {
125
+ name: "definition",
126
+ type: "table",
127
+ description: "Category name, description, and priority.",
128
+ },
129
+ ],
130
+ examples: [
131
+ {
132
+ code: 'config.defineCategory {\n name = "Editor",\n description = "Page editor behavior.",\n priority = 50,\n}',
133
+ },
134
+ ],
38
135
  },
39
- "config.getValues": () => {
40
- return stripFunctions(config.values);
136
+ "config.getValues": {
137
+ callback: () => {
138
+ return stripFunctions(config.values);
139
+ },
140
+ description: "Gets all configuration values as a single table.",
141
+ returns: [{ type: "table", description: "All configuration values." }],
41
142
  },
42
- "config.getSchemas": () => {
43
- return stripFunctions(config.schemas);
143
+ "config.getSchemas": {
144
+ callback: () => {
145
+ return stripFunctions(config.schemas);
146
+ },
147
+ description: "Gets all defined configuration schemas.",
148
+ returns: [
149
+ {
150
+ type: "table",
151
+ description: "Schemas keyed by configuration path.",
152
+ },
153
+ ],
44
154
  },
45
- "config.getCategories": () => {
46
- return { ...config.categories };
155
+ "config.getCategories": {
156
+ callback: () => {
157
+ return { ...config.categories };
158
+ },
159
+ description: "Gets all Configuration Manager UI categories.",
160
+ returns: [
161
+ { type: "table", description: "Category definitions keyed by name." },
162
+ ],
47
163
  },
48
164
  };
49
165
  }