@bendyline/docblocks 2.6.1 → 2.7.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 (62) hide show
  1. package/THIRD_PARTY_NOTICES.txt +33 -33
  2. package/dist/capabilities-CdQAc745.d.ts +1233 -0
  3. package/dist/catalog-BQK05tne.d.ts +122 -0
  4. package/dist/chunk-245W57QP.js +31 -0
  5. package/dist/{chunk-2WDJ27EH.js → chunk-46AGYHDY.js} +8 -0
  6. package/dist/{chunk-4437YWLE.js → chunk-6UPO2GE6.js} +4 -2
  7. package/dist/{chunk-UMWCTEXW.js → chunk-7SN7YQL4.js} +211 -40
  8. package/dist/{chunk-OQCPBOQA.js → chunk-DAPFSEKY.js} +35 -0
  9. package/dist/{chunk-M62ZGJ5I.js → chunk-HABG2JM3.js} +0 -17
  10. package/dist/{chunk-YM7O3PWO.js → chunk-HKOOHM67.js} +20 -12
  11. package/dist/chunk-IDYE73TU.js +77 -0
  12. package/dist/{chunk-KLQKU74Q.js → chunk-IGB2VSRE.js} +124 -330
  13. package/dist/{chunk-BVY7GGNV.js → chunk-L6PRMR7T.js} +137 -95
  14. package/dist/chunk-LPIJ6HKL.js +19 -0
  15. package/dist/{chunk-SJBB5OD7.js → chunk-QS5WTLUZ.js} +10 -8
  16. package/dist/chunk-R2JX5N3F.js +382 -0
  17. package/dist/{chunk-ZO7G53QP.js → chunk-UDEWLHQI.js} +4 -2
  18. package/dist/chunk-UK7R3RC7.js +514 -0
  19. package/dist/chunk-XTNWCNSK.js +464 -0
  20. package/dist/chunk-YHSFRPBS.js +42 -0
  21. package/dist/chunk-YNUQG4GF.js +1173 -0
  22. package/dist/document/index.d.ts +31 -1
  23. package/dist/document/index.js +7 -6
  24. package/dist/filesystem/electron.d.ts +8 -34
  25. package/dist/filesystem/electron.js +7 -4
  26. package/dist/filesystem/host.d.ts +28 -0
  27. package/dist/filesystem/host.js +103 -0
  28. package/dist/filesystem/index.d.ts +52 -6
  29. package/dist/filesystem/index.js +36 -15
  30. package/dist/filesystem/indexeddb.d.ts +8 -1
  31. package/dist/filesystem/indexeddb.js +7 -6
  32. package/dist/filesystem/memory.d.ts +2 -1
  33. package/dist/filesystem/memory.js +3 -2
  34. package/dist/filesystem/native.d.ts +2 -1
  35. package/dist/filesystem/native.js +3 -2
  36. package/dist/filesystem-v2-C8AdSXUN.d.ts +74 -0
  37. package/dist/host/index.d.ts +127 -5
  38. package/dist/host/index.js +100 -4
  39. package/dist/host/mobile.d.ts +389 -0
  40. package/dist/host/mobile.js +315 -0
  41. package/dist/host-provider-v2-CyLTRKJF.d.ts +51 -0
  42. package/dist/index.d.ts +8 -5
  43. package/dist/index.js +153 -34
  44. package/dist/mcp/index.d.ts +2 -2
  45. package/dist/mcp/zod.d.ts +23 -1
  46. package/dist/mcp/zod.js +1 -0
  47. package/dist/settings-HrrCikVX.d.ts +170 -0
  48. package/dist/types-BvDnr3YU.d.ts +88 -0
  49. package/dist/{types-Oy9U5FST.d.ts → types-d0yvHUJz.d.ts} +6 -0
  50. package/dist/{types-Bb3yccTH.d.ts → v2-BVnZZyd4.d.ts} +1 -86
  51. package/dist/vscode/index.d.ts +43 -1
  52. package/dist/vscode/index.js +67 -1
  53. package/dist/workspace/index.d.ts +10 -6
  54. package/dist/workspace/index.js +4 -2
  55. package/dist/workspace-index/index.d.ts +240 -0
  56. package/dist/workspace-index/index.js +1287 -0
  57. package/dist/workspace-settings/index.d.ts +301 -0
  58. package/dist/workspace-settings/index.js +820 -0
  59. package/package.json +22 -2
  60. package/dist/chunk-6XHOOBYF.js +0 -36
  61. package/dist/chunk-QI6YKTQV.js +0 -110
  62. package/dist/types-DBYraXxE.d.ts +0 -557
@@ -0,0 +1,1233 @@
1
+ import { F as FileCommitResult, c as FileSystemEntry, b as FileMeta } from './types-BvDnr3YU.js';
2
+ import { D as DocBlocksHostFsV2API } from './filesystem-v2-C8AdSXUN.js';
3
+
4
+ /** Provider-neutral catalog management. IDs never authorize filesystem access. */
5
+ interface AiKnowledgeCatalog {
6
+ readonly id: string;
7
+ readonly name: string;
8
+ readonly description: string;
9
+ readonly version: string;
10
+ readonly installedVersion: string | null;
11
+ readonly enabled: boolean;
12
+ readonly updateAvailable: boolean;
13
+ readonly downloadBytes: number | null;
14
+ readonly documents: number | null;
15
+ readonly state: 'available' | 'installed' | 'downloading' | 'error';
16
+ readonly percent: number | null;
17
+ readonly message: string | null;
18
+ }
19
+ interface AiKnowledgeState {
20
+ readonly catalogs: readonly AiKnowledgeCatalog[];
21
+ /**
22
+ * A one-time model download that improves knowledge results, sized for the
23
+ * offer. Null when not offered: installed, unavailable, or no enabled
24
+ * catalog would use it. The provider decides; people only see the offer.
25
+ */
26
+ readonly improvement: {
27
+ readonly downloadBytes: number;
28
+ readonly downloading: boolean;
29
+ } | null;
30
+ }
31
+ type AiKnowledgeAction = {
32
+ readonly action: 'install' | 'remove' | 'enable' | 'disable' | 'cancel';
33
+ readonly catalogId: string;
34
+ }
35
+ /** Start the `improvement` download. */
36
+ | {
37
+ readonly action: 'improve';
38
+ };
39
+ interface AiKnowledgeAPI {
40
+ state(): Promise<AiResult<AiKnowledgeState>>;
41
+ update(action: AiKnowledgeAction): Promise<AiResult<null>>;
42
+ }
43
+ declare function parseAiKnowledgeAction(value: unknown): AiKnowledgeAction | null;
44
+ declare function parseAiKnowledgeState(value: unknown): AiKnowledgeState | null;
45
+
46
+ /**
47
+ * The host-agnostic AI seam.
48
+ *
49
+ * Nothing here names a provider product. Model ids are opaque strings, the
50
+ * vocabulary is "provider" and "connection", and the capability is described by
51
+ * what it can do rather than by what is installed behind it. That is deliberate:
52
+ * the desktop bridge, the VS Code bridge and any future mobile bridge all
53
+ * satisfy this contract, and `packages/react` must never learn which one it is
54
+ * talking to.
55
+ *
56
+ * Every operation that can fail for an expected reason returns `AiResult`
57
+ * rather than rejecting. A provider that is not running, a user who declines
58
+ * approval and a model that is still downloading are ordinary states of this
59
+ * feature, not exceptions, and a renderer must be able to render them without a
60
+ * try/catch around every call.
61
+ */
62
+ /** An expected-failure envelope. Rejections stay reserved for genuine bugs. */
63
+ type AiResult<T> = {
64
+ readonly ok: true;
65
+ readonly value: T;
66
+ } | {
67
+ readonly ok: false;
68
+ readonly error: AiError;
69
+ };
70
+ type AiErrorCode =
71
+ /** No provider is installed, running, or reachable. */
72
+ 'provider-unavailable' | 'approval-denied' | 'approval-timeout' | 'approval-expired'
73
+ /** The stored credential no longer carries the scopes this operation needs. */
74
+ | 'approval-required'
75
+ /** The provider is reachable but has its connected-app surface switched off. */
76
+ | 'inference-disabled'
77
+ /** The provider believes this app already holds a credential we no longer have. */
78
+ | 'already-connected' | 'model-unavailable' | 'model-download-failed'
79
+ /** A hosted runtime was expected but is missing or unusable on this build. */
80
+ | 'runtime-missing' | 'workspace-not-registered'
81
+ /** The host does not implement this operation at all. */
82
+ | 'unsupported' | 'budget-exceeded' | 'rate-limited' | 'timeout' | 'cancelled' | 'unknown';
83
+ interface AiError {
84
+ readonly code: AiErrorCode;
85
+ /** Shown to the user. Never truncate a provider's own explanation into this. */
86
+ readonly message: string;
87
+ /** Optional diagnostic detail for logs; not for display. */
88
+ readonly detail?: string;
89
+ }
90
+ /**
91
+ * Why AI is not on offer.
92
+ *
93
+ * `opt-out` is first-class rather than an absence, because "the user has not
94
+ * switched this on" must never be reported as a broken provider. A host may
95
+ * still check whether its companion provider is installed so the settings UI
96
+ * can explain the connection only when it is relevant; it must not connect or
97
+ * start a hosted provider while this state holds.
98
+ */
99
+ type AiUnavailableReason = 'opt-out' | 'platform-unsupported' | 'not-installed' | 'not-running' | 'disconnected';
100
+ type AiConnectionStep = 'detecting' | 'awaiting-approval' | 'preparing-model' | 'preparing-workspace';
101
+ interface AiProgress {
102
+ /** Short machine-ish label, e.g. `weights`. Bounded; not free prose. */
103
+ readonly phase: string;
104
+ readonly message: string;
105
+ /** 0-100, or null when the work reports no measurable total. */
106
+ readonly percent: number | null;
107
+ }
108
+ interface AiProviderInfo {
109
+ readonly name: string;
110
+ readonly version: string | null;
111
+ /** Whether the provider runs as a separate install or inside this app. */
112
+ readonly mode: 'installed' | 'hosted' | 'remote';
113
+ }
114
+ interface AiModelInfo {
115
+ readonly id: string;
116
+ readonly label: string;
117
+ /** True when inference happens on this device. */
118
+ readonly local: boolean;
119
+ /** Token budget, when the provider reports one. Drives prompt budgeting. */
120
+ readonly contextWindow: number | null;
121
+ readonly isDefault: boolean;
122
+ /** Omitted by hosts whose listing contains only ready models. */
123
+ readonly availability?: 'available' | 'unavailable' | 'download-required' | 'downloading';
124
+ /** Native readiness explanation, suitable for the model picker. */
125
+ readonly unavailableReason?: string;
126
+ }
127
+ /** A provider-catalog model that can be added to this device. */
128
+ interface AiModelDownloadInfo {
129
+ readonly id: string;
130
+ readonly label: string;
131
+ readonly contextWindow: number | null;
132
+ /** Expected weight download size, when the provider reports one. */
133
+ readonly downloadBytes: number | null;
134
+ readonly state: 'download-required' | 'downloading';
135
+ }
136
+ type AiStatus = {
137
+ readonly kind: 'unavailable';
138
+ readonly reason: AiUnavailableReason;
139
+ } | {
140
+ readonly kind: 'connecting';
141
+ readonly step: AiConnectionStep;
142
+ /**
143
+ * A short challenge the user types into the provider to approve this app.
144
+ * It is a one-time proof of presence, not a credential, which is why it
145
+ * may cross to the renderer for display.
146
+ */
147
+ readonly verificationCode: string | null;
148
+ readonly progress: AiProgress | null;
149
+ } | {
150
+ readonly kind: 'ready';
151
+ readonly provider: AiProviderInfo;
152
+ readonly model: AiModelInfo | null;
153
+ readonly activeRequests: number;
154
+ } | {
155
+ readonly kind: 'error';
156
+ readonly error: AiError;
157
+ readonly retryable: boolean;
158
+ };
159
+ type AiReviewMode = 'off' | 'explicit' | 'implicit';
160
+ interface AiPreferences {
161
+ readonly enabled: boolean;
162
+ /** Preferred model id, or null to take the provider's default. */
163
+ readonly model: string | null;
164
+ readonly reviewMode: AiReviewMode;
165
+ }
166
+ type AiPreferencesPatch = Partial<AiPreferences>;
167
+ interface AiChatMessage {
168
+ readonly role: 'system' | 'user' | 'assistant';
169
+ readonly content: string;
170
+ }
171
+ /**
172
+ * What the request is for.
173
+ *
174
+ * This exists so the host can apply different budgets to a person waiting on a
175
+ * draft and a background review pass. It is never sent to the model.
176
+ */
177
+ type AiChatPurpose = 'write' | 'review' | 'illustrate' | 'chat';
178
+ interface AiChatRequest {
179
+ readonly messages: readonly AiChatMessage[];
180
+ /** Opaque provider model id. Omitted means "the host's current choice". */
181
+ readonly model?: string;
182
+ readonly temperature?: number;
183
+ /** Optional caller cap. Writing without one uses the host/model's available capacity. */
184
+ readonly maxTokens?: number;
185
+ readonly purpose: AiChatPurpose;
186
+ }
187
+ interface AiChatCompletion {
188
+ readonly text: string;
189
+ readonly model: string;
190
+ readonly finishReason: 'stop' | 'length' | 'cancelled';
191
+ readonly usage: {
192
+ readonly promptTokens: number;
193
+ readonly completionTokens: number;
194
+ } | null;
195
+ }
196
+ /** Request-local metadata. Unknown counts stay null; streamed chunks are not tokens. */
197
+ interface AiChatProgress {
198
+ readonly phase: 'starting' | 'queued' | 'loading_model' | 'prefill' | 'reasoning' | 'generating';
199
+ /** Engine-reported percentage for model loading or prompt processing only. */
200
+ readonly percent: number | null;
201
+ /** Exact engine count, including private reasoning when the engine counts it. */
202
+ readonly outputTokens: number | null;
203
+ readonly tokensPerSecond: number | null;
204
+ }
205
+ type AiChatEvent = {
206
+ readonly kind: 'progress';
207
+ readonly progress: AiChatProgress;
208
+ } | {
209
+ readonly kind: 'delta';
210
+ readonly text: string;
211
+ } | {
212
+ readonly kind: 'done';
213
+ readonly completion: AiChatCompletion;
214
+ } | {
215
+ readonly kind: 'error';
216
+ readonly error: AiError;
217
+ };
218
+ interface AiChatHandle {
219
+ readonly done: Promise<AiResult<AiChatCompletion>>;
220
+ cancel(): void;
221
+ }
222
+ /**
223
+ * One explicitly requested model install.
224
+ *
225
+ * Cancelling stops this caller's progress stream. A provider-owned background
226
+ * download may still finish and appear in the next model refresh.
227
+ */
228
+ interface AiModelInstallHandle {
229
+ readonly done: Promise<AiResult<AiModelInfo>>;
230
+ cancel(): void;
231
+ }
232
+ type AiModelInstallEvent = {
233
+ readonly kind: 'progress';
234
+ readonly progress: AiProgress;
235
+ } | {
236
+ readonly kind: 'done';
237
+ readonly model: AiModelInfo;
238
+ } | {
239
+ readonly kind: 'error';
240
+ readonly error: AiError;
241
+ };
242
+ interface AiEnsureWorkspaceHandle {
243
+ readonly done: Promise<AiResult<{
244
+ readonly indexed: boolean;
245
+ }>>;
246
+ cancel(): void;
247
+ }
248
+ interface AiSearchQuery {
249
+ readonly query: string;
250
+ readonly mode: 'names' | 'full';
251
+ readonly maxResults?: number;
252
+ }
253
+ interface AiSearchHit {
254
+ /** Canonical root-relative workspace path. Never an absolute filesystem path. */
255
+ readonly path: string;
256
+ readonly title: string | null;
257
+ readonly snippet: string | null;
258
+ readonly score: number;
259
+ }
260
+ interface AiSearchResult {
261
+ readonly hits: readonly AiSearchHit[];
262
+ readonly truncated: boolean;
263
+ }
264
+ interface AiImageRequest {
265
+ readonly prompt: string;
266
+ readonly width?: number;
267
+ readonly height?: number;
268
+ }
269
+ interface AiImageResult {
270
+ readonly mimeType: 'image/png';
271
+ readonly data: ArrayBuffer;
272
+ readonly width: number;
273
+ readonly height: number;
274
+ }
275
+ interface AiTranscribeRequest {
276
+ readonly audio: ArrayBuffer;
277
+ readonly mimeType: string;
278
+ readonly language?: string;
279
+ }
280
+ interface AiTranscript {
281
+ readonly text: string;
282
+ readonly language: string | null;
283
+ }
284
+ interface AiSynthesizeRequest {
285
+ readonly text: string;
286
+ readonly voice?: string;
287
+ }
288
+ interface AiSpeechAudio {
289
+ readonly mimeType: 'audio/wav';
290
+ readonly data: ArrayBuffer;
291
+ }
292
+ /**
293
+ * The AI namespace on `DocBlocksHostAPI`.
294
+ *
295
+ * The members below the core group are optional one by one rather than as a
296
+ * block, because they fail independently: a paired-device mobile host could
297
+ * plausibly transcribe from its own microphone while offering no image
298
+ * generation and no workspace index. `deriveHostCapabilities` observes each,
299
+ * so a host cannot claim an ability whose member is missing.
300
+ *
301
+ * A host that cannot do AI at all — an unsupported platform — omits this whole
302
+ * namespace rather than exposing one that always fails.
303
+ */
304
+ interface DocBlocksHostAiAPI {
305
+ /** Optional catalog browsing, downloads, and retrieval model setup. */
306
+ readonly knowledge?: AiKnowledgeAPI;
307
+ /** Whether the host's user-installed companion provider is present. */
308
+ providerInstalled(): Promise<boolean>;
309
+ status(): Promise<AiStatus>;
310
+ onStatus(listener: (status: AiStatus) => void): () => void;
311
+ getPreferences(): Promise<AiPreferences>;
312
+ setPreferences(patch: AiPreferencesPatch): Promise<AiPreferences>;
313
+ /**
314
+ * Run detection and approval. Called only from an explicit user gesture —
315
+ * never at startup, where a silent reconnect is the host's own business.
316
+ */
317
+ connect(): Promise<AiResult<AiStatus>>;
318
+ disconnect(): Promise<AiResult<null>>;
319
+ models(): Promise<AiResult<readonly AiModelInfo[]>>;
320
+ /** Catalog models this host can download. Optional for older providers. */
321
+ availableModels?(): Promise<AiResult<readonly AiModelDownloadInfo[]>>;
322
+ /** Start a model download from an explicit user gesture. */
323
+ installModel?(modelId: string, onProgress?: (progress: AiProgress) => void): AiModelInstallHandle;
324
+ /**
325
+ * Start a streamed completion.
326
+ *
327
+ * The correlation id is minted by the bridge, not by the caller, so a
328
+ * renderer cannot address another renderer's stream.
329
+ */
330
+ chat(request: AiChatRequest, onEvent: (event: AiChatEvent) => void): AiChatHandle;
331
+ ensureWorkspace?(workspaceId: string, onProgress?: (progress: AiProgress) => void): AiEnsureWorkspaceHandle;
332
+ search?(workspaceId: string, query: AiSearchQuery): Promise<AiResult<AiSearchResult>>;
333
+ generateImage?(request: AiImageRequest): Promise<AiResult<AiImageResult>>;
334
+ /** @deprecated Speech lives on `DocBlocksHostAPI.speech`; no host implements this. */
335
+ transcribe?(request: AiTranscribeRequest): Promise<AiResult<AiTranscript>>;
336
+ /** @deprecated Speech lives on `DocBlocksHostAPI.speech`; no host implements this. */
337
+ synthesize?(request: AiSynthesizeRequest): Promise<AiResult<AiSpeechAudio>>;
338
+ }
339
+
340
+ /**
341
+ * Git host API — wire types for the desktop shell's git integration.
342
+ *
343
+ * The desktop app spawns the user's system git from the main process, so
344
+ * credentials (helpers, SSH agent, signing config and raw remote URLs) remain
345
+ * in the main process. Everything here is generic git; the only host-specific
346
+ * conveniences are remote-URL parsing (`GitRemoteInfo.web`) and PR creation
347
+ * via the GitHub CLI when installed.
348
+ *
349
+ * Expected git failures cross IPC as data (`GitResult`), never as thrown
350
+ * errors — `ipcMain.handle` rejections lose structure. Throwing is reserved
351
+ * for contract violations (unregistered root, path escape).
352
+ */
353
+
354
+ /** Every git operation resolves this — expected failures are data, not throws. */
355
+ type GitResult<T> = {
356
+ ok: true;
357
+ value: T;
358
+ } | {
359
+ ok: false;
360
+ error: GitError;
361
+ };
362
+ interface GitError {
363
+ code: GitErrorCode;
364
+ /** One-line human summary (classified stderr or synthesized). */
365
+ message: string;
366
+ /** Truncated raw stderr for a details disclosure in the UI. */
367
+ stderr?: string;
368
+ exitCode?: number;
369
+ }
370
+ type GitErrorCode = 'git-not-available' | 'not-a-repository' | 'auth-failed' | 'remote-not-found' | 'no-upstream' | 'network' | 'non-fast-forward' | 'merge-conflict' | 'uncommitted-changes' | 'detached-head' | 'unborn-branch' | 'identity-not-configured' | 'branch-exists' | 'invalid-ref-name' | 'nothing-to-commit' | 'invalid-url' | 'destination-exists' | 'pr-exists' | 'gh-not-available' | 'timeout' | 'cancelled' | 'permission-denied' | 'unknown';
371
+ interface GitCapabilities {
372
+ /** False in store-sandboxed builds, when git is absent, or git < 2.23. */
373
+ gitAvailable: boolean;
374
+ gitVersion: string | null;
375
+ ghAvailable: boolean;
376
+ ghVersion: string | null;
377
+ }
378
+ interface GitRepoDetection {
379
+ isRepo: boolean;
380
+ /** False when the workspace root is a subdirectory of a larger repo. */
381
+ rootIsToplevel?: boolean;
382
+ /** Main-owned repository authority; absent while expanded access is ungranted. */
383
+ repositoryId?: string;
384
+ /** True when Git metadata or the work tree extends beyond the workspace grant. */
385
+ requiresExpandedGrant?: boolean;
386
+ /**
387
+ * Display path of the enclosing repository root. Present only alongside
388
+ * `requiresExpandedGrant` so the in-app consent surface can name what the
389
+ * user would be granting access to.
390
+ */
391
+ repositoryRoot?: string;
392
+ }
393
+ type GitFileStatusCode = 'added' | 'modified' | 'deleted' | 'renamed' | 'copied' | 'type-changed' | 'untracked' | 'ignored' | 'unmerged';
394
+ interface GitFileChange {
395
+ /** Slash-prefixed path relative to the WORKSPACE root (fs API convention). */
396
+ path: string;
397
+ /** Previous path for renames/copies (workspace-relative, slash-prefixed). */
398
+ origPath?: string;
399
+ /** Index-vs-HEAD state (staged), if any. */
400
+ index?: GitFileStatusCode;
401
+ /** Worktree-vs-index state (unstaged), if any. */
402
+ worktree?: GitFileStatusCode;
403
+ conflicted: boolean;
404
+ }
405
+ interface GitStatus {
406
+ /** Branch name; null when detached or unborn with no branch. */
407
+ branch: string | null;
408
+ detached: boolean;
409
+ /** HEAD points at a branch with no commits yet. */
410
+ unborn: boolean;
411
+ /** Full HEAD SHA; null when unborn. */
412
+ head: string | null;
413
+ /** Upstream ref short name, e.g. "origin/main". */
414
+ upstream: string | null;
415
+ ahead: number;
416
+ behind: number;
417
+ changes: GitFileChange[];
418
+ /** True when `changes` was capped. */
419
+ truncated: boolean;
420
+ operation: 'merge' | 'rebase' | 'cherry-pick' | 'revert' | 'bisect' | null;
421
+ }
422
+ interface GitBranchInfo {
423
+ /** "main" for local branches, "origin/main" for remote entries. */
424
+ name: string;
425
+ kind: 'local' | 'remote';
426
+ current: boolean;
427
+ upstream: string | null;
428
+ ahead: number;
429
+ behind: number;
430
+ upstreamGone: boolean;
431
+ headSha: string;
432
+ headSubject: string;
433
+ /** ISO 8601. */
434
+ headDate: string;
435
+ }
436
+ interface GitLogOptions {
437
+ /** Workspace-relative file path — per-file history (follows renames). */
438
+ path?: string;
439
+ /** Default 100; the host caps at 1000. */
440
+ maxCount?: number;
441
+ skip?: number;
442
+ }
443
+ interface GitLogEntry {
444
+ sha: string;
445
+ parents: string[];
446
+ authorName: string;
447
+ authorEmail: string;
448
+ /** ISO 8601. */
449
+ authorDate: string;
450
+ subject: string;
451
+ /** Ref decorations ("HEAD -> main", "origin/main", "tag: v1"). */
452
+ refs: string[];
453
+ }
454
+ /** Typed revision selector — the renderer never passes raw rev strings. */
455
+ type GitRevision = {
456
+ kind: 'head';
457
+ } | {
458
+ kind: 'index';
459
+ } | {
460
+ kind: 'commit';
461
+ sha: string;
462
+ } | {
463
+ kind: 'parent-of';
464
+ sha: string;
465
+ };
466
+ interface GitFileAtRevision {
467
+ /** Null when the file is absent at that revision, or binary/too large. */
468
+ content: string | null;
469
+ binary: boolean;
470
+ }
471
+ interface GitRemoteInfo {
472
+ name: string;
473
+ /** Sanitized web location. Raw fetch/push URLs never cross IPC. */
474
+ web: {
475
+ host: string;
476
+ owner: string;
477
+ repo: string;
478
+ webUrl: string;
479
+ } | null;
480
+ }
481
+ interface GitCloneProgress {
482
+ /** e.g. "Receiving objects", "Resolving deltas", "Checking out files". */
483
+ phase: string;
484
+ percent: number | null;
485
+ /** Raw progress line for display. */
486
+ detail?: string;
487
+ }
488
+ interface GitCloneHandle {
489
+ /**
490
+ * Resolves `ok(null)` if the user cancels the destination picker; on
491
+ * success resolves the registered workspace ready to open.
492
+ */
493
+ result: Promise<GitResult<ElectronWorkspaceInfo | null>>;
494
+ /** Kill git and remove the partial clone directory. */
495
+ cancel: () => void;
496
+ }
497
+ interface DocBlocksHostGitAPI {
498
+ capabilities(): Promise<GitCapabilities>;
499
+ /**
500
+ * Detect a persisted workspace by opaque workspace id and mint repository
501
+ * authority. Never prompts: a workspace inside a larger repository comes
502
+ * back with `requiresExpandedGrant` and no `repositoryId` until the user
503
+ * opts in through `grantExpandedRepo`.
504
+ */
505
+ detectRepo(workspaceId: string): Promise<GitResult<GitRepoDetection>>;
506
+ /**
507
+ * Grant expanded access to the repository enclosing the workspace and mint
508
+ * repository authority. Must be called from an explicit user gesture. The
509
+ * choice is remembered for that repository; `always` remembers it for every
510
+ * repository opened from a subfolder.
511
+ */
512
+ grantExpandedRepo(workspaceId: string, opts?: {
513
+ always?: boolean;
514
+ }): Promise<GitResult<GitRepoDetection>>;
515
+ init(workspaceId: string): Promise<GitResult<void>>;
516
+ status(repositoryId: string): Promise<GitResult<GitStatus>>;
517
+ stage(repositoryId: string, paths: string[]): Promise<GitResult<void>>;
518
+ unstage(repositoryId: string, paths: string[]): Promise<GitResult<void>>;
519
+ /** Revert tracked files to HEAD; delete untracked ones. Destructive — the renderer confirms. */
520
+ discard(repositoryId: string, paths: string[]): Promise<GitResult<void>>;
521
+ /**
522
+ * With `paths`: stages exactly those files (`add -A -- <paths>`) and commits
523
+ * them, leaving other staged entries staged. Without: commits the index as-is.
524
+ */
525
+ commit(repositoryId: string, message: string, paths?: string[]): Promise<GitResult<{
526
+ sha: string;
527
+ }>>;
528
+ push(repositoryId: string, opts?: {
529
+ setUpstream?: boolean;
530
+ }): Promise<GitResult<void>>;
531
+ pull(repositoryId: string): Promise<GitResult<void>>;
532
+ fetch(repositoryId: string): Promise<GitResult<void>>;
533
+ listBranches(repositoryId: string): Promise<GitResult<GitBranchInfo[]>>;
534
+ createBranch(repositoryId: string, name: string, opts?: {
535
+ checkout?: boolean;
536
+ }): Promise<GitResult<void>>;
537
+ checkoutBranch(repositoryId: string, name: string): Promise<GitResult<void>>;
538
+ log(repositoryId: string, opts?: GitLogOptions): Promise<GitResult<GitLogEntry[]>>;
539
+ /** Files changed in one commit plus the full message body. */
540
+ commitFiles(repositoryId: string, sha: string): Promise<GitResult<{
541
+ body: string;
542
+ files: GitFileChange[];
543
+ }>>;
544
+ readFileAtRevision(repositoryId: string, path: string, revision: GitRevision): Promise<GitResult<GitFileAtRevision>>;
545
+ listRemotes(repositoryId: string): Promise<GitResult<GitRemoteInfo[]>>;
546
+ /**
547
+ * Clone a remote repository. The main process shows a native picker for the
548
+ * destination parent directory, then registers the clone as a workspace.
549
+ */
550
+ clone(url: string, onProgress?: (p: GitCloneProgress) => void): GitCloneHandle;
551
+ /** `gh pr create --web` (falls back to `gh pr view --web` if one exists). */
552
+ createPullRequest(repositoryId: string): Promise<GitResult<void>>;
553
+ /**
554
+ * Throttled status stream for a workspace root. Emits once immediately on
555
+ * subscribe. Returns an unsubscribe function.
556
+ */
557
+ onStatusChanged(repositoryId: string, listener: (status: GitStatus) => void): () => void;
558
+ }
559
+
560
+ /**
561
+ * The host-agnostic speech seam: dictation (speech to text) and narration
562
+ * (text to speech).
563
+ *
564
+ * This is deliberately separate from `ai`. The AI namespace is shaped around a
565
+ * provider connection — installed or hosted, approved or not — while speech
566
+ * engines run inside the host itself and need only a downloaded model. A host
567
+ * may offer speech without AI and AI without speech.
568
+ *
569
+ * Shapes follow the vocabulary of the Gezel audio schemas (transcribe requests
570
+ * with a continuity prompt, synthesis streamed as independently playable
571
+ * chunks, the `ready` / `download-required` / `unavailable` /
572
+ * `permission-required` readiness states) so the two products describe the
573
+ * same engines the same way. Nothing here names an engine: model and voice ids
574
+ * are opaque strings.
575
+ *
576
+ * Every operation that can fail for an expected reason returns `SpeechResult`
577
+ * rather than rejecting; a missing model is an ordinary state, not a bug.
578
+ */
579
+ /** An expected-failure envelope. Rejections stay reserved for genuine bugs. */
580
+ type SpeechResult<T> = {
581
+ readonly ok: true;
582
+ readonly value: T;
583
+ } | {
584
+ readonly ok: false;
585
+ readonly error: SpeechError;
586
+ };
587
+ type SpeechErrorCode =
588
+ /** The operation needs a model that has not been downloaded. */
589
+ 'model-missing' | 'model-download-failed'
590
+ /** The engine cannot run on this machine (missing runtime, OS too old). */
591
+ | 'engine-unavailable'
592
+ /** The engine started but failed this request, or crashed. */
593
+ | 'engine-failed' | 'invalid-request'
594
+ /** Too many requests in flight; retry after the current one finishes. */
595
+ | 'busy' | 'timeout' | 'cancelled' | 'unknown';
596
+ interface SpeechError {
597
+ readonly code: SpeechErrorCode;
598
+ /** Shown to the user. */
599
+ readonly message: string;
600
+ /** Optional diagnostic detail for logs; not for display. */
601
+ readonly detail?: string;
602
+ }
603
+ type SpeechModelKind = 'stt' | 'tts';
604
+ type SpeechReadinessState = 'ready'
605
+ /** A model must be downloaded first; never started without a user gesture. */
606
+ | 'download-required'
607
+ /** The engine cannot run here; `reason` says why. */
608
+ | 'unavailable'
609
+ /** The operating system has refused microphone access to this app. */
610
+ | 'permission-required';
611
+ /** Readiness never initiates a download, asks for permission, or warms a model. */
612
+ interface SpeechReadiness {
613
+ readonly state: SpeechReadinessState;
614
+ readonly reason?: string;
615
+ /** The model that would serve requests, when one is chosen. */
616
+ readonly model?: string;
617
+ }
618
+ interface SpeechStatus {
619
+ readonly stt: SpeechReadiness;
620
+ readonly tts: SpeechReadiness;
621
+ }
622
+ interface SpeechModelInfo {
623
+ readonly id: string;
624
+ readonly kind: SpeechModelKind;
625
+ readonly label: string;
626
+ readonly description: string;
627
+ readonly downloadBytes: number;
628
+ readonly installed: boolean;
629
+ /** An older app download exists; update it explicitly before using this model. */
630
+ readonly updateRequired?: boolean;
631
+ /**
632
+ * Where the installed copy lives. `shared` is a verified read-only copy
633
+ * another local app already downloaded, which costs this app no disk.
634
+ */
635
+ readonly source: 'app' | 'shared' | null;
636
+ /** This installation uses the shared speech cache; Remove releases only its references. */
637
+ readonly sharedStorage?: boolean;
638
+ readonly recommended: boolean;
639
+ readonly license: string;
640
+ readonly licenseUrl: string;
641
+ }
642
+ interface SpeechVoiceInfo {
643
+ readonly id: string;
644
+ readonly label: string;
645
+ /** BCP 47 tag, e.g. `en-US`. */
646
+ readonly language: string;
647
+ readonly gender: 'female' | 'male' | null;
648
+ /** The TTS model that speaks this voice. */
649
+ readonly modelId: string;
650
+ }
651
+ interface SpeechCatalog {
652
+ readonly models: readonly SpeechModelInfo[];
653
+ readonly voices: readonly SpeechVoiceInfo[];
654
+ }
655
+ interface SpeechProgress {
656
+ readonly phase: 'downloading' | 'verifying';
657
+ readonly receivedBytes: number;
658
+ /** Null when the server reports no length. */
659
+ readonly totalBytes: number | null;
660
+ }
661
+ type SpeechInstallEvent = {
662
+ readonly kind: 'progress';
663
+ readonly progress: SpeechProgress;
664
+ } | {
665
+ readonly kind: 'done';
666
+ readonly model: SpeechModelInfo;
667
+ } | {
668
+ readonly kind: 'error';
669
+ readonly error: SpeechError;
670
+ };
671
+ interface SpeechInstallHandle {
672
+ readonly done: Promise<SpeechResult<SpeechModelInfo>>;
673
+ cancel(): void;
674
+ }
675
+ interface SpeechPreferences {
676
+ /** Dictation model id, or null for the recommended installed model. */
677
+ readonly sttModel: string | null;
678
+ /** Narration voice id, or null for the default voice. */
679
+ readonly voice: string | null;
680
+ /** Narration speed, 0.5 to 2. */
681
+ readonly speed: number;
682
+ }
683
+ type SpeechPreferencesPatch = Partial<SpeechPreferences>;
684
+ interface SpeechTranscribeRequest {
685
+ /** One self-contained take. */
686
+ readonly audio: ArrayBuffer;
687
+ readonly mimeType: 'audio/wav';
688
+ /**
689
+ * Recent transcript tail for continuity of vocabulary and punctuation. It is
690
+ * a hint to the recogniser, never text to re-transcribe.
691
+ */
692
+ readonly prompt?: string;
693
+ readonly language?: string;
694
+ }
695
+ interface SpeechSegment {
696
+ /** Seconds from the start of the take. */
697
+ readonly start: number;
698
+ readonly end: number;
699
+ readonly text: string;
700
+ }
701
+ interface SpeechTranscript {
702
+ /** Trimmed; empty when the take held no speech. */
703
+ readonly text: string;
704
+ readonly language: string | null;
705
+ readonly segments: readonly SpeechSegment[];
706
+ readonly durationMs: number;
707
+ }
708
+ interface SpeechSynthesizeRequest {
709
+ readonly text: string;
710
+ /** Voice id, or omitted for the preferred voice. */
711
+ readonly voice?: string;
712
+ /** 0.5 to 2, or omitted for the preferred speed. */
713
+ readonly speed?: number;
714
+ }
715
+ /** Model-derived word allocation, relative to the chunk's audio and request text. */
716
+ interface SpeechWordTiming {
717
+ readonly textStart: number;
718
+ readonly textEnd: number;
719
+ readonly startSec: number;
720
+ readonly endSec: number;
721
+ }
722
+ /** One independently playable piece of a synthesis, usually a sentence. */
723
+ interface SpeechAudioChunk {
724
+ readonly index: number;
725
+ /** Mono 32-bit float samples. */
726
+ readonly pcm: ArrayBuffer;
727
+ readonly sampleRate: number;
728
+ readonly durationSec: number;
729
+ /** UTF-16 range of the request text this chunk speaks. */
730
+ readonly textStart: number;
731
+ readonly textEnd: number;
732
+ /** Optional for engines without model durations. Expanded words may share a source range. */
733
+ readonly wordTimings?: readonly SpeechWordTiming[];
734
+ }
735
+ interface SpeechSynthesisSummary {
736
+ readonly voice: string;
737
+ readonly model: string;
738
+ readonly sampleRate: number;
739
+ readonly durationSec: number;
740
+ readonly chunks: number;
741
+ }
742
+ /**
743
+ * A finite synthesis stream. Audio always arrives as at least one `chunk`
744
+ * before `done`, so a caller can play chunks as they come.
745
+ */
746
+ type SpeechSynthesizeEvent = {
747
+ readonly kind: 'progress';
748
+ readonly phase: 'loading' | 'synthesizing';
749
+ readonly completedCharacters: number;
750
+ readonly totalCharacters: number;
751
+ } | {
752
+ readonly kind: 'chunk';
753
+ readonly chunk: SpeechAudioChunk;
754
+ } | {
755
+ readonly kind: 'done';
756
+ readonly summary: SpeechSynthesisSummary;
757
+ } | {
758
+ readonly kind: 'error';
759
+ readonly error: SpeechError;
760
+ };
761
+ interface SpeechSynthesisHandle {
762
+ readonly done: Promise<SpeechResult<SpeechSynthesisSummary>>;
763
+ cancel(): void;
764
+ }
765
+ /**
766
+ * The speech namespace on `DocBlocksHostAPI`.
767
+ *
768
+ * `transcribe` and `synthesize` are optional one by one: an engine can be
769
+ * missing for this platform while the other works, and
770
+ * `deriveHostCapabilities` observes each. A host with neither engine omits the
771
+ * whole namespace.
772
+ */
773
+ interface DocBlocksHostSpeechAPI {
774
+ status(): Promise<SpeechStatus>;
775
+ onStatus(listener: (status: SpeechStatus) => void): () => void;
776
+ catalog(): Promise<SpeechResult<SpeechCatalog>>;
777
+ /** Start a model download. Called only from an explicit user gesture. */
778
+ installModel(modelId: string, onProgress?: (progress: SpeechProgress) => void): SpeechInstallHandle;
779
+ removeModel(modelId: string): Promise<SpeechResult<null>>;
780
+ getPreferences(): Promise<SpeechPreferences>;
781
+ setPreferences(patch: SpeechPreferencesPatch): Promise<SpeechPreferences>;
782
+ /** Warm an engine ahead of a request, e.g. when the user starts dictating. */
783
+ prepare(kind: SpeechModelKind): Promise<SpeechResult<null>>;
784
+ transcribe?(request: SpeechTranscribeRequest): Promise<SpeechResult<SpeechTranscript>>;
785
+ synthesize?(request: SpeechSynthesizeRequest, onEvent: (event: SpeechSynthesizeEvent) => void): SpeechSynthesisHandle;
786
+ }
787
+
788
+ /**
789
+ * DocBlocksHostAPI — the contract exposed by the Electron desktop shell
790
+ * to its renderer process via contextBridge.
791
+ *
792
+ * This file is the single source of truth for the host ↔ renderer
793
+ * bridge. The Electron preload script exposes an implementation matching
794
+ * this shape; the React renderer calls it through `window.docBlocksHost`.
795
+ */
796
+
797
+ /** Filesystem operations scoped to a main-owned registered workspace id. */
798
+ interface DocBlocksHostFsAPI {
799
+ readFile(workspaceId: string, path: string): Promise<string | null>;
800
+ writeFile(workspaceId: string, path: string, content: string): Promise<void>;
801
+ commitFile(workspaceId: string, path: string, content: string, expectedContent: string | null): Promise<FileCommitResult>;
802
+ delete(workspaceId: string, path: string): Promise<void>;
803
+ rename(workspaceId: string, oldPath: string, newPath: string): Promise<void>;
804
+ readDirectory(workspaceId: string, path: string): Promise<FileSystemEntry[]>;
805
+ exists(workspaceId: string, path: string): Promise<boolean>;
806
+ createDirectory(workspaceId: string, path: string): Promise<void>;
807
+ stat(workspaceId: string, path: string): Promise<FileMeta | null>;
808
+ readBinary(workspaceId: string, path: string): Promise<ArrayBuffer | null>;
809
+ writeBinary(workspaceId: string, path: string, data: ArrayBuffer | Uint8Array): Promise<void>;
810
+ /**
811
+ * Subscribe to change notifications for a watched root. Returns an
812
+ * unsubscribe function. The main process uses chokidar under the hood.
813
+ */
814
+ watch(workspaceId: string, onChange: (path: string) => void): () => void;
815
+ }
816
+ /** Descriptor returned for an Electron-managed workspace (backed by a folder). */
817
+ interface ElectronWorkspaceInfo {
818
+ id: string;
819
+ name: string;
820
+ /** Display/persistence metadata only. Privileged host calls accept `id`, never this path. */
821
+ rootPath: string;
822
+ /**
823
+ * True for the app's own default workspace — the one DocBlocks creates and
824
+ * may seed with its starter document. Folders the user picked or cloned are
825
+ * never default. Hosts that cannot tell omit it.
826
+ */
827
+ isDefault?: boolean;
828
+ }
829
+ /** Workspace-management operations exposed to the renderer. */
830
+ interface DocBlocksHostWorkspacesAPI {
831
+ /** List only folders already persisted as trusted main-process roots. */
832
+ list(): Promise<ElectronWorkspaceInfo[]>;
833
+ /**
834
+ * Return the default workspace (creating DocBlocks inside the operating
835
+ * system's Documents folder on first call, or the user's configured default).
836
+ */
837
+ getDefault(): Promise<ElectronWorkspaceInfo>;
838
+ /**
839
+ * Open the native folder picker. Returns null if the user cancels.
840
+ * The selected folder is registered in the main process whitelist.
841
+ */
842
+ pickFolder?(): Promise<ElectronWorkspaceInfo | null>;
843
+ /**
844
+ * Re-register a previously picker-approved workspace by main-owned id.
845
+ * Called on app startup before any filesystem operation.
846
+ */
847
+ register(workspaceId: string): Promise<void>;
848
+ /** Remove a workspace from the trusted whitelist. */
849
+ unregister(id: string): Promise<void>;
850
+ }
851
+ /** Shell operations — reveal in Finder/Explorer, open external URLs. */
852
+ interface DocBlocksHostShellAPI {
853
+ /** Reveal a registered workspace root or one root-relative entry. */
854
+ revealInFolder?(workspaceId: string, workspacePath?: string): Promise<void>;
855
+ /** Open a registered workspace root in Finder or the platform file manager. */
856
+ openWorkspaceFolder?(workspaceId: string): Promise<void>;
857
+ /** Open a URL in the default browser. */
858
+ openExternal(url: string): Promise<void>;
859
+ }
860
+ /** Native clipboard operations exposed through the trusted host boundary. */
861
+ interface DocBlocksHostClipboardAPI {
862
+ /** Replace the system clipboard's plain-text contents. */
863
+ writeText(text: string): Promise<void>;
864
+ /** Resolve a registered workspace entry in main and copy its absolute path. */
865
+ writeWorkspacePath?(workspaceId: string, workspacePath: string): Promise<void>;
866
+ }
867
+ /** Exact, main-owned export authority. The display path is never authority. */
868
+ interface HostExportTargetGrant {
869
+ /** Null means this is a display-only suggestion and save must show a picker. */
870
+ grantId: string | null;
871
+ displayPath: string;
872
+ }
873
+ /** Native export target selection and binary file writing. */
874
+ interface DocBlocksHostExportAPI {
875
+ /** A share-sheet outcome is never a durable export target. Android can only report presentation. */
876
+ share?(filename: string, data: ArrayBuffer | Uint8Array): Promise<'shared' | 'presented' | 'cancelled'>;
877
+ /** Resolve an exact remembered grant or a display-only host suggestion. */
878
+ resolveTarget?(documentId: string, filename: string): Promise<HostExportTargetGrant>;
879
+ /** Open the native Save dialog and remember the selected target. */
880
+ pickTarget?(documentId: string, filename: string, currentGrantId?: string | null): Promise<HostExportTargetGrant | null>;
881
+ /** Write only to an exact target granted by the main process. */
882
+ save(documentId: string, filename: string, grantId: string | null, data: ArrayBuffer | Uint8Array): Promise<HostExportTargetGrant | null>;
883
+ /**
884
+ * Open a chunked upload for an export too large for one `save` message.
885
+ * Authority is settled before any bytes move: the grant must already exist.
886
+ * Returns an owner-scoped transfer id.
887
+ */
888
+ beginSave?(documentId: string, filename: string, grantId: string, size: number): Promise<string>;
889
+ /** Append the next in-order chunk of `EXPORT_TRANSFER_LIMITS.chunkBytes` or fewer. */
890
+ writeChunk?(transferId: string, offset: number, data: ArrayBuffer | Uint8Array): Promise<void>;
891
+ /** Publish a complete upload to its granted target; null when replacement is declined. */
892
+ finishSave?(transferId: string): Promise<HostExportTargetGrant | null>;
893
+ /** Discard an upload. Idempotent, and harmless after `finishSave`. */
894
+ closeTransfer?(transferId: string): Promise<void>;
895
+ }
896
+ /** System ffmpeg detection. */
897
+ interface DocBlocksHostFfmpegAPI {
898
+ /** True if `ffmpeg` is available on PATH. */
899
+ available(): Promise<boolean>;
900
+ /** Version string from `ffmpeg -version`, or null if unavailable. */
901
+ version(): Promise<string | null>;
902
+ }
903
+ /** Auto-updater control. */
904
+ interface DocBlocksHostUpdaterAPI {
905
+ /** Kick off a check without collapsing a failed check into "no update". */
906
+ checkForUpdates(): Promise<UpdateCheckResult>;
907
+ /** Current app version string. */
908
+ getVersion(): Promise<string>;
909
+ /**
910
+ * Quit the app and apply a downloaded update. Should only be called
911
+ * after an `UpdaterStatus` of kind `'downloaded'` has been observed.
912
+ */
913
+ quitAndInstall(): Promise<UpdateInstallResult>;
914
+ /**
915
+ * Subscribe to updater status events. Returns an unsubscribe function.
916
+ */
917
+ onStatus(listener: (status: UpdaterStatus) => void): () => void;
918
+ }
919
+ type UpdateInstallResult = 'installing' | 'cancelled' | 'not-ready';
920
+ type UpdateCheckResult = {
921
+ kind: 'available';
922
+ version: string;
923
+ } | {
924
+ kind: 'not-available';
925
+ } | {
926
+ kind: 'store-managed';
927
+ } | {
928
+ kind: 'error';
929
+ message: string;
930
+ };
931
+ type UpdaterStatus = {
932
+ kind: 'checking';
933
+ } | {
934
+ kind: 'available';
935
+ version: string;
936
+ releaseNotes?: string;
937
+ releaseUrl?: string;
938
+ } | {
939
+ kind: 'not-available';
940
+ } | {
941
+ kind: 'downloading';
942
+ percent: number;
943
+ } | {
944
+ kind: 'downloaded';
945
+ version: string;
946
+ releaseNotes?: string;
947
+ releaseUrl?: string;
948
+ } | {
949
+ kind: 'error';
950
+ message: string;
951
+ };
952
+ /** Menu-command events pushed from the main process to the renderer. */
953
+ type MenuCommand = 'file:new' | 'file:openFolder' | 'file:revealWorkspace' | 'file:settings' | 'git:commit' | 'git:push' | 'git:pull' | 'git:fetch' | 'git:newBranch' | 'git:switchBranch' | 'git:history' | 'git:clone' | 'git:openOnRemote' | 'git:createPullRequest' | 'help:about' | 'help:checkForUpdates' | 'help:viewOnGitHub'
954
+ /** Open (or refocus) the find bar in the active document. */
955
+ | 'edit:find'
956
+ /** Start or stop dictation into the active editor. */
957
+ | 'edit:toggleDictation'
958
+ /** Read the document (or the selection) aloud, or stop reading. */
959
+ | 'edit:readAloud';
960
+ /**
961
+ * One pinned document mirrored from the renderer into native menu surfaces.
962
+ *
963
+ * The renderer owns the authoritative pinned list (localStorage). The main
964
+ * process only caches the latest snapshot so the tray / menu bar can offer a
965
+ * one-click shortcut per document; it never persists or mutates it.
966
+ */
967
+ interface HostPinnedDocument {
968
+ readonly workspaceId: string;
969
+ readonly workspaceName: string;
970
+ /** Canonical, root-relative workspace path. */
971
+ readonly path: string;
972
+ }
973
+ /**
974
+ * Renderer-to-main channel that keeps native menu surfaces in sync with
975
+ * renderer-owned state.
976
+ */
977
+ interface DocBlocksHostMenuAPI {
978
+ /**
979
+ * Publish the current pinned-document list so the desktop tray can offer a
980
+ * shortcut per document that activates it in the main window. Replaces any
981
+ * previously published list; send an empty array to clear the shortcuts.
982
+ */
983
+ setPinnedDocuments(documents: readonly HostPinnedDocument[]): void;
984
+ }
985
+ /**
986
+ * OS open-file / deep-link event resolved by the host.
987
+ * - 'workspace-file': a file inside an already-registered workspace.
988
+ * - 'external-file': a loose markdown file outside any workspace — opened in a
989
+ * transient workspace and saved through an opaque resource grant.
990
+ * - 'external-bundle': a `.dbk`/zip bundle — unpacked into a transient
991
+ * workspace and re-zipped through an opaque resource grant on save.
992
+ */
993
+ type OpenRequest = {
994
+ kind: 'workspace-file';
995
+ workspaceId: string;
996
+ /** Slash-prefixed path relative to the workspace root. */
997
+ path: string;
998
+ } | {
999
+ kind: 'external-file';
1000
+ resourceId: string;
1001
+ name: string;
1002
+ } | {
1003
+ kind: 'external-bundle';
1004
+ resourceId: string;
1005
+ name: string;
1006
+ };
1007
+ /**
1008
+ * Read/write access to individual OS files the user has explicitly opened
1009
+ * (via "Open With" / deep link), outside the workspace-root whitelist. The
1010
+ * main process gates these with owner-scoped, exact-file capabilities.
1011
+ */
1012
+ interface DocBlocksHostExternalAPI {
1013
+ readText(resourceId: string): Promise<string | null>;
1014
+ readBinary(resourceId: string): Promise<ArrayBuffer | null>;
1015
+ writeText(resourceId: string, content: string): Promise<void>;
1016
+ writeBinary(resourceId: string, data: ArrayBuffer | Uint8Array): Promise<void>;
1017
+ commitText(resourceId: string, content: string, expectedContent: string | null): Promise<FileCommitResult>;
1018
+ commitBinary(resourceId: string, data: ArrayBuffer | Uint8Array, expectedVersion: string | null): Promise<ExternalBinaryCommitResult>;
1019
+ /** Revoke one external resource when its transient document is closed. */
1020
+ revoke(resourceId: string): Promise<void>;
1021
+ }
1022
+ type ExternalBinaryCommitResult = {
1023
+ status: 'committed';
1024
+ version: string;
1025
+ } | {
1026
+ status: 'conflict';
1027
+ version: string | null;
1028
+ data: ArrayBuffer | null;
1029
+ };
1030
+ /**
1031
+ * Environment metadata provided by the host.
1032
+ *
1033
+ * Read synchronously by the renderer, so a host implementation must have
1034
+ * these values in hand before it exposes the bridge — they cannot be awaited.
1035
+ *
1036
+ * Every field is main-process authority. A host must derive them from what it
1037
+ * actually knows about itself (in Electron: `app.getVersion()` and
1038
+ * `app.isPackaged`) and never from renderer-visible environment variables:
1039
+ * `npm_package_version` exists only under an `npm run` script and `NODE_ENV`
1040
+ * is unset in a packaged app, so both silently lie in exactly the builds users
1041
+ * install. `appVersion` in particular reaches users through the About surface
1042
+ * and issue-report URLs.
1043
+ */
1044
+ interface HostEnvironment {
1045
+ /** Which shell installed this bridge. Ask a capability, not this, wherever possible. */
1046
+ surface: HostSurfaceKind;
1047
+ /** Short user-facing label for About and issue reports: 'desktop', 'iOS', 'Android'. */
1048
+ surfaceLabel: string;
1049
+ platform: HostPlatform;
1050
+ /** Real, user-facing app version. Never a placeholder like '0.0.0'. */
1051
+ appVersion: string;
1052
+ /** True only in an unpackaged development run. */
1053
+ isDev: boolean;
1054
+ }
1055
+ type HostCloseReason = 'window-close' | 'app-quit' | 'update-install' | 'reload' | 'force-reload'
1056
+ /**
1057
+ * The OS is suspending the app. Unlike every reason above this is a deadline,
1058
+ * not a negotiation: iOS and Android will not let the app veto it, so a
1059
+ * `blocked` result cannot raise a dialog and must simply be journalled.
1060
+ */
1061
+ | 'app-background';
1062
+ interface HostPrepareCloseRequest {
1063
+ requestId: string;
1064
+ reason: HostCloseReason;
1065
+ /** Absolute epoch-millisecond deadline chosen by the main process. */
1066
+ deadline: number;
1067
+ }
1068
+ type HostPrepareCloseResult = {
1069
+ status: 'ready';
1070
+ persistedRevision: number;
1071
+ } | {
1072
+ status: 'blocked';
1073
+ code: 'save-failed' | 'external-conflict' | 'not-ready';
1074
+ message: string;
1075
+ };
1076
+ /**
1077
+ * Main-owned close handshake. Preload privately correlates replies; the
1078
+ * renderer never chooses request or window identifiers.
1079
+ */
1080
+ interface DocBlocksHostLifecycleAPI {
1081
+ /** Request the main-owned guarded close path for this renderer window. */
1082
+ requestWindowClose?(): void;
1083
+ onPrepareClose(listener: (request: HostPrepareCloseRequest) => Promise<HostPrepareCloseResult>): () => void;
1084
+ onCancelClose?(listener: (requestId: string) => void): () => void;
1085
+ /** Observe current durable content after a native app returns to the foreground. */
1086
+ onResume?(listener: () => void): () => void;
1087
+ /** Return true when a dialog, drawer, or document navigation consumed Android Back. */
1088
+ onBack?(listener: () => boolean): () => void;
1089
+ }
1090
+ /** The full DocBlocks desktop host API. */
1091
+ interface DocBlocksHostAPI {
1092
+ env: HostEnvironment;
1093
+ fsV2: DocBlocksHostFsV2API;
1094
+ workspaces: DocBlocksHostWorkspacesAPI;
1095
+ /**
1096
+ * Everything below is optional because a host may genuinely not provide it —
1097
+ * a mobile shell has no updater, no native menu bar, and no Git. Marking
1098
+ * them optional is what turns "audit every isElectronHost() call site" into a
1099
+ * list the typechecker produces. Never reach for one directly: ask
1100
+ * `hostSupports(...)` first, or read it through a capability-gated branch.
1101
+ */
1102
+ fs?: DocBlocksHostFsAPI;
1103
+ external?: DocBlocksHostExternalAPI;
1104
+ shell?: DocBlocksHostShellAPI;
1105
+ clipboard?: DocBlocksHostClipboardAPI;
1106
+ exports?: DocBlocksHostExportAPI;
1107
+ ffmpeg?: DocBlocksHostFfmpegAPI;
1108
+ git?: DocBlocksHostGitAPI;
1109
+ updater?: DocBlocksHostUpdaterAPI;
1110
+ lifecycle?: DocBlocksHostLifecycleAPI;
1111
+ menu?: DocBlocksHostMenuAPI;
1112
+ /**
1113
+ * Absent entirely on a host that cannot do AI — an unsupported platform
1114
+ * omits the namespace rather than exposing one whose every call fails.
1115
+ */
1116
+ ai?: DocBlocksHostAiAPI;
1117
+ /**
1118
+ * Absent on a host with no speech engine for this platform. Present
1119
+ * independently of `ai`: speech engines run in the host and need only a
1120
+ * downloaded model, not a provider connection.
1121
+ */
1122
+ speech?: DocBlocksHostSpeechAPI;
1123
+ /**
1124
+ * Subscribe to menu commands dispatched by the native menu.
1125
+ * Returns an unsubscribe function.
1126
+ */
1127
+ onMenuCommand?(listener: (cmd: MenuCommand) => void): () => void;
1128
+ /**
1129
+ * Subscribe to open-file / open-url requests from the OS.
1130
+ * Returns an unsubscribe function.
1131
+ */
1132
+ onOpenRequest(listener: (request: OpenRequest) => void): () => void;
1133
+ }
1134
+
1135
+ /**
1136
+ * Which shell installed the bridge.
1137
+ *
1138
+ * This is an identity question, and almost nothing should be asking it — ask a
1139
+ * capability instead. It exists for the handful of places that genuinely differ
1140
+ * by product rather than by ability, such as which documentation URL to open.
1141
+ */
1142
+ type HostSurfaceKind = 'electron' | 'capacitor';
1143
+ type HostPlatform = 'darwin' | 'win32' | 'linux' | 'ios' | 'android';
1144
+ declare const HOST_SURFACE_KINDS: readonly HostSurfaceKind[];
1145
+ declare const HOST_PLATFORMS: readonly HostPlatform[];
1146
+ /**
1147
+ * What the installed host can actually do.
1148
+ *
1149
+ * Every member is a question a caller genuinely asks. None is a proxy for
1150
+ * "which shell is this" — that was the failure of `isElectronHost()`, which
1151
+ * stood in for a dozen unrelated decisions and which no mobile host could
1152
+ * answer honestly either way.
1153
+ */
1154
+ interface HostCapabilities {
1155
+ /** The host owns durable workspace roots, addressed by registered id. */
1156
+ readonly nativeWorkspaces: boolean;
1157
+ /** `workspaces.list()` is authoritative and must be reconciled at startup. */
1158
+ readonly workspaceAuthority: boolean;
1159
+ readonly workspaceFolderPicker: boolean;
1160
+ readonly defaultWorkspace: boolean;
1161
+ readonly filesystemV2: boolean;
1162
+ /** Can show an entry in the OS file manager. iOS cannot. */
1163
+ readonly revealInFileManager: boolean;
1164
+ readonly openWorkspaceFolder: boolean;
1165
+ readonly externalNavigation: boolean;
1166
+ readonly clipboard: boolean;
1167
+ /** Any host-owned save path at all. */
1168
+ readonly exportDestinations: boolean;
1169
+ /** Remembered, re-resolvable export targets (`resolveTarget`/`pickTarget`). */
1170
+ readonly exportRememberedTargets: boolean;
1171
+ readonly externalResources: boolean;
1172
+ readonly openRequests: boolean;
1173
+ readonly menuCommands: boolean;
1174
+ readonly pinnedDocumentMirror: boolean;
1175
+ readonly guardedClose: boolean;
1176
+ readonly git: boolean;
1177
+ readonly updater: boolean;
1178
+ readonly systemFfmpeg: boolean;
1179
+ /**
1180
+ * Assisted writing and review: a streamed completion plus a status channel.
1181
+ *
1182
+ * The AI abilities below are separate flags rather than one, because they are
1183
+ * independently absent. A paired-device mobile host could plausibly transcribe
1184
+ * from its own microphone while offering no image generation and no index.
1185
+ */
1186
+ readonly aiAssist: boolean;
1187
+ /** The host can list and download provider-catalog models. */
1188
+ readonly aiModelManagement: boolean;
1189
+ readonly aiKnowledgeManagement: boolean;
1190
+ /** The host can bind a workspace folder to a provider-side index. */
1191
+ readonly aiWorkspaceIndex: boolean;
1192
+ readonly aiSearch: boolean;
1193
+ readonly aiImages: boolean;
1194
+ /** @deprecated Speech moved to `host.speech`; use `speechInput`. Always false on current hosts. */
1195
+ readonly aiTranscription: boolean;
1196
+ /** @deprecated Speech moved to `host.speech`; use `speechOutput`. Always false on current hosts. */
1197
+ readonly aiSpeechSynthesis: boolean;
1198
+ /** Dictation: the host can transcribe recorded takes. */
1199
+ readonly speechInput: boolean;
1200
+ /** Narration: the host can synthesize speech from text. */
1201
+ readonly speechOutput: boolean;
1202
+ /** The host can list, download, and remove speech models. */
1203
+ readonly speechModelManagement: boolean;
1204
+ /** Documents live in the web origin (IndexedDB) rather than on the host. */
1205
+ readonly browserOriginStorage: boolean;
1206
+ /** The host draws the window chrome, so the shell must leave room for it. */
1207
+ readonly ownsWindowChrome: boolean;
1208
+ }
1209
+ /** All-false (plus browser storage) — exported so callers can render hostless. */
1210
+ declare const NO_HOST_CAPABILITIES: HostCapabilities;
1211
+ /**
1212
+ * Parse the host-declared environment.
1213
+ *
1214
+ * `env` is data crossing a trust boundary, so it gets a real parser rather than
1215
+ * a cast. An unknown surface or platform, an unbounded string, or an unexpected
1216
+ * key makes the whole host untrusted — `deriveHostCapabilities` then reports
1217
+ * nothing, which fails closed instead of half-enabling a bridge we do not
1218
+ * understand.
1219
+ */
1220
+ declare function parseHostEnvironment(value: unknown): HostEnvironment | null;
1221
+ /**
1222
+ * Derive capabilities from what the bridge actually exposes.
1223
+ *
1224
+ * Observed, never self-declared: a host cannot claim a capability whose API
1225
+ * member is missing, nor hide one whose member is present, so the two can never
1226
+ * drift. It also means preload or plugin version skew degrades to "unsupported"
1227
+ * rather than to a runtime TypeError on first use. This mirrors what
1228
+ * `ElectronFileSystemProviderV2` already does when it feature-detects the
1229
+ * chunked-transfer methods.
1230
+ */
1231
+ declare function deriveHostCapabilities(host: unknown): HostCapabilities;
1232
+
1233
+ export { type GitCapabilities as $, type AiChatCompletion as A, type AiSearchHit as B, type AiSearchQuery as C, type AiSearchResult as D, type AiSpeechAudio as E, type AiStatus as F, type AiSynthesizeRequest as G, type AiTranscribeRequest as H, type AiTranscript as I, type AiUnavailableReason as J, type DocBlocksHostAPI as K, type DocBlocksHostAiAPI as L, type DocBlocksHostClipboardAPI as M, type DocBlocksHostExportAPI as N, type DocBlocksHostExternalAPI as O, type DocBlocksHostFfmpegAPI as P, type DocBlocksHostFsAPI as Q, type DocBlocksHostGitAPI as R, type DocBlocksHostLifecycleAPI as S, type DocBlocksHostMenuAPI as T, type DocBlocksHostShellAPI as U, type DocBlocksHostSpeechAPI as V, type DocBlocksHostUpdaterAPI as W, type DocBlocksHostWorkspacesAPI as X, type ElectronWorkspaceInfo as Y, type ExternalBinaryCommitResult as Z, type GitBranchInfo as _, type AiChatEvent as a, type GitCloneHandle as a0, type GitCloneProgress as a1, type GitError as a2, type GitErrorCode as a3, type GitFileAtRevision as a4, type GitFileChange as a5, type GitFileStatusCode as a6, type GitLogEntry as a7, type GitLogOptions as a8, type GitRemoteInfo as a9, type SpeechPreferences as aA, type SpeechPreferencesPatch as aB, type SpeechProgress as aC, type SpeechReadiness as aD, type SpeechReadinessState as aE, type SpeechResult as aF, type SpeechSegment as aG, type SpeechStatus as aH, type SpeechSynthesisHandle as aI, type SpeechSynthesisSummary as aJ, type SpeechSynthesizeEvent as aK, type SpeechSynthesizeRequest as aL, type SpeechTranscribeRequest as aM, type SpeechTranscript as aN, type SpeechVoiceInfo as aO, type SpeechWordTiming as aP, type UpdateCheckResult as aQ, type UpdateInstallResult as aR, type UpdaterStatus as aS, deriveHostCapabilities as aT, parseAiKnowledgeAction as aU, parseAiKnowledgeState as aV, parseHostEnvironment as aW, type GitRepoDetection as aa, type GitResult as ab, type GitRevision as ac, type GitStatus as ad, HOST_PLATFORMS as ae, HOST_SURFACE_KINDS as af, type HostCapabilities as ag, type HostCloseReason as ah, type HostEnvironment as ai, type HostExportTargetGrant as aj, type HostPinnedDocument as ak, type HostPlatform as al, type HostPrepareCloseRequest as am, type HostPrepareCloseResult as an, type HostSurfaceKind as ao, type MenuCommand as ap, NO_HOST_CAPABILITIES as aq, type OpenRequest as ar, type SpeechAudioChunk as as, type SpeechCatalog as at, type SpeechError as au, type SpeechErrorCode as av, type SpeechInstallEvent as aw, type SpeechInstallHandle as ax, type SpeechModelInfo as ay, type SpeechModelKind as az, type AiChatHandle as b, type AiChatMessage as c, type AiChatProgress as d, type AiChatPurpose as e, type AiChatRequest as f, type AiConnectionStep as g, type AiEnsureWorkspaceHandle as h, type AiError as i, type AiErrorCode as j, type AiImageRequest as k, type AiImageResult as l, type AiKnowledgeAPI as m, type AiKnowledgeAction as n, type AiKnowledgeCatalog as o, type AiKnowledgeState as p, type AiModelDownloadInfo as q, type AiModelInfo as r, type AiModelInstallEvent as s, type AiModelInstallHandle as t, type AiPreferences as u, type AiPreferencesPatch as v, type AiProgress as w, type AiProviderInfo as x, type AiResult as y, type AiReviewMode as z };