@lovision/plugin-spec
v1.1.0
Published
Single source of truth for the Instinct plugin RPC contract.
Readme
@lovision/plugin-spec
Single source of truth for the Instinct plugin RPC contract.
Published from the main branch through npm Trusted Publishing.
Owns:
Frameenvelope types (ADR-001) shared by@lovision/plugin-hostand@lovision/plugin-sdkRpcErrorCode+PluginError(8 fixed codes, V2 spec §10.9.3)defineCapability/Capability<P, R>for declaring methods (ADR-002)generateDispatchfor turning a capability table into a runtime dispatcher with zod validation on both ends- Sample capabilities
ping/echoexercised by Step 1 tests
Minimal usage
import { z } from "zod";
import { defineCapability, generateDispatch } from "@lovision/plugin-spec";
const NodesUpdate = defineCapability({
method: "nodes.update",
permission: ["document:write.style"],
params: z.object({ updates: z.array(z.object({ id: z.string() })) }),
result: z.object({ newVersion: z.number() }),
});
const dispatch = generateDispatch(
{ update: NodesUpdate },
{
update: async ({ updates }) => ({ newVersion: updates.length }),
},
);
await dispatch("nodes.update", { updates: [{ id: "n0" }] });Manifest schema (Step 2)
./manifest/ owns the zod schema for manifest.json (V2 spec §9 + ADR-004)
and the Bundle V1 envelope:
import {
ManifestSchema,
BundleV1Schema,
resolveLocalizedString,
expandPermission,
groupPermissionsForDisplay,
HOST_API_VERSION,
} from "@lovision/plugin-spec";
const manifest = ManifestSchema.parse({
id: "com.example.demo",
name: { en: "Demo", "zh-CN": "演示" },
apiVersion: "1.0",
editorType: ["design"],
main: "dist/main.js",
documentAccess: "current-page",
permissions: ["document:read", "document:write.style"],
commands: [{ id: "run", name: { en: "Run", "zh-CN": "运行" } }],
});
resolveLocalizedString(manifest.name, "zh-CN"); // "演示"
expandPermission("document:write"); // 6 fine-grained scopes
groupPermissionsForDisplay([
"document:write.style",
"document:write.text",
"network:fetch",
"domain:api.example.com",
]);
// => [{ id: "document-write", ... }, { id: "network", ... }]Semantic checks (apiVersion compatibility, command id uniqueness, permission
sugar dedupe, description truncation) live in
@lovision/plugin-host/manifest-loader so diagnostics get stable codes
across zod versions.
Facade capabilities (Step 3)
./facade-capabilities/ defines the first wave of host-facade RPC methods
(V2 spec §10.2), grouped under HostFacadeCapabilities:
| defineCapability export | method | permission | direction |
| ------------------------- | ------------------- | ----------------------- | --------------- |
| SelectionGet | selection.get | selection:read | worker → host |
| SelectionSet | selection.set | selection:write | worker → host |
| SelectionClear | selection.clear | selection:write | worker → host |
| SelectionCount | selection.count | selection:read | worker → host |
| NodesUpdate | nodes.update | document:write.style | worker → host |
| DocumentSnapshot | document.snapshot | document:read | worker → host |
| StorageLocalGet/Set/Delete | storage.local.* | storage:local | worker → host |
| StorageDocumentGet/Set/Delete | storage.document.* | storage:document | worker → host |
| StorageNodeGet/Set/Delete | storage.node.* | storage:node | worker → host |
| Notify | notify | notify | worker → host |
| ClosePlugin | closePlugin | (none) | worker → host |
| RunCommand | __runCommand | (none, internal) | host → worker |
nodes.update (Step 4) accepts the full style set (opacity, visible,
locked, name, rotation, blendMode, fills, strokes) plus an
optional expectedVersion for the WRITE_CONFLICT handshake. Position /
size / parent / type land in Step 9.
document.snapshot (Step 4) returns a frozen SceneSnapshot tree
({ pageId, root, version, takenAt }). Per-node-type fields live in
extraFields: Record<string, unknown> passthrough (ADR-005). Step 5 also
adds JSONValueSchema for storage.* methods and QuotaExceededDataSchema
for quota-aware error handling. WriteConflictDataSchema is also exported
from here for SDK error handlers:
import {
DocumentSnapshot,
type SceneSnapshot,
WriteConflictDataSchema,
} from "@lovision/plugin-spec";Step 6 extends WriteConflictDataSchema to stay backward-compatible with
the Step 4 payload while adding node-granular diagnostics:
WriteConflictDataSchema.parse({
currentVersion: 8,
expectedVersion: 6,
conflictingNodes: ["n1"],
missing: ["n2"],
});generateDispatch now carries an optional context generic so facade impls
get a typed (params, ctx) callback while Step 1 callers stay
zero-argument:
import { generateDispatch, HostFacadeCapabilities } from "@lovision/plugin-spec";
const dispatch = generateDispatch(
HostFacadeCapabilities,
hostFacadeImpls, // ImplsFor<typeof HostFacadeCapabilities, FacadeContext>
facadeContext, // injected per-invoke by PluginManager
);Step 1 scope (capability spec)
Step 1's defineCapability only consumes params / result for runtime
validation. Step 3 wires real impls + permission gates in the host;
Step 11 will add admin overrides on top. SDK type emit and MCP tool emit
(ADR-002) are still deferred to later steps.
