@garretapp/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/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # @garretapp/sdk
2
+
3
+ Build widgets for **Garret** — the desktop widget layer. One SDK for a widget's UI (React hooks +
4
+ a native design system) and its optional host (raw Node).
5
+
6
+ ```bash
7
+ npm install @garretapp/sdk
8
+ ```
9
+
10
+ ## UI (React)
11
+
12
+ `@garretapp/sdk/react` gives you hooks to talk to Garret plus a generic component library that matches
13
+ the native macOS look. Link the app-served theme once and compose:
14
+
15
+ ```tsx
16
+ import { createRoot } from 'react-dom/client'
17
+ import {
18
+ useGarret, useActive, useInstanceConfig, useOpenSettings,
19
+ Scroll, Item, Accordion, Badge, Dot, EmptyState, ErrorState,
20
+ SettingsPanel, FieldGroup, Field, TextInput, Select, Switch
21
+ } from '@garretapp/sdk/react'
22
+
23
+ function Widget() {
24
+ const g = useGarret()
25
+ const { cfg, set, loaded } = useInstanceConfig({ query: '' })
26
+ // g.fetch (brokered), g.shared.storage/secrets, g.instanceStorage, g.openExternal, …
27
+ return <Scroll>{/* compose Item / Badge / Dot / … */}</Scroll>
28
+ }
29
+ createRoot(document.getElementById('root')!).render(<Widget />)
30
+ ```
31
+
32
+ ```html
33
+ <!-- served by Garret on your widget's own origin -->
34
+ <link rel="stylesheet" href="~theme.css" />
35
+ ```
36
+
37
+ **Hooks:** `useGarret`, `useActive`, `useOpenSettings`, `useInstanceConfig`, `useConfig`, `useProps`,
38
+ `useHost`, `useHostEvent`, `useStream`.
39
+
40
+ **Components (generic building blocks):** `Scroll`, `Item`, `Accordion`, `Badge`, `Dot`,
41
+ `EmptyState`, `ErrorState`, `SettingsPanel`, `FieldGroup`, `Field`, `TextInput`, `NumberInput`,
42
+ `Select`, `Switch`. Tones are generic — `neutral | accent | success | warning | danger` — you map
43
+ your domain to them.
44
+
45
+ `@garretapp/sdk/ui` exposes the same platform without React (`getGarret()`).
46
+
47
+ ## Host (Node)
48
+
49
+ `@garretapp/sdk/host` — `defineHost()` for widgets that ship a raw-Node backend (spawn processes,
50
+ native deps). Optional; pure-UI widgets don't need it.
51
+
52
+ ## Capabilities
53
+
54
+ A widget declares what it can reach in its `garret.manifest.json` (`network:<host>`, `secrets`,
55
+ `openExternal`, `embed`, `windows`, …); Garret enforces them in its main process.
56
+
57
+ MIT © Sudharsan Selvaraj
@@ -0,0 +1,29 @@
1
+ // src/errors.ts
2
+ var GarretError = class _GarretError extends Error {
3
+ code;
4
+ hint;
5
+ constructor(code, message, options) {
6
+ super(message);
7
+ this.name = "GarretError";
8
+ this.code = code;
9
+ this.hint = options?.hint;
10
+ Object.setPrototypeOf(this, _GarretError.prototype);
11
+ }
12
+ };
13
+ function garretErrorFromWire(code, message, hint) {
14
+ const known = [
15
+ "BINARY_NOT_FOUND",
16
+ "NOT_FOUND",
17
+ "PERMISSION",
18
+ "UNAVAILABLE",
19
+ "BAD_ARGS",
20
+ "TIMEOUT",
21
+ "CANCELLED",
22
+ "NETWORK",
23
+ "INTERNAL"
24
+ ];
25
+ const c = known.includes(code) ? code : "INTERNAL";
26
+ return new GarretError(c, message, hint ? { hint } : void 0);
27
+ }
28
+
29
+ export { GarretError, garretErrorFromWire };
@@ -0,0 +1,9 @@
1
+ // src/types.ts
2
+ function defineManifest(manifest) {
3
+ return manifest;
4
+ }
5
+ function defineConfig(schema) {
6
+ return schema;
7
+ }
8
+
9
+ export { defineConfig, defineManifest };
@@ -0,0 +1,206 @@
1
+ import { GarretError, garretErrorFromWire } from './chunk-ENIDFSMM.js';
2
+
3
+ // src/client.ts
4
+ var MAX_PREATTACH_CHUNKS = 1e3;
5
+ var IDLE_TIMEOUT_MS = 3e4;
6
+ function createHostClient(transport, opts) {
7
+ const calls = /* @__PURE__ */ new Map();
8
+ const events = /* @__PURE__ */ new Map();
9
+ const eventBuffer = /* @__PURE__ */ new Map();
10
+ let seq = 0;
11
+ const nextId = () => `${opts.instanceId}:${++seq}`;
12
+ const settle = (id, fn) => {
13
+ const s = calls.get(id);
14
+ if (!s) return;
15
+ clearTimeout(s.timer);
16
+ calls.delete(id);
17
+ fn(s);
18
+ };
19
+ const timeoutError = (method) => new GarretError("TIMEOUT", `"${method}" timed out`);
20
+ const off = transport.onMessage((msg) => {
21
+ switch (msg.t) {
22
+ case "res":
23
+ settle(msg.id, (s) => s.resolve(msg.result));
24
+ break;
25
+ case "stream_end":
26
+ settle(msg.id, (s) => {
27
+ s.onEnd.forEach((cb) => cb(msg.result));
28
+ s.resolve(msg.result);
29
+ });
30
+ break;
31
+ case "err":
32
+ settle(msg.id, (s) => {
33
+ const e = garretErrorFromWire(msg.code, msg.message, msg.hint);
34
+ s.onError.forEach((cb) => cb(e));
35
+ s.reject(e);
36
+ });
37
+ break;
38
+ case "stream_err":
39
+ settle(msg.id, (s) => {
40
+ const e = garretErrorFromWire(msg.code, msg.message);
41
+ s.onError.forEach((cb) => cb(e));
42
+ s.reject(e);
43
+ });
44
+ break;
45
+ case "chunk": {
46
+ const s = calls.get(msg.id);
47
+ if (!s) break;
48
+ clearTimeout(s.timer);
49
+ s.timer = setTimeout(() => settle(msg.id, (st) => st.reject(timeoutError(msg.id))), IDLE_TIMEOUT_MS);
50
+ if (s.onData.length) s.onData.forEach((cb) => cb(msg.data));
51
+ else if (s.buffer.length < MAX_PREATTACH_CHUNKS) s.buffer.push(msg.data);
52
+ else s.overflow = true;
53
+ break;
54
+ }
55
+ case "event": {
56
+ const subs = events.get(msg.channel);
57
+ if (subs && subs.size) subs.forEach((cb) => cb(msg.payload));
58
+ else {
59
+ const buf = eventBuffer.get(msg.channel) ?? [];
60
+ if (buf.length < 100) buf.push(msg.payload);
61
+ eventBuffer.set(msg.channel, buf);
62
+ }
63
+ break;
64
+ }
65
+ }
66
+ });
67
+ function makeCall(method, args) {
68
+ const id = nextId();
69
+ let resolve;
70
+ let reject;
71
+ const promise = new Promise((res, rej) => {
72
+ resolve = res;
73
+ reject = rej;
74
+ });
75
+ promise.catch(() => {
76
+ });
77
+ const s = {
78
+ onData: [],
79
+ onEnd: [],
80
+ onError: [],
81
+ buffer: [],
82
+ overflow: false,
83
+ resolve,
84
+ reject,
85
+ timer: setTimeout(() => {
86
+ const e = timeoutError(method);
87
+ settle(id, (st) => {
88
+ st.onError.forEach((cb) => cb(e));
89
+ st.reject(e);
90
+ });
91
+ }, IDLE_TIMEOUT_MS)
92
+ };
93
+ calls.set(id, s);
94
+ transport.send({ t: "req", id, method, args });
95
+ const flush = (cb) => {
96
+ const buf = s.buffer;
97
+ s.buffer = [];
98
+ buf.forEach((c) => cb(c));
99
+ };
100
+ const call = {
101
+ then: (onF, onR) => promise.then(onF, onR),
102
+ catch: (onR) => promise.catch(onR),
103
+ finally: (cb) => promise.finally(cb),
104
+ result: () => promise,
105
+ onData(cb) {
106
+ s.onData.push(cb);
107
+ flush(cb);
108
+ return call;
109
+ },
110
+ onEnd(cb) {
111
+ s.onEnd.push(cb);
112
+ return call;
113
+ },
114
+ onError(cb) {
115
+ s.onError.push(cb);
116
+ return call;
117
+ },
118
+ cancel() {
119
+ if (!calls.has(id)) return;
120
+ clearTimeout(s.timer);
121
+ calls.delete(id);
122
+ transport.send({ t: "cancel", id });
123
+ }
124
+ };
125
+ return call;
126
+ }
127
+ const base = {
128
+ on(channel, cb) {
129
+ let set = events.get(channel);
130
+ if (!set) events.set(channel, set = /* @__PURE__ */ new Set());
131
+ set.add(cb);
132
+ const buffered = eventBuffer.get(channel);
133
+ if (buffered?.length) {
134
+ eventBuffer.delete(channel);
135
+ buffered.forEach((p) => cb(p));
136
+ }
137
+ return () => set.delete(cb);
138
+ },
139
+ dispose() {
140
+ off();
141
+ for (const s of calls.values()) clearTimeout(s.timer);
142
+ calls.clear();
143
+ events.clear();
144
+ eventBuffer.clear();
145
+ }
146
+ };
147
+ return new Proxy(base, {
148
+ get(target, prop) {
149
+ if (prop in target) return target[prop];
150
+ return (args) => makeCall(prop, args);
151
+ }
152
+ });
153
+ }
154
+
155
+ // src/platform.ts
156
+ function getRuntime() {
157
+ return typeof window !== "undefined" ? window.__garret : void 0;
158
+ }
159
+ function getHostTransport() {
160
+ return getRuntime()?.hostTransport ?? null;
161
+ }
162
+ function getInstanceId() {
163
+ return getRuntime()?.instanceId ?? "dev";
164
+ }
165
+ function nope() {
166
+ throw new GarretError("UNAVAILABLE", "not running inside Garret");
167
+ }
168
+ var stubStorage = { get: nope, set: nope, delete: nope, keys: nope, clear: nope };
169
+ var stubSecrets = { get: nope, set: nope, delete: nope };
170
+ function getGarret() {
171
+ const rt = getRuntime();
172
+ if (rt) return rt;
173
+ return {
174
+ inGarret: false,
175
+ storage: stubStorage,
176
+ instanceStorage: stubStorage,
177
+ secrets: stubSecrets,
178
+ shared: { storage: stubStorage, secrets: stubSecrets },
179
+ fetch: async () => {
180
+ throw new GarretError("UNAVAILABLE", "g.fetch is only available inside Garret");
181
+ },
182
+ service: () => ({ status: nope, query: nope }),
183
+ notify: () => {
184
+ },
185
+ openExternal: async () => false,
186
+ clipboard: { readText: nope, writeText: nope },
187
+ active: true,
188
+ onActiveChange: () => () => {
189
+ },
190
+ onOpenSettings: () => () => {
191
+ },
192
+ surfaces: { open: nope, onClosed: () => () => {
193
+ } },
194
+ window: { setAspectRatio: () => {
195
+ }, resize: () => {
196
+ }, close: () => {
197
+ } },
198
+ onReady: (cb) => {
199
+ cb({});
200
+ return () => {
201
+ };
202
+ }
203
+ };
204
+ }
205
+
206
+ export { createHostClient, getGarret, getHostTransport, getInstanceId, getRuntime };
package/dist/host.cjs ADDED
@@ -0,0 +1,263 @@
1
+ 'use strict';
2
+
3
+ var child_process = require('child_process');
4
+ var fs = require('fs');
5
+ var path = require('path');
6
+ var crypto = require('crypto');
7
+
8
+ // src/host.ts
9
+
10
+ // src/errors.ts
11
+ var GarretError = class _GarretError extends Error {
12
+ code;
13
+ hint;
14
+ constructor(code, message, options) {
15
+ super(message);
16
+ this.name = "GarretError";
17
+ this.code = code;
18
+ this.hint = options?.hint;
19
+ Object.setPrototypeOf(this, _GarretError.prototype);
20
+ }
21
+ };
22
+
23
+ // src/host.ts
24
+ var port = globalThis.process?.parentPort;
25
+ function send(msg) {
26
+ port?.postMessage(msg);
27
+ }
28
+ var STREAM_RUNTIME = /* @__PURE__ */ Symbol("garret.stream");
29
+ function isStream(v) {
30
+ return typeof v === "object" && v !== null && STREAM_RUNTIME in v;
31
+ }
32
+ function defineHost(factory) {
33
+ const children = /* @__PURE__ */ new Set();
34
+ const disposers = [];
35
+ const streams = /* @__PURE__ */ new Map();
36
+ let disposed = false;
37
+ const scrubbedEnv = (base) => {
38
+ const env = {};
39
+ for (const [k, v] of Object.entries(base)) if (!k.startsWith("GARRET_")) env[k] = v;
40
+ return env;
41
+ };
42
+ const track = (child) => {
43
+ children.add(child);
44
+ child.on("exit", () => children.delete(child));
45
+ const r = child;
46
+ r.text = () => new Promise((resolve, reject) => {
47
+ let out = "";
48
+ child.stdout?.on("data", (d) => out += d.toString());
49
+ child.on("error", reject);
50
+ child.on("close", () => resolve(out));
51
+ });
52
+ return r;
53
+ };
54
+ const ctx = {
55
+ emit: (channel, payload) => send({ t: "event", channel, payload }),
56
+ stream: (fn) => ({ [STREAM_RUNTIME]: fn }),
57
+ // Scrub GARRET_* from the FINAL merged env, always — a caller-supplied opts.env must never
58
+ // reopen the leak (the vault key would otherwise reach adb/scrcpy/shells the author didn't write).
59
+ spawn: (argv, opts) => track(
60
+ child_process.spawn(argv[0], argv.slice(1), {
61
+ ...opts,
62
+ shell: false,
63
+ env: scrubbedEnv({ ...process.env, ...opts?.env ?? {} })
64
+ })
65
+ ),
66
+ spawnShell: (command, opts) => track(child_process.spawn(command, { ...opts, shell: true, env: scrubbedEnv({ ...process.env, ...opts?.env ?? {} }) })),
67
+ resolveBinary,
68
+ storage: makeStorage(dataDir),
69
+ secrets: makeSecrets(dataDir, secretKey),
70
+ // Pack-shared store only when the host was launched with GARRET_PACK_SHARED_DIR (pack declares `shared`).
71
+ shared: process.env.GARRET_PACK_SHARED_DIR ? { storage: makeStorage(sharedDir), secrets: makeSecrets(sharedDir, sharedSecretKey) } : void 0,
72
+ fetch: (...a) => fetch(...a),
73
+ onDispose: (cb) => disposers.push(cb),
74
+ log: (...args) => console.error("[host]", ...args)
75
+ };
76
+ async function runDispose() {
77
+ if (disposed) return;
78
+ disposed = true;
79
+ for (const ac of streams.values()) ac.abort();
80
+ for (const cb of disposers) {
81
+ try {
82
+ await cb();
83
+ } catch {
84
+ }
85
+ }
86
+ for (const c of children) c.kill();
87
+ process.exit(0);
88
+ }
89
+ function driveStream(id, fn) {
90
+ const ac = new AbortController();
91
+ streams.set(id, ac);
92
+ const done = (final) => {
93
+ if (!streams.has(id)) return;
94
+ streams.delete(id);
95
+ final();
96
+ };
97
+ const out = {
98
+ push: (chunk) => streams.has(id) && send({ t: "chunk", id, data: chunk }),
99
+ end: (result) => done(() => send({ t: "stream_end", id, result })),
100
+ error: (err) => done(() => send({ t: "stream_err", id, ...wireError(err) }))
101
+ };
102
+ Promise.resolve().then(() => fn(out, ac.signal)).catch((err) => out.error(err));
103
+ }
104
+ async function handleCall(id, method, args) {
105
+ const methods = ready;
106
+ const fn = methods?.[method];
107
+ if (typeof fn !== "function") {
108
+ return send({ t: "err", id, code: "BAD_ARGS", message: `unknown method: ${method}` });
109
+ }
110
+ try {
111
+ const result = await fn(args);
112
+ if (isStream(result)) driveStream(id, result[STREAM_RUNTIME]);
113
+ else send({ t: "res", id, result });
114
+ } catch (err) {
115
+ send({ t: "err", id, ...wireError(err) });
116
+ }
117
+ }
118
+ let ready;
119
+ port?.on("message", (e) => {
120
+ const msg = e.data;
121
+ if (!msg || typeof msg !== "object") return;
122
+ switch (msg.t) {
123
+ case "req":
124
+ case "stream_start":
125
+ void handleCall(msg.id, msg.method, msg.args);
126
+ break;
127
+ case "cancel":
128
+ streams.get(msg.id)?.abort();
129
+ streams.delete(msg.id);
130
+ break;
131
+ case "dispose":
132
+ void runDispose();
133
+ break;
134
+ }
135
+ });
136
+ process.on("SIGTERM", () => void runDispose());
137
+ Promise.resolve().then(() => factory(ctx)).then((methods) => {
138
+ ready = methods;
139
+ send({ t: "ready" });
140
+ }).catch((err) => {
141
+ console.error("[host] factory failed during startup:", err);
142
+ process.exit(1);
143
+ });
144
+ }
145
+ function wireError(err) {
146
+ if (err instanceof GarretError) return { code: err.code, message: err.message, hint: err.hint };
147
+ if (err instanceof Error && err.name === "AbortError") return { code: "CANCELLED", message: err.message };
148
+ return { code: "INTERNAL", message: err instanceof Error ? err.message : String(err) };
149
+ }
150
+ var PROBE = {
151
+ darwin: ["/opt/homebrew/bin", "/usr/local/bin", "/opt/homebrew/sbin"],
152
+ linux: ["/usr/local/bin", "/usr/bin", "/snap/bin", `${process.env.HOME}/.local/bin`],
153
+ win32: [`${process.env.LOCALAPPDATA}\\Microsoft\\WinGet\\Packages`, "C:\\ProgramData\\chocolatey\\bin"]
154
+ };
155
+ async function resolveBinary(name, opts) {
156
+ const platform = process.platform;
157
+ const exe = platform === "win32" ? `${name}.exe` : name;
158
+ const dirs = [...process.env.PATH?.split(path.delimiter) ?? [], ...PROBE[platform] ?? []];
159
+ for (const dir of dirs) {
160
+ if (!dir || dir.includes("undefined")) continue;
161
+ const full = path.join(dir, exe);
162
+ try {
163
+ fs.accessSync(full, fs.constants.X_OK);
164
+ return full;
165
+ } catch {
166
+ }
167
+ }
168
+ throw new GarretError("BINARY_NOT_FOUND", `${name} not found on PATH`, {
169
+ hint: opts?.hint ?? `install ${name} via your package manager`
170
+ });
171
+ }
172
+ function dirFromEnv(name) {
173
+ const dir = process.env[name];
174
+ if (!dir) throw new GarretError("UNAVAILABLE", "storage/secrets unavailable (no data dir)");
175
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
176
+ return dir;
177
+ }
178
+ var dataDir = () => dirFromEnv("GARRET_EXT_DATA_DIR");
179
+ var sharedDir = () => dirFromEnv("GARRET_PACK_SHARED_DIR");
180
+ function readJson(file) {
181
+ try {
182
+ return JSON.parse(fs.readFileSync(file, "utf8"));
183
+ } catch {
184
+ return {};
185
+ }
186
+ }
187
+ function writeJsonAtomic(file, obj) {
188
+ const tmp = `${file}.${crypto.randomBytes(4).toString("hex")}.tmp`;
189
+ fs.writeFileSync(tmp, JSON.stringify(obj));
190
+ fs.renameSync(tmp, file);
191
+ }
192
+ function makeChain() {
193
+ let chain = Promise.resolve();
194
+ return (fn) => {
195
+ const next = chain.then(fn, fn);
196
+ chain = next.catch(() => void 0);
197
+ return next;
198
+ };
199
+ }
200
+ function makeStorage(dir) {
201
+ const queue = makeChain();
202
+ const file = () => path.join(dir(), "storage.json");
203
+ return {
204
+ get: async (key) => readJson(file())[key],
205
+ set: (key, value) => queue(() => {
206
+ const f = file();
207
+ const all = readJson(f);
208
+ all[key] = value;
209
+ writeJsonAtomic(f, all);
210
+ }),
211
+ delete: (key) => queue(() => {
212
+ const f = file();
213
+ const all = readJson(f);
214
+ delete all[key];
215
+ writeJsonAtomic(f, all);
216
+ }),
217
+ keys: async () => Object.keys(readJson(file())),
218
+ clear: () => queue(() => writeJsonAtomic(file(), {}))
219
+ };
220
+ }
221
+ function keyFromEnv(name) {
222
+ const hex = process.env[name];
223
+ if (!hex) throw new GarretError("UNAVAILABLE", "secrets unavailable on this platform");
224
+ return Buffer.from(hex, "hex");
225
+ }
226
+ var secretKey = () => keyFromEnv("GARRET_EXT_SECRET_KEY");
227
+ var sharedSecretKey = () => keyFromEnv("GARRET_PACK_SHARED_KEY");
228
+ function makeSecrets(dir, getKey) {
229
+ const queue = makeChain();
230
+ const file = () => path.join(dir(), "secrets.json");
231
+ return {
232
+ get: async (key) => {
233
+ const box = readJson(file())[key];
234
+ if (!box) return void 0;
235
+ const decipher = crypto.createDecipheriv("aes-256-gcm", getKey(), Buffer.from(box.iv, "base64"));
236
+ decipher.setAuthTag(Buffer.from(box.tag, "base64"));
237
+ try {
238
+ return decipher.update(Buffer.from(box.ct, "base64")).toString("utf8") + decipher.final("utf8");
239
+ } catch {
240
+ throw new GarretError("INTERNAL", `secret "${key}" failed to decrypt`);
241
+ }
242
+ },
243
+ set: (key, value) => queue(() => {
244
+ const iv = crypto.randomBytes(12);
245
+ const cipher = crypto.createCipheriv("aes-256-gcm", getKey(), iv);
246
+ const ct = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
247
+ const box = { v: 1, iv: iv.toString("base64"), tag: cipher.getAuthTag().toString("base64"), ct: ct.toString("base64") };
248
+ const f = file();
249
+ const all = readJson(f);
250
+ all[key] = box;
251
+ writeJsonAtomic(f, all);
252
+ }),
253
+ delete: (key) => queue(() => {
254
+ const f = file();
255
+ const all = readJson(f);
256
+ delete all[key];
257
+ writeJsonAtomic(f, all);
258
+ })
259
+ };
260
+ }
261
+
262
+ exports.GarretError = GarretError;
263
+ exports.defineHost = defineHost;
@@ -0,0 +1,54 @@
1
+ import { SpawnOptions, ChildProcess } from 'node:child_process';
2
+ import { E as EventMap, S as Stream } from './types-BxSYAH_H.cjs';
3
+ export { G as GarretError } from './types-BxSYAH_H.cjs';
4
+
5
+ interface StreamOut<Chunk, Result> {
6
+ push(chunk: Chunk): void;
7
+ end(result: Result): void;
8
+ error(err: unknown): void;
9
+ }
10
+ type StreamFn<Chunk, Result> = (out: StreamOut<Chunk, Result>, signal: AbortSignal) => void | Promise<void>;
11
+ /** A ChildProcess with a convenience `.text()` (buffers stdout to a string). */
12
+ interface SpawnResult extends ChildProcess {
13
+ text(): Promise<string>;
14
+ }
15
+ interface Storage {
16
+ get<T = unknown>(key: string): Promise<T | undefined>;
17
+ set(key: string, value: unknown): Promise<void>;
18
+ delete(key: string): Promise<void>;
19
+ keys(): Promise<string[]>;
20
+ clear(): Promise<void>;
21
+ }
22
+ interface Secrets {
23
+ get(key: string): Promise<string | undefined>;
24
+ set(key: string, value: string): Promise<void>;
25
+ delete(key: string): Promise<void>;
26
+ }
27
+ interface HostContext<Events extends EventMap = EventMap> {
28
+ emit<K extends keyof Events & string>(channel: K, payload: Events[K]): void;
29
+ stream<Chunk, Result = void>(fn: StreamFn<Chunk, Result>): Stream<Chunk, Result>;
30
+ spawn(argv: string[], opts?: SpawnOptions): SpawnResult;
31
+ spawnShell(command: string, opts?: SpawnOptions): SpawnResult;
32
+ resolveBinary(name: string, opts?: {
33
+ hint?: string;
34
+ }): Promise<string>;
35
+ storage: Storage;
36
+ secrets: Secrets;
37
+ /** Pack-shared storage/secrets — present ONLY when the pack declares a `shared` namespace. All the
38
+ * pack's widgets read/write the same store (e.g. one account token); isolated from other packs. */
39
+ shared?: {
40
+ storage: Storage;
41
+ secrets: Secrets;
42
+ };
43
+ fetch: typeof fetch;
44
+ onDispose(cb: () => void | Promise<void>): void;
45
+ log(...args: unknown[]): void;
46
+ }
47
+ type Methods<Api> = Record<keyof Api, (args?: any) => any>;
48
+ /**
49
+ * Define your host. `factory(ctx)` returns the methods your UI calls (use function declarations so
50
+ * a method can call a sibling — see docs P9). May be async; `ready` is sent only after it resolves.
51
+ */
52
+ declare function defineHost<Api extends Methods<Api>, Events extends EventMap = EventMap>(factory: (ctx: HostContext<Events>) => Api | Promise<Api>): void;
53
+
54
+ export { type HostContext, type Secrets, type SpawnResult, type Storage, Stream, type StreamFn, type StreamOut, defineHost };
package/dist/host.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import { SpawnOptions, ChildProcess } from 'node:child_process';
2
+ import { E as EventMap, S as Stream } from './types-BxSYAH_H.js';
3
+ export { G as GarretError } from './types-BxSYAH_H.js';
4
+
5
+ interface StreamOut<Chunk, Result> {
6
+ push(chunk: Chunk): void;
7
+ end(result: Result): void;
8
+ error(err: unknown): void;
9
+ }
10
+ type StreamFn<Chunk, Result> = (out: StreamOut<Chunk, Result>, signal: AbortSignal) => void | Promise<void>;
11
+ /** A ChildProcess with a convenience `.text()` (buffers stdout to a string). */
12
+ interface SpawnResult extends ChildProcess {
13
+ text(): Promise<string>;
14
+ }
15
+ interface Storage {
16
+ get<T = unknown>(key: string): Promise<T | undefined>;
17
+ set(key: string, value: unknown): Promise<void>;
18
+ delete(key: string): Promise<void>;
19
+ keys(): Promise<string[]>;
20
+ clear(): Promise<void>;
21
+ }
22
+ interface Secrets {
23
+ get(key: string): Promise<string | undefined>;
24
+ set(key: string, value: string): Promise<void>;
25
+ delete(key: string): Promise<void>;
26
+ }
27
+ interface HostContext<Events extends EventMap = EventMap> {
28
+ emit<K extends keyof Events & string>(channel: K, payload: Events[K]): void;
29
+ stream<Chunk, Result = void>(fn: StreamFn<Chunk, Result>): Stream<Chunk, Result>;
30
+ spawn(argv: string[], opts?: SpawnOptions): SpawnResult;
31
+ spawnShell(command: string, opts?: SpawnOptions): SpawnResult;
32
+ resolveBinary(name: string, opts?: {
33
+ hint?: string;
34
+ }): Promise<string>;
35
+ storage: Storage;
36
+ secrets: Secrets;
37
+ /** Pack-shared storage/secrets — present ONLY when the pack declares a `shared` namespace. All the
38
+ * pack's widgets read/write the same store (e.g. one account token); isolated from other packs. */
39
+ shared?: {
40
+ storage: Storage;
41
+ secrets: Secrets;
42
+ };
43
+ fetch: typeof fetch;
44
+ onDispose(cb: () => void | Promise<void>): void;
45
+ log(...args: unknown[]): void;
46
+ }
47
+ type Methods<Api> = Record<keyof Api, (args?: any) => any>;
48
+ /**
49
+ * Define your host. `factory(ctx)` returns the methods your UI calls (use function declarations so
50
+ * a method can call a sibling — see docs P9). May be async; `ready` is sent only after it resolves.
51
+ */
52
+ declare function defineHost<Api extends Methods<Api>, Events extends EventMap = EventMap>(factory: (ctx: HostContext<Events>) => Api | Promise<Api>): void;
53
+
54
+ export { type HostContext, type Secrets, type SpawnResult, type Storage, Stream, type StreamFn, type StreamOut, defineHost };