Build a Multilo extension.
A Multilo extension runs inside the desktop app’s extension host and programs against the global multilo API — the same way the built-in grammar, style, and citation tools do.
Anatomy
An extension is two things:
- A manifest (
manifest.json) declaring identity, what it contributes, and when it activates. - A module (your
package.entryPoint) exportingactivate(context)and optionallydeactivate().
Your first extension
/// <reference types="@multilo/extension-api" />
export function activate(context: ExtensionContext): void {
context.subscriptions.push(
multilo.commands.registerCommand("acme.hello", () =>
multilo.window.showMessage("info", "Hello from my extension!"),
),
);
}
export function deactivate(): void {}Everything you register goes on context.subscriptions so it is disposed when your extension deactivates.
Contribution points
- diagnostics — register a provider returning offset-based
Diagnostic[]; the host renders them and offers one-click fixes when you setreplacement. - commands — register handlers with
multilo.commands.registerCommand; invoke them withexecuteCommand. - configuration— typed settings with defaults; users edit them in your extension’s Settings panel, and you read them with
multilo.workspace.getConfiguration().get(key, fallback).
multilo.languages.registerDiagnosticProvider(
{ languages: ["markdown", "plaintext"], filePatterns: ["*.md", "*.txt"] },
{
provideDiagnostics(doc): Diagnostic[] {
const out: Diagnostic[] = [];
for (const m of doc.getText().matchAll(/\bTODO\b/g)) {
out.push({
kind: "todo",
offset: m.index ?? 0,
length: m[0].length,
message: "unresolved TODO.",
severity: multilo.DiagnosticSeverity.Info,
ruleId: "acme-todo",
suggestion: "Resolve or remove before publishing.",
});
}
return out;
},
},
);Activation events
Extensions are lazy by default. Declare activationEvents:
-
onStartupFinished— shortly after launch -
onLanguage:markdown— when a matching document opens -
onCommand:<id>— when one of your commands runs
The manifest
Validated by the JSON schema (the $schema line gives you autocomplete in most editors):
{
"$schema": "https://www.multilo.com/schemas/extension-manifest.schema.json",
"manifestVersion": 1,
"extensionId": "acme.todo",
"publisher": "acme",
"name": "todo",
"displayName": "TODO finder",
"description": "Flags unresolved TODOs.",
"version": "0.1.0",
"runtime": "web_worker",
"permissions": ["workspace.readText", "diagnostics.provide", "commands.register"],
"activationEvents": ["onLanguage:markdown", "onCommand:acme.hello"],
"engines": { "multilo": "^2.0.0" },
"package": { "entryPoint": "dist/extension.js", "packageSizeKb": 0 },
"capabilities": [
{ "kind": "diagnostics", "title": "TODO finder",
"languages": ["markdown"], "filePatterns": ["*.md"], "diagnosticKinds": [] }
],
"contributes": {
"commands": [{ "command": "acme.hello", "title": "Acme: Hello" }]
}
}Permissions & sandbox
Extensions are sandboxed (Web Worker) and deny-by-default: the host only honors an API call if the matching permission is declared in permissions (e.g. workspace.readText, diagnostics.provide). Privileged runtimes (node, process.spawn, network.*) are gated to first-party / verified publishers.
Packaging & publishing
- Build your
entryPoint(e.g.dist/extension.js). - Zip the package; compute its SHA-256 and size; fill in
package.packageUrl,packageSha256,packageSizeKb. - Submit it through the Multilo marketplace for review.
The @multilo/extension-api package ships the type definitions, the manifest schema, and a Hello-World template to copy.