@mutka-explorer/module 1.0.0-rc.2

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 (3) hide show
  1. package/README.md +72 -0
  2. package/index.d.ts +737 -0
  3. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # @mutka-explorer/module
2
+
3
+ TypeScript types for authoring [Mutka](https://github.com/ilianAZZ/mutka) modules.
4
+
5
+ A Mutka module is a single self-contained ESM file that
6
+ `export default`s a module definition. It **imports nothing at runtime** — it
7
+ reaches the system only through the `host` object passed to `setup(host)`, and
8
+ every `host.*` call is checked against the permissions it declares.
9
+
10
+ This package ships **only type definitions** (`.d.ts`, zero runtime code). You
11
+ `import type` from it, TypeScript erases the import at compile time, and your
12
+ built `index.js` stays self-contained — exactly what Mutka loads.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm i -D @mutka-explorer/module
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```ts
23
+ import type { SandboxModuleDef } from "@mutka-explorer/module";
24
+
25
+ const mod: SandboxModuleDef = {
26
+ id: "you.hello",
27
+ name: "Hello",
28
+ version: "1.0.0",
29
+ permissions: ["fs:read"],
30
+ commands: [
31
+ { id: "you.hello.count", label: "Count items", contextMenu: true, when: { selection: "any" } },
32
+ ],
33
+ setup(host) {
34
+ host.onCommand("you.hello.count", async (snap) => {
35
+ const items = await host.fs.readDir(snap.currentDirectory);
36
+ host.log(`${items.length} items`);
37
+ });
38
+ },
39
+ };
40
+
41
+ export default mod;
42
+ ```
43
+
44
+ `host` is fully typed (`host.fs`, `host.ui`, `host.net`, `host.dialog`, …), as
45
+ are permissions, `when` clauses, the declarative `UINode` tree, and `FormSchema`.
46
+
47
+ ### Build to a single file
48
+
49
+ Mutka loads one ESM file with the default export intact. Bundle your TypeScript
50
+ (and any pure-JS dependencies) down to one `index.js`, e.g. with
51
+ [tsup](https://tsup.egoist.dev/):
52
+
53
+ ```bash
54
+ tsup src/index.ts --format esm --bundle
55
+ ```
56
+
57
+ > The module runs in a Web Worker with **no DOM and no native network**
58
+ > (`fetch`/`XMLHttpRequest`/`WebSocket` are blocked). Use `host.net` for HTTP.
59
+ > Pure-logic libraries bundle fine; DOM- or network-dependent ones do not.
60
+
61
+ ## What's exported
62
+
63
+ Types only: `SandboxModuleDef`, `SandboxHostApi` (the `host`), `ModulePermission`,
64
+ `SandboxCommand`, `SandboxOpenHandler`, `WhenClause`, `UINode`, `FormSchema`,
65
+ `FileItem`, `HostSnapshot`, the contribution shapes (panels, columns, status-bar,
66
+ file icons, discovery sources), and their supporting types.
67
+
68
+ ## Versioning
69
+
70
+ The package version tracks the Mutka app release it was generated from, so the
71
+ types always match a shipped host API. See the app's `docs/` for the full module
72
+ architecture.
package/index.d.ts ADDED
@@ -0,0 +1,737 @@
1
+ // Type definitions for @mutka-explorer/module v1.0.0-rc.2
2
+ // Mutka module SDK — author-facing types only (no runtime code).
3
+ // Generated from the Mutka app source; do not edit by hand.
4
+
5
+ /**
6
+ * Permissions a module may request. A module must declare every capability it
7
+ * uses; the gateway (core/sandbox/gateway.ts) denies any capability whose
8
+ * permission is absent from the module's manifest.
9
+ */
10
+ export type ModulePermission = "fs:read" | "fs:write" | "fs:temp" | "clipboard:read" | "clipboard:write" | "navigation" | "view" | "dialog" | "network:public" | "network:local" | "storage" | "secrets" | "ui" | "discovery" | "shell";
11
+ /**
12
+ * A declarative entry a module contributes to the left "Places" sidebar.
13
+ * Serializable — crosses the worker boundary. Clicking navigates to `path`
14
+ * or runs `command` (provide one).
15
+ */
16
+ export interface SidebarItem {
17
+ id: string;
18
+ label: string;
19
+ /** Icon registry key (e.g. "cloud", "folder") or an emoji. */
20
+ icon?: string;
21
+ /** Group header. Use SidebarCategories or any custom string. */
22
+ category?: string;
23
+ /** Navigate here when clicked. */
24
+ path?: string;
25
+ /** Or run this command id when clicked. */
26
+ command?: string;
27
+ /**
28
+ * Show a remove (✕) affordance. Clicking it emits "sidebar:item-remove" with
29
+ * this item's id — the owning module listens and updates its dynamic items.
30
+ */
31
+ removable?: boolean;
32
+ }
33
+ /** A single entry returned by the Rust `read_dir` command. */
34
+ export interface FileItem {
35
+ /** Filename without path, e.g. "document.pdf" */
36
+ name: string;
37
+ /** Absolute POSIX path, e.g. "/Users/ilian/Documents/document.pdf" */
38
+ path: string;
39
+ /** True if this entry is a directory */
40
+ isDir: boolean;
41
+ /** File size in bytes. 0 for directories. */
42
+ size: number;
43
+ /** Last-modified timestamp in UNIX seconds */
44
+ modified: number;
45
+ /** Lowercase extension without dot, e.g. "pdf". Undefined for dirs or extensionless files. */
46
+ extension?: string;
47
+ /** True for dotfiles (name starts with "."). The UI dims these like Finder. */
48
+ isHidden: boolean;
49
+ /**
50
+ * True if this entry is a symbolic link. `isDir`/`size`/`modified` reflect the
51
+ * link's TARGET (Rust follows it), so a link to a folder navigates in-app.
52
+ */
53
+ isSymlink: boolean;
54
+ /**
55
+ * True if this directory is a macOS package/bundle (.app, .bundle, …). Though
56
+ * `isDir` is true, the UI launches it like a file and shows its real icon
57
+ * instead of navigating into it.
58
+ */
59
+ isPackage: boolean;
60
+ /**
61
+ * True if this folder carries a custom Finder icon (an "Icon\r" file). Still
62
+ * navigable, but the UI shows its real icon instead of the generic folder one.
63
+ */
64
+ hasCustomIcon: boolean;
65
+ }
66
+ export interface ClipboardState {
67
+ /** Items currently held in the clipboard */
68
+ items: FileItem[];
69
+ /** Whether items were copied or cut. Null when clipboard is empty. */
70
+ operation: "copy" | "cut" | null;
71
+ }
72
+ /** An application able to open a file — returned by the `sys.appsForFile` capability. */
73
+ export interface AppInfo {
74
+ /** Display name, e.g. "Visual Studio Code". */
75
+ name: string;
76
+ /** Absolute bundle path, e.g. "/Applications/Visual Studio Code.app". */
77
+ path: string;
78
+ /** The app's icon as a base64 PNG data-URI (empty if unavailable). */
79
+ icon: string;
80
+ /** True for the app macOS would use by default for this file. */
81
+ isDefault: boolean;
82
+ }
83
+ declare const MENU_ZONES: readonly [
84
+ "file",
85
+ "background",
86
+ "breadcrumb",
87
+ "sidebar"
88
+ ];
89
+ export type MenuZone = (typeof MENU_ZONES)[number];
90
+ export type WhenSelection = "any" | "none" | "some" | "single" | "multiple" | "singleDir" | "singleFile" | "files" | "dirs";
91
+ export interface WhenClause {
92
+ selection?: WhenSelection;
93
+ /** Gate on clipboard contents (for a Paste command). */
94
+ clipboard?: "hasItems";
95
+ /** Only when EVERY selected item's file name is one of these (e.g. ["index.js"]
96
+ * to show an action only on module files). Case-sensitive. */
97
+ fileNames?: string[];
98
+ /** Only when EVERY selected item's extension is one of these (lowercased, no dot,
99
+ * e.g. ["js"]). */
100
+ extensions?: string[];
101
+ }
102
+ export interface SandboxCommand {
103
+ id: string;
104
+ label: string;
105
+ icon?: string;
106
+ shortcut?: string;
107
+ contextMenu?: boolean;
108
+ contextMenuCategory?: string;
109
+ /**
110
+ * Which UI regions this command appears in when right-clicked. Omit to use the
111
+ * default (file rows + empty file-list background). Use e.g. ["breadcrumb"] to
112
+ * target the path bar, or ["sidebar"] for a panel. See core/menu/menuZone.ts.
113
+ */
114
+ contextMenuZones?: MenuZone[];
115
+ when?: WhenClause;
116
+ }
117
+ export interface SandboxOpenMatch {
118
+ isDir?: boolean;
119
+ extensions?: string[];
120
+ /** Match macOS packages/bundles (.app, …). Use `false` to exclude them. */
121
+ isPackage?: boolean;
122
+ }
123
+ export interface SandboxOpenHandler {
124
+ id: string;
125
+ priority?: number;
126
+ match: SandboxOpenMatch;
127
+ handler: string;
128
+ }
129
+ /** Gate a column on the current directory. Omit a field to not constrain it. */
130
+ export interface ColumnDirMatch {
131
+ /** Show only under these path prefixes (a leading "~" is expanded host-side). */
132
+ pathPrefixes?: string[];
133
+ /** …or when the directory path contains one of these substrings. */
134
+ pathContains?: string[];
135
+ }
136
+ /** What a single cell renders — plain data, shown via text / <img src> only. */
137
+ export interface ColumnCell {
138
+ /** Primary text of the cell. */
139
+ text?: string;
140
+ /** A data:image/...;base64 icon (injection-safe, rendered via <img src>). */
141
+ icon?: string;
142
+ /** Text colour — MUST be a `var(--...)` token; anything else is dropped. */
143
+ tint?: string;
144
+ /** A short pill (e.g. "⤓", "3"). */
145
+ badge?: string;
146
+ }
147
+ export interface ColumnContribution {
148
+ /** Unique column id, e.g. "com.exif.dimensions". */
149
+ id: string;
150
+ /** Header label. */
151
+ label: string;
152
+ /** Default width in px (defaults to 100). */
153
+ width?: number;
154
+ /** Cell alignment. */
155
+ align?: "start" | "end";
156
+ /** Directory-level gate. Omit to show in every directory. */
157
+ dirMatch?: ColumnDirMatch;
158
+ /** Item-level gate. Omit to compute a value for every item. */
159
+ cellMatch?: SandboxOpenMatch;
160
+ }
161
+ export interface FileIconContribution {
162
+ /** Extensions (without dot, case-insensitive) this icon applies to, e.g. ["pdf"]. */
163
+ extensions: string[];
164
+ /** The icon image as a data:image/...;base64,... URI. */
165
+ image: string;
166
+ }
167
+ /** Text emphasis for a `text` node. */
168
+ export type UITextWeight = "normal" | "medium" | "bold";
169
+ /** Relative text size for a `text` node. */
170
+ export type UITextSize = "sm" | "md" | "lg";
171
+ /** Visual intent for a `button` node. */
172
+ export type UIButtonVariant = "default" | "primary" | "danger";
173
+ /** One row in a `list` node — clicking it fires `action` with `value` (or `id`). */
174
+ export interface UIListItem {
175
+ id: string;
176
+ label: string;
177
+ detail?: string;
178
+ /** Icon registry key or emoji. */
179
+ icon?: string;
180
+ /** Text colour — MUST be a `var(--…)` token; anything else is dropped. */
181
+ tint?: string;
182
+ /** UI-event handler id fired on click (registered via host.onUIEvent). */
183
+ action?: string;
184
+ /** Payload passed to the handler; defaults to the item's `id`. */
185
+ value?: unknown;
186
+ }
187
+ /** A single declarative UI node. Rendered host-side, never as innerHTML. */
188
+ export type UINode = {
189
+ type: "vstack";
190
+ gap?: number;
191
+ children: UINode[];
192
+ } | {
193
+ type: "hstack";
194
+ gap?: number;
195
+ align?: "start" | "center" | "end";
196
+ children: UINode[];
197
+ } | {
198
+ type: "text";
199
+ text: string;
200
+ tint?: string;
201
+ weight?: UITextWeight;
202
+ size?: UITextSize;
203
+ muted?: boolean;
204
+ } | {
205
+ type: "row";
206
+ label: string;
207
+ value?: string;
208
+ icon?: string;
209
+ } | {
210
+ type: "button";
211
+ label: string;
212
+ action: string;
213
+ icon?: string;
214
+ variant?: UIButtonVariant;
215
+ value?: unknown;
216
+ } | {
217
+ type: "list";
218
+ items: UIListItem[];
219
+ } | {
220
+ type: "badge";
221
+ text: string;
222
+ tint?: string;
223
+ } | {
224
+ type: "icon";
225
+ name: string;
226
+ } | {
227
+ type: "divider";
228
+ } | {
229
+ type: "spacer";
230
+ size?: number;
231
+ }
232
+ /** An image — `src` MUST be a data:image/… URI (rendered via <img src> only). */
233
+ | {
234
+ type: "image";
235
+ src: string;
236
+ alt?: string;
237
+ }
238
+ /** A form built from a JSON-Schema subset; submitting fires `action` with the values object. */
239
+ | {
240
+ type: "form";
241
+ schema: FormSchema;
242
+ action: string;
243
+ submitLabel?: string;
244
+ };
245
+ /** Renders as: text input, number input, checkbox, or (with `enum`) a select. */
246
+ export interface FormProperty {
247
+ type: "string" | "number" | "integer" | "boolean";
248
+ title?: string;
249
+ description?: string;
250
+ default?: string | number | boolean;
251
+ /** Allowed values → rendered as a select. */
252
+ enum?: string[];
253
+ /** Optional labels parallel to `enum` (falls back to the value). */
254
+ enumLabels?: string[];
255
+ /** String rendering hint. `textarea` → multiline, `password` → masked. */
256
+ format?: "password" | "textarea" | "email" | "path";
257
+ minimum?: number;
258
+ maximum?: number;
259
+ minLength?: number;
260
+ maxLength?: number;
261
+ }
262
+ export interface FormSchema {
263
+ type: "object";
264
+ properties: Record<string, FormProperty>;
265
+ /** Keys that must be non-empty before the form can submit. */
266
+ required?: string[];
267
+ }
268
+ export interface PanelContribution {
269
+ /** Surface id — render into it via host.ui.render(id, node). */
270
+ id: string;
271
+ /** Tab tooltip / accessible label. */
272
+ title: string;
273
+ /** Icon registry key or emoji shown in the sidebar tab strip. */
274
+ icon: string;
275
+ /** Preferred edge. Defaults to "right". */
276
+ side?: "left" | "right";
277
+ /** Default panel width in px (clamped 180–480). */
278
+ defaultWidth?: number;
279
+ }
280
+ export interface SettingsSectionContribution {
281
+ /** Surface id — render into it via host.ui.render(id, node). */
282
+ id: string;
283
+ /** Section header shown in the Settings panel. */
284
+ title: string;
285
+ }
286
+ /** What happens when a status-bar item is clicked. */
287
+ export type StatusBarAction = {
288
+ command: string;
289
+ } | {
290
+ popover: string;
291
+ };
292
+ export interface StatusBarItem {
293
+ /** Unique within the owning module. */
294
+ id: string;
295
+ text?: string;
296
+ /** Icon registry key or emoji. */
297
+ icon?: string;
298
+ /** Text/icon colour — MUST be a `var(--…)` token; anything else is dropped. */
299
+ tint?: string;
300
+ /** A short pill (e.g. "↑2", "3"). */
301
+ badge?: string;
302
+ tooltip?: string;
303
+ /** Which end of the bar. Defaults to "right". */
304
+ side?: "left" | "right";
305
+ onClick?: StatusBarAction;
306
+ }
307
+ /** Who made a module, for display in the Modules UI. All fields optional. */
308
+ export interface ModuleAuthor {
309
+ /** Display name shown on the card. */
310
+ name?: string;
311
+ /** GitHub login (user or org). Drives the avatar + profile link. When the
312
+ * module is installed from a GitHub repo and this is omitted, the catalog
313
+ * defaults it to the repo owner. */
314
+ github?: string;
315
+ }
316
+ interface SandboxManifest {
317
+ id: string;
318
+ name: string;
319
+ version: string;
320
+ description?: string;
321
+ /** Display image: a `data:image/...` URI or an `https://` URL. Rendered via
322
+ * <img src> only, so it is injection-safe. */
323
+ icon?: string;
324
+ /** Author shown in the Modules UI (avatar + profile link). */
325
+ author?: ModuleAuthor;
326
+ /** Free-form tags for discovery filtering (e.g. ["files", "git"]). */
327
+ tags?: string[];
328
+ permissions: ModulePermission[];
329
+ commands: SandboxCommand[];
330
+ openHandlers: SandboxOpenHandler[];
331
+ /** Declarative left-sidebar entries. */
332
+ sidebarItems: SidebarItem[];
333
+ /** URI schemes this module provides a virtual file system for. */
334
+ fileSystemProviders: string[];
335
+ /** File-type icon overrides (by extension) this module contributes. */
336
+ fileIcons: FileIconContribution[];
337
+ /** Custom list-view columns this module contributes. */
338
+ columns: ColumnContribution[];
339
+ /** Declarative side-pane panels this module contributes. */
340
+ panels: PanelContribution[];
341
+ /** Declarative settings sections this module contributes. */
342
+ settingsSections: SettingsSectionContribution[];
343
+ /** Module-discovery sources this module contributes (served over host RPC). */
344
+ discoverySources: DiscoverySourceDecl[];
345
+ /** Buttons this module adds to the Modules overlay (Browse tab). */
346
+ moduleManagerButtons: ModuleManagerButton[];
347
+ }
348
+ /** A button a module contributes to the Modules overlay. Clicking it runs the
349
+ * module's host.onUIEvent(id, …) handler (no payload). */
350
+ export interface ModuleManagerButton {
351
+ id: string;
352
+ label: string;
353
+ /** Optional icon key from the icon registry (unknown keys render as nothing). */
354
+ icon?: string;
355
+ }
356
+ /** A discovery source a module declares (and serves via host.onDiscover/onFetchSource). */
357
+ export interface DiscoverySourceDecl {
358
+ id: string;
359
+ label: string;
360
+ }
361
+ /** Serializable snapshot of app state handed to a command when it runs. */
362
+ export interface HostSnapshot {
363
+ selectedItems: FileItem[];
364
+ /** The current directory's visible items, in display order (sorted/filtered). */
365
+ orderedItems: FileItem[];
366
+ currentDirectory: string;
367
+ clipboard: ClipboardState;
368
+ }
369
+ /** The file-system provider operations a module may implement (mirrors hostProxy). */
370
+ export type ProviderMethod = "list" | "openFile" | "createFolder" | "createFile" | "deleteItem" | "renameItem" | "copyFiles" | "moveFiles";
371
+ /** The discovery operations a module may implement (mirrors hostProxy). */
372
+ export type DiscoveryMethod = "discover" | "fetchSource";
373
+ /** Author display info, resolved to concrete avatar/profile URLs by a source. */
374
+ export interface CatalogAuthor {
375
+ name?: string;
376
+ /** GitHub login (user or org). */
377
+ github?: string;
378
+ avatarUrl?: string;
379
+ profileUrl?: string;
380
+ }
381
+ /**
382
+ * A module surfaced by a discovery source — metadata only, no code yet. The core
383
+ * fetches the source (via the source's `fetchSource(ref)`) and probes it at
384
+ * install. `permissions` are advisory (filtering/preview); the authoritative set
385
+ * comes from probing the fetched source.
386
+ */
387
+ export interface ModuleListing {
388
+ /** Discovery source that surfaced this (its declared `id`). */
389
+ sourceId: string;
390
+ /** Opaque, source-encoded locator passed back to `fetchSource`. */
391
+ ref: string;
392
+ id: string;
393
+ name: string;
394
+ version: string;
395
+ description?: string;
396
+ /** Display image (data: URI or https URL). */
397
+ icon?: string;
398
+ author?: CatalogAuthor | null;
399
+ permissions: ModulePermission[];
400
+ tags?: string[];
401
+ /** External page for the module (repo URL, etc.). */
402
+ homepageUrl?: string;
403
+ }
404
+ /** Filters + pagination passed to a discovery source. */
405
+ export interface DiscoveryQuery {
406
+ text?: string;
407
+ permissions?: ModulePermission[];
408
+ tags?: string[];
409
+ extension?: string;
410
+ /** 1-based page number. */
411
+ page?: number;
412
+ perPage?: number;
413
+ }
414
+ /** A page of discovery results. */
415
+ export interface DiscoveryResult {
416
+ listings: ModuleListing[];
417
+ /** Next page number, or undefined when there are no more results. */
418
+ nextPage?: number;
419
+ }
420
+ export type CommandHandler = (snapshot: HostSnapshot) => void | Promise<void>;
421
+ export type OpenHandler = (item: FileItem) => void | Promise<void>;
422
+ export type ColumnProvider = (item: FileItem) => ColumnCell | null | Promise<ColumnCell | null>;
423
+ export type EventHandler = (payload: unknown) => void;
424
+ export type ListHandler = (path: string) => FileItem[] | Promise<FileItem[]>;
425
+ export type OpenFileHandler = (path: string) => void | Promise<void>;
426
+ export type WriteHandler = (path: string) => void | Promise<void>;
427
+ export type RenameProviderHandler = (from: string, to: string) => void | Promise<void>;
428
+ export type TransferHandler = (paths: string[], dest: string) => void | Promise<void>;
429
+ export type ProviderHandler = ListHandler | OpenFileHandler | WriteHandler | RenameProviderHandler | TransferHandler;
430
+ /** A UI-event handler (button/list/form interaction) registered via host.onUIEvent. */
431
+ export type UIEventHandler = (value: unknown) => void | Promise<void>;
432
+ /** A discovery source's two handlers, registered via host.onDiscover / onFetchSource. */
433
+ export type DiscoverHandler = (query: DiscoveryQuery) => Promise<DiscoveryResult>;
434
+ export type FetchSourceHandler = (ref: string) => Promise<string>;
435
+ /** Options for host.net.request — a host-proxied HTTP call (bypasses CORS). */
436
+ export interface NetRequestOptions {
437
+ url: string;
438
+ method?: string;
439
+ headers?: Record<string, string>;
440
+ /** Text (UTF-8) or raw bytes (e.g. from host.fs.readBytes for an upload). */
441
+ body?: string | Uint8Array;
442
+ }
443
+ /** What host.net.request resolves to. */
444
+ export interface NetResponse {
445
+ status: number;
446
+ headers: Record<string, string>;
447
+ /** Body decoded as UTF-8 text (JSON/XML/text APIs). */
448
+ body: string;
449
+ /** Body as raw bytes (binary downloads). */
450
+ bytes: Uint8Array;
451
+ }
452
+ /** What host.board.readFiles resolves to — the pasteboard's pending file list. */
453
+ export interface ClipboardFiles {
454
+ paths: string[];
455
+ operation: "copy" | "cut";
456
+ }
457
+ /** Whether a file's data is materialized locally or still cloud-only. */
458
+ export type CloudStatus = "downloaded" | "cloud";
459
+ export interface SandboxHostApi {
460
+ fs: {
461
+ /** List a directory's entries (works for local paths and provider schemes). */
462
+ readDir(path: string): Promise<FileItem[]>;
463
+ /** Open an item with the system default handler. */
464
+ openItem(path: string): Promise<void>;
465
+ /** Read a file's raw bytes. */
466
+ readBytes(path: string): Promise<Uint8Array>;
467
+ /** Whether a file is materialized locally or cloud-only. */
468
+ cloudStatus(path: string): Promise<CloudStatus>;
469
+ copyFiles(paths: string[], dest: string): Promise<void>;
470
+ moveFiles(paths: string[], dest: string): Promise<void>;
471
+ deleteItem(path: string): Promise<void>;
472
+ renameItem(from: string, to: string): Promise<void>;
473
+ createFile(path: string): Promise<void>;
474
+ createFolder(path: string): Promise<void>;
475
+ };
476
+ board: {
477
+ /** Read the pasteboard's pending file list, or null if it holds no files. */
478
+ readFiles(): Promise<ClipboardFiles | null>;
479
+ writeFiles(paths: string[], operation: "copy" | "cut"): Promise<void>;
480
+ };
481
+ nav: {
482
+ navigate(path: string): Promise<void>;
483
+ goBack(): Promise<void>;
484
+ goForward(): Promise<void>;
485
+ goUp(): Promise<void>;
486
+ };
487
+ tabs: {
488
+ openTab(path: string): Promise<void>;
489
+ openTabInBackground(path: string): Promise<void>;
490
+ /** Whether this module's runtime is bound to the active tab. */
491
+ isActive(): Promise<boolean>;
492
+ };
493
+ /** Drive view state: the current selection and the active sort. */
494
+ selection: {
495
+ set(items: FileItem[]): Promise<void>;
496
+ };
497
+ view: {
498
+ setSort(sort: {
499
+ key: string;
500
+ dir: "asc" | "desc";
501
+ }): Promise<void>;
502
+ toggleSort(key: string): Promise<void>;
503
+ /** Toggle whether hidden/system files (dotfiles) are shown. */
504
+ toggleHidden(): Promise<void>;
505
+ /** Explicitly set whether hidden/system files are shown. */
506
+ setShowHidden(value: boolean): Promise<void>;
507
+ };
508
+ dialog: {
509
+ /** Text-input dialog. Resolves with the entered string, or null if cancelled. */
510
+ prompt(options: {
511
+ message: string;
512
+ placeholder?: string;
513
+ defaultValue?: string;
514
+ }): Promise<string | null>;
515
+ /** Yes/no dialog. Resolves true (confirm) or false (cancel). */
516
+ confirm(options: {
517
+ message: string;
518
+ detail?: string;
519
+ destructive?: boolean;
520
+ }): Promise<boolean>;
521
+ /** Show a single-choice list. Resolves with the chosen option's value, or null. */
522
+ choose(options: {
523
+ message: string;
524
+ options: {
525
+ label: string;
526
+ value: string;
527
+ detail?: string;
528
+ icon?: string;
529
+ }[];
530
+ }): Promise<string | null>;
531
+ /** Open a Mutka file browser to pick one file. Resolves with its path, or null. */
532
+ pickFile(options?: {
533
+ title?: string;
534
+ initialDir?: string;
535
+ fileNames?: string[];
536
+ }): Promise<string | null>;
537
+ };
538
+ /** The app's home directory store (distinct from the OS home, sys.homeDir). */
539
+ home: {
540
+ /** Read the current app home directory. */
541
+ get(): Promise<string>;
542
+ /** Set the app home directory (any module may override it). */
543
+ set(path: string): Promise<void>;
544
+ };
545
+ /** Toggle the settings overlay open/closed. */
546
+ settings: {
547
+ toggle(): Promise<void>;
548
+ };
549
+ /** Render declarative UI (a serializable UINode tree). Gated by `ui`. */
550
+ ui: {
551
+ /** Render/replace the UINode shown in a named surface (panel/settings/popover). */
552
+ render(surfaceId: string, node: UINode): Promise<void>;
553
+ /** Clear a surface so it renders empty. */
554
+ clear(surfaceId: string): Promise<void>;
555
+ /** Open a modal with `node`, or pass null to close the current one. */
556
+ modal(node: UINode | null): Promise<void>;
557
+ };
558
+ /** Bottom status-bar items (e.g. a git widget). Gated by `ui`. */
559
+ statusbar: {
560
+ /** Add or replace one status-bar item (keyed by its id). */
561
+ set(item: StatusBarItem): Promise<void>;
562
+ /** Remove a status-bar item by id. */
563
+ remove(itemId: string): Promise<void>;
564
+ };
565
+ sys: {
566
+ /** The OS home directory. */
567
+ homeDir(): Promise<string>;
568
+ /** The last visited local directory, or null on first run. */
569
+ lastDir(): Promise<string | null>;
570
+ /** Write bytes to a temp file and return its path (Uint8Array, or a base64
571
+ * string for a Finder-dropped file). Gated by `fs:temp`. */
572
+ writeTempFile(filename: string, data: string | Uint8Array): Promise<string>;
573
+ quickLook(path: string): Promise<void>;
574
+ /** Refresh an already-open Quick Look panel to preview `path` (else no-op). */
575
+ previewUpdate(path: string): Promise<void>;
576
+ /** Apps that can open a file (Launch Services), default flagged + first. */
577
+ appsForFile(path: string): Promise<AppInfo[]>;
578
+ /** Open a file with a specific application bundle path. */
579
+ openWith(path: string, appPath: string): Promise<void>;
580
+ /** Start a native OS file drag of `paths`, previewed by `icon` (data-URI/path). */
581
+ startDrag(paths: string[], icon?: string): Promise<void>;
582
+ };
583
+ /**
584
+ * Host-proxied HTTP (avoids CORS, gated by `network:public` / `network:local`). One role:
585
+ * it sends a request and returns the response — it never touches the filesystem.
586
+ * To upload, read bytes via fs.readBytes (fs:read) and pass them as `body`; to
587
+ * save a response, write `bytes` via fs.* / sys.writeTempFile.
588
+ */
589
+ net: {
590
+ request(options: NetRequestOptions): Promise<NetResponse>;
591
+ };
592
+ /** Module tooling (gated by the `discovery` permission). */
593
+ modules: {
594
+ /** Validate an ESM source in a throwaway worker and return its manifest. */
595
+ probe(source: string): Promise<SandboxManifest>;
596
+ /** Propose a module source for install — opens the permission-review dialog so
597
+ * the user approves before anything is written. */
598
+ install(source: string): Promise<void>;
599
+ };
600
+ /** Per-module persisted config (gated by the `storage` permission). */
601
+ config: {
602
+ /** Read a stored value, or null if the key was never set. */
603
+ get(key: string): Promise<string | null>;
604
+ set(key: string, value: string): Promise<void>;
605
+ };
606
+ /** Per-module credentials in the macOS Keychain (gated by `secrets`). */
607
+ secrets: {
608
+ /** Read a stored credential, or null if absent. */
609
+ get(key: string): Promise<string | null>;
610
+ set(key: string, value: string): Promise<void>;
611
+ delete(key: string): Promise<void>;
612
+ };
613
+ /** Re-read the current directory after a mutation. */
614
+ refresh(): Promise<void>;
615
+ /** Run an item through the open-resolution pipeline (keyboard double-click). */
616
+ activate(item: FileItem): Promise<void>;
617
+ /** Register the function that runs when one of this module's commands fires. */
618
+ onCommand(commandId: string, handler: CommandHandler): void;
619
+ /** Register the function that runs when an item matches one of this module's open handlers. */
620
+ onOpen(handlerId: string, handler: OpenHandler): void;
621
+ /** Register the value provider for one of this module's custom columns. */
622
+ onColumn(columnId: string, provider: ColumnProvider): void;
623
+ /** Register a handler fired when a button/list/form in this module's UI is used. */
624
+ onUIEvent(handlerId: string, handler: UIEventHandler): void;
625
+ /** Register the directory-listing handler for a file system provider scheme. */
626
+ onList(scheme: string, handler: ListHandler): void;
627
+ /** Register the file-open handler for a file system provider scheme. */
628
+ onOpenFile(scheme: string, handler: OpenFileHandler): void;
629
+ /** Register the create-folder handler for a file system provider scheme. */
630
+ onCreateFolder(scheme: string, handler: WriteHandler): void;
631
+ /** Register the create-file handler for a file system provider scheme. */
632
+ onCreateFile(scheme: string, handler: WriteHandler): void;
633
+ /** Register the delete handler for a file system provider scheme. */
634
+ onDeleteItem(scheme: string, handler: WriteHandler): void;
635
+ /** Register the rename/move handler for a file system provider scheme. */
636
+ onRenameItem(scheme: string, handler: RenameProviderHandler): void;
637
+ /** Register the copy handler (sources may be local → upload, or same-scheme). */
638
+ onCopyFiles(scheme: string, handler: TransferHandler): void;
639
+ /** Register the move handler (same-scheme sources). */
640
+ onMoveFiles(scheme: string, handler: TransferHandler): void;
641
+ /** Register the discover handler for a declared discovery source. */
642
+ onDiscover(sourceId: string, handler: DiscoverHandler): void;
643
+ /** Register the fetch-source handler for a declared discovery source. */
644
+ onFetchSource(sourceId: string, handler: FetchSourceHandler): void;
645
+ /** Replace this module's dynamic left-sidebar items (e.g. a bookmarks list). */
646
+ sidebar: {
647
+ set(items: SidebarItem[]): void;
648
+ };
649
+ /** Subscribe to a whitelisted app event. */
650
+ events: {
651
+ on(event: string, handler: EventHandler): void;
652
+ };
653
+ /** Forwarded to the host console, prefixed with the module id. */
654
+ log(...args: unknown[]): void;
655
+ }
656
+ export interface SandboxModuleDef {
657
+ /** Unique module id, "author.name" convention. */
658
+ id: string;
659
+ name?: string;
660
+ version?: string;
661
+ description?: string;
662
+ /**
663
+ * Display image for the Modules UI: a `data:image/...` URI (self-contained) or
664
+ * an `https://` URL. Rendered via <img src> only, so it is injection-safe.
665
+ */
666
+ icon?: string;
667
+ /**
668
+ * Who made this module — shown in the Modules UI as an avatar + profile link.
669
+ * `author.github` (a user or org login) drives the avatar; when omitted and the
670
+ * module is installed from a GitHub repo, it defaults to the repo owner.
671
+ */
672
+ author?: ModuleAuthor;
673
+ /** Free-form tags for discovery filtering, e.g. ["files", "git", "viewer"]. */
674
+ tags?: string[];
675
+ /** Every privileged capability this module uses MUST be listed here. */
676
+ permissions?: ModulePermission[];
677
+ /** Commands surfaced into the app's menus / toolbar. */
678
+ commands?: SandboxCommand[];
679
+ /** Open handlers (double-click behavior) by item type. */
680
+ openHandlers?: SandboxOpenHandler[];
681
+ /** Declarative entries in the left "Places" sidebar, grouped by category. */
682
+ sidebarItems?: SidebarItem[];
683
+ /**
684
+ * File-type icon overrides: ship your own logo (a base64 data:image/... URI)
685
+ * for a set of extensions, replacing the native macOS icon. Rendered via
686
+ * <img src> only, so it's injection-safe.
687
+ */
688
+ fileIcons?: FileIconContribution[];
689
+ /**
690
+ * Custom list-view columns. Each column declares declarative applicability
691
+ * (which directories it shows in, which items get a value) and its value is
692
+ * produced by a provider registered in setup via host.onColumn(id, handler).
693
+ */
694
+ columns?: ColumnContribution[];
695
+ /**
696
+ * Declarative side-pane panels. Each declares a tab (id/title/icon/side); fill
697
+ * it from setup with host.ui.render(id, node) — a serializable UINode tree the
698
+ * host renders natively. Buttons/lists/forms in the tree fire UI-event handlers
699
+ * registered via host.onUIEvent(id, handler). Requires the `ui` permission.
700
+ */
701
+ panels?: PanelContribution[];
702
+ /**
703
+ * Declarative settings sections shown inside the app's Settings panel. Same
704
+ * model as `panels`: declare {id, title}, fill via host.ui.render(id, node).
705
+ * Requires the `ui` permission.
706
+ */
707
+ settingsSections?: SettingsSectionContribution[];
708
+ /**
709
+ * URI schemes this module provides a virtual file system for (e.g. "nextcloud").
710
+ * Register the handlers in setup with host.onList(scheme, …) / host.onOpenFile(…).
711
+ * Works in BOTH runtimes: built-ins call providers in-process; community modules
712
+ * serve each op over a worker round-trip. Note the worker realm has no DOM APIs
713
+ * (DOMParser, etc.), so providers needing those should ship as built-ins.
714
+ */
715
+ fileSystemProviders?: string[];
716
+ /**
717
+ * Module-discovery sources this module provides (e.g. a GitLab or local-folder
718
+ * source). Declare `{ id, label }` here, then in setup serve them with
719
+ * host.onDiscover(id, …) and host.onFetchSource(id, …). The id appears in the
720
+ * Modules "Browse" tab; results are validated + installed by the core. Gated by
721
+ * the `discovery` permission (plus whatever the fetch needs, e.g. `network:public`).
722
+ */
723
+ discoverySources?: DiscoverySourceDecl[];
724
+ /**
725
+ * Buttons to add to the Modules overlay (Browse tab). Declare `{ id, label, icon? }`
726
+ * and register the click handler with `host.onUIEvent(id, …)`. Lets a module
727
+ * surface an action there (e.g. an "Import local file" installer button).
728
+ */
729
+ moduleManagerButtons?: ModuleManagerButton[];
730
+ /**
731
+ * Runs once after load. Register command/open handlers and event subscriptions
732
+ * here. Reaches the system only through `host.*` (each gated by permissions).
733
+ */
734
+ setup?: (host: SandboxHostApi) => void | Promise<void>;
735
+ }
736
+
737
+ export {};
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@mutka-explorer/module",
3
+ "version": "1.0.0-rc.2",
4
+ "description": "TypeScript types for authoring Mutka modules — the SandboxModuleDef shape plus the full host API. Types only, no runtime code.",
5
+ "license": "MIT",
6
+ "author": "Mutka contributors",
7
+ "homepage": "https://github.com/ilianAZZ/mutka",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ilianAZZ/mutka.git",
11
+ "directory": "packages/module-sdk"
12
+ },
13
+ "keywords": [
14
+ "mutka",
15
+ "module",
16
+ "types",
17
+ "sdk",
18
+ "file-explorer"
19
+ ],
20
+ "types": "./index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./index.d.ts"
24
+ }
25
+ },
26
+ "files": [
27
+ "index.d.ts",
28
+ "README.md"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "scripts": {
34
+ "build": "node build.mjs",
35
+ "prepublishOnly": "node build.mjs"
36
+ },
37
+ "devDependencies": {
38
+ "dts-bundle-generator": "^9.5.1",
39
+ "typescript": "^5.5.3"
40
+ }
41
+ }