@silo-code/sdk 0.6.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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/context-keys.d.ts +19 -0
  4. package/dist/context-keys.d.ts.map +1 -0
  5. package/dist/context-keys.js +2 -0
  6. package/dist/context-keys.js.map +1 -0
  7. package/dist/dnd-service.d.ts +140 -0
  8. package/dist/dnd-service.d.ts.map +1 -0
  9. package/dist/dnd-service.js +17 -0
  10. package/dist/dnd-service.js.map +1 -0
  11. package/dist/domain-types.d.ts +237 -0
  12. package/dist/domain-types.d.ts.map +1 -0
  13. package/dist/domain-types.js +11 -0
  14. package/dist/domain-types.js.map +1 -0
  15. package/dist/editor-service.d.ts +175 -0
  16. package/dist/editor-service.d.ts.map +1 -0
  17. package/dist/editor-service.js +2 -0
  18. package/dist/editor-service.js.map +1 -0
  19. package/dist/extension-storage.d.ts +26 -0
  20. package/dist/extension-storage.d.ts.map +1 -0
  21. package/dist/extension-storage.js +2 -0
  22. package/dist/extension-storage.js.map +1 -0
  23. package/dist/file-service.d.ts +84 -0
  24. package/dist/file-service.d.ts.map +1 -0
  25. package/dist/file-service.js +2 -0
  26. package/dist/file-service.js.map +1 -0
  27. package/dist/index.d.ts +32 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +22 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/layout-service.d.ts +46 -0
  32. package/dist/layout-service.d.ts.map +1 -0
  33. package/dist/layout-service.js +2 -0
  34. package/dist/layout-service.js.map +1 -0
  35. package/dist/permissions.d.ts +41 -0
  36. package/dist/permissions.d.ts.map +1 -0
  37. package/dist/permissions.js +40 -0
  38. package/dist/permissions.js.map +1 -0
  39. package/dist/process-service.d.ts +132 -0
  40. package/dist/process-service.d.ts.map +1 -0
  41. package/dist/process-service.js +2 -0
  42. package/dist/process-service.js.map +1 -0
  43. package/dist/terminal-service.d.ts +38 -0
  44. package/dist/terminal-service.d.ts.map +1 -0
  45. package/dist/terminal-service.js +2 -0
  46. package/dist/terminal-service.js.map +1 -0
  47. package/dist/theme-service.d.ts +87 -0
  48. package/dist/theme-service.d.ts.map +1 -0
  49. package/dist/theme-service.js +2 -0
  50. package/dist/theme-service.js.map +1 -0
  51. package/dist/types.d.ts +495 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +2 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/ui-service.d.ts +469 -0
  56. package/dist/ui-service.d.ts.map +1 -0
  57. package/dist/ui-service.js +2 -0
  58. package/dist/ui-service.js.map +1 -0
  59. package/dist/use-focus-group.d.ts +202 -0
  60. package/dist/use-focus-group.d.ts.map +1 -0
  61. package/dist/use-focus-group.js +236 -0
  62. package/dist/use-focus-group.js.map +1 -0
  63. package/dist/use-service-state.d.ts +36 -0
  64. package/dist/use-service-state.d.ts.map +1 -0
  65. package/dist/use-service-state.js +25 -0
  66. package/dist/use-service-state.js.map +1 -0
  67. package/dist/workspace-service.d.ts +72 -0
  68. package/dist/workspace-service.d.ts.map +1 -0
  69. package/dist/workspace-service.js +2 -0
  70. package/dist/workspace-service.js.map +1 -0
  71. package/package.json +54 -0
  72. package/src/context-keys.ts +18 -0
  73. package/src/dnd-service.ts +151 -0
  74. package/src/domain-types.ts +252 -0
  75. package/src/editor-service.ts +196 -0
  76. package/src/extension-storage.ts +25 -0
  77. package/src/file-service.ts +90 -0
  78. package/src/index.ts +151 -0
  79. package/src/layout-service.ts +49 -0
  80. package/src/permissions.ts +55 -0
  81. package/src/process-service.ts +143 -0
  82. package/src/terminal-service.ts +41 -0
  83. package/src/theme-service.ts +102 -0
  84. package/src/types.ts +513 -0
  85. package/src/ui-service.ts +487 -0
  86. package/src/use-focus-group.test.ts +168 -0
  87. package/src/use-focus-group.ts +382 -0
  88. package/src/use-service-state.ts +43 -0
  89. package/src/workspace-service.ts +76 -0
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Public domain types shared across the `ctx` services. These describe the
3
+ * persisted, user-facing shapes an extension sees through the consumer services
4
+ * (e.g. {@link WorkspaceService}, {@link TerminalService}, {@link ThemeService}).
5
+ * The host's internal state module re-exports these so app code names a single
6
+ * source of truth.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+
11
+ /**
12
+ * The kind of a terminal session.
13
+ *
14
+ * @category Core Types
15
+ * @public
16
+ */
17
+ export type TerminalKind = "shell" | "claude" | "pi";
18
+
19
+ /**
20
+ * A terminal tab record in a workspace.
21
+ *
22
+ * @category Core Types
23
+ * @public
24
+ */
25
+ export interface TerminalRecord {
26
+ id: string;
27
+ sessionId: string;
28
+ kind: TerminalKind;
29
+ title: string;
30
+ /**
31
+ * A user-assigned name (via the tab's "Rename…" menu). When set, it wins over
32
+ * the PTY-derived {@link TerminalRecord.title} and stays put until the user
33
+ * renames again or the terminal is closed. Cleared by renaming to an empty
34
+ * string, which hands the title back to PTY auto-derivation.
35
+ */
36
+ customName?: string;
37
+ /** Working directory override. Falls back to ws.folder when absent. */
38
+ cwd?: string;
39
+ /** ISO timestamp of the last output we observed; used to pick a workspace's "primary" terminal. */
40
+ lastActiveAt?: string;
41
+ }
42
+
43
+ /**
44
+ * The two modes of the one editor surface: a read-write text editor, or a
45
+ * read-only two-model diff. Absent on a record means `"text"`.
46
+ *
47
+ * @category Core Types
48
+ * @public
49
+ */
50
+ export type EditorMode = "text" | "diff";
51
+
52
+ /**
53
+ * An editor tab record in a workspace — a text editor or a diff.
54
+ *
55
+ * @category Core Types
56
+ * @public
57
+ */
58
+ export interface EditorRecord {
59
+ id: string;
60
+ /** null for an untitled buffer that hasn't been saved yet. */
61
+ filePath: string | null;
62
+ title: string;
63
+ /** When true, the tab is a temporary preview that gets replaced by the next single-click open. */
64
+ isPreview?: boolean;
65
+ /**
66
+ * Which mode this record renders in. Absent ⇒ `"text"`. A `"diff"` record
67
+ * additionally carries {@link EditorRecord.providerId}/{@link EditorRecord.args}
68
+ * and always has a non-null `filePath`.
69
+ */
70
+ mode?: EditorMode;
71
+ /**
72
+ * Diff mode only: which registered diff-content provider resolves the two
73
+ * sides (e.g. "silo.git"). The diff is content-agnostic — the provider owns
74
+ * what the two sides contain.
75
+ */
76
+ providerId?: string;
77
+ /**
78
+ * Diff mode only: serializable args the provider needs to (re)compute content
79
+ * on mount / restart.
80
+ */
81
+ args?: Record<string, unknown>;
82
+ /**
83
+ * The chosen editor *view* for this tab, referencing an {@link Editor.id}
84
+ * (e.g. `"text"`, `"silo.markdown-preview"`). Absent ⇒ the host renders the
85
+ * highest-priority matching editor (the default). Honored only when the
86
+ * referenced editor is still registered **and** still matches the file;
87
+ * otherwise the host falls back to priority resolution (so a stale value left
88
+ * by an uninstalled extension never breaks the tab). Orthogonal to
89
+ * {@link EditorRecord.mode}: `viewType` selects among `"text"`-mode editors; a
90
+ * `"diff"` record ignores it.
91
+ */
92
+ viewType?: string;
93
+ }
94
+
95
+ /**
96
+ * Which slot a side panel renders in.
97
+ *
98
+ * @category Core Types
99
+ * @public
100
+ */
101
+ export type SidePanelSlot = "left" | "right" | "left-bottom" | "right-bottom";
102
+
103
+ /**
104
+ * A workspace — the unit Silo switches between, keeping its terminals, editors,
105
+ * and layout alive. Read via {@link WorkspaceService}.
106
+ *
107
+ * @category Core Types
108
+ * @public
109
+ */
110
+ export interface Workspace {
111
+ id: string;
112
+ name: string;
113
+ folder: string;
114
+ /** Additional folders beyond the primary one. */
115
+ extraFolders?: string[];
116
+ createdAt: string;
117
+ lastOpenedAt: string;
118
+ terminals: TerminalRecord[];
119
+ /** Editor tabs — text editors and diffs alike (a diff is a record with `mode: "diff"`). */
120
+ editors: EditorRecord[];
121
+ dockLayout: unknown | null;
122
+ /** Scroll positions keyed by editor record ID: { top, left } in pixels. */
123
+ editorScrollPositions?: Record<string, { top: number; left: number }>;
124
+ /**
125
+ * ISO timestamp of when the workspace was soft-closed, or null/undefined
126
+ * if the workspace is open. Closed workspaces are hidden from the main
127
+ * list and surfaced in a "reopen" picker.
128
+ */
129
+ closedAt?: string | null;
130
+ /** Per-workspace side panel state — saved/restored on workspace switch. */
131
+ sidePanelLocations?: Record<string, SidePanelSlot>;
132
+ sidePanelOrder?: Record<string, number>;
133
+ activeSidePanelTabs?: Record<string, string>;
134
+ sidePanelScrollPositions?: Record<string, number>;
135
+ extensionState?: Record<string, Record<string, unknown>>;
136
+ leftPanelCollapsed?: boolean;
137
+ rightPanelCollapsed?: boolean;
138
+ /** ID of the current preview (temporary) editor, if any. */
139
+ previewEditorId?: string | null;
140
+ }
141
+
142
+ /**
143
+ * Light or dark theme base.
144
+ *
145
+ * @category Core Types
146
+ * @public
147
+ */
148
+ export type ThemeBase = "dark" | "light";
149
+
150
+ /**
151
+ * The full theme-override surface, in type form: every `--silo-*` token a theme
152
+ * preset's `vars` may recolor. Per the theming contract it spans **the design
153
+ * tokens' generic colors + font families** _and_ **all the component tokens**.
154
+ * The keys are the literal CSS custom-property names; renaming a key here
155
+ * renames the token in `theme.css` in lockstep. Font-sizes and the radius scale
156
+ * are intentionally absent (not theme-overridable).
157
+ *
158
+ * @see docs/architecture-audit/theming-contract.md
159
+ * @category Core Types
160
+ * @public
161
+ */
162
+ export interface ThemeVars {
163
+ // ── Design tokens — generic colors (also consumable by extensions) ──
164
+ "--silo-color-bg": string;
165
+ "--silo-color-bg-hover": string;
166
+ "--silo-color-bg-active": string;
167
+ "--silo-color-text": string;
168
+ "--silo-color-text-hi": string;
169
+ "--silo-color-text-lo": string;
170
+ "--silo-color-accent": string;
171
+ "--silo-color-accent-2": string;
172
+ "--silo-color-border": string;
173
+ "--silo-color-border-strong": string;
174
+ "--silo-color-ok": string;
175
+ "--silo-color-warn": string;
176
+ "--silo-color-err": string;
177
+ "--silo-color-input-bg": string;
178
+ "--silo-color-input-text": string;
179
+ "--silo-color-button-bg": string;
180
+ "--silo-color-button-text": string;
181
+ // ── Design tokens — font families ──
182
+ "--silo-font-ui"?: string;
183
+ "--silo-font-mono"?: string;
184
+ // ── Component tokens — content (editor / terminal / tabs) colors ──
185
+ "--silo-content-text": string;
186
+ "--silo-content-terminal-bg": string;
187
+ "--silo-content-editor-bg": string;
188
+ "--silo-content-editor-selection": string;
189
+ "--silo-content-editor-selection-inactive": string;
190
+ "--silo-content-editor-text-dim": string;
191
+ "--silo-content-editor-text-faint": string;
192
+ "--silo-content-tab-bg": string;
193
+ "--silo-content-tab-tray-bg": string;
194
+ "--silo-content-tab-tray-text": string;
195
+ "--silo-content-tab-text": string;
196
+ "--silo-content-tab-text-inactive": string;
197
+ "--silo-content-tab-text-active": string;
198
+ // ── Component tokens — breadcrumb colors ──
199
+ "--silo-breadcrumb-bg": string;
200
+ "--silo-breadcrumb-text": string;
201
+ "--silo-breadcrumb-text-active": string;
202
+ "--silo-breadcrumb-icon": string;
203
+ // ── Component tokens — status bar colors ──
204
+ "--silo-statusbar-bg": string;
205
+ "--silo-statusbar-text": string;
206
+ "--silo-statusbar-bg-hover": string;
207
+ // ── Component tokens — side-tab colors ──
208
+ "--silo-tab-text": string;
209
+ "--silo-tab-text-active": string;
210
+ "--silo-tab-bg-hover": string;
211
+ "--silo-tab-border-active": string;
212
+ // ── Component tokens — menu / dropdown colors (the host <Menu> primitive) ──
213
+ "--silo-menu-bg": string;
214
+ "--silo-menu-text": string;
215
+ "--silo-menu-item-hover-bg": string;
216
+ "--silo-menu-border": string;
217
+ // ── Component tokens — modal shell (ctx.ui.confirm/prompt + the host Modal) ──
218
+ "--silo-modal-bg": string;
219
+ "--silo-modal-border": string;
220
+ // ── Component tokens — notify (toast) surface (ctx.ui.notify) ──
221
+ "--silo-notify-bg": string;
222
+ "--silo-notify-text": string;
223
+ "--silo-notify-text-hi": string;
224
+ }
225
+
226
+ /**
227
+ * A persisted custom theme.
228
+ *
229
+ * @category Core Types
230
+ * @public
231
+ */
232
+ export interface CustomTheme {
233
+ id: string;
234
+ /**
235
+ * `2` since the `--silo-*` token rename (theming-contract.md › Migration).
236
+ * v1 themes used the legacy bare names (`--bg`, `--text-hi`, …).
237
+ */
238
+ version: 2;
239
+ name: string;
240
+ base: ThemeBase;
241
+ colorScheme: "dark" | "light";
242
+ vars: Partial<ThemeVars>;
243
+ }
244
+
245
+ /**
246
+ * A {@link CustomTheme} without its `id` — the shape exported/imported as a
247
+ * shareable theme file.
248
+ *
249
+ * @category Core Types
250
+ * @public
251
+ */
252
+ export type ThemeExport = Omit<CustomTheme, "id">;
@@ -0,0 +1,196 @@
1
+ import type { Disposable } from "./types";
2
+
3
+ // `ctx.editors` — the editor & document domain (public contract). Opening files
4
+ // into editor tabs, the active-editor save/close commands, and the diff-content
5
+ // provider registry. The host implementation lives in the extension host.
6
+
7
+ /**
8
+ * Options for the editor open methods.
9
+ *
10
+ * @category Consumer Services
11
+ * @public
12
+ */
13
+ export interface OpenFileOptions {
14
+ /** Target workspace. Defaults to the active workspace. */
15
+ workspaceId?: string;
16
+ /**
17
+ * Open as a preview tab (single-click style — replaced by the next preview,
18
+ * italic title) instead of a permanent editor. Defaults to false.
19
+ */
20
+ preview?: boolean;
21
+ /**
22
+ * Open with a specific editor view — an {@link Editor.id}, e.g. from an
23
+ * "Open With" menu. Persisted on the tab as {@link EditorRecord.viewType}.
24
+ * Ignored if no registered editor with that id matches the path (the host
25
+ * falls back to the highest-priority match). When omitted the default view is
26
+ * used and any existing tab's view is left unchanged.
27
+ */
28
+ viewType?: string;
29
+ }
30
+
31
+ /**
32
+ * One editor view that can render a given file — its id, user-facing label, and
33
+ * whether it is the view the host would pick by default. Returned by
34
+ * {@link EditorService.editorsFor}; used to build "Open With" menus and the
35
+ * breadcrumb view-switcher.
36
+ *
37
+ * @category Consumer Services
38
+ * @public
39
+ */
40
+ export interface EditorViewInfo {
41
+ /** The editor's id (an {@link Editor.id}); pass back as `viewType`. */
42
+ id: string;
43
+ /** Human-facing label (falls back to {@link EditorViewInfo.id}). */
44
+ label: string;
45
+ /** True for the editor the host resolves by default (highest priority). */
46
+ isDefault: boolean;
47
+ }
48
+
49
+ /**
50
+ * Save callbacks an editor viewer registers via
51
+ * {@link EditorService.registerSaveHandler}, so the active-editor `save` /
52
+ * `saveAs` commands can dispatch to whichever editor is focused.
53
+ *
54
+ * @category Consumer Services
55
+ * @public
56
+ */
57
+ export interface EditorSaveHandlers {
58
+ /** Save the editor's contents. */
59
+ save: () => void | Promise<void>;
60
+ /** Save-as (prompt for a new path). Optional. */
61
+ saveAs?: () => void | Promise<void>;
62
+ }
63
+
64
+ /**
65
+ * What to open in a diff view, passed to {@link EditorService.openDiff}. The
66
+ * diff is **generic** — it renders two contents and knows nothing about where
67
+ * they come from. `providerId` names a {@link DiffContentProvider} (registered
68
+ * via {@link EditorService.registerDiffContentProvider}) that resolves the two
69
+ * sides; `args` is the serializable payload that provider needs (e.g. a git
70
+ * revision/mode) and is persisted so the content can be recomputed on restart.
71
+ *
72
+ * @category Consumer Services
73
+ * @public
74
+ */
75
+ export interface OpenDiffSpec {
76
+ /** The file the diff is OF — drives language detection, breadcrumb, title. */
77
+ filePath: string;
78
+ /** Id of the registered content provider that resolves the two sides. */
79
+ providerId: string;
80
+ /** Serializable args handed back to the provider to (re)compute content. */
81
+ args?: Record<string, unknown>;
82
+ /** Tab title. Defaults to the file's base name. */
83
+ title?: string;
84
+ }
85
+
86
+ /** The two sides of a diff, resolved by a {@link DiffContentProvider}.
87
+ *
88
+ * @category Consumer Services
89
+ * @public
90
+ */
91
+ export interface DiffContent {
92
+ /** Left/original side. */
93
+ original: string;
94
+ /** Right/modified side. */
95
+ modified: string;
96
+ }
97
+
98
+ /**
99
+ * The request a {@link DiffContentProvider} receives to resolve a diff's two
100
+ * sides — the {@link OpenDiffSpec}'s `filePath`/`args` plus the folder of the
101
+ * workspace the diff lives in (the natural cwd for path-relative providers).
102
+ *
103
+ * @category Consumer Services
104
+ * @public
105
+ */
106
+ export interface DiffContentRequest {
107
+ /** The file the diff is OF (from {@link OpenDiffSpec.filePath}). */
108
+ filePath: string;
109
+ /** The provider's args (from {@link OpenDiffSpec.args}). */
110
+ args?: Record<string, unknown>;
111
+ /** Folder of the workspace the diff lives in, or `null` if none. */
112
+ workspaceFolder: string | null;
113
+ }
114
+
115
+ /**
116
+ * Resolves the two sides of a diff on demand — called by the host whenever a
117
+ * diff panel mounts (open, tab switch, app restart), so content stays a pure
118
+ * computed view and never has to be persisted. Register one with
119
+ * {@link EditorService.registerDiffContentProvider}.
120
+ *
121
+ * @category Consumer Services
122
+ * @public
123
+ */
124
+ export type DiffContentProvider = (
125
+ request: DiffContentRequest,
126
+ ) => Promise<DiffContent>;
127
+
128
+ /**
129
+ * The editor & document domain, exposed as {@link ExtensionContext.editors}.
130
+ * Open files into editor tabs, drive the active editor (save / close), and let
131
+ * editor viewers register save handlers. The single entry point for opening
132
+ * editors — prefer it over reaching into workspace/editor state.
133
+ *
134
+ * @category Consumer Services
135
+ * @public
136
+ */
137
+ export interface EditorService {
138
+ /**
139
+ * Open a file in an editor tab. Promotes an existing preview, focuses an
140
+ * already-open tab, or opens a new one.
141
+ */
142
+ open(path: string, opts?: OpenFileOptions): void;
143
+ /** Open a fresh untitled editor. */
144
+ openUntitled(opts?: OpenFileOptions): void;
145
+ /**
146
+ * Open a diff view. The content is supplied by the {@link OpenDiffSpec.providerId | provider}
147
+ * named in `spec` — the editor itself is content-agnostic.
148
+ */
149
+ openDiff(spec: OpenDiffSpec, opts?: OpenFileOptions): void;
150
+ /** Save the active editor. Returns false if there's no active saveable editor. */
151
+ save(): boolean;
152
+ /** Save-as the active editor. Returns false if unavailable. */
153
+ saveAs(): boolean;
154
+ /** Close the active dock panel. Returns false if there's nothing to close. */
155
+ closeActive(): boolean;
156
+ /**
157
+ * List the editor views that match `path` (or `null` for an untitled buffer),
158
+ * highest-priority first, each flagged whether it's the one the host resolves
159
+ * by default. Read-only enumeration for building "Open With" menus and the
160
+ * breadcrumb view-switcher. Returns `[]` only when nothing matches — in
161
+ * practice never empty for a real path, since the core text editor matches
162
+ * everything.
163
+ */
164
+ editorsFor(path: string | null): EditorViewInfo[];
165
+ /**
166
+ * Switch an already-open editor tab to a different view in place — without
167
+ * closing and reopening it — and persist the choice on the tab. No-op if the
168
+ * editor isn't found, `viewType` names no registered editor, or that editor
169
+ * doesn't match the tab's file. The panel remounts onto the new view, so
170
+ * per-instance state (scroll, selection) resets — expected, it's a different
171
+ * presenter.
172
+ */
173
+ setViewType(
174
+ editorId: string,
175
+ viewType: string,
176
+ opts?: { workspaceId?: string },
177
+ ): void;
178
+ /**
179
+ * Register save handlers for an editor instance (by its `editorId`), so the
180
+ * active-editor `save` / `saveAs` dispatch to it while it's focused. Dispose
181
+ * to unregister (do this when the editor unmounts).
182
+ */
183
+ registerSaveHandler(
184
+ editorId: string,
185
+ handlers: EditorSaveHandlers,
186
+ ): Disposable;
187
+ /**
188
+ * Register a {@link DiffContentProvider} under `providerId`. A diff opened
189
+ * with that `providerId` (see {@link OpenDiffSpec}) resolves its two sides
190
+ * through this provider, on every mount. Dispose to unregister.
191
+ */
192
+ registerDiffContentProvider(
193
+ providerId: string,
194
+ provider: DiffContentProvider,
195
+ ): Disposable;
196
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Namespaced, persisted key/value storage handed to side-panel components
3
+ * via `SidePanelProps.storage`. Each panel id gets its own bag; values are
4
+ * persisted alongside the rest of the app state.
5
+ *
6
+ * The store is hydrated asynchronously after the panels mount, so consumers
7
+ * that need to wait for restored values should check `props.hydrated` or
8
+ * use `subscribe` to re-read once it flips.
9
+ *
10
+ * @category Consumer Services
11
+ * @public
12
+ */
13
+ export interface ExtensionStorage {
14
+ /** Read a value. Returns `fallback` if the key is missing. */
15
+ get<T>(key: string): T | undefined;
16
+ get<T>(key: string, fallback: T): T;
17
+ /** Write a value. `undefined` deletes the key. */
18
+ set(key: string, value: unknown): void;
19
+ /**
20
+ * Subscribe to changes in this namespace. Called on any set within this
21
+ * namespace, and also whenever the underlying app state finishes hydrating
22
+ * (so callers can re-read after persisted state loads).
23
+ */
24
+ subscribe(listener: () => void): () => void;
25
+ }
@@ -0,0 +1,90 @@
1
+ import type { Disposable } from "./types";
2
+
3
+ // `ctx.files` — host-mediated filesystem access. The public contract; the host
4
+ // implementation (a single chokepoint over the privileged Tauri fs/watch
5
+ // commands) lives in the extension host.
6
+
7
+ /**
8
+ * Metadata for a single directory entry, as returned by
9
+ * {@link FileService.readDir}.
10
+ *
11
+ * @category Core Types
12
+ * @public
13
+ */
14
+ export interface FileMeta {
15
+ /** The entry's base name (no path). */
16
+ name: string;
17
+ /** Absolute path to the entry. */
18
+ path: string;
19
+ /** True if the entry is a directory. */
20
+ isDir: boolean;
21
+ /** Size in bytes (0 for directories). */
22
+ size: number;
23
+ /** Last-modified time, milliseconds since the Unix epoch. */
24
+ modifiedMs: number;
25
+ }
26
+
27
+ /**
28
+ * A filesystem change event delivered to a {@link FileService.watch} listener,
29
+ * for changes under that watch's path.
30
+ *
31
+ * @category Core Types
32
+ * @public
33
+ */
34
+ export interface FileChangeEvent {
35
+ /** The paths that changed in this event. */
36
+ paths: string[];
37
+ /** The backend's change kind (e.g. `"create"`, `"modify"`, `"remove"`). */
38
+ kind: string;
39
+ }
40
+
41
+ /**
42
+ * The filesystem domain, exposed as {@link ExtensionContext.files}. All access
43
+ * is host-mediated: extensions read, write, and watch the filesystem through
44
+ * here rather than calling Tauri directly.
45
+ *
46
+ * **Paths are workspace-scoped.** A relative path resolves against the open
47
+ * workspace folder (`"src/index.ts"` → `<workspace>/src/index.ts`); an absolute
48
+ * path is allowed only if it falls inside a workspace folder. A path outside the
49
+ * workspace throws {@link PathDeniedError} unless the extension declared the
50
+ * matching {@link Permission} (`fs:read` for reads, `fs:write` for writes).
51
+ * First-party (bundled) extensions are unscoped. Prefer relative paths — they're
52
+ * portable across machines.
53
+ *
54
+ * Watching is host-owned: {@link FileService.watch} expresses intent — "tell me
55
+ * about changes under this path" — and the host owns the underlying OS
56
+ * watcher(s). Many in-workspace subscriptions are served from a single,
57
+ * ref-counted workspace watcher the host manages; extensions never start or
58
+ * stop watchers themselves, and each listener receives only events scoped to
59
+ * its path.
60
+ *
61
+ * @category Consumer Services
62
+ * @public
63
+ */
64
+ export interface FileService {
65
+ /** Read a file's contents as UTF-8 text. */
66
+ readText(path: string): Promise<string>;
67
+ /** Read a file's raw bytes. */
68
+ readBytes(path: string): Promise<ArrayBuffer>;
69
+ /** List a directory's immediate entries. */
70
+ readDir(path: string): Promise<FileMeta[]>;
71
+ /** Resolve true if a file or directory exists at `path`. */
72
+ pathExists(path: string): Promise<boolean>;
73
+ /** Write UTF-8 text to a file, creating or overwriting it. */
74
+ writeText(path: string, content: string): Promise<void>;
75
+ /** Create a directory (and any missing parents). */
76
+ createDir(path: string): Promise<void>;
77
+ /** Rename / move a file or directory. */
78
+ rename(oldPath: string, newPath: string): Promise<void>;
79
+ /** Delete a file or directory. */
80
+ delete(path: string): Promise<void>;
81
+ /** Reveal a path in the OS file manager (Finder / Explorer). */
82
+ reveal(path: string): Promise<void>;
83
+ /**
84
+ * Watch `path` recursively, invoking `listener` for each change under it.
85
+ * Returns a {@link Disposable} that stops listening when disposed. Watcher
86
+ * lifecycle is the host's concern — in-workspace paths ride the host's
87
+ * ref-counted workspace watcher rather than each spinning up its own.
88
+ */
89
+ watch(path: string, listener: (event: FileChangeEvent) => void): Disposable;
90
+ }