@vonzio/plugin-api 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.
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Every capability a plugin may declare, as a runtime-iterable tuple.
3
+ * Adding a capability is additive (minor bump); removing/renaming is
4
+ * breaking (major bump). Keep the ordering grouped by domain to match §5.
5
+ */
6
+ export declare const PLUGIN_CAPABILITIES: readonly ["storage.kv", "db.scoped", "db.access", "encryption.encrypt", "encryption.decrypt", "integrations.read.masked", "integrations.read.decrypted", "integrations.write", "profiles.read", "profiles.resolve", "workspaces.read", "workspaces.write", "auth.gate", "presence.register", "tasks.submit", "sessions.register", "sessions.extend", "sessions.setStatus", "sessions.getConnectedIds", "orchestrator.wake", "events.append", "events.read", "events.subscribe", "dashboard.push", "images.rewrite", "models.list", "notifications.channel", "mcp.register", "scheduler.run", "http.outbound", "secrets.mtls"];
7
+ /**
8
+ * The capability union. Derived from the runtime tuple so there is exactly
9
+ * one place to edit. Total count: 31 (asserted by capabilities.test.ts).
10
+ */
11
+ export type PluginCapability = (typeof PLUGIN_CAPABILITIES)[number];
12
+ /** Type guard: is an arbitrary string a known capability? */
13
+ export declare function isPluginCapability(value: string): value is PluginCapability;
14
+ /**
15
+ * How a capability is enforced against its surface.
16
+ * - `property`: the whole surface object is included iff the cap is granted.
17
+ * A property-access Proxy can gate this on its own.
18
+ * - `method`: the cap unlocks specific named methods on a surface shared
19
+ * with other caps (e.g. `core.sessionLifecycle.register`). The membrane
20
+ * assembles the surface method-by-method; a property Proxy can't split it.
21
+ * - `argument`: the cap is distinguished from a sibling cap by a call
22
+ * ARGUMENT, not a method name (integrations masked-vs-decrypted via the
23
+ * `opts.decrypt` flag). The membrane wraps the methods to enforce/clamp
24
+ * the argument.
25
+ */
26
+ export type SurfaceKind = "property" | "method" | "argument";
27
+ export interface CapabilitySurface {
28
+ /**
29
+ * Dotted surface path. `core.*` lives behind the legibility membrane
30
+ * (`ctx.core`); `ctx.*` is a top-level context surface attached by
31
+ * conditional inclusion (membrane only wraps `core`, so non-core
32
+ * surfaces are gated by presence — see §7 + membrane.ts).
33
+ */
34
+ surface: string;
35
+ kind: SurfaceKind;
36
+ /**
37
+ * For `method` / `argument` kinds: the methods on `surface` this cap
38
+ * unlocks. Omitted for `property` (the whole surface is unlocked).
39
+ */
40
+ methods?: readonly string[];
41
+ /** One-line description, mirrors the §5 doc comment. */
42
+ description: string;
43
+ }
44
+ /**
45
+ * Maps every capability to the surface it unlocks. Total `Record` over the
46
+ * union — capabilities.test.ts asserts every member of PLUGIN_CAPABILITIES
47
+ * has an entry so a newly-added capability can't ship ungated.
48
+ *
49
+ * Read-method names for `core.integrations` are shared by the masked and
50
+ * decrypted caps; they differ only by the `opts.decrypt` argument the
51
+ * membrane clamps. `backfillExternalId` is a read-path lazy index helper
52
+ * and travels with the read caps.
53
+ */
54
+ export declare const CAPABILITY_SURFACE_MAP: Record<PluginCapability, CapabilitySurface>;
55
+ /**
56
+ * Capability pairs that are effectively root and are REFUSED for external
57
+ * plugins at load (§3 step 9, §5). Built-ins are exempt. Each inner array
58
+ * is an AND-set: an external plugin declaring all members is refused.
59
+ *
60
+ * `secrets.mtls` is deliberately NOT listed: the cert/key files are
61
+ * operator-provisioned (policy-declared host paths the plugin can't choose)
62
+ * and resolve to an OPAQUE ref the plugin can't read, so it can't exfiltrate
63
+ * the key even alongside `integrations.read.decrypted` — the design-time
64
+ * guarantee that motivates the combos above doesn't apply here.
65
+ */
66
+ export declare const ROOT_EQUIVALENT_COMBINATIONS: ReadonlyArray<readonly PluginCapability[]>;
67
+ /** Capabilities only built-in plugins may declare (§5). */
68
+ export declare const BUILTIN_ONLY_CAPABILITIES: ReadonlySet<PluginCapability>;
69
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +1,286 @@
1
+ // Plugin capability enum + the surface map that routes each capability to
2
+ // the `ctx` / `ctx.core` surface it unlocks. This file is the SINGLE SOURCE
3
+ // OF TRUTH consumed by the loader (to validate manifests + cross-check the
4
+ // operator policy) and by the membrane (to assemble the per-plugin `core`
5
+ // object method-by-method and to decide which non-core `ctx` surfaces to
6
+ // attach). See docs/PLUGIN_LOADER_SPEC.md §5, §7.
7
+ //
8
+ // The runtime array `PLUGIN_CAPABILITIES` is authoritative; the
9
+ // `PluginCapability` type is derived from it so a completeness test can
10
+ // iterate every member and the map can be a total `Record`.
11
+ /**
12
+ * Every capability a plugin may declare, as a runtime-iterable tuple.
13
+ * Adding a capability is additive (minor bump); removing/renaming is
14
+ * breaking (major bump). Keep the ordering grouped by domain to match §5.
15
+ */
16
+ export const PLUGIN_CAPABILITIES = [
17
+ // ── Storage (preferred for new plugins) ──────────────────────────
18
+ "storage.kv",
19
+ // ── Database (use sparingly) ─────────────────────────────────────
20
+ "db.scoped",
21
+ "db.access",
22
+ // ── Crypto ───────────────────────────────────────────────────────
23
+ "encryption.encrypt",
24
+ "encryption.decrypt",
25
+ // ── Integrations ─────────────────────────────────────────────────
26
+ "integrations.read.masked",
27
+ "integrations.read.decrypted",
28
+ "integrations.write",
29
+ // ── Profiles ─────────────────────────────────────────────────────
30
+ "profiles.read",
31
+ "profiles.resolve",
32
+ // ── Workspaces ───────────────────────────────────────────────────
33
+ "workspaces.read",
34
+ "workspaces.write",
35
+ // ── Auth ─────────────────────────────────────────────────────────
36
+ "auth.gate",
37
+ // ── Presence ─────────────────────────────────────────────────────
38
+ "presence.register",
39
+ // ── Tasks / sessions / orchestration ─────────────────────────────
40
+ "tasks.submit",
41
+ "sessions.register",
42
+ "sessions.extend",
43
+ "sessions.setStatus",
44
+ "sessions.getConnectedIds",
45
+ "orchestrator.wake",
46
+ // ── Event log + dashboard push ───────────────────────────────────
47
+ "events.append",
48
+ "events.read",
49
+ "events.subscribe",
50
+ "dashboard.push",
51
+ // ── Chat-surface utilities ───────────────────────────────────────
52
+ "images.rewrite",
53
+ "models.list",
54
+ // ── Plugin-contributed surfaces ──────────────────────────────────
55
+ "notifications.channel",
56
+ "mcp.register",
57
+ "scheduler.run",
58
+ // ── Outbound HTTP ────────────────────────────────────────────────
59
+ "http.outbound",
60
+ // ── Secrets (operator-provisioned material) ──────────────────────
61
+ "secrets.mtls",
62
+ ];
63
+ /** O(1) membership check used by the manifest validator. */
64
+ const CAPABILITY_SET = new Set(PLUGIN_CAPABILITIES);
65
+ /** Type guard: is an arbitrary string a known capability? */
66
+ export function isPluginCapability(value) {
67
+ return CAPABILITY_SET.has(value);
68
+ }
69
+ /**
70
+ * Maps every capability to the surface it unlocks. Total `Record` over the
71
+ * union — capabilities.test.ts asserts every member of PLUGIN_CAPABILITIES
72
+ * has an entry so a newly-added capability can't ship ungated.
73
+ *
74
+ * Read-method names for `core.integrations` are shared by the masked and
75
+ * decrypted caps; they differ only by the `opts.decrypt` argument the
76
+ * membrane clamps. `backfillExternalId` is a read-path lazy index helper
77
+ * and travels with the read caps.
78
+ */
79
+ export const CAPABILITY_SURFACE_MAP = {
80
+ "storage.kv": {
81
+ surface: "ctx.storage",
82
+ kind: "property",
83
+ description: "Per-plugin namespaced key/value store backed by plugin_storage.",
84
+ },
85
+ "db.scoped": {
86
+ surface: "core.db",
87
+ kind: "property",
88
+ description: "Scoped Drizzle handle restricted to manifest.schemaPrefix tables; raw SQL refused.",
89
+ },
90
+ "db.access": {
91
+ surface: "core.db",
92
+ kind: "property",
93
+ description: "Unscoped Drizzle handle + raw SQL. Built-ins only.",
94
+ },
95
+ "encryption.encrypt": {
96
+ surface: "core.encryption",
97
+ kind: "method",
98
+ methods: ["encrypt"],
99
+ description: "Encrypt plugin-owned secrets for persistence.",
100
+ },
101
+ "encryption.decrypt": {
102
+ surface: "core.encryption",
103
+ kind: "method",
104
+ methods: ["decrypt"],
105
+ description: "Decrypt plugin-owned secrets.",
106
+ },
107
+ "integrations.read.masked": {
108
+ surface: "core.integrations",
109
+ kind: "argument",
110
+ methods: [
111
+ "get",
112
+ "getByUserAndType",
113
+ "listByType",
114
+ "listByUserAndType",
115
+ "findByTypeAndExternalId",
116
+ "listByTypeAndExternalId",
117
+ "backfillExternalId",
118
+ ],
119
+ description: "Read integration rows with secrets MASKED (opts.decrypt clamped to false).",
120
+ },
121
+ "integrations.read.decrypted": {
122
+ surface: "core.integrations",
123
+ kind: "argument",
124
+ methods: [
125
+ "get",
126
+ "getByUserAndType",
127
+ "listByType",
128
+ "listByUserAndType",
129
+ "findByTypeAndExternalId",
130
+ "listByTypeAndExternalId",
131
+ "backfillExternalId",
132
+ ],
133
+ description: "Read integration rows with secrets DECRYPTED. Highest-risk read.",
134
+ },
135
+ "integrations.write": {
136
+ surface: "core.integrations",
137
+ kind: "method",
138
+ methods: ["create", "update", "delete"],
139
+ description: "Create / update / delete integration rows.",
140
+ },
141
+ "profiles.read": {
142
+ surface: "core.profiles",
143
+ kind: "property",
144
+ description: "Narrow read-only profile lookup (list / get).",
145
+ },
146
+ "profiles.resolve": {
147
+ surface: "core.profileResolver",
148
+ kind: "property",
149
+ description: "Full ResolvedProfile lookup (credentials, env, setup_commands).",
150
+ },
151
+ "workspaces.read": {
152
+ surface: "core.workspaces",
153
+ kind: "method",
154
+ methods: ["get", "list"],
155
+ description: "Workspace get + list.",
156
+ },
157
+ "workspaces.write": {
158
+ surface: "core.workspaces",
159
+ kind: "method",
160
+ methods: ["update"],
161
+ description: "Workspace mutations (name, starred, archived, tags, model_override).",
162
+ },
163
+ "auth.gate": {
164
+ surface: "core.authHook",
165
+ kind: "property",
166
+ description: "Opt route scopes into the user-auth hook.",
167
+ },
168
+ "presence.register": {
169
+ surface: "core.sessionPresence",
170
+ kind: "property",
171
+ description: "Register a chat-surface presence provider.",
172
+ },
173
+ "tasks.submit": {
174
+ surface: "core.tasks",
175
+ kind: "property",
176
+ description: "Submit new tasks.",
177
+ },
178
+ "sessions.register": {
179
+ surface: "core.sessionLifecycle",
180
+ kind: "method",
181
+ methods: ["register"],
182
+ description: "Register a new session row.",
183
+ },
184
+ "sessions.extend": {
185
+ surface: "core.sessionLifecycle",
186
+ kind: "method",
187
+ methods: ["extendExpiry"],
188
+ description: "Push session expiry forward.",
189
+ },
190
+ "sessions.setStatus": {
191
+ surface: "core.sessionLifecycle",
192
+ kind: "method",
193
+ methods: ["setStatus"],
194
+ description: "Move sessions between status states.",
195
+ },
196
+ "sessions.getConnectedIds": {
197
+ surface: "core.sessionLifecycle",
198
+ kind: "method",
199
+ methods: ["getConnectedSessionIds"],
200
+ description: "Read the set of dashboard-connected session ids.",
201
+ },
202
+ "orchestrator.wake": {
203
+ surface: "core.orchestrator",
204
+ kind: "property",
205
+ description: "Wake a workspace container before submitting a task.",
206
+ },
207
+ "events.append": {
208
+ surface: "core.eventLog",
209
+ kind: "method",
210
+ methods: ["append"],
211
+ description: "Append entries to the dashboard timeline.",
212
+ },
213
+ "events.read": {
214
+ surface: "core.eventLog",
215
+ kind: "method",
216
+ methods: ["read"],
217
+ description: "Read entries from the dashboard timeline.",
218
+ },
219
+ "events.subscribe": {
220
+ surface: "ctx.sessionEvents",
221
+ kind: "property",
222
+ description: "Subscribe to orchestrator session events (task:token, task:done).",
223
+ },
224
+ "dashboard.push": {
225
+ surface: "core.connectionManager",
226
+ kind: "property",
227
+ description: "Push messages to dashboard WebSocket clients.",
228
+ },
229
+ "images.rewrite": {
230
+ surface: "core.imageRewriter",
231
+ kind: "property",
232
+ description: "Strip inline images from agent output.",
233
+ },
234
+ "models.list": {
235
+ surface: "core.modelList",
236
+ kind: "property",
237
+ description: "List models available to a profile.",
238
+ },
239
+ "notifications.channel": {
240
+ surface: "ctx.notificationBus",
241
+ kind: "property",
242
+ description: 'Claim a notification kind ("telegram", "slack", "email").',
243
+ },
244
+ "mcp.register": {
245
+ surface: "ctx.mcpRegistry",
246
+ kind: "property",
247
+ description: "Contribute an MCP server.",
248
+ },
249
+ "scheduler.run": {
250
+ surface: "ctx.scheduler",
251
+ kind: "property",
252
+ description: "Register cron + interval scheduled work.",
253
+ },
254
+ "http.outbound": {
255
+ surface: "ctx.http",
256
+ kind: "property",
257
+ description: "Use ctx.http.fetch. Requires manifest.outboundHosts populated.",
258
+ },
259
+ "secrets.mtls": {
260
+ surface: "ctx.secrets",
261
+ kind: "property",
262
+ description: "Use ctx.secrets.mtls(name) to resolve an operator-provisioned mTLS client cert/key " +
263
+ "(declared in manifest.mtlsSecrets, mapped to host files in policy) into an opaque ref " +
264
+ "for ctx.http.fetch({ mtls }). The plugin never reads the key bytes.",
265
+ },
266
+ };
267
+ /**
268
+ * Capability pairs that are effectively root and are REFUSED for external
269
+ * plugins at load (§3 step 9, §5). Built-ins are exempt. Each inner array
270
+ * is an AND-set: an external plugin declaring all members is refused.
271
+ *
272
+ * `secrets.mtls` is deliberately NOT listed: the cert/key files are
273
+ * operator-provisioned (policy-declared host paths the plugin can't choose)
274
+ * and resolve to an OPAQUE ref the plugin can't read, so it can't exfiltrate
275
+ * the key even alongside `integrations.read.decrypted` — the design-time
276
+ * guarantee that motivates the combos above doesn't apply here.
277
+ */
278
+ export const ROOT_EQUIVALENT_COMBINATIONS = [
279
+ ["integrations.read.decrypted", "db.scoped"],
280
+ ["integrations.read.decrypted", "db.access"],
281
+ ];
282
+ /** Capabilities only built-in plugins may declare (§5). */
283
+ export const BUILTIN_ONLY_CAPABILITIES = new Set([
284
+ "db.access",
285
+ ]);
286
+ //# sourceMappingURL=capabilities.js.map
package/errors.d.ts ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Refusal reason codes emitted in the audit "plugin refused" event (§11).
3
+ * Kept as a runtime tuple so the audit logger + tests can enumerate them.
4
+ */
5
+ export declare const REFUSAL_REASONS: readonly ["manifest_invalid", "api_version_incompatible", "external_db_access", "external_db_scoped_not_opted_in", "external_root_combination", "policy_missing", "policy_hash_mismatch", "policy_capability_drift", "policy_outbound_host_drift", "policy_mtls_secret_drift", "policy_version_mismatch", "unapproved_frontend", "mtls_secret_unreadable", "schema_prefix_invalid", "frontend_path_escape", "backend_path_escape", "package_unresolvable", "absolute_route_denied"];
6
+ export type RefusalReason = (typeof REFUSAL_REASONS)[number];
7
+ /**
8
+ * Thrown by the membrane's `get` trap when a plugin accesses a `core`
9
+ * surface (or a non-core ctx surface) it did not declare + get granted.
10
+ * Throwing at the violation site (rather than returning undefined) names
11
+ * the underlying bug instead of surfacing a confusing downstream TypeError.
12
+ */
13
+ export declare class CapabilityViolationError extends Error {
14
+ readonly plugin: string;
15
+ readonly capability: string | null;
16
+ readonly key: string;
17
+ constructor(args: {
18
+ plugin: string;
19
+ capability: string | null;
20
+ key: string;
21
+ });
22
+ }
23
+ /**
24
+ * Thrown by `ctx.http.fetch` / webSocket / eventSource when the target
25
+ * hostname is not in the manifest∩policy outbound allowlist (§10).
26
+ */
27
+ export declare class OutboundHostViolationError extends Error {
28
+ readonly plugin: string;
29
+ readonly host: string;
30
+ constructor(args: {
31
+ plugin: string;
32
+ host: string;
33
+ });
34
+ }
35
+ /**
36
+ * Thrown by the scoped Drizzle wrapper when a `db.scoped` plugin references
37
+ * a table outside its schema prefix, or attempts raw SQL (§9).
38
+ */
39
+ export declare class DbScopeViolationError extends Error {
40
+ readonly plugin: string;
41
+ readonly schemaPrefix: string;
42
+ readonly offending: string;
43
+ constructor(args: {
44
+ plugin: string;
45
+ schemaPrefix: string;
46
+ offending: string;
47
+ });
48
+ }
49
+ /**
50
+ * Thrown by the loader when a plugin is refused at load. Carries the §11
51
+ * reason code + an operator-facing remediation hint where applicable. The
52
+ * loader catches this per-plugin, emits the refusal audit event, and skips
53
+ * the plugin (boot continues).
54
+ */
55
+ export declare class PluginRefusedError extends Error {
56
+ readonly plugin: string;
57
+ readonly reason: RefusalReason;
58
+ readonly remediation?: string;
59
+ readonly detail?: Record<string, unknown>;
60
+ constructor(args: {
61
+ plugin: string;
62
+ reason: RefusalReason;
63
+ message: string;
64
+ remediation?: string;
65
+ detail?: Record<string, unknown>;
66
+ });
67
+ }
68
+ /** Thrown by the policy validator when the operator policy file is malformed. */
69
+ export declare class PolicyViolationError extends Error {
70
+ constructor(message: string);
71
+ }
72
+ //# sourceMappingURL=errors.d.ts.map
package/errors.js ADDED
@@ -0,0 +1,106 @@
1
+ // Error types thrown by the loader + the runtime membrane/wrappers. Exported
2
+ // from @vonzio/plugin-api so plugin authors and the attack corpus can assert
3
+ // on them by class. See docs/PLUGIN_LOADER_SPEC.md §7, §9, §10, §11.
4
+ /**
5
+ * Refusal reason codes emitted in the audit "plugin refused" event (§11).
6
+ * Kept as a runtime tuple so the audit logger + tests can enumerate them.
7
+ */
8
+ export const REFUSAL_REASONS = [
9
+ "manifest_invalid",
10
+ "api_version_incompatible",
11
+ "external_db_access",
12
+ "external_db_scoped_not_opted_in",
13
+ "external_root_combination",
14
+ "policy_missing",
15
+ "policy_hash_mismatch",
16
+ "policy_capability_drift",
17
+ "policy_outbound_host_drift",
18
+ "policy_mtls_secret_drift",
19
+ "policy_version_mismatch",
20
+ "unapproved_frontend",
21
+ "mtls_secret_unreadable",
22
+ "schema_prefix_invalid",
23
+ "frontend_path_escape",
24
+ "backend_path_escape",
25
+ // Loader-internal refusals not in the §11 enumeration but needed in
26
+ // practice (malformed env entry, unresolvable package, etc.).
27
+ "package_unresolvable",
28
+ "absolute_route_denied",
29
+ ];
30
+ /**
31
+ * Thrown by the membrane's `get` trap when a plugin accesses a `core`
32
+ * surface (or a non-core ctx surface) it did not declare + get granted.
33
+ * Throwing at the violation site (rather than returning undefined) names
34
+ * the underlying bug instead of surfacing a confusing downstream TypeError.
35
+ */
36
+ export class CapabilityViolationError extends Error {
37
+ plugin;
38
+ capability;
39
+ key;
40
+ constructor(args) {
41
+ super(`Plugin "${args.plugin}" accessed "${args.key}" without a granted capability` +
42
+ (args.capability ? ` (needs ${args.capability})` : ""));
43
+ this.name = "CapabilityViolationError";
44
+ this.plugin = args.plugin;
45
+ this.capability = args.capability;
46
+ this.key = args.key;
47
+ }
48
+ }
49
+ /**
50
+ * Thrown by `ctx.http.fetch` / webSocket / eventSource when the target
51
+ * hostname is not in the manifest∩policy outbound allowlist (§10).
52
+ */
53
+ export class OutboundHostViolationError extends Error {
54
+ plugin;
55
+ host;
56
+ constructor(args) {
57
+ super(`Plugin "${args.plugin}" attempted outbound request to disallowed host "${args.host}"`);
58
+ this.name = "OutboundHostViolationError";
59
+ this.plugin = args.plugin;
60
+ this.host = args.host;
61
+ }
62
+ }
63
+ /**
64
+ * Thrown by the scoped Drizzle wrapper when a `db.scoped` plugin references
65
+ * a table outside its schema prefix, or attempts raw SQL (§9).
66
+ */
67
+ export class DbScopeViolationError extends Error {
68
+ plugin;
69
+ schemaPrefix;
70
+ offending;
71
+ constructor(args) {
72
+ super(`Plugin "${args.plugin}" (schema "${args.schemaPrefix}") attempted to access "${args.offending}" outside its scope`);
73
+ this.name = "DbScopeViolationError";
74
+ this.plugin = args.plugin;
75
+ this.schemaPrefix = args.schemaPrefix;
76
+ this.offending = args.offending;
77
+ }
78
+ }
79
+ /**
80
+ * Thrown by the loader when a plugin is refused at load. Carries the §11
81
+ * reason code + an operator-facing remediation hint where applicable. The
82
+ * loader catches this per-plugin, emits the refusal audit event, and skips
83
+ * the plugin (boot continues).
84
+ */
85
+ export class PluginRefusedError extends Error {
86
+ plugin;
87
+ reason;
88
+ remediation;
89
+ detail;
90
+ constructor(args) {
91
+ super(args.message);
92
+ this.name = "PluginRefusedError";
93
+ this.plugin = args.plugin;
94
+ this.reason = args.reason;
95
+ this.remediation = args.remediation;
96
+ this.detail = args.detail;
97
+ }
98
+ }
99
+ /** Thrown by the policy validator when the operator policy file is malformed. */
100
+ export class PolicyViolationError extends Error {
101
+ constructor(message) {
102
+ super(message);
103
+ this.name = "PolicyViolationError";
104
+ }
105
+ }
106
+ //# sourceMappingURL=errors.js.map
package/frontend.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The shape a plugin's frontend entry point must default-export from
3
+ * its `/frontend` module. Called once during dashboard boot. The
4
+ * plugin imports the dashboard registry directly (e.g.
5
+ * `import { registerSettingsSection } from "@vonzio/dashboard-registry/api"`)
6
+ * and calls whichever register* methods it needs.
7
+ *
8
+ * Errors thrown here are caught by the dashboard's plugin loader and
9
+ * surface as a console warning -- the rest of the dashboard keeps
10
+ * rendering.
11
+ */
12
+ export type PluginFrontendEntry = () => void;
13
+ //# sourceMappingURL=frontend.d.ts.map
package/frontend.js ADDED
@@ -0,0 +1,16 @@
1
+ // Frontend half of the plugin contract. v0.1 is intentionally minimal:
2
+ // a plugin's frontend entry is just a default-exported function called
3
+ // once at dashboard boot. The plugin registers whatever UI it needs by
4
+ // importing from `@vonzio/dashboard-registry/api` directly -- that registry
5
+ // already supports settings sections, nav items, topbar slots, composer
6
+ // slots, workspace header slots, onboarding steps, and routes, so
7
+ // plugins don't need a separate slot taxonomy.
8
+ //
9
+ // This file deliberately defines no slot enum and no DashboardSlots
10
+ // map: those would either duplicate the dashboard registry's types or
11
+ // drift from them. The dashboard registry IS the truth.
12
+ //
13
+ // Type-only react import keeps backend-only plugins from acquiring a
14
+ // runtime react dep.
15
+ export {};
16
+ //# sourceMappingURL=frontend.js.map