@hudhod/sdk 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.
- package/LICENSE +21 -0
- package/README.md +197 -0
- package/dist/index.d.ts +1044 -0
- package/dist/index.js +56 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hudhod
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# @hudhod/sdk
|
|
2
|
+
|
|
3
|
+
The public API surface for building [hudhod](../../README.md) extensions.
|
|
4
|
+
|
|
5
|
+
hudhod is an in-browser IDE built on WebContainers. This package describes what
|
|
6
|
+
an extension can do. It is **types-first**: the only values it ships are
|
|
7
|
+
`defineExtension()` and `isHudhodError()`, so importing it adds essentially
|
|
8
|
+
nothing to your bundle.
|
|
9
|
+
|
|
10
|
+
The same API backs the AI agent tool layer, so anything an extension can do, an
|
|
11
|
+
agent can do.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @hudhod/sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { defineExtension } from "@hudhod/sdk";
|
|
23
|
+
|
|
24
|
+
export default defineExtension({
|
|
25
|
+
manifest: {
|
|
26
|
+
id: "acme.todo-finder",
|
|
27
|
+
name: "TODO Finder",
|
|
28
|
+
version: "1.0.0",
|
|
29
|
+
activationEvents: ["onCommand:acme.todoFinder.scan"],
|
|
30
|
+
contributes: {
|
|
31
|
+
commands: [{ id: "acme.todoFinder.scan", title: "Scan for TODOs" }],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
async activate({ hudhod, subscriptions }) {
|
|
35
|
+
subscriptions.push(
|
|
36
|
+
hudhod.commands.registerCommand("acme.todoFinder.scan", async () => {
|
|
37
|
+
const { matches } = await hudhod.search.findInFiles("TODO", {
|
|
38
|
+
include: ["**/*.ts", "**/*.tsx"],
|
|
39
|
+
});
|
|
40
|
+
await hudhod.window.showMessage(`Found ${matches.length} TODOs`);
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Design rules
|
|
48
|
+
|
|
49
|
+
Three constraints shape every signature in this package:
|
|
50
|
+
|
|
51
|
+
1. **Everything is async.** Even operations that could be synchronous today
|
|
52
|
+
return a promise, so the host can later move extensions into a worker or
|
|
53
|
+
sandboxed frame without a breaking change.
|
|
54
|
+
2. **Everything is structured-cloneable.** No classes, no functions, and no live
|
|
55
|
+
objects cross the API boundary — with the single deliberate exception of
|
|
56
|
+
`ProcessHandle`, which carries streams and is therefore same-context only.
|
|
57
|
+
Use `ProcessInfo` when you need a serialisable snapshot.
|
|
58
|
+
3. **Paths are absolute and POSIX-style**, rooted at the workspace root (`/`).
|
|
59
|
+
Relative paths are rejected rather than resolved against ambient state.
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
The root object is `HudhodApi`, delivered as `context.hudhod` on activation.
|
|
64
|
+
|
|
65
|
+
| Namespace | Purpose |
|
|
66
|
+
| ----------- | ----------------------------------------------- |
|
|
67
|
+
| `fs` | Read and write files |
|
|
68
|
+
| `workspace` | Workspace edits, edit history, revert |
|
|
69
|
+
| `search` | Find files by glob, search and replace in files |
|
|
70
|
+
| `diff` | Compare text, create and apply unified patches |
|
|
71
|
+
| `process` | Spawn processes, run one-shot commands |
|
|
72
|
+
| `terminal` | Create and drive interactive shells |
|
|
73
|
+
| `commands` | Register and invoke commands |
|
|
74
|
+
| `window` | Notifications, prompts, contributed panels |
|
|
75
|
+
|
|
76
|
+
### `hudhod.fs`
|
|
77
|
+
|
|
78
|
+
| Method | Returns |
|
|
79
|
+
| ---------------------------------------- | --------------------------- |
|
|
80
|
+
| `readFile(path)` | `Promise<Uint8Array>` |
|
|
81
|
+
| `readTextFile(path)` | `Promise<string>` |
|
|
82
|
+
| `writeFile(path, data, options?)` | `Promise<void>` |
|
|
83
|
+
| `writeTextFile(path, content, options?)` | `Promise<void>` |
|
|
84
|
+
| `createFile(path, options?)` | `Promise<void>` |
|
|
85
|
+
| `createDirectory(path)` | `Promise<void>` |
|
|
86
|
+
| `delete(path, options?)` | `Promise<void>` |
|
|
87
|
+
| `rename(from, to, options?)` | `Promise<void>` |
|
|
88
|
+
| `copy(from, to, options?)` | `Promise<void>` |
|
|
89
|
+
| `stat(path)` | `Promise<FileStat>` |
|
|
90
|
+
| `exists(path)` | `Promise<boolean>` |
|
|
91
|
+
| `readDirectory(path)` | `Promise<DirectoryEntry[]>` |
|
|
92
|
+
| `watch(path, listener, options?)` | `Disposable` |
|
|
93
|
+
| `onDidChangeFile` | `Event<FileChangeEvent[]>` |
|
|
94
|
+
|
|
95
|
+
Writes create missing parent directories by default. Change events are debounced
|
|
96
|
+
and delivered in batches.
|
|
97
|
+
|
|
98
|
+
### `hudhod.process`
|
|
99
|
+
|
|
100
|
+
| Method | Returns |
|
|
101
|
+
| --------------------------------- | ------------------------ |
|
|
102
|
+
| `spawn(command, args?, options?)` | `Promise<ProcessHandle>` |
|
|
103
|
+
| `exec(command, args?, options?)` | `Promise<ExecResult>` |
|
|
104
|
+
| `list()` | `Promise<ProcessInfo[]>` |
|
|
105
|
+
| `kill(id)` | `Promise<boolean>` |
|
|
106
|
+
| `onDidStartProcess` | `Event<ProcessInfo>` |
|
|
107
|
+
| `onDidExitProcess` | `Event<ProcessInfo>` |
|
|
108
|
+
|
|
109
|
+
`exec()` is guarded so a runaway command cannot hang the browser tab:
|
|
110
|
+
|
|
111
|
+
| Option | Default | Disable with |
|
|
112
|
+
| ---------------- | --------- | ------------ |
|
|
113
|
+
| `timeout` | 60 000 ms | `false` |
|
|
114
|
+
| `maxOutputBytes` | 1 MiB | `false` |
|
|
115
|
+
|
|
116
|
+
Breaching either guard kills the process and throws, and the thrown error
|
|
117
|
+
carries the output collected so far on `partialOutput`.
|
|
118
|
+
|
|
119
|
+
> **stdout and stderr are merged.** The WebContainer runtime exposes a single
|
|
120
|
+
> output stream per process, so `ExecResult` has one `output` field rather than
|
|
121
|
+
> an `stderr` that would always be empty.
|
|
122
|
+
|
|
123
|
+
### `hudhod.workspace`
|
|
124
|
+
|
|
125
|
+
Agent edits go through `applyEdit()`, which supports two modes:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
// Apply now; revertable via the edit history, and undoable in the editor.
|
|
129
|
+
await hudhod.workspace.applyEdit(edits, { mode: "immediate", label: "Fix types" });
|
|
130
|
+
|
|
131
|
+
// Show the user a diff and wait. Nothing is written unless they accept.
|
|
132
|
+
const { applied } = await hudhod.workspace.applyEdit(edits, { mode: "review" });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Edits within a file are applied bottom-up so earlier ranges stay valid, and the
|
|
136
|
+
whole set is atomic — if one file fails, none are written.
|
|
137
|
+
|
|
138
|
+
### `hudhod.commands`
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const sub = hudhod.commands.registerCommand("demo.run", handler, {
|
|
142
|
+
title: "Run Demo",
|
|
143
|
+
category: "Demo",
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Commands without a `title` are callable but stay hidden from the palette.
|
|
148
|
+
|
|
149
|
+
### `hudhod.window`
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
hudhod.window.registerPanel(
|
|
153
|
+
"demo.stats",
|
|
154
|
+
(container) => {
|
|
155
|
+
container.textContent = "Hello";
|
|
156
|
+
return () => {
|
|
157
|
+
/* cleanup on close */
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
{ title: "Stats", location: "right" },
|
|
161
|
+
);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Error handling
|
|
165
|
+
|
|
166
|
+
Match on `code`, never on message text:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { isHudhodError } from "@hudhod/sdk";
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await hudhod.fs.readTextFile("/missing.ts");
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (isHudhodError(error) && error.code === "FileNotFound") {
|
|
175
|
+
// handle it
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Codes: `FileNotFound`, `FileExists`, `NotADirectory`, `NotAFile`,
|
|
181
|
+
`DirectoryNotEmpty`, `InvalidPath`, `CommandNotFound`, `CommandExists`,
|
|
182
|
+
`ProcessTimeout`, `OutputLimitExceeded`, `PatchFailed`, `Cancelled`.
|
|
183
|
+
|
|
184
|
+
## Activation events
|
|
185
|
+
|
|
186
|
+
Prefer lazy activation; every `onStartup` extension delays workbench boot.
|
|
187
|
+
|
|
188
|
+
| Event | Fires when |
|
|
189
|
+
| ------------------- | ----------------------------- |
|
|
190
|
+
| `onStartup` | The workbench is ready |
|
|
191
|
+
| `onCommand:<id>` | A command is first invoked |
|
|
192
|
+
| `onFileOpen:<glob>` | A matching file is opened |
|
|
193
|
+
| `onView:<panelId>` | A contributed panel is opened |
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1044 @@
|
|
|
1
|
+
//#region src/lifecycle.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Core lifecycle primitives shared by every hudhod API namespace.
|
|
4
|
+
*
|
|
5
|
+
* These are declared as *contracts* only — the runtime implementations live in
|
|
6
|
+
* `@hudhod/core`. Keeping them here means extension authors never need to depend
|
|
7
|
+
* on the host runtime just to type a subscription.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* A resource that must be released when it is no longer needed.
|
|
13
|
+
*
|
|
14
|
+
* Every subscription and registration in the hudhod API returns a `Disposable`.
|
|
15
|
+
* Push them onto {@link ExtensionContext.subscriptions} and the host will clean
|
|
16
|
+
* them up automatically when your extension is deactivated.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const sub = hudhod.fs.watch("/src", () => console.log("changed"));
|
|
21
|
+
* context.subscriptions.push(sub);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
interface Disposable {
|
|
25
|
+
/** Releases the underlying resource. Calling this more than once is a no-op. */
|
|
26
|
+
dispose(): void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A function that registers a listener and returns a {@link Disposable} to
|
|
30
|
+
* unregister it.
|
|
31
|
+
*
|
|
32
|
+
* @typeParam T - The payload delivered to listeners.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const sub = hudhod.process.onDidExitProcess((info) => {
|
|
37
|
+
* console.log(`${info.command} exited with ${info.exitCode}`);
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
type Event<T> = (listener: (event: T) => unknown) => Disposable;
|
|
42
|
+
/**
|
|
43
|
+
* Signals that a long-running operation should be abandoned.
|
|
44
|
+
*
|
|
45
|
+
* Search and other potentially expensive operations accept one so callers can
|
|
46
|
+
* bail out early.
|
|
47
|
+
*/
|
|
48
|
+
interface CancellationToken {
|
|
49
|
+
/** Whether cancellation has already been requested. */
|
|
50
|
+
readonly isCancellationRequested: boolean;
|
|
51
|
+
/** Fires once, when cancellation is first requested. */
|
|
52
|
+
readonly onCancellationRequested: Event<void>;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/commands.d.ts
|
|
56
|
+
/** A command that has been registered with the host. */
|
|
57
|
+
interface CommandDescriptor {
|
|
58
|
+
/** Unique identifier, conventionally `namespace.verbNoun`. */
|
|
59
|
+
readonly id: string;
|
|
60
|
+
/** Label shown in the command palette. */
|
|
61
|
+
readonly title: string;
|
|
62
|
+
/** Optional grouping shown alongside the title. */
|
|
63
|
+
readonly category?: string;
|
|
64
|
+
/** Identifier of the extension that contributed the command. */
|
|
65
|
+
readonly extensionId?: string;
|
|
66
|
+
}
|
|
67
|
+
/** Options for {@link CommandsApi.registerCommand}. */
|
|
68
|
+
interface RegisterCommandOptions {
|
|
69
|
+
/**
|
|
70
|
+
* Label shown in the command palette. Commands without a title are callable
|
|
71
|
+
* but stay hidden from the palette.
|
|
72
|
+
*/
|
|
73
|
+
readonly title?: string;
|
|
74
|
+
/** Optional grouping shown alongside the title. */
|
|
75
|
+
readonly category?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Register and invoke commands.
|
|
79
|
+
*
|
|
80
|
+
* Commands are the seam between UI affordances and behaviour: anything the
|
|
81
|
+
* palette, a menu, or an agent can trigger is a command.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* context.subscriptions.push(
|
|
86
|
+
* hudhod.commands.registerCommand(
|
|
87
|
+
* "demo.formatAll",
|
|
88
|
+
* async () => { await hudhod.process.exec("npm", ["run", "fmt"]); },
|
|
89
|
+
* { title: "Format All Files", category: "Demo" },
|
|
90
|
+
* ),
|
|
91
|
+
* );
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
interface CommandsApi {
|
|
95
|
+
/**
|
|
96
|
+
* Registers a command handler.
|
|
97
|
+
* @throws When `id` is already registered.
|
|
98
|
+
* @returns A {@link Disposable} that unregisters the command.
|
|
99
|
+
*/
|
|
100
|
+
registerCommand(id: string, handler: (...args: readonly unknown[]) => unknown, options?: RegisterCommandOptions): Disposable;
|
|
101
|
+
/**
|
|
102
|
+
* Invokes a registered command.
|
|
103
|
+
* @throws A `CommandNotFound` error when `id` is not registered.
|
|
104
|
+
*/
|
|
105
|
+
executeCommand<T = unknown>(id: string, ...args: readonly unknown[]): Promise<T>;
|
|
106
|
+
/** Lists every registered command. */
|
|
107
|
+
getCommands(): Promise<CommandDescriptor[]>;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/diff.d.ts
|
|
111
|
+
/**
|
|
112
|
+
* Diff and patch API types.
|
|
113
|
+
*
|
|
114
|
+
* @packageDocumentation
|
|
115
|
+
*/
|
|
116
|
+
/** How two pieces of text differ, as a sequence of ordered hunks. */
|
|
117
|
+
interface DiffChange {
|
|
118
|
+
/** Whether this run of lines was added, removed, or left untouched. */
|
|
119
|
+
readonly type: "added" | "removed" | "unchanged";
|
|
120
|
+
/** The lines covered by this hunk, without trailing newlines. */
|
|
121
|
+
readonly lines: readonly string[];
|
|
122
|
+
}
|
|
123
|
+
/** Options controlling how text is compared. */
|
|
124
|
+
interface DiffOptions {
|
|
125
|
+
/**
|
|
126
|
+
* Ignore differences that consist only of whitespace.
|
|
127
|
+
* @defaultValue false
|
|
128
|
+
*/
|
|
129
|
+
readonly ignoreWhitespace?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Ignore differences in letter casing.
|
|
132
|
+
* @defaultValue false
|
|
133
|
+
*/
|
|
134
|
+
readonly ignoreCase?: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Lines of unchanged context to keep around each hunk in a unified patch.
|
|
137
|
+
* @defaultValue 3
|
|
138
|
+
*/
|
|
139
|
+
readonly context?: number;
|
|
140
|
+
}
|
|
141
|
+
/** Aggregate statistics for a diff. */
|
|
142
|
+
interface DiffStat {
|
|
143
|
+
/** Number of added lines. */
|
|
144
|
+
readonly added: number;
|
|
145
|
+
/** Number of removed lines. */
|
|
146
|
+
readonly removed: number;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Compare text and files, and apply unified patches.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* const patch = await hudhod.diff.createPatch("/src/a.ts", oldText, newText);
|
|
154
|
+
* await hudhod.diff.applyPatch("/src/a.ts", patch);
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
interface DiffApi {
|
|
158
|
+
/** Compares two strings line by line. */
|
|
159
|
+
diffText(original: string, modified: string, options?: DiffOptions): Promise<DiffChange[]>;
|
|
160
|
+
/** Reads both paths and compares their contents. */
|
|
161
|
+
diffFiles(originalPath: string, modifiedPath: string, options?: DiffOptions): Promise<DiffChange[]>;
|
|
162
|
+
/** Summarises how many lines a change adds and removes. */
|
|
163
|
+
diffStat(original: string, modified: string, options?: DiffOptions): Promise<DiffStat>;
|
|
164
|
+
/** Produces a unified diff, using `path` for the file headers. */
|
|
165
|
+
createPatch(path: string, original: string, modified: string, options?: DiffOptions): Promise<string>;
|
|
166
|
+
/**
|
|
167
|
+
* Applies a unified diff to a file on disk.
|
|
168
|
+
* @throws A `PatchFailed` error when the patch does not apply cleanly.
|
|
169
|
+
*/
|
|
170
|
+
applyPatch(path: string, patch: string): Promise<void>;
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/fs.d.ts
|
|
174
|
+
/** The kind of entry a path points at. */
|
|
175
|
+
type FileType = "file" | "directory" | "symlink";
|
|
176
|
+
/** Metadata describing a single file system entry. */
|
|
177
|
+
interface FileStat {
|
|
178
|
+
/** Whether the entry is a file, directory, or symlink. */
|
|
179
|
+
readonly type: FileType;
|
|
180
|
+
/** Size in bytes. Always `0` for directories. */
|
|
181
|
+
readonly size: number;
|
|
182
|
+
/** Last-modified time, in milliseconds since the Unix epoch. */
|
|
183
|
+
readonly mtime: number;
|
|
184
|
+
}
|
|
185
|
+
/** A single child returned by {@link FileSystemApi.readDirectory}. */
|
|
186
|
+
interface DirectoryEntry {
|
|
187
|
+
/** Entry name, without any leading directory component. */
|
|
188
|
+
readonly name: string;
|
|
189
|
+
/** Absolute path to the entry. */
|
|
190
|
+
readonly path: string;
|
|
191
|
+
/** Whether the entry is a file, directory, or symlink. */
|
|
192
|
+
readonly type: FileType;
|
|
193
|
+
}
|
|
194
|
+
/** How a watched path changed. */
|
|
195
|
+
type FileChangeType = "created" | "changed" | "deleted";
|
|
196
|
+
/** A single file system change notification. */
|
|
197
|
+
interface FileChangeEvent {
|
|
198
|
+
/** What happened to the path. */
|
|
199
|
+
readonly type: FileChangeType;
|
|
200
|
+
/** Absolute path that changed. */
|
|
201
|
+
readonly path: string;
|
|
202
|
+
}
|
|
203
|
+
/** Options for {@link FileSystemApi.writeFile} and {@link FileSystemApi.writeTextFile}. */
|
|
204
|
+
interface WriteFileOptions {
|
|
205
|
+
/**
|
|
206
|
+
* Create the file when it does not exist.
|
|
207
|
+
* @defaultValue true
|
|
208
|
+
*/
|
|
209
|
+
readonly create?: boolean;
|
|
210
|
+
/**
|
|
211
|
+
* Overwrite the file when it already exists. When `false` and the file
|
|
212
|
+
* exists, a `FileExists` error is thrown.
|
|
213
|
+
* @defaultValue true
|
|
214
|
+
*/
|
|
215
|
+
readonly overwrite?: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Create missing parent directories.
|
|
218
|
+
* @defaultValue true
|
|
219
|
+
*/
|
|
220
|
+
readonly createParents?: boolean;
|
|
221
|
+
}
|
|
222
|
+
/** Options for {@link FileSystemApi.delete}. */
|
|
223
|
+
interface DeleteOptions {
|
|
224
|
+
/**
|
|
225
|
+
* Recursively delete directory contents. Deleting a non-empty directory
|
|
226
|
+
* without this flag throws.
|
|
227
|
+
* @defaultValue false
|
|
228
|
+
*/
|
|
229
|
+
readonly recursive?: boolean;
|
|
230
|
+
}
|
|
231
|
+
/** Options for {@link FileSystemApi.rename} and {@link FileSystemApi.copy}. */
|
|
232
|
+
interface MoveOptions {
|
|
233
|
+
/**
|
|
234
|
+
* Replace the destination when it already exists.
|
|
235
|
+
* @defaultValue false
|
|
236
|
+
*/
|
|
237
|
+
readonly overwrite?: boolean;
|
|
238
|
+
}
|
|
239
|
+
/** Options for {@link FileSystemApi.watch}. */
|
|
240
|
+
interface WatchOptions {
|
|
241
|
+
/**
|
|
242
|
+
* Watch nested directories as well.
|
|
243
|
+
* @defaultValue true
|
|
244
|
+
*/
|
|
245
|
+
readonly recursive?: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* Glob patterns to ignore. Defaults to the workspace `files.watcherExclude`
|
|
248
|
+
* setting (`node_modules`, `.git`, `dist`, …).
|
|
249
|
+
*/
|
|
250
|
+
readonly excludes?: readonly string[];
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Read and write the workspace file system.
|
|
254
|
+
*
|
|
255
|
+
* Every method is asynchronous and accepts/returns only structured-cloneable
|
|
256
|
+
* values, so the whole namespace remains usable across a worker boundary.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
260
|
+
* await hudhod.fs.writeTextFile("/src/greet.ts", "export const hi = () => 'hi';");
|
|
261
|
+
* const source = await hudhod.fs.readTextFile("/src/greet.ts");
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
interface FileSystemApi {
|
|
265
|
+
/**
|
|
266
|
+
* Reads a file as raw bytes.
|
|
267
|
+
* @throws A `FileNotFound` error when `path` does not exist.
|
|
268
|
+
* @throws A `NotAFile` error when `path` is a directory.
|
|
269
|
+
*/
|
|
270
|
+
readFile(path: string): Promise<Uint8Array>;
|
|
271
|
+
/**
|
|
272
|
+
* Reads a file and decodes it as UTF-8 text.
|
|
273
|
+
* @throws A `FileNotFound` error when `path` does not exist.
|
|
274
|
+
*/
|
|
275
|
+
readTextFile(path: string): Promise<string>;
|
|
276
|
+
/** Writes raw bytes, creating parent directories by default. */
|
|
277
|
+
writeFile(path: string, data: Uint8Array, options?: WriteFileOptions): Promise<void>;
|
|
278
|
+
/** Writes UTF-8 text, creating parent directories by default. */
|
|
279
|
+
writeTextFile(path: string, content: string, options?: WriteFileOptions): Promise<void>;
|
|
280
|
+
/**
|
|
281
|
+
* Creates an empty file.
|
|
282
|
+
* @throws A `FileExists` error when the file exists and `overwrite` is not set.
|
|
283
|
+
*/
|
|
284
|
+
createFile(path: string, options?: WriteFileOptions): Promise<void>;
|
|
285
|
+
/** Creates a directory, including any missing parents. Succeeds if it already exists. */
|
|
286
|
+
createDirectory(path: string): Promise<void>;
|
|
287
|
+
/**
|
|
288
|
+
* Deletes a file or directory.
|
|
289
|
+
* @throws A `FileNotFound` error when `path` does not exist.
|
|
290
|
+
*/
|
|
291
|
+
delete(path: string, options?: DeleteOptions): Promise<void>;
|
|
292
|
+
/** Moves or renames an entry. */
|
|
293
|
+
rename(from: string, to: string, options?: MoveOptions): Promise<void>;
|
|
294
|
+
/** Copies a file or directory tree. */
|
|
295
|
+
copy(from: string, to: string, options?: MoveOptions): Promise<void>;
|
|
296
|
+
/**
|
|
297
|
+
* Retrieves metadata for a path.
|
|
298
|
+
* @throws A `FileNotFound` error when `path` does not exist.
|
|
299
|
+
*/
|
|
300
|
+
stat(path: string): Promise<FileStat>;
|
|
301
|
+
/** Resolves to `true` when the path exists. Never throws for missing paths. */
|
|
302
|
+
exists(path: string): Promise<boolean>;
|
|
303
|
+
/** Lists the immediate children of a directory, sorted directories-first then by name. */
|
|
304
|
+
readDirectory(path: string): Promise<DirectoryEntry[]>;
|
|
305
|
+
/**
|
|
306
|
+
* Watches a path for changes.
|
|
307
|
+
*
|
|
308
|
+
* Events are debounced and delivered in batches, so a single listener call
|
|
309
|
+
* may describe several changes at once.
|
|
310
|
+
*
|
|
311
|
+
* @returns A {@link Disposable} that stops the watch.
|
|
312
|
+
*/
|
|
313
|
+
watch(path: string, listener: (events: readonly FileChangeEvent[]) => unknown, options?: WatchOptions): Disposable;
|
|
314
|
+
/** Fires for every change anywhere in the workspace, after exclusion filtering. */
|
|
315
|
+
readonly onDidChangeFile: Event<readonly FileChangeEvent[]>;
|
|
316
|
+
}
|
|
317
|
+
//#endregion
|
|
318
|
+
//#region src/keybindings.d.ts
|
|
319
|
+
/**
|
|
320
|
+
* A keybinding declared statically by an extension, binding a key sequence to a command.
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```ts
|
|
324
|
+
* { command: "hudhod.newFile.create", key: "ctrl+n", mac: "cmd+n" }
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
interface KeybindingContribution {
|
|
328
|
+
/** The command id to invoke. */
|
|
329
|
+
readonly command: string;
|
|
330
|
+
/** Key sequence (e.g. `"ctrl+n"`, `"ctrl+shift+p"`). Modifiers: `ctrl`, `shift`, `alt`, `cmd`/`meta`. */
|
|
331
|
+
readonly key: string;
|
|
332
|
+
/** Optional macOS-specific override. Defaults to `key` on mac if not provided. */
|
|
333
|
+
readonly mac?: string;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* A resolved keybinding after conflict resolution and platform normalization.
|
|
337
|
+
*
|
|
338
|
+
* Multiple extensions may contribute the same key; the last registration wins.
|
|
339
|
+
*/
|
|
340
|
+
interface ResolvedKeybinding {
|
|
341
|
+
/** The normalized key sequence for the current platform. */
|
|
342
|
+
readonly key: string;
|
|
343
|
+
/** The command id to invoke. */
|
|
344
|
+
readonly command: string;
|
|
345
|
+
/** Where the keybinding came from. */
|
|
346
|
+
readonly source: "extension" | "builtin";
|
|
347
|
+
/** The extension id if source is `"extension"`. */
|
|
348
|
+
readonly extensionId?: string;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Register and resolve keybindings.
|
|
352
|
+
*/
|
|
353
|
+
interface KeybindingsApi {
|
|
354
|
+
/**
|
|
355
|
+
* Registers a keybinding.
|
|
356
|
+
*
|
|
357
|
+
* If the same `key` is already bound, the new binding replaces the old one.
|
|
358
|
+
* Return the {@link Disposable} to restore the previous binding when disposed.
|
|
359
|
+
*/
|
|
360
|
+
registerKeybinding(binding: KeybindingContribution): Disposable;
|
|
361
|
+
/** Lists all registered keybindings. */
|
|
362
|
+
getKeybindings(): Promise<ResolvedKeybinding[]>;
|
|
363
|
+
}
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/process.d.ts
|
|
366
|
+
/** Options shared by {@link ProcessApi.spawn} and {@link ProcessApi.exec}. */
|
|
367
|
+
interface SpawnOptions {
|
|
368
|
+
/**
|
|
369
|
+
* Working directory for the process.
|
|
370
|
+
* @defaultValue The workspace root.
|
|
371
|
+
*/
|
|
372
|
+
readonly cwd?: string;
|
|
373
|
+
/** Extra environment variables, merged over the runtime defaults. */
|
|
374
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
375
|
+
/**
|
|
376
|
+
* Allocate a pseudo-terminal with these dimensions. Required for programs
|
|
377
|
+
* that render interactive UI or colourised output.
|
|
378
|
+
*/
|
|
379
|
+
readonly terminal?: {
|
|
380
|
+
readonly cols: number;
|
|
381
|
+
readonly rows: number;
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/** Options for {@link ProcessApi.exec}. */
|
|
385
|
+
interface ExecOptions extends SpawnOptions {
|
|
386
|
+
/**
|
|
387
|
+
* Kill the process after this many milliseconds. Pass `false` to wait
|
|
388
|
+
* indefinitely — only do this for processes you are certain will terminate.
|
|
389
|
+
* @defaultValue 60000
|
|
390
|
+
*/
|
|
391
|
+
readonly timeout?: number | false;
|
|
392
|
+
/**
|
|
393
|
+
* Stop buffering after this many bytes of output and kill the process. Pass
|
|
394
|
+
* `false` to buffer without limit.
|
|
395
|
+
* @defaultValue 1048576 (1 MiB)
|
|
396
|
+
*/
|
|
397
|
+
readonly maxOutputBytes?: number | false;
|
|
398
|
+
}
|
|
399
|
+
/** The outcome of a completed {@link ProcessApi.exec} call. */
|
|
400
|
+
interface ExecResult {
|
|
401
|
+
/** Exit code. `0` conventionally means success. */
|
|
402
|
+
readonly exitCode: number;
|
|
403
|
+
/** Merged stdout and stderr, decoded as UTF-8. */
|
|
404
|
+
readonly output: string;
|
|
405
|
+
/** Whether `output` was cut short by `maxOutputBytes`. */
|
|
406
|
+
readonly truncated: boolean;
|
|
407
|
+
/** Wall-clock duration in milliseconds. */
|
|
408
|
+
readonly durationMs: number;
|
|
409
|
+
}
|
|
410
|
+
/** Lifecycle state of a tracked process. */
|
|
411
|
+
type ProcessStatus = "running" | "exited" | "killed";
|
|
412
|
+
/** A structured-cloneable snapshot of a process, safe to send across a worker boundary. */
|
|
413
|
+
interface ProcessInfo {
|
|
414
|
+
/** Host-assigned identifier, unique for the session. */
|
|
415
|
+
readonly id: string;
|
|
416
|
+
/** The executable that was spawned. */
|
|
417
|
+
readonly command: string;
|
|
418
|
+
/** Arguments passed to the executable. */
|
|
419
|
+
readonly args: readonly string[];
|
|
420
|
+
/** Start time in milliseconds since the Unix epoch. */
|
|
421
|
+
readonly startedAt: number;
|
|
422
|
+
/** Current lifecycle state. */
|
|
423
|
+
readonly status: ProcessStatus;
|
|
424
|
+
/** Exit code, present once the process has finished. */
|
|
425
|
+
readonly exitCode?: number;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* A live handle to a running process.
|
|
429
|
+
*
|
|
430
|
+
* Unlike {@link ProcessInfo} this holds streams, so it cannot cross a worker
|
|
431
|
+
* boundary and is only available to same-context callers.
|
|
432
|
+
*/
|
|
433
|
+
interface ProcessHandle extends ProcessInfo {
|
|
434
|
+
/** Merged stdout and stderr, as a stream of decoded text chunks. */
|
|
435
|
+
readonly output: ReadableStream<string>;
|
|
436
|
+
/** Standard input. Only writable when the process was spawned with a terminal. */
|
|
437
|
+
readonly input: WritableStream<string>;
|
|
438
|
+
/** Resolves with the exit code when the process finishes. */
|
|
439
|
+
readonly exit: Promise<number>;
|
|
440
|
+
/** Terminates the process. Safe to call after it has already exited. */
|
|
441
|
+
kill(): void;
|
|
442
|
+
/** Resizes the pseudo-terminal. No-op when the process has no terminal. */
|
|
443
|
+
resize(dimensions: {
|
|
444
|
+
cols: number;
|
|
445
|
+
rows: number;
|
|
446
|
+
}): void;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Spawn and manage processes inside the workspace container.
|
|
450
|
+
*
|
|
451
|
+
* @example Running a one-shot command
|
|
452
|
+
* ```ts
|
|
453
|
+
* const { exitCode, output } = await hudhod.process.exec("node", ["-v"]);
|
|
454
|
+
* ```
|
|
455
|
+
*
|
|
456
|
+
* @example Streaming a long-running process
|
|
457
|
+
* ```ts
|
|
458
|
+
* const proc = await hudhod.process.spawn("npm", ["run", "build"]);
|
|
459
|
+
* await proc.output.pipeTo(new WritableStream({ write: (c) => console.log(c) }));
|
|
460
|
+
* ```
|
|
461
|
+
*/
|
|
462
|
+
interface ProcessApi {
|
|
463
|
+
/**
|
|
464
|
+
* Starts a process and returns immediately with a live handle.
|
|
465
|
+
* The caller owns the handle and is responsible for killing it.
|
|
466
|
+
*/
|
|
467
|
+
spawn(command: string, args?: readonly string[], options?: SpawnOptions): Promise<ProcessHandle>;
|
|
468
|
+
/**
|
|
469
|
+
* Runs a command to completion and buffers its output.
|
|
470
|
+
*
|
|
471
|
+
* Guarded by a timeout and an output cap so a runaway command cannot hang
|
|
472
|
+
* the browser tab. Both guards are overridable, including disabling them.
|
|
473
|
+
*
|
|
474
|
+
* @throws A `ProcessTimeout` error when `timeout` elapses. The error carries
|
|
475
|
+
* the partial output collected so far.
|
|
476
|
+
* @throws An `OutputLimitExceeded` error when `maxOutputBytes` is exceeded.
|
|
477
|
+
*/
|
|
478
|
+
exec(command: string, args?: readonly string[], options?: ExecOptions): Promise<ExecResult>;
|
|
479
|
+
/** Lists every process the host is currently tracking. */
|
|
480
|
+
list(): Promise<ProcessInfo[]>;
|
|
481
|
+
/** Kills a process by its {@link ProcessInfo.id}. Resolves `false` if unknown. */
|
|
482
|
+
kill(id: string): Promise<boolean>;
|
|
483
|
+
/** Fires whenever a process starts. */
|
|
484
|
+
readonly onDidStartProcess: Event<ProcessInfo>;
|
|
485
|
+
/** Fires whenever a process exits, for any reason. */
|
|
486
|
+
readonly onDidExitProcess: Event<ProcessInfo>;
|
|
487
|
+
}
|
|
488
|
+
//#endregion
|
|
489
|
+
//#region src/search.d.ts
|
|
490
|
+
/** Options for {@link SearchApi.findFiles}. */
|
|
491
|
+
interface FindFilesOptions {
|
|
492
|
+
/**
|
|
493
|
+
* Glob patterns to exclude.
|
|
494
|
+
* @defaultValue The workspace `search.exclude` setting.
|
|
495
|
+
*/
|
|
496
|
+
readonly exclude?: readonly string[];
|
|
497
|
+
/**
|
|
498
|
+
* Stop after this many matches.
|
|
499
|
+
* @defaultValue 1000
|
|
500
|
+
*/
|
|
501
|
+
readonly maxResults?: number;
|
|
502
|
+
/** Abort the walk early. */
|
|
503
|
+
readonly token?: CancellationToken;
|
|
504
|
+
}
|
|
505
|
+
/** Options for {@link SearchApi.findInFiles}. */
|
|
506
|
+
interface FindInFilesOptions extends FindFilesOptions {
|
|
507
|
+
/**
|
|
508
|
+
* Glob patterns to include.
|
|
509
|
+
* @defaultValue All files.
|
|
510
|
+
*/
|
|
511
|
+
readonly include?: readonly string[];
|
|
512
|
+
/**
|
|
513
|
+
* Treat `query` as a regular expression.
|
|
514
|
+
* @defaultValue false
|
|
515
|
+
*/
|
|
516
|
+
readonly isRegex?: boolean;
|
|
517
|
+
/**
|
|
518
|
+
* Match case exactly.
|
|
519
|
+
* @defaultValue false
|
|
520
|
+
*/
|
|
521
|
+
readonly caseSensitive?: boolean;
|
|
522
|
+
/**
|
|
523
|
+
* Only match whole words.
|
|
524
|
+
* @defaultValue false
|
|
525
|
+
*/
|
|
526
|
+
readonly wholeWord?: boolean;
|
|
527
|
+
}
|
|
528
|
+
/** A single match within a file. */
|
|
529
|
+
interface SearchMatch {
|
|
530
|
+
/** Absolute path of the containing file. */
|
|
531
|
+
readonly path: string;
|
|
532
|
+
/** One-based line number. */
|
|
533
|
+
readonly line: number;
|
|
534
|
+
/** Zero-based column offset of the match start, in UTF-16 code units. */
|
|
535
|
+
readonly column: number;
|
|
536
|
+
/** Length of the matched text, in UTF-16 code units. */
|
|
537
|
+
readonly length: number;
|
|
538
|
+
/** The full text of the matching line, for display. */
|
|
539
|
+
readonly preview: string;
|
|
540
|
+
}
|
|
541
|
+
/** The outcome of a {@link SearchApi.findInFiles} call. */
|
|
542
|
+
interface SearchResult {
|
|
543
|
+
/** Every match found, in file then line order. */
|
|
544
|
+
readonly matches: readonly SearchMatch[];
|
|
545
|
+
/** Whether the search stopped early because `maxResults` was reached. */
|
|
546
|
+
readonly limitHit: boolean;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Find files by name and text within files.
|
|
550
|
+
*
|
|
551
|
+
* @example
|
|
552
|
+
* ```ts
|
|
553
|
+
* const paths = await hudhod.search.findFiles("src/**\/*.ts");
|
|
554
|
+
* const { matches } = await hudhod.search.findInFiles("TODO", { include: ["**\/*.ts"] });
|
|
555
|
+
* ```
|
|
556
|
+
*/
|
|
557
|
+
interface SearchApi {
|
|
558
|
+
/**
|
|
559
|
+
* Returns absolute paths matching a glob pattern.
|
|
560
|
+
* Honours the workspace exclude settings unless overridden.
|
|
561
|
+
*/
|
|
562
|
+
findFiles(include: string, options?: FindFilesOptions): Promise<string[]>;
|
|
563
|
+
/** Searches file contents for `query`. */
|
|
564
|
+
findInFiles(query: string, options?: FindInFilesOptions): Promise<SearchResult>;
|
|
565
|
+
/**
|
|
566
|
+
* Replaces every match of `query` with `replacement`.
|
|
567
|
+
* @returns The number of files modified.
|
|
568
|
+
*/
|
|
569
|
+
replaceInFiles(query: string, replacement: string, options?: FindInFilesOptions): Promise<number>;
|
|
570
|
+
}
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region src/terminal.d.ts
|
|
573
|
+
/** Options for {@link TerminalApi.create}. */
|
|
574
|
+
interface CreateTerminalOptions {
|
|
575
|
+
/**
|
|
576
|
+
* Tab title.
|
|
577
|
+
* @defaultValue "Terminal"
|
|
578
|
+
*/
|
|
579
|
+
readonly name?: string;
|
|
580
|
+
/**
|
|
581
|
+
* Working directory.
|
|
582
|
+
* @defaultValue The workspace root.
|
|
583
|
+
*/
|
|
584
|
+
readonly cwd?: string;
|
|
585
|
+
}
|
|
586
|
+
/** A shell session backed by a real pseudo-terminal. */
|
|
587
|
+
interface Terminal extends Disposable {
|
|
588
|
+
/** Host-assigned identifier, unique for the session. */
|
|
589
|
+
readonly id: string;
|
|
590
|
+
/** Tab title. */
|
|
591
|
+
readonly name: string;
|
|
592
|
+
/** Writes text to the terminal's stdin. */
|
|
593
|
+
sendText(text: string, addNewline?: boolean): Promise<void>;
|
|
594
|
+
/** Brings the terminal's panel to the foreground. */
|
|
595
|
+
show(): Promise<void>;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Create and control interactive shells.
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* ```ts
|
|
602
|
+
* const term = await hudhod.terminal.create({ name: "Build" });
|
|
603
|
+
* await term.sendText("npm run build");
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
interface TerminalApi {
|
|
607
|
+
/** Opens a new shell session. */
|
|
608
|
+
create(options?: CreateTerminalOptions): Promise<Terminal>;
|
|
609
|
+
/** Every terminal currently open. */
|
|
610
|
+
readonly terminals: readonly Terminal[];
|
|
611
|
+
/** Fires when a terminal is created. */
|
|
612
|
+
readonly onDidOpenTerminal: Event<Terminal>;
|
|
613
|
+
/** Fires when a terminal is disposed. */
|
|
614
|
+
readonly onDidCloseTerminal: Event<Terminal>;
|
|
615
|
+
}
|
|
616
|
+
//#endregion
|
|
617
|
+
//#region src/window.d.ts
|
|
618
|
+
/** Severity of a notification. */
|
|
619
|
+
type MessageSeverity = "info" | "warning" | "error";
|
|
620
|
+
/** Options for {@link WindowApi.showInputBox}. */
|
|
621
|
+
interface InputBoxOptions {
|
|
622
|
+
/** Dialog heading. */
|
|
623
|
+
readonly title?: string;
|
|
624
|
+
/** Placeholder shown while the field is empty. */
|
|
625
|
+
readonly placeholder?: string;
|
|
626
|
+
/** Initial field value. */
|
|
627
|
+
readonly value?: string;
|
|
628
|
+
/** Label for the confirm button. */
|
|
629
|
+
readonly confirmLabel?: string;
|
|
630
|
+
/** Return a message to block submission, or `undefined` to allow it. */
|
|
631
|
+
readonly validate?: (value: string) => string | undefined;
|
|
632
|
+
}
|
|
633
|
+
/** A selectable entry in a quick pick. */
|
|
634
|
+
interface QuickPickItem {
|
|
635
|
+
/** Primary text. */
|
|
636
|
+
readonly label: string;
|
|
637
|
+
/** Secondary text shown beside the label. */
|
|
638
|
+
readonly description?: string;
|
|
639
|
+
/** Opaque value returned on selection. Defaults to `label`. */
|
|
640
|
+
readonly value?: string;
|
|
641
|
+
}
|
|
642
|
+
/** Options for {@link WindowApi.showQuickPick}. */
|
|
643
|
+
interface QuickPickOptions {
|
|
644
|
+
/** Dialog heading. */
|
|
645
|
+
readonly title?: string;
|
|
646
|
+
/** Placeholder shown in the filter field. */
|
|
647
|
+
readonly placeholder?: string;
|
|
648
|
+
}
|
|
649
|
+
/** Where a contributed panel should dock by default. */
|
|
650
|
+
type PanelLocation = "left" | "right" | "bottom" | "center";
|
|
651
|
+
/** Options for {@link WindowApi.registerPanel}. */
|
|
652
|
+
interface RegisterPanelOptions {
|
|
653
|
+
/** Tab title. */
|
|
654
|
+
readonly title: string;
|
|
655
|
+
/**
|
|
656
|
+
* Preferred dock location.
|
|
657
|
+
* @defaultValue "bottom"
|
|
658
|
+
*/
|
|
659
|
+
readonly location?: PanelLocation;
|
|
660
|
+
/** Initial width in pixels, for `left` and `right` panels. */
|
|
661
|
+
readonly initialWidth?: number;
|
|
662
|
+
/** Initial height in pixels, for `bottom` panels. */
|
|
663
|
+
readonly initialHeight?: number;
|
|
664
|
+
/**
|
|
665
|
+
* Open the panel as soon as it is registered.
|
|
666
|
+
* @defaultValue false
|
|
667
|
+
*/
|
|
668
|
+
readonly openImmediately?: boolean;
|
|
669
|
+
}
|
|
670
|
+
/** Options for {@link WindowApi.registerView}. */
|
|
671
|
+
interface RegisterViewOptions {
|
|
672
|
+
/** Header title declared by the view contribution. */
|
|
673
|
+
readonly title: string;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Renders a contributed panel's content into a host-provided element.
|
|
677
|
+
*
|
|
678
|
+
* Returning a cleanup function is optional but recommended; the host calls it
|
|
679
|
+
* when the panel closes.
|
|
680
|
+
*/
|
|
681
|
+
type PanelRenderer = (container: HTMLElement) => void | (() => void) | Promise<void | (() => void)>;
|
|
682
|
+
/** The file currently focused in the editor. */
|
|
683
|
+
interface ActiveEditor {
|
|
684
|
+
/** Absolute path of the open file. */
|
|
685
|
+
readonly path: string;
|
|
686
|
+
/** Whether the file has unsaved changes. */
|
|
687
|
+
readonly dirty: boolean;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Show UI and contribute panels.
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
* ```ts
|
|
694
|
+
* hudhod.window.registerPanel("demo.stats", (el) => {
|
|
695
|
+
* el.textContent = "Hello from an extension panel";
|
|
696
|
+
* }, { title: "Stats", location: "right" });
|
|
697
|
+
* ```
|
|
698
|
+
*/
|
|
699
|
+
interface WindowApi {
|
|
700
|
+
/** Shows a transient notification. */
|
|
701
|
+
showMessage(message: string, severity?: MessageSeverity): Promise<void>;
|
|
702
|
+
/** Prompts for a single line of text. Resolves `undefined` if cancelled. */
|
|
703
|
+
showInputBox(options?: InputBoxOptions): Promise<string | undefined>;
|
|
704
|
+
/** Prompts the user to pick one item. Resolves `undefined` if cancelled. */
|
|
705
|
+
showQuickPick(items: readonly QuickPickItem[], options?: QuickPickOptions): Promise<string | undefined>;
|
|
706
|
+
/**
|
|
707
|
+
* Contributes a panel to the workbench layout.
|
|
708
|
+
* @returns A {@link Disposable} that removes the panel.
|
|
709
|
+
*/
|
|
710
|
+
registerPanel(id: string, render: PanelRenderer, options: RegisterPanelOptions): Disposable;
|
|
711
|
+
/** Supplies the body renderer for a contributed view. */
|
|
712
|
+
registerView(id: string, render: PanelRenderer, options: RegisterViewOptions): Disposable;
|
|
713
|
+
/** Opens a panel, or focuses it when already open. */
|
|
714
|
+
openPanel(id: string): Promise<void>;
|
|
715
|
+
/** Closes a panel. Resolves `false` when it was not open. */
|
|
716
|
+
closePanel(id: string): Promise<boolean>;
|
|
717
|
+
/** Opens a file in the editor. */
|
|
718
|
+
openFile(path: string): Promise<void>;
|
|
719
|
+
/** The currently focused editor, if any. */
|
|
720
|
+
readonly activeEditor: ActiveEditor | undefined;
|
|
721
|
+
/** Fires when editor focus moves to a different file. */
|
|
722
|
+
readonly onDidChangeActiveEditor: Event<ActiveEditor | undefined>;
|
|
723
|
+
}
|
|
724
|
+
//#endregion
|
|
725
|
+
//#region src/workspace.d.ts
|
|
726
|
+
/** A range within a text document, using zero-based line and character offsets. */
|
|
727
|
+
interface Range {
|
|
728
|
+
/** Zero-based start line. */
|
|
729
|
+
readonly startLine: number;
|
|
730
|
+
/** Zero-based start character offset, in UTF-16 code units. */
|
|
731
|
+
readonly startCharacter: number;
|
|
732
|
+
/** Zero-based end line. */
|
|
733
|
+
readonly endLine: number;
|
|
734
|
+
/** Zero-based end character offset, in UTF-16 code units. */
|
|
735
|
+
readonly endCharacter: number;
|
|
736
|
+
}
|
|
737
|
+
/** Replaces `range` with `text`. Omit `range` to replace the whole file. */
|
|
738
|
+
interface TextEdit {
|
|
739
|
+
/** The region to replace. When absent, the entire file is replaced. */
|
|
740
|
+
readonly range?: Range;
|
|
741
|
+
/** The replacement text. Use an empty string to delete. */
|
|
742
|
+
readonly text: string;
|
|
743
|
+
}
|
|
744
|
+
/** A set of edits applied to one file. */
|
|
745
|
+
interface FileEdit {
|
|
746
|
+
/** Absolute path of the file to modify. */
|
|
747
|
+
readonly path: string;
|
|
748
|
+
/** Edits to apply, resolved against the file's original content. */
|
|
749
|
+
readonly edits: readonly TextEdit[];
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* How an edit should reach the user.
|
|
753
|
+
*
|
|
754
|
+
* - `immediate` — apply straight away. The change is recorded in the edit
|
|
755
|
+
* history and can be reverted, and lands in the editor's undo stack when the
|
|
756
|
+
* file is open.
|
|
757
|
+
* - `review` — show the user a diff and wait. Nothing is written unless they
|
|
758
|
+
* accept.
|
|
759
|
+
*/
|
|
760
|
+
type EditMode = "immediate" | "review";
|
|
761
|
+
/** Options for {@link WorkspaceApi.applyEdit}. */
|
|
762
|
+
interface ApplyEditOptions {
|
|
763
|
+
/**
|
|
764
|
+
* Whether to apply the edit directly or ask the user first.
|
|
765
|
+
* @defaultValue "immediate"
|
|
766
|
+
*/
|
|
767
|
+
readonly mode?: EditMode;
|
|
768
|
+
/** Human-readable description, shown in review UI and the edit history. */
|
|
769
|
+
readonly label?: string;
|
|
770
|
+
}
|
|
771
|
+
/** The outcome of an {@link WorkspaceApi.applyEdit} call. */
|
|
772
|
+
interface ApplyEditResult {
|
|
773
|
+
/** Whether the edit was written to disk. `false` when the user rejected a review. */
|
|
774
|
+
readonly applied: boolean;
|
|
775
|
+
/** Paths that were modified. */
|
|
776
|
+
readonly paths: readonly string[];
|
|
777
|
+
/**
|
|
778
|
+
* Identifier for this change in the edit history. Pass to
|
|
779
|
+
* {@link WorkspaceApi.revertEdit} to undo it.
|
|
780
|
+
*/
|
|
781
|
+
readonly editId?: string;
|
|
782
|
+
}
|
|
783
|
+
/** A recorded workspace edit, retained so it can be reverted. */
|
|
784
|
+
interface EditHistoryEntry {
|
|
785
|
+
/** Unique identifier for the change. */
|
|
786
|
+
readonly id: string;
|
|
787
|
+
/** Description supplied when the edit was applied. */
|
|
788
|
+
readonly label: string;
|
|
789
|
+
/** When the edit was applied, in milliseconds since the Unix epoch. */
|
|
790
|
+
readonly timestamp: number;
|
|
791
|
+
/** Paths the edit touched. */
|
|
792
|
+
readonly paths: readonly string[];
|
|
793
|
+
/** Whether the change has since been reverted. */
|
|
794
|
+
readonly reverted: boolean;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Workspace-level operations: roots, snapshots, and edits.
|
|
798
|
+
*
|
|
799
|
+
* @example Applying an agent edit the user must approve
|
|
800
|
+
* ```ts
|
|
801
|
+
* const result = await hudhod.workspace.applyEdit(
|
|
802
|
+
* [{ path: "/src/index.ts", edits: [{ text: nextSource }] }],
|
|
803
|
+
* { mode: "review", label: "Add error handling" },
|
|
804
|
+
* );
|
|
805
|
+
* ```
|
|
806
|
+
*/
|
|
807
|
+
interface WorkspaceApi {
|
|
808
|
+
/** Absolute path of the workspace root. */
|
|
809
|
+
readonly rootPath: string;
|
|
810
|
+
/**
|
|
811
|
+
* Applies edits across one or more files.
|
|
812
|
+
*
|
|
813
|
+
* Edits within a single file are applied bottom-up so earlier ranges stay
|
|
814
|
+
* valid. The whole set is atomic: if any file fails, none are written.
|
|
815
|
+
*/
|
|
816
|
+
applyEdit(edits: readonly FileEdit[], options?: ApplyEditOptions): Promise<ApplyEditResult>;
|
|
817
|
+
/** Undoes a previously applied edit, restoring the prior contents. */
|
|
818
|
+
revertEdit(editId: string): Promise<boolean>;
|
|
819
|
+
/** Returns the recorded edit history, newest first. */
|
|
820
|
+
editHistory(): Promise<EditHistoryEntry[]>;
|
|
821
|
+
/** Fires after any workspace edit is applied or reverted. */
|
|
822
|
+
readonly onDidApplyEdit: Event<EditHistoryEntry>;
|
|
823
|
+
}
|
|
824
|
+
//#endregion
|
|
825
|
+
//#region src/api.d.ts
|
|
826
|
+
/**
|
|
827
|
+
* Everything an extension can reach.
|
|
828
|
+
*
|
|
829
|
+
* An instance is passed to {@link ExtensionContext.hudhod} on activation.
|
|
830
|
+
* The same surface backs the AI agent tool layer, so anything an extension can
|
|
831
|
+
* do, an agent can do.
|
|
832
|
+
*/
|
|
833
|
+
interface HudhodApi {
|
|
834
|
+
/** Semver version of the host runtime. */
|
|
835
|
+
readonly version: string;
|
|
836
|
+
/** Read and write files. */
|
|
837
|
+
readonly fs: FileSystemApi;
|
|
838
|
+
/** Workspace roots, edits, and edit history. */
|
|
839
|
+
readonly workspace: WorkspaceApi;
|
|
840
|
+
/** Find files and search their contents. */
|
|
841
|
+
readonly search: SearchApi;
|
|
842
|
+
/** Compare text and apply patches. */
|
|
843
|
+
readonly diff: DiffApi;
|
|
844
|
+
/** Spawn and manage processes. */
|
|
845
|
+
readonly process: ProcessApi;
|
|
846
|
+
/** Create and control interactive shells. */
|
|
847
|
+
readonly terminal: TerminalApi;
|
|
848
|
+
/** Register and invoke commands. */
|
|
849
|
+
readonly commands: CommandsApi;
|
|
850
|
+
/** Register and resolve keybindings. */
|
|
851
|
+
readonly keybindings: KeybindingsApi;
|
|
852
|
+
/** Show UI and contribute panels. */
|
|
853
|
+
readonly window: WindowApi;
|
|
854
|
+
}
|
|
855
|
+
//#endregion
|
|
856
|
+
//#region src/errors.d.ts
|
|
857
|
+
/**
|
|
858
|
+
* Error codes shared between the host and extensions.
|
|
859
|
+
*
|
|
860
|
+
* The host throws `HudhodError` instances carrying one of these codes. Matching
|
|
861
|
+
* on {@link HudhodErrorCode} is stable across versions; matching on message
|
|
862
|
+
* text is not.
|
|
863
|
+
*
|
|
864
|
+
* @packageDocumentation
|
|
865
|
+
*/
|
|
866
|
+
/** Every error code the host can raise. */
|
|
867
|
+
type HudhodErrorCode = /** The requested path does not exist. */
|
|
868
|
+
"FileNotFound"
|
|
869
|
+
/** The path already exists and `overwrite` was not set. */ | "FileExists"
|
|
870
|
+
/** A directory was expected but the path is a file. */ | "NotADirectory"
|
|
871
|
+
/** A file was expected but the path is a directory. */ | "NotAFile"
|
|
872
|
+
/** The directory is not empty and `recursive` was not set. */ | "DirectoryNotEmpty"
|
|
873
|
+
/** The path is malformed, relative, or escapes the workspace root. */ | "InvalidPath"
|
|
874
|
+
/** A command id was invoked but never registered. */ | "CommandNotFound"
|
|
875
|
+
/** A command id was registered twice. */ | "CommandExists"
|
|
876
|
+
/** A process exceeded its configured timeout and was killed. */ | "ProcessTimeout"
|
|
877
|
+
/** A process produced more output than its configured cap allowed. */ | "OutputLimitExceeded"
|
|
878
|
+
/** A unified diff did not apply cleanly. */ | "PatchFailed"
|
|
879
|
+
/** The operation was cancelled by its caller. */ | "Cancelled";
|
|
880
|
+
/** An error raised by the hudhod host. */
|
|
881
|
+
interface HudhodError extends Error {
|
|
882
|
+
/** Stable, machine-readable classification of the failure. */
|
|
883
|
+
readonly code: HudhodErrorCode;
|
|
884
|
+
/** The path involved, for file system errors. */
|
|
885
|
+
readonly path?: string;
|
|
886
|
+
/** Output collected before the failure, for process errors. */
|
|
887
|
+
readonly partialOutput?: string;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Narrows an unknown caught value to a {@link HudhodError}.
|
|
891
|
+
*
|
|
892
|
+
* @example
|
|
893
|
+
* ```ts
|
|
894
|
+
* try {
|
|
895
|
+
* await hudhod.fs.readTextFile("/missing.ts");
|
|
896
|
+
* } catch (error) {
|
|
897
|
+
* if (isHudhodError(error) && error.code === "FileNotFound") {
|
|
898
|
+
* // handle it
|
|
899
|
+
* }
|
|
900
|
+
* }
|
|
901
|
+
* ```
|
|
902
|
+
*/
|
|
903
|
+
declare function isHudhodError(value: unknown): value is HudhodError;
|
|
904
|
+
//#endregion
|
|
905
|
+
//#region src/extension.d.ts
|
|
906
|
+
/**
|
|
907
|
+
* When an extension should be loaded.
|
|
908
|
+
*
|
|
909
|
+
* - `onStartup` — activate as soon as the workbench is ready.
|
|
910
|
+
* - `onCommand:<id>` — activate the first time a command is invoked.
|
|
911
|
+
* - `onFileOpen:<glob>` — activate when a matching file is opened.
|
|
912
|
+
* - `onView:<panelId>` — activate when a contributed panel is opened.
|
|
913
|
+
*
|
|
914
|
+
* Prefer lazy events over `onStartup`; every eager extension delays boot.
|
|
915
|
+
*/
|
|
916
|
+
type ActivationEvent = "onStartup" | `onCommand:${string}` | `onFileOpen:${string}` | `onView:${string}`;
|
|
917
|
+
/** A command declared statically, so the palette can list it before activation. */
|
|
918
|
+
interface CommandContribution {
|
|
919
|
+
/** Unique identifier, matching the id passed to `registerCommand`. */
|
|
920
|
+
readonly id: string;
|
|
921
|
+
/** Label shown in the command palette. */
|
|
922
|
+
readonly title: string;
|
|
923
|
+
/** Optional grouping shown alongside the title. */
|
|
924
|
+
readonly category?: string;
|
|
925
|
+
}
|
|
926
|
+
/** A panel declared statically, so the layout can reserve a slot for it. */
|
|
927
|
+
interface PanelContribution {
|
|
928
|
+
/** Unique identifier, matching the id passed to `registerPanel`. */
|
|
929
|
+
readonly id: string;
|
|
930
|
+
/** Tab title. */
|
|
931
|
+
readonly title: string;
|
|
932
|
+
/** Opaque to the SDK; the host application defines what a valid icon value is. */
|
|
933
|
+
readonly icon?: unknown;
|
|
934
|
+
/**
|
|
935
|
+
* Preferred dock location.
|
|
936
|
+
* @defaultValue "bottom"
|
|
937
|
+
*/
|
|
938
|
+
readonly location?: "left" | "right" | "bottom" | "center";
|
|
939
|
+
}
|
|
940
|
+
/** An activity-bar container that can hold one or more contributed views. */
|
|
941
|
+
interface ViewContainerContribution {
|
|
942
|
+
/** Unique identifier, matching the id passed to `registerPanel`. */
|
|
943
|
+
readonly id: string;
|
|
944
|
+
/** Tab title. */
|
|
945
|
+
readonly title: string;
|
|
946
|
+
/** Opaque to the SDK; the host application defines what a valid icon value is. */
|
|
947
|
+
readonly icon?: unknown;
|
|
948
|
+
/** Preferred dock location. */
|
|
949
|
+
readonly location?: "left" | "right" | "bottom" | "center";
|
|
950
|
+
}
|
|
951
|
+
/** A collapsible body section contributed to a view container. */
|
|
952
|
+
interface ViewContribution {
|
|
953
|
+
/** Unique identifier, matching the id passed to `registerView`. */
|
|
954
|
+
readonly id: string;
|
|
955
|
+
/** Header text shown by the workbench. */
|
|
956
|
+
readonly title: string;
|
|
957
|
+
/** Id of the view container that owns this view. */
|
|
958
|
+
readonly container: string;
|
|
959
|
+
/** Sort order within the container; unordered views follow ordered ones. */
|
|
960
|
+
readonly order?: number;
|
|
961
|
+
}
|
|
962
|
+
/** Static declarations an extension makes to the workbench. */
|
|
963
|
+
interface Contributions {
|
|
964
|
+
/** Commands the extension provides. */
|
|
965
|
+
readonly commands?: readonly CommandContribution[];
|
|
966
|
+
/** Panels the extension provides. */
|
|
967
|
+
readonly panels?: readonly PanelContribution[];
|
|
968
|
+
/** Activity-bar containers the extension provides. */
|
|
969
|
+
readonly viewContainers?: readonly ViewContainerContribution[];
|
|
970
|
+
/** Accordion views the extension provides. */
|
|
971
|
+
readonly views?: readonly ViewContribution[];
|
|
972
|
+
/** Keybindings the extension provides. */
|
|
973
|
+
readonly keybindings?: readonly KeybindingContribution[];
|
|
974
|
+
}
|
|
975
|
+
/** Identity and capabilities of an extension. */
|
|
976
|
+
interface ExtensionManifest {
|
|
977
|
+
/** Unique identifier, conventionally `publisher.name`. */
|
|
978
|
+
readonly id: string;
|
|
979
|
+
/** Human-readable name. */
|
|
980
|
+
readonly name: string;
|
|
981
|
+
/** Semver version string. */
|
|
982
|
+
readonly version: string;
|
|
983
|
+
/** Short summary shown in extension listings. */
|
|
984
|
+
readonly description?: string;
|
|
985
|
+
/** Events that trigger activation. Defaults to `["onStartup"]`. */
|
|
986
|
+
readonly activationEvents?: readonly ActivationEvent[];
|
|
987
|
+
/** Static contributions to the workbench. */
|
|
988
|
+
readonly contributes?: Contributions;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Per-extension state handed to {@link Extension.activate}.
|
|
992
|
+
*/
|
|
993
|
+
interface ExtensionContext {
|
|
994
|
+
/** The manifest this extension was loaded with. */
|
|
995
|
+
readonly manifest: ExtensionManifest;
|
|
996
|
+
/**
|
|
997
|
+
* Disposables cleaned up automatically on deactivation.
|
|
998
|
+
* Push every subscription and registration here.
|
|
999
|
+
*/
|
|
1000
|
+
readonly subscriptions: Disposable[];
|
|
1001
|
+
/** The full host API. Identical to the module-scoped `hudhod` object. */
|
|
1002
|
+
readonly hudhod: HudhodApi;
|
|
1003
|
+
}
|
|
1004
|
+
/** The shape an extension module must export. */
|
|
1005
|
+
interface Extension {
|
|
1006
|
+
/** Describes the extension to the host. */
|
|
1007
|
+
readonly manifest: ExtensionManifest;
|
|
1008
|
+
/** Called once, when an activation event fires. */
|
|
1009
|
+
activate(context: ExtensionContext): void | Promise<void>;
|
|
1010
|
+
/** Called once, before the extension is unloaded. */
|
|
1011
|
+
deactivate?(): void | Promise<void>;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Declares an extension with full type inference.
|
|
1015
|
+
*
|
|
1016
|
+
* This is an identity function — it exists purely so the object literal is
|
|
1017
|
+
* checked against {@link Extension} at the definition site rather than at the
|
|
1018
|
+
* point of use.
|
|
1019
|
+
*
|
|
1020
|
+
* @example
|
|
1021
|
+
* ```ts
|
|
1022
|
+
* export default defineExtension({
|
|
1023
|
+
* manifest: {
|
|
1024
|
+
* id: "acme.hello",
|
|
1025
|
+
* name: "Hello",
|
|
1026
|
+
* version: "1.0.0",
|
|
1027
|
+
* activationEvents: ["onCommand:acme.hello.greet"],
|
|
1028
|
+
* contributes: {
|
|
1029
|
+
* commands: [{ id: "acme.hello.greet", title: "Say Hello" }],
|
|
1030
|
+
* },
|
|
1031
|
+
* },
|
|
1032
|
+
* activate(context) {
|
|
1033
|
+
* context.subscriptions.push(
|
|
1034
|
+
* context.hudhod.commands.registerCommand("acme.hello.greet", () =>
|
|
1035
|
+
* context.hudhod.window.showMessage("Hello!"),
|
|
1036
|
+
* ),
|
|
1037
|
+
* );
|
|
1038
|
+
* },
|
|
1039
|
+
* });
|
|
1040
|
+
* ```
|
|
1041
|
+
*/
|
|
1042
|
+
declare function defineExtension(extension: Extension): Extension;
|
|
1043
|
+
//#endregion
|
|
1044
|
+
export { type ActivationEvent, type ActiveEditor, type ApplyEditOptions, type ApplyEditResult, type CancellationToken, type CommandContribution, type CommandDescriptor, type CommandsApi, type Contributions, type CreateTerminalOptions, type DeleteOptions, type DiffApi, type DiffChange, type DiffOptions, type DiffStat, type DirectoryEntry, type Disposable, type EditHistoryEntry, type EditMode, type Event, type ExecOptions, type ExecResult, type Extension, type ExtensionContext, type ExtensionManifest, type FileChangeEvent, type FileChangeType, type FileEdit, type FileStat, type FileSystemApi, type FileType, type FindFilesOptions, type FindInFilesOptions, type HudhodApi, type HudhodError, type HudhodErrorCode, type InputBoxOptions, type KeybindingContribution, type KeybindingsApi, type MessageSeverity, type MoveOptions, type PanelContribution, type PanelLocation, type PanelRenderer, type ProcessApi, type ProcessHandle, type ProcessInfo, type ProcessStatus, type QuickPickItem, type QuickPickOptions, type Range, type RegisterCommandOptions, type RegisterPanelOptions, type RegisterViewOptions, type ResolvedKeybinding, type SearchApi, type SearchMatch, type SearchResult, type SpawnOptions, type Terminal, type TerminalApi, type TextEdit, type ViewContainerContribution, type ViewContribution, type WatchOptions, type WindowApi, type WorkspaceApi, type WriteFileOptions, defineExtension, isHudhodError };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Narrows an unknown caught value to a {@link HudhodError}.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* try {
|
|
8
|
+
* await hudhod.fs.readTextFile("/missing.ts");
|
|
9
|
+
* } catch (error) {
|
|
10
|
+
* if (isHudhodError(error) && error.code === "FileNotFound") {
|
|
11
|
+
* // handle it
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
function isHudhodError(value) {
|
|
17
|
+
return value instanceof Error && typeof value.code === "string";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/extension.ts
|
|
22
|
+
/**
|
|
23
|
+
* Declares an extension with full type inference.
|
|
24
|
+
*
|
|
25
|
+
* This is an identity function — it exists purely so the object literal is
|
|
26
|
+
* checked against {@link Extension} at the definition site rather than at the
|
|
27
|
+
* point of use.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* export default defineExtension({
|
|
32
|
+
* manifest: {
|
|
33
|
+
* id: "acme.hello",
|
|
34
|
+
* name: "Hello",
|
|
35
|
+
* version: "1.0.0",
|
|
36
|
+
* activationEvents: ["onCommand:acme.hello.greet"],
|
|
37
|
+
* contributes: {
|
|
38
|
+
* commands: [{ id: "acme.hello.greet", title: "Say Hello" }],
|
|
39
|
+
* },
|
|
40
|
+
* },
|
|
41
|
+
* activate(context) {
|
|
42
|
+
* context.subscriptions.push(
|
|
43
|
+
* context.hudhod.commands.registerCommand("acme.hello.greet", () =>
|
|
44
|
+
* context.hudhod.window.showMessage("Hello!"),
|
|
45
|
+
* ),
|
|
46
|
+
* );
|
|
47
|
+
* },
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
function defineExtension(extension) {
|
|
52
|
+
return extension;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { defineExtension, isHudhodError };
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hudhod/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Public API surface for building hudhod extensions — an in-browser IDE runtime powered by WebContainers.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"browser-ide",
|
|
7
|
+
"extensions",
|
|
8
|
+
"hudhod",
|
|
9
|
+
"ide",
|
|
10
|
+
"sdk",
|
|
11
|
+
"webcontainer"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsdown",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
}
|
|
33
|
+
}
|