@hudhod/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1017 @@
1
+ import { a as ProviderEntry, i as FileSystemProvider, n as SpawnedProcess, o as ProviderStat, r as SpawnerOptions, s as ProviderWatchOptions, t as ProcessSpawner } from "./spawner-CqGITKbd.js";
2
+ import { z } from "zod";
3
+ import { ActivationEvent, ActiveEditor, CancellationToken, CommandDescriptor, CommandsApi, DeleteOptions, DiffApi, DiffChange, DiffOptions, DiffStat, DirectoryEntry, Disposable, Event, ExecOptions, ExecResult, Extension, ExtensionManifest, FileChangeEvent, FileStat, FileSystemApi, FindFilesOptions, FindInFilesOptions, HudhodApi, HudhodError, HudhodErrorCode, InputBoxOptions, KeybindingContribution, MessageSeverity, MoveOptions, PanelContribution, PanelRenderer, ProcessApi, ProcessHandle, ProcessInfo, QuickPickItem, QuickPickOptions, RegisterCommandOptions, RegisterPanelOptions, RegisterViewOptions, ResolvedKeybinding, SearchApi, SearchResult, SpawnOptions, TerminalApi, ViewContribution, WatchOptions, WindowApi, WorkspaceApi, WriteFileOptions } from "@hudhod/sdk";
4
+
5
+ //#region src/base/cancellation.d.ts
6
+
7
+ /** A token that is never cancelled. Useful as a default parameter. */
8
+ declare const CancellationTokenNone: CancellationToken;
9
+ /**
10
+ * Creates a {@link CancellationToken} and controls when it fires.
11
+ *
12
+ * Listeners registered after cancellation are invoked immediately, so a late
13
+ * subscriber cannot miss the signal.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const source = new CancellationTokenSource();
18
+ * const results = await search(query, source.token);
19
+ * source.cancel();
20
+ * ```
21
+ */
22
+ declare class CancellationTokenSource implements Disposable {
23
+ #private;
24
+ /** The token to hand to cancellable operations. */
25
+ readonly token: CancellationToken;
26
+ constructor();
27
+ /** Whether cancellation has been requested. */
28
+ get isCancellationRequested(): boolean;
29
+ /** Requests cancellation. Subsequent calls are no-ops. */
30
+ cancel(): void;
31
+ /** Releases listeners without cancelling. */
32
+ dispose(): void;
33
+ }
34
+ /**
35
+ * Adapts an {@link AbortSignal} into a {@link CancellationToken}.
36
+ *
37
+ * Lets callers pass the platform-standard signal to hudhod APIs.
38
+ */
39
+ declare function tokenFromAbortSignal(signal: AbortSignal): CancellationToken;
40
+ //#endregion
41
+ //#region src/base/disposable.d.ts
42
+ /**
43
+ * Wraps a callback as a {@link Disposable} that runs at most once.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const sub = toDisposable(() => clearInterval(timer));
48
+ * sub.dispose();
49
+ * sub.dispose(); // no-op
50
+ * ```
51
+ */
52
+ declare function toDisposable(onDispose: () => void): Disposable;
53
+ /** A {@link Disposable} that does nothing. Useful as a default return value. */
54
+ declare const NO_OP_DISPOSABLE: Disposable;
55
+ /**
56
+ * Collects disposables and releases them together.
57
+ *
58
+ * Disposal runs in reverse insertion order, so resources are torn down in the
59
+ * opposite order they were set up. A failure in one disposable does not
60
+ * prevent the rest from running; all errors are collected and rethrown
61
+ * together via {@link AggregateError}.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * const store = new DisposableStore();
66
+ * store.add(emitter.event(handler));
67
+ * store.add(toDisposable(() => socket.close()));
68
+ * store.dispose();
69
+ * ```
70
+ */
71
+ declare class DisposableStore implements Disposable {
72
+ #private;
73
+ /** Whether {@link dispose} has already run. */
74
+ get isDisposed(): boolean;
75
+ /** Number of disposables currently held. */
76
+ get size(): number;
77
+ /**
78
+ * Registers a disposable.
79
+ *
80
+ * When the store is already disposed the argument is disposed immediately,
81
+ * which keeps late registrations from leaking.
82
+ *
83
+ * @returns The same disposable, for convenient chaining.
84
+ */
85
+ add<T extends Disposable>(disposable: T): T;
86
+ /** Disposes and forgets a single entry. Returns `false` when not held. */
87
+ delete(disposable: Disposable): boolean;
88
+ /** Disposes everything held without marking the store itself as disposed. */
89
+ clear(): void;
90
+ /** Disposes everything held and blocks further use. Safe to call repeatedly. */
91
+ dispose(): void;
92
+ }
93
+ //#endregion
94
+ //#region src/base/errors.d.ts
95
+ /** Extra context attached to a {@link HudhodError}. */
96
+ interface HudhodErrorDetails {
97
+ /** The path involved, for file system errors. */
98
+ readonly path?: string;
99
+ /** Output collected before the failure, for process errors. */
100
+ readonly partialOutput?: string;
101
+ /** The underlying failure, when this error wraps another. */
102
+ readonly cause?: unknown;
103
+ }
104
+ /**
105
+ * Builds a {@link HudhodError}.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * throw createError("FileNotFound", "No such file: /a.ts", { path: "/a.ts" });
110
+ * ```
111
+ */
112
+ declare function createError(code: HudhodErrorCode, message: string, details?: HudhodErrorDetails): HudhodError;
113
+ /** The path does not exist. */
114
+ declare function fileNotFound(path: string): HudhodError;
115
+ /** The path exists and overwriting was not permitted. */
116
+ declare function fileExists(path: string): HudhodError;
117
+ /** A directory was required but the path is not one. */
118
+ declare function notADirectory(path: string): HudhodError;
119
+ /** A file was required but the path is not one. */
120
+ declare function notAFile(path: string): HudhodError;
121
+ /** The directory still has children and `recursive` was not set. */
122
+ declare function directoryNotEmpty(path: string): HudhodError;
123
+ /** The path is malformed, relative, or escapes the workspace root. */
124
+ declare function invalidPath(path: string, reason: string): HudhodError;
125
+ //#endregion
126
+ //#region src/base/event.d.ts
127
+ /**
128
+ * Produces an {@link Event} and the means to fire it.
129
+ *
130
+ * Listener errors are isolated: one throwing listener never prevents the others
131
+ * from running. Errors are reported to `onListenerError` instead of
132
+ * propagating to the caller of {@link fire}, because an emitter's producer
133
+ * generally cannot do anything useful about a consumer's failure.
134
+ *
135
+ * @typeParam T - The payload delivered to listeners.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * const emitter = new Emitter<string>();
140
+ * const sub = emitter.event((name) => console.log(name));
141
+ * emitter.fire("world");
142
+ * sub.dispose();
143
+ * ```
144
+ */
145
+ declare class Emitter<T> implements Disposable {
146
+ #private;
147
+ /**
148
+ * @param options.onListenerError - Called when a listener throws.
149
+ * Defaults to `console.error`.
150
+ */
151
+ constructor(options?: {
152
+ onListenerError?: (error: unknown) => void;
153
+ });
154
+ /** Number of listeners currently subscribed. */
155
+ get listenerCount(): number;
156
+ /**
157
+ * Subscribes to this emitter.
158
+ *
159
+ * Registering the same function twice yields a single subscription, matching
160
+ * `Set` semantics; disposing either handle removes it.
161
+ */
162
+ readonly event: Event<T>;
163
+ /**
164
+ * Delivers `value` to every current listener.
165
+ *
166
+ * Iterates a snapshot, so listeners added or removed during delivery do not
167
+ * affect the in-flight dispatch.
168
+ */
169
+ fire(value: T): void;
170
+ /** Removes all listeners and blocks further subscription. */
171
+ dispose(): void;
172
+ }
173
+ //#endregion
174
+ //#region src/base/paths.d.ts
175
+ /**
176
+ * POSIX-style path utilities.
177
+ *
178
+ * hudhod paths are always absolute and rooted at the workspace root. Keeping a
179
+ * dedicated implementation here — rather than reaching for Node's `path` —
180
+ * means the core runs unchanged in the browser and stays free of platform
181
+ * separator quirks.
182
+ *
183
+ * @packageDocumentation
184
+ */
185
+ /** The workspace root. */
186
+ declare const ROOT = "/";
187
+ /**
188
+ * Normalises a path: collapses duplicate slashes, resolves `.` and `..`, and
189
+ * strips any trailing slash.
190
+ *
191
+ * @throws An `InvalidPath` error when the path is relative, empty, or traverses
192
+ * above the workspace root.
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * normalizePath("/src//lib/../index.ts"); // "/src/index.ts"
197
+ * ```
198
+ */
199
+ declare function normalizePath(path: string): string;
200
+ /**
201
+ * Joins segments onto a base path and normalises the result.
202
+ *
203
+ * @example
204
+ * ```ts
205
+ * joinPath("/src", "lib", "index.ts"); // "/src/lib/index.ts"
206
+ * ```
207
+ */
208
+ declare function joinPath(base: string, ...segments: string[]): string;
209
+ /**
210
+ * Returns the parent directory. The root is its own parent.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * dirname("/src/index.ts"); // "/src"
215
+ * dirname("/"); // "/"
216
+ * ```
217
+ */
218
+ declare function dirname(path: string): string;
219
+ /**
220
+ * Returns the final segment. The root has an empty basename.
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * basename("/src/index.ts"); // "index.ts"
225
+ * ```
226
+ */
227
+ declare function basename(path: string): string;
228
+ /**
229
+ * Returns the lowercased extension including the leading dot, or an empty
230
+ * string when there is none.
231
+ *
232
+ * A leading dot marks a hidden file rather than an extension, so `.gitignore`
233
+ * has no extension.
234
+ *
235
+ * @example
236
+ * ```ts
237
+ * extname("/src/App.TSX"); // ".tsx"
238
+ * extname("/.gitignore"); // ""
239
+ * ```
240
+ */
241
+ declare function extname(path: string): string;
242
+ /**
243
+ * Expresses `path` relative to `from`, without a leading slash.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * relativePath("/src", "/src/lib/a.ts"); // "lib/a.ts"
248
+ * ```
249
+ */
250
+ declare function relativePath(from: string, path: string): string;
251
+ /**
252
+ * Whether `path` is `parent` or sits underneath it.
253
+ *
254
+ * Compares whole segments, so `/src` does not contain `/src-old`.
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * isSubPath("/src", "/src/a.ts"); // true
259
+ * isSubPath("/src", "/src-old"); // false
260
+ * ```
261
+ */
262
+ declare function isSubPath(parent: string, path: string): boolean;
263
+ /**
264
+ * Splits a path into its segments. The root yields an empty array.
265
+ *
266
+ * @example
267
+ * ```ts
268
+ * pathSegments("/src/lib/a.ts"); // ["src", "lib", "a.ts"]
269
+ * ```
270
+ */
271
+ declare function pathSegments(path: string): string[];
272
+ //#endregion
273
+ //#region src/commands/command-registry.d.ts
274
+ /**
275
+ * Registers and invokes commands.
276
+ *
277
+ * @example
278
+ * ```ts
279
+ * const commands = new CommandRegistry();
280
+ * commands.registerCommand("demo.hello", () => "Hello", { title: "Say Hello" });
281
+ * await commands.executeCommand("demo.hello");
282
+ * ```
283
+ */
284
+ declare class CommandRegistry implements CommandsApi, Disposable {
285
+ #private;
286
+ /** Fires whenever the command catalog changes. */
287
+ readonly onDidChangeCommands: Event<readonly CommandDescriptor[]>;
288
+ /**
289
+ * Registers a command handler.
290
+ *
291
+ * @throws A `CommandExists` error when `id` is already registered.
292
+ */
293
+ registerCommand(id: string, handler: (...args: readonly unknown[]) => unknown, options?: RegisterCommandOptions): Disposable;
294
+ /**
295
+ * Invokes a registered command.
296
+ *
297
+ * @throws A `CommandNotFound` error when no handler is registered for `id`.
298
+ */
299
+ executeCommand<T = unknown>(id: string, ...args: readonly unknown[]): Promise<T>;
300
+ /** Lists registered commands, alphabetically by title then id. */
301
+ getCommands(): Promise<CommandDescriptor[]>;
302
+ /** Removes all registered commands and listeners. */
303
+ dispose(): void;
304
+ }
305
+ //#endregion
306
+ //#region src/keybindings/keybinding-registry.d.ts
307
+ /**
308
+ * Registers and resolves keybindings.
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * const keybindings = new KeybindingRegistry("other");
313
+ * keybindings.registerKeybinding({
314
+ * command: "demo.greet",
315
+ * key: "ctrl+n",
316
+ * mac: "cmd+n",
317
+ * });
318
+ * const binding = await keybindings.resolve({ key: "n", ctrlKey: true, ... });
319
+ * // => { key: "ctrl+n", command: "demo.greet", source: "extension" }
320
+ * ```
321
+ */
322
+ declare class KeybindingRegistry implements Disposable {
323
+ #private;
324
+ /** Fires whenever the keybinding catalog changes. */
325
+ readonly onDidChangeKeybindings: Event<readonly ResolvedKeybinding[]>;
326
+ /**
327
+ * @param platform Platform identifier. Use `"mac"` for macOS; otherwise `"other"`.
328
+ */
329
+ constructor(platform?: "mac" | "other");
330
+ /**
331
+ * Registers a keybinding.
332
+ *
333
+ * If the same key is already bound, this replaces it. Disposing the returned
334
+ * {@link Disposable} restores the previous binding.
335
+ *
336
+ * @throws invalidKeybinding when the key syntax is malformed.
337
+ */
338
+ registerKeybinding(binding: KeybindingContribution, options?: {
339
+ source?: "extension" | "builtin";
340
+ extensionId?: string;
341
+ }): Disposable;
342
+ /**
343
+ * Resolves a keyboard event to a keybinding, if one is registered.
344
+ *
345
+ * @param event A keyboard event-like object with `key`, `ctrlKey`, `metaKey`, `shiftKey`, `altKey`.
346
+ * @returns The registered keybinding, or `undefined` if no match.
347
+ */
348
+ resolve(event: {
349
+ readonly key: string;
350
+ readonly ctrlKey: boolean;
351
+ readonly metaKey: boolean;
352
+ readonly shiftKey: boolean;
353
+ readonly altKey: boolean;
354
+ }): ResolvedKeybinding | undefined;
355
+ /** Lists all registered keybindings, with top-of-stack (most recent) entries first. */
356
+ getKeybindings(): Promise<ResolvedKeybinding[]>;
357
+ /** Removes all registered keybindings and listeners. */
358
+ dispose(): void;
359
+ }
360
+ //#endregion
361
+ //#region src/keybindings/keybinding-parser.d.ts
362
+ /**
363
+ * Keybinding key sequence parsing and normalization.
364
+ *
365
+ * Keybindings are platform-independent at the manifest level; resolution to the
366
+ * current platform happens at runtime. This module defines how to parse and
367
+ * canonicalize key sequences.
368
+ *
369
+ * @packageDocumentation
370
+ */
371
+ /**
372
+ * A parsed and normalized key sequence.
373
+ *
374
+ * Modifiers are always in a canonical order: ctrl/cmd, shift, alt.
375
+ */
376
+ interface NormalizedKeybinding {
377
+ ctrl: boolean;
378
+ shift: boolean;
379
+ alt: boolean;
380
+ key: string;
381
+ }
382
+ /**
383
+ * Parses a keybinding string like `"ctrl+shift+p"` into components.
384
+ *
385
+ * @throws Error when the syntax is malformed.
386
+ * @example
387
+ * parseKeybinding("ctrl+n") => { ctrl: true, shift: false, alt: false, key: "n" }
388
+ * parseKeybinding("cmd+shift+k") => { ctrl: true, shift: true, alt: false, key: "k" }
389
+ */
390
+ declare function parseKeybinding(binding: string): NormalizedKeybinding;
391
+ /**
392
+ * Converts a {@link NormalizedKeybinding} back to a canonical string.
393
+ *
394
+ * @example
395
+ * keybindingToString({ ctrl: true, shift: true, alt: false, key: "p" }) => "ctrl+shift+p"
396
+ */
397
+ declare function keybindingToString(binding: NormalizedKeybinding): string;
398
+ /**
399
+ * Resolves a keyboard event to a canonical key string, or `undefined` if no binding.
400
+ *
401
+ * Returns a string like `"ctrl+shift+p"` that can be matched against registered bindings.
402
+ */
403
+ declare function keybindingFromEvent(event: {
404
+ readonly key: string;
405
+ readonly ctrlKey: boolean;
406
+ readonly metaKey: boolean;
407
+ readonly shiftKey: boolean;
408
+ readonly altKey: boolean;
409
+ }): string;
410
+ //#endregion
411
+ //#region src/window/window-service.d.ts
412
+ /**
413
+ * Host-supplied implementation of UI operations.
414
+ *
415
+ * The service delegates all UI interactions to this provider, allowing the host
416
+ * to supply React components, native dialogs, or any other UI implementation.
417
+ */
418
+ interface WindowUiProvider {
419
+ showMessage(message: string, severity?: MessageSeverity): Promise<void>;
420
+ showInputBox(options?: InputBoxOptions): Promise<string | undefined>;
421
+ showQuickPick(items: readonly QuickPickItem[], options?: QuickPickOptions): Promise<string | undefined>;
422
+ registerPanel(id: string, render: PanelRenderer, options: RegisterPanelOptions): Disposable;
423
+ registerView(id: string, render: PanelRenderer, options: RegisterViewOptions): Disposable;
424
+ openPanel(id: string): Promise<void>;
425
+ closePanel(id: string): Promise<boolean>;
426
+ openFile(path: string): Promise<void>;
427
+ readonly activeEditor: ActiveEditor | undefined;
428
+ readonly onDidChangeActiveEditor: Event<ActiveEditor | undefined>;
429
+ }
430
+ /**
431
+ * Provides window and UI APIs to extensions.
432
+ *
433
+ * @example
434
+ * ```ts
435
+ * const provider = createMyWindowUiProvider();
436
+ * const window = new WindowService(provider);
437
+ * await window.showMessage("Hello!");
438
+ * ```
439
+ */
440
+ declare class WindowService implements WindowApi, Disposable {
441
+ #private;
442
+ constructor(provider: WindowUiProvider);
443
+ showMessage(message: string, severity?: MessageSeverity): Promise<void>;
444
+ showInputBox(options?: InputBoxOptions): Promise<string | undefined>;
445
+ showQuickPick(items: readonly QuickPickItem[], options?: QuickPickOptions): Promise<string | undefined>;
446
+ registerPanel(id: string, render: PanelRenderer, options: RegisterPanelOptions): Disposable;
447
+ registerView(id: string, render: PanelRenderer, options: RegisterViewOptions): Disposable;
448
+ openPanel(id: string): Promise<void>;
449
+ closePanel(id: string): Promise<boolean>;
450
+ openFile(path: string): Promise<void>;
451
+ get activeEditor(): ActiveEditor | undefined;
452
+ get onDidChangeActiveEditor(): Event<ActiveEditor | undefined>;
453
+ dispose(): void;
454
+ }
455
+ //#endregion
456
+ //#region src/workspace/config.d.ts
457
+ /**
458
+ * Workspace-wide configuration.
459
+ *
460
+ * These defaults were previously hardcoded inside the file system layer, which
461
+ * meant a caller could not search `node_modules` even when they wanted to.
462
+ * Hoisting them here makes the policy explicit and overridable — per workspace
463
+ * and per call.
464
+ *
465
+ * @packageDocumentation
466
+ */
467
+ /** Glob patterns excluded from directory listings and the file tree. */
468
+ declare const DEFAULT_FILES_EXCLUDE: readonly string[];
469
+ /** Glob patterns excluded from search. Broader than the tree exclusions. */
470
+ declare const DEFAULT_SEARCH_EXCLUDE: readonly string[];
471
+ /** Glob patterns whose changes are ignored by watchers. */
472
+ declare const DEFAULT_WATCHER_EXCLUDE: readonly string[];
473
+ /**
474
+ * Patterns omitted when snapshotting a workspace for storage.
475
+ *
476
+ * Everything here is either reproducible from `package.json` or machine-local.
477
+ */
478
+ declare const DEFAULT_SNAPSHOT_EXCLUDE: readonly string[];
479
+ /** Tunable workspace policy. */
480
+ interface WorkspaceConfig {
481
+ /** Absolute path of the workspace root. */
482
+ readonly rootPath: string;
483
+ /** Patterns hidden from directory listings and the file tree. */
484
+ readonly filesExclude: readonly string[];
485
+ /** Patterns skipped by search, unless a call overrides them. */
486
+ readonly searchExclude: readonly string[];
487
+ /** Patterns whose changes never reach watchers. */
488
+ readonly watcherExclude: readonly string[];
489
+ /** Patterns omitted from workspace snapshots. */
490
+ readonly snapshotExclude: readonly string[];
491
+ /** Largest file, in bytes, that search will read. */
492
+ readonly maxSearchFileBytes: number;
493
+ }
494
+ /** Partial overrides accepted by {@link createWorkspaceConfig}. */
495
+ type WorkspaceConfigOverrides = Partial<WorkspaceConfig>;
496
+ /**
497
+ * Builds a {@link WorkspaceConfig}, filling in defaults.
498
+ *
499
+ * @example
500
+ * ```ts
501
+ * // Search node_modules too, but keep every other default.
502
+ * const config = createWorkspaceConfig({ searchExclude: ["**\/.git/**"] });
503
+ * ```
504
+ */
505
+ declare function createWorkspaceConfig(overrides?: WorkspaceConfigOverrides): WorkspaceConfig;
506
+ //#endregion
507
+ //#region src/fs/file-system-service.d.ts
508
+ /** Options for {@link FileSystemService}. */
509
+ interface FileSystemServiceOptions {
510
+ /** Workspace policy, chiefly the exclusion patterns. */
511
+ readonly config?: WorkspaceConfig;
512
+ /**
513
+ * Milliseconds to coalesce change events over. Set to `0` to deliver
514
+ * synchronously, which makes tests deterministic.
515
+ * @defaultValue 20
516
+ */
517
+ readonly debounceMs?: number;
518
+ }
519
+ /**
520
+ * Reads and writes workspace files.
521
+ *
522
+ * @example
523
+ * ```ts
524
+ * const fs = new FileSystemService(new InMemoryFileSystemProvider());
525
+ * await fs.writeTextFile("/src/a.ts", "export const a = 1;");
526
+ * await fs.readTextFile("/src/a.ts");
527
+ * ```
528
+ */
529
+ declare class FileSystemService implements FileSystemApi, Disposable {
530
+ #private;
531
+ constructor(provider: FileSystemProvider, options?: FileSystemServiceOptions);
532
+ /** The workspace policy in force. */
533
+ get config(): WorkspaceConfig;
534
+ /**
535
+ * Fires for every change in the workspace, after exclusion filtering and
536
+ * debouncing.
537
+ *
538
+ * The underlying provider watch is established lazily on first subscription
539
+ * and torn down when the last listener leaves, so an idle workspace does no
540
+ * watching at all.
541
+ */
542
+ readonly onDidChangeFile: Event<readonly FileChangeEvent[]>;
543
+ readFile(path: string): Promise<Uint8Array>;
544
+ readTextFile(path: string): Promise<string>;
545
+ writeFile(path: string, data: Uint8Array, options?: WriteFileOptions): Promise<void>;
546
+ writeTextFile(path: string, content: string, options?: WriteFileOptions): Promise<void>;
547
+ createFile(path: string, options?: WriteFileOptions): Promise<void>;
548
+ /** Creates a directory and every missing ancestor. Succeeds if it exists. */
549
+ createDirectory(path: string): Promise<void>;
550
+ delete(path: string, options?: DeleteOptions): Promise<void>;
551
+ rename(from: string, to: string, options?: MoveOptions): Promise<void>;
552
+ copy(from: string, to: string, options?: MoveOptions): Promise<void>;
553
+ stat(path: string): Promise<FileStat>;
554
+ exists(path: string): Promise<boolean>;
555
+ /**
556
+ * Lists a directory, hiding entries matched by `filesExclude` and sorting
557
+ * directories first, then by name.
558
+ */
559
+ readDirectory(path: string): Promise<DirectoryEntry[]>;
560
+ /**
561
+ * Lists a directory, optionally without applying `filesExclude`.
562
+ *
563
+ * `filesExclude` is a presentation policy — it governs what the file tree
564
+ * shows. Search has its own `searchExclude`, so it must be able to walk the
565
+ * unfiltered listing; otherwise a caller could never search `node_modules`
566
+ * even by explicitly clearing the search exclusions.
567
+ */
568
+ listDirectory(path: string, options: {
569
+ applyExcludes: boolean;
570
+ }): Promise<DirectoryEntry[]>;
571
+ watch(path: string, listener: (events: readonly FileChangeEvent[]) => unknown, options?: WatchOptions): Disposable;
572
+ /** Stops watching and releases listeners. */
573
+ dispose(): void;
574
+ }
575
+ //#endregion
576
+ //#region src/diff/diff-service.d.ts
577
+ /**
578
+ * Compares text and applies patches.
579
+ *
580
+ * @example
581
+ * ```ts
582
+ * const diff = new DiffService(fs);
583
+ * const patch = await diff.createPatch("/a.ts", before, after);
584
+ * await diff.applyPatch("/a.ts", patch);
585
+ * ```
586
+ */
587
+ declare class DiffService implements DiffApi {
588
+ #private;
589
+ constructor(fileSystem: FileSystemService);
590
+ diffText(original: string, modified: string, options?: DiffOptions): Promise<DiffChange[]>;
591
+ diffFiles(originalPath: string, modifiedPath: string, options?: DiffOptions): Promise<DiffChange[]>;
592
+ diffStat(original: string, modified: string, options?: DiffOptions): Promise<DiffStat>;
593
+ createPatch(path: string, original: string, modified: string, options?: DiffOptions): Promise<string>;
594
+ /**
595
+ * Applies a unified diff to a file.
596
+ *
597
+ * @throws A `PatchFailed` error when the patch does not apply cleanly, which
598
+ * usually means the file changed after the patch was produced.
599
+ */
600
+ applyPatch(path: string, patch: string): Promise<void>;
601
+ }
602
+ //#endregion
603
+ //#region src/panels/panel-registry.d.ts
604
+ /** Resolved metadata for a contributed panel. */
605
+ interface PanelInfo {
606
+ /** Unique identifier, matching the id passed to `registerPanel`. */
607
+ readonly id: string;
608
+ /** Tab title. */
609
+ readonly title: string;
610
+ /** Opaque icon value supplied by the manifest contribution. */
611
+ readonly icon?: unknown;
612
+ /** Dock location, defaulted to `"bottom"` when the contribution omits it. */
613
+ readonly location: "left" | "right" | "bottom" | "center";
614
+ /** Whether the panel comes from an extension or the built-in shell. */
615
+ readonly source: "extension" | "builtin";
616
+ /** Id of the owning extension, when the source is an extension. */
617
+ readonly extensionId?: string;
618
+ }
619
+ /**
620
+ * Registers and lists contributed panels.
621
+ *
622
+ * @example
623
+ * ```ts
624
+ * const panels = new PanelRegistry();
625
+ * const sub = panels.registerPanel(
626
+ * { id: "demo.logs", title: "Logs" },
627
+ * { extensionId: "demo" },
628
+ * );
629
+ * panels.getPanels();
630
+ * // => [{ id: "demo.logs", title: "Logs", location: "bottom", source: "extension", extensionId: "demo" }]
631
+ * sub.dispose();
632
+ * ```
633
+ */
634
+ declare class PanelRegistry implements Disposable {
635
+ #private;
636
+ /** Fires whenever the panel catalog changes. */
637
+ readonly onDidChangePanels: Event<readonly PanelInfo[]>;
638
+ /**
639
+ * Registers a panel contribution.
640
+ *
641
+ * If the same id is already registered, this replaces it. Disposing the returned
642
+ * {@link Disposable} restores the previous registration.
643
+ */
644
+ registerPanel(contribution: PanelContribution, options?: {
645
+ source?: "extension" | "builtin";
646
+ extensionId?: string;
647
+ }): Disposable;
648
+ /** Lists the active panel per id, sorted by id for stable UI ordering. */
649
+ getPanels(): readonly PanelInfo[];
650
+ /** Removes all registered panels and listeners. */
651
+ dispose(): void;
652
+ }
653
+ //#endregion
654
+ //#region src/views/view-registry.d.ts
655
+ interface ViewInfo {
656
+ readonly id: string;
657
+ readonly title: string;
658
+ readonly container: string;
659
+ readonly order?: number;
660
+ readonly source: "extension" | "builtin";
661
+ readonly extensionId?: string;
662
+ readonly registrationOrder: number;
663
+ }
664
+ /** Tracks contributed views independently from activity-bar containers. */
665
+ declare class ViewRegistry implements Disposable {
666
+ #private;
667
+ readonly onDidChangeViews: Event<readonly ViewInfo[]>;
668
+ registerView(contribution: ViewContribution, options?: {
669
+ source?: "extension" | "builtin";
670
+ extensionId?: string;
671
+ }): Disposable;
672
+ getViews(): readonly ViewInfo[];
673
+ getViewsForContainer(containerId: string): readonly ViewInfo[];
674
+ dispose(): void;
675
+ }
676
+ //#endregion
677
+ //#region src/extensions/extension-host.d.ts
678
+ /** Current lifecycle state of a registered extension. */
679
+ type ExtensionStatus = "registered" | "activating" | "active" | "failed";
680
+ /** A serializable snapshot of a registered extension. */
681
+ interface ExtensionInfo {
682
+ /** Validated extension manifest. */
683
+ readonly manifest: ExtensionManifest;
684
+ /** Current lifecycle state. */
685
+ readonly status: ExtensionStatus;
686
+ /** Activation error message when status is `failed`. */
687
+ readonly error?: string;
688
+ }
689
+ /**
690
+ * Loads and activates trusted, first-party extensions.
691
+ *
692
+ * The host deliberately does not sandbox code or impose permissions: hudhod's
693
+ * current extension model is curated and first-party. Activation is deduplicated
694
+ * so concurrent triggers only call an extension's `activate` method once.
695
+ *
696
+ * @example
697
+ * ```ts
698
+ * const host = new InProcessExtensionHost(hudhod, { panels, views });
699
+ * host.register(extension);
700
+ * await host.activateByEvent("onStartup");
701
+ * ```
702
+ */
703
+ declare class InProcessExtensionHost implements Disposable {
704
+ #private;
705
+ constructor(hudhod: HudhodApi, registries: {
706
+ panels: PanelRegistry;
707
+ views: ViewRegistry;
708
+ });
709
+ /**
710
+ * Registers an extension without running its activation hook.
711
+ *
712
+ * Contributed keybindings and panels are registered immediately so they can be
713
+ * discovered before the extension's activate hook runs, enabling lazy activation.
714
+ *
715
+ * @throws `ZodError` when the manifest is malformed.
716
+ * @throws `Error` when another extension already owns the same id.
717
+ */
718
+ register(extension: Extension): Disposable;
719
+ /** Lists registered extensions without exposing mutable host state. */
720
+ getExtensions(): readonly ExtensionInfo[];
721
+ /** Activates every extension that declared `event`. */
722
+ activateByEvent(event: ActivationEvent): Promise<void>;
723
+ /** Activates one extension by id. */
724
+ activate(extensionId: string): Promise<void>;
725
+ /** Deactivates one extension and releases its registered resources. */
726
+ deactivate(extensionId: string): Promise<boolean>;
727
+ /** Deactivates every extension and blocks further registration. */
728
+ dispose(): void;
729
+ }
730
+ //#endregion
731
+ //#region src/extensions/manifest.d.ts
732
+ /** Validates the serializable shape of an extension manifest. */
733
+ declare const extensionManifestSchema: z.ZodObject<{
734
+ id: z.ZodString;
735
+ name: z.ZodString;
736
+ version: z.ZodString;
737
+ description: z.ZodOptional<z.ZodString>;
738
+ activationEvents: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodLiteral<"onStartup">, z.ZodString, z.ZodString, z.ZodString]>>>;
739
+ contributes: z.ZodOptional<z.ZodObject<{
740
+ commands: z.ZodOptional<z.ZodArray<z.ZodObject<{
741
+ id: z.ZodString;
742
+ title: z.ZodString;
743
+ category: z.ZodOptional<z.ZodString>;
744
+ }, z.core.$strip>>>;
745
+ panels: z.ZodOptional<z.ZodArray<z.ZodObject<{
746
+ id: z.ZodString;
747
+ title: z.ZodString;
748
+ icon: z.ZodOptional<z.ZodUnknown>;
749
+ location: z.ZodOptional<z.ZodEnum<{
750
+ left: "left";
751
+ right: "right";
752
+ bottom: "bottom";
753
+ center: "center";
754
+ }>>;
755
+ }, z.core.$strip>>>;
756
+ viewContainers: z.ZodOptional<z.ZodArray<z.ZodObject<{
757
+ id: z.ZodString;
758
+ title: z.ZodString;
759
+ icon: z.ZodOptional<z.ZodUnknown>;
760
+ location: z.ZodOptional<z.ZodEnum<{
761
+ left: "left";
762
+ right: "right";
763
+ bottom: "bottom";
764
+ center: "center";
765
+ }>>;
766
+ }, z.core.$strip>>>;
767
+ views: z.ZodOptional<z.ZodArray<z.ZodObject<{
768
+ id: z.ZodString;
769
+ title: z.ZodString;
770
+ container: z.ZodString;
771
+ order: z.ZodOptional<z.ZodNumber>;
772
+ }, z.core.$strip>>>;
773
+ keybindings: z.ZodOptional<z.ZodArray<z.ZodObject<{
774
+ command: z.ZodString;
775
+ key: z.ZodString;
776
+ mac: z.ZodOptional<z.ZodString>;
777
+ }, z.core.$strip>>>;
778
+ }, z.core.$strip>>;
779
+ }, z.core.$strip>;
780
+ /**
781
+ * Validates a manifest or throws `ZodError` with field-level diagnostics.
782
+ *
783
+ * @example
784
+ * ```ts
785
+ * const manifest = parseExtensionManifest(rawJson);
786
+ * ```
787
+ */
788
+ declare function parseExtensionManifest(value: unknown): ExtensionManifest;
789
+ //#endregion
790
+ //#region src/fs/in-memory-provider.d.ts
791
+ /** Options for {@link InMemoryFileSystemProvider}. */
792
+ interface InMemoryFileSystemProviderOptions {
793
+ /**
794
+ * Clock used for `mtime` values. Inject a fake to make timestamps
795
+ * deterministic in tests.
796
+ * @defaultValue `Date.now`
797
+ */
798
+ readonly now?: () => number;
799
+ }
800
+ /**
801
+ * A complete file system held in a `Map`.
802
+ *
803
+ * @example
804
+ * ```ts
805
+ * const provider = new InMemoryFileSystemProvider();
806
+ * await provider.createDirectory("/src");
807
+ * await provider.writeFile("/src/a.ts", new TextEncoder().encode("export {};"));
808
+ * ```
809
+ */
810
+ declare class InMemoryFileSystemProvider implements FileSystemProvider {
811
+ #private;
812
+ readonly name = "in-memory";
813
+ constructor(options?: InMemoryFileSystemProviderOptions);
814
+ /**
815
+ * Seeds the provider from a plain object, for concise test fixtures.
816
+ *
817
+ * Parent directories are created automatically.
818
+ *
819
+ * @example
820
+ * ```ts
821
+ * const provider = InMemoryFileSystemProvider.from({
822
+ * "/package.json": "{}",
823
+ * "/src/index.ts": "export const a = 1;",
824
+ * });
825
+ * ```
826
+ */
827
+ static from(files: Readonly<Record<string, string>>, options?: InMemoryFileSystemProviderOptions): InMemoryFileSystemProvider;
828
+ /** Every path currently stored, sorted. Intended for test assertions. */
829
+ snapshot(): string[];
830
+ readFile(path: string): Promise<Uint8Array>;
831
+ writeFile(path: string, data: Uint8Array): Promise<void>;
832
+ createDirectory(path: string): Promise<void>;
833
+ delete(path: string, options: {
834
+ recursive: boolean;
835
+ }): Promise<void>;
836
+ rename(from: string, to: string, options: {
837
+ overwrite: boolean;
838
+ }): Promise<void>;
839
+ stat(path: string): Promise<ProviderStat>;
840
+ readDirectory(path: string): Promise<ProviderEntry[]>;
841
+ watch(path: string, options: ProviderWatchOptions, listener: (events: readonly FileChangeEvent[]) => void): Disposable;
842
+ }
843
+ //#endregion
844
+ //#region src/process/fake-spawner.d.ts
845
+ /** How a faked command should behave. */
846
+ interface FakeCommandBehaviour {
847
+ /** Chunks emitted on the output stream, in order. */
848
+ readonly output?: readonly string[];
849
+ /**
850
+ * Exit code once the output is exhausted.
851
+ * @defaultValue 0
852
+ */
853
+ readonly exitCode?: number;
854
+ /**
855
+ * Milliseconds to wait before exiting. The process stays running until then,
856
+ * which is what lets timeout behaviour be tested.
857
+ * @defaultValue 0
858
+ */
859
+ readonly delayMs?: number;
860
+ /**
861
+ * Never exit on its own. Only a `kill()` will end it. Useful for modelling
862
+ * servers and watchers.
863
+ * @defaultValue false
864
+ */
865
+ readonly neverExits?: boolean;
866
+ }
867
+ /** A record of one spawn call. */
868
+ interface RecordedSpawn {
869
+ /** The executable requested. */
870
+ readonly command: string;
871
+ /** The arguments requested. */
872
+ readonly args: readonly string[];
873
+ /** The options the service passed through. */
874
+ readonly options: SpawnerOptions;
875
+ }
876
+ /**
877
+ * A spawner that replays scripted behaviour.
878
+ *
879
+ * @example
880
+ * ```ts
881
+ * const spawner = new FakeProcessSpawner();
882
+ * spawner.register("node", { output: ["v22.0.0\n"] });
883
+ * const { output } = await new ProcessService(spawner).exec("node", ["-v"]);
884
+ * ```
885
+ */
886
+ declare class FakeProcessSpawner implements ProcessSpawner {
887
+ #private;
888
+ readonly name = "fake";
889
+ /** Every spawn that has been requested, in order. */
890
+ get spawns(): readonly RecordedSpawn[];
891
+ /** Scripts a command, keyed by executable name. */
892
+ register(command: string, behaviour: FakeCommandBehaviour): this;
893
+ /** Sets the behaviour used for unregistered commands. */
894
+ setFallback(behaviour: FakeCommandBehaviour): this;
895
+ spawn(command: string, args: readonly string[], options: SpawnerOptions): Promise<SpawnedProcess>;
896
+ }
897
+ //#endregion
898
+ //#region src/process/process-service.d.ts
899
+ /** Default wall-clock limit for {@link ProcessService.exec}. */
900
+ declare const DEFAULT_EXEC_TIMEOUT_MS = 60000;
901
+ /** Default output cap for {@link ProcessService.exec}, in bytes. */
902
+ declare const DEFAULT_MAX_OUTPUT_BYTES: number;
903
+ /** Options for {@link ProcessService}. */
904
+ interface ProcessServiceOptions {
905
+ /**
906
+ * Clock used for `startedAt` and duration measurement.
907
+ * @defaultValue `Date.now`
908
+ */
909
+ readonly now?: () => number;
910
+ }
911
+ /**
912
+ * Spawns and supervises processes.
913
+ *
914
+ * @example
915
+ * ```ts
916
+ * const processes = new ProcessService(spawner);
917
+ * const { exitCode, output } = await processes.exec("node", ["-v"]);
918
+ * ```
919
+ */
920
+ declare class ProcessService implements ProcessApi {
921
+ #private;
922
+ constructor(spawner: ProcessSpawner, options?: ProcessServiceOptions);
923
+ /** Fires whenever a process starts. */
924
+ readonly onDidStartProcess: Event<ProcessInfo>;
925
+ /** Fires whenever a process exits, for any reason. */
926
+ readonly onDidExitProcess: Event<ProcessInfo>;
927
+ spawn(command: string, args?: readonly string[], options?: SpawnOptions): Promise<ProcessHandle>;
928
+ exec(command: string, args?: readonly string[], options?: ExecOptions): Promise<ExecResult>;
929
+ list(): Promise<ProcessInfo[]>;
930
+ kill(id: string): Promise<boolean>;
931
+ /** Kills every running process and releases listeners. */
932
+ dispose(): void;
933
+ }
934
+ //#endregion
935
+ //#region src/search/search-service.d.ts
936
+ /**
937
+ * Finds files and searches their contents.
938
+ *
939
+ * @example
940
+ * ```ts
941
+ * const search = new SearchService(fs);
942
+ * const paths = await search.findFiles("src/**\/*.ts");
943
+ * const { matches } = await search.findInFiles("TODO");
944
+ * ```
945
+ */
946
+ declare class SearchService implements SearchApi {
947
+ #private;
948
+ constructor(fileSystem: FileSystemService);
949
+ findFiles(include: string, options?: FindFilesOptions): Promise<string[]>;
950
+ findInFiles(query: string, options?: FindInFilesOptions): Promise<SearchResult>;
951
+ replaceInFiles(query: string, replacement: string, options?: FindInFilesOptions): Promise<number>;
952
+ }
953
+ //#endregion
954
+ //#region src/runtime/runtime.d.ts
955
+ interface HudhodRuntime {
956
+ readonly fs: FileSystemService;
957
+ readonly search: SearchService;
958
+ readonly diff: DiffService;
959
+ readonly process: ProcessService;
960
+ readonly commands: CommandRegistry;
961
+ readonly keybindings: KeybindingRegistry;
962
+ readonly panels: PanelRegistry;
963
+ readonly views: ViewRegistry;
964
+ readonly window: WindowService;
965
+ readonly extensions: InProcessExtensionHost;
966
+ readonly api: HudhodApi;
967
+ dispose(): void;
968
+ }
969
+ interface CreateHudhodRuntimeOptions {
970
+ readonly fileSystemProvider: FileSystemProvider;
971
+ readonly processSpawner: ProcessSpawner;
972
+ readonly windowUiProvider: WindowUiProvider;
973
+ readonly platform?: "mac" | "other";
974
+ readonly version?: string;
975
+ readonly workspace?: WorkspaceApi;
976
+ readonly terminal?: TerminalApi;
977
+ }
978
+ /** Creates an environment-agnostic Hudhod runtime from host-provided adapters. */
979
+ declare function createHudhodRuntime(options: CreateHudhodRuntimeOptions): HudhodRuntime;
980
+ //#endregion
981
+ //#region src/services/service-registry.d.ts
982
+ /** A unique, typed key used to retrieve a service. */
983
+ interface ServiceIdentifier<T> {
984
+ /** Human-readable name used in diagnostics. */
985
+ readonly description: string;
986
+ /** Unique symbol backing the identifier. */
987
+ readonly key: symbol;
988
+ /** Type-only marker preserving `T` across calls. */
989
+ readonly __service?: T;
990
+ }
991
+ /** Creates a typed service identifier. */
992
+ declare function createServiceIdentifier<T>(description: string): ServiceIdentifier<T>;
993
+ type ServiceFactory<T> = (registry: ServiceRegistry) => T;
994
+ /**
995
+ * Owns workspace-scoped services.
996
+ *
997
+ * @example
998
+ * ```ts
999
+ * const fsId = createServiceIdentifier<FileSystemService>("fs");
1000
+ * const services = new ServiceRegistry();
1001
+ * services.register(fsId, () => new FileSystemService(provider));
1002
+ * const fs = services.get(fsId);
1003
+ * ```
1004
+ */
1005
+ declare class ServiceRegistry implements Disposable {
1006
+ #private;
1007
+ /** Registers a lazy factory for a service. */
1008
+ register<T>(identifier: ServiceIdentifier<T>, factory: ServiceFactory<T>): void;
1009
+ /** Retrieves and lazily creates a registered service. */
1010
+ get<T>(identifier: ServiceIdentifier<T>): T;
1011
+ /** Whether a service factory is registered. */
1012
+ has(identifier: ServiceIdentifier<unknown>): boolean;
1013
+ /** Releases every created disposable service in reverse creation order. */
1014
+ dispose(): void;
1015
+ }
1016
+ //#endregion
1017
+ export { CancellationTokenNone, CancellationTokenSource, CommandRegistry, type CreateHudhodRuntimeOptions, DEFAULT_EXEC_TIMEOUT_MS, DEFAULT_FILES_EXCLUDE, DEFAULT_MAX_OUTPUT_BYTES, DEFAULT_SEARCH_EXCLUDE, DEFAULT_SNAPSHOT_EXCLUDE, DEFAULT_WATCHER_EXCLUDE, DiffService, DisposableStore, Emitter, type ExtensionInfo, type ExtensionStatus, type FakeCommandBehaviour, FakeProcessSpawner, type FileSystemProvider, FileSystemService, type FileSystemServiceOptions, HudhodErrorDetails, type HudhodRuntime, InMemoryFileSystemProvider, type InMemoryFileSystemProviderOptions, InProcessExtensionHost, KeybindingRegistry, NO_OP_DISPOSABLE, type NormalizedKeybinding, type PanelInfo, PanelRegistry, ProcessService, type ProcessServiceOptions, type ProcessSpawner, type ProviderEntry, type ProviderStat, type ProviderWatchOptions, ROOT, type RecordedSpawn, SearchService, type ServiceIdentifier, ServiceRegistry, type SpawnedProcess, type SpawnerOptions, type ViewInfo, ViewRegistry, WindowService, type WindowUiProvider, type WorkspaceConfig, type WorkspaceConfigOverrides, basename, createError, createHudhodRuntime, createServiceIdentifier, createWorkspaceConfig, directoryNotEmpty, dirname, extensionManifestSchema, extname, fileExists, fileNotFound, invalidPath, isSubPath, joinPath, keybindingFromEvent, keybindingToString, normalizePath, notADirectory, notAFile, parseExtensionManifest, parseKeybinding, pathSegments, relativePath, toDisposable, tokenFromAbortSignal };