@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/dist/react.cjs ADDED
@@ -0,0 +1,496 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/react.ts
7
+
8
+ // src/errors.ts
9
+ var GarretError = class _GarretError extends Error {
10
+ code;
11
+ hint;
12
+ constructor(code, message, options) {
13
+ super(message);
14
+ this.name = "GarretError";
15
+ this.code = code;
16
+ this.hint = options?.hint;
17
+ Object.setPrototypeOf(this, _GarretError.prototype);
18
+ }
19
+ };
20
+ function garretErrorFromWire(code, message, hint) {
21
+ const known = [
22
+ "BINARY_NOT_FOUND",
23
+ "NOT_FOUND",
24
+ "PERMISSION",
25
+ "UNAVAILABLE",
26
+ "BAD_ARGS",
27
+ "TIMEOUT",
28
+ "CANCELLED",
29
+ "NETWORK",
30
+ "INTERNAL"
31
+ ];
32
+ const c = known.includes(code) ? code : "INTERNAL";
33
+ return new GarretError(c, message, hint ? { hint } : void 0);
34
+ }
35
+
36
+ // src/client.ts
37
+ var MAX_PREATTACH_CHUNKS = 1e3;
38
+ var IDLE_TIMEOUT_MS = 3e4;
39
+ function createHostClient(transport, opts) {
40
+ const calls = /* @__PURE__ */ new Map();
41
+ const events = /* @__PURE__ */ new Map();
42
+ const eventBuffer = /* @__PURE__ */ new Map();
43
+ let seq = 0;
44
+ const nextId = () => `${opts.instanceId}:${++seq}`;
45
+ const settle = (id, fn) => {
46
+ const s = calls.get(id);
47
+ if (!s) return;
48
+ clearTimeout(s.timer);
49
+ calls.delete(id);
50
+ fn(s);
51
+ };
52
+ const timeoutError = (method) => new GarretError("TIMEOUT", `"${method}" timed out`);
53
+ const off = transport.onMessage((msg) => {
54
+ switch (msg.t) {
55
+ case "res":
56
+ settle(msg.id, (s) => s.resolve(msg.result));
57
+ break;
58
+ case "stream_end":
59
+ settle(msg.id, (s) => {
60
+ s.onEnd.forEach((cb) => cb(msg.result));
61
+ s.resolve(msg.result);
62
+ });
63
+ break;
64
+ case "err":
65
+ settle(msg.id, (s) => {
66
+ const e = garretErrorFromWire(msg.code, msg.message, msg.hint);
67
+ s.onError.forEach((cb) => cb(e));
68
+ s.reject(e);
69
+ });
70
+ break;
71
+ case "stream_err":
72
+ settle(msg.id, (s) => {
73
+ const e = garretErrorFromWire(msg.code, msg.message);
74
+ s.onError.forEach((cb) => cb(e));
75
+ s.reject(e);
76
+ });
77
+ break;
78
+ case "chunk": {
79
+ const s = calls.get(msg.id);
80
+ if (!s) break;
81
+ clearTimeout(s.timer);
82
+ s.timer = setTimeout(() => settle(msg.id, (st) => st.reject(timeoutError(msg.id))), IDLE_TIMEOUT_MS);
83
+ if (s.onData.length) s.onData.forEach((cb) => cb(msg.data));
84
+ else if (s.buffer.length < MAX_PREATTACH_CHUNKS) s.buffer.push(msg.data);
85
+ else s.overflow = true;
86
+ break;
87
+ }
88
+ case "event": {
89
+ const subs = events.get(msg.channel);
90
+ if (subs && subs.size) subs.forEach((cb) => cb(msg.payload));
91
+ else {
92
+ const buf = eventBuffer.get(msg.channel) ?? [];
93
+ if (buf.length < 100) buf.push(msg.payload);
94
+ eventBuffer.set(msg.channel, buf);
95
+ }
96
+ break;
97
+ }
98
+ }
99
+ });
100
+ function makeCall(method, args) {
101
+ const id = nextId();
102
+ let resolve;
103
+ let reject;
104
+ const promise = new Promise((res, rej) => {
105
+ resolve = res;
106
+ reject = rej;
107
+ });
108
+ promise.catch(() => {
109
+ });
110
+ const s = {
111
+ onData: [],
112
+ onEnd: [],
113
+ onError: [],
114
+ buffer: [],
115
+ overflow: false,
116
+ resolve,
117
+ reject,
118
+ timer: setTimeout(() => {
119
+ const e = timeoutError(method);
120
+ settle(id, (st) => {
121
+ st.onError.forEach((cb) => cb(e));
122
+ st.reject(e);
123
+ });
124
+ }, IDLE_TIMEOUT_MS)
125
+ };
126
+ calls.set(id, s);
127
+ transport.send({ t: "req", id, method, args });
128
+ const flush = (cb) => {
129
+ const buf = s.buffer;
130
+ s.buffer = [];
131
+ buf.forEach((c) => cb(c));
132
+ };
133
+ const call = {
134
+ then: (onF, onR) => promise.then(onF, onR),
135
+ catch: (onR) => promise.catch(onR),
136
+ finally: (cb) => promise.finally(cb),
137
+ result: () => promise,
138
+ onData(cb) {
139
+ s.onData.push(cb);
140
+ flush(cb);
141
+ return call;
142
+ },
143
+ onEnd(cb) {
144
+ s.onEnd.push(cb);
145
+ return call;
146
+ },
147
+ onError(cb) {
148
+ s.onError.push(cb);
149
+ return call;
150
+ },
151
+ cancel() {
152
+ if (!calls.has(id)) return;
153
+ clearTimeout(s.timer);
154
+ calls.delete(id);
155
+ transport.send({ t: "cancel", id });
156
+ }
157
+ };
158
+ return call;
159
+ }
160
+ const base = {
161
+ on(channel, cb) {
162
+ let set = events.get(channel);
163
+ if (!set) events.set(channel, set = /* @__PURE__ */ new Set());
164
+ set.add(cb);
165
+ const buffered = eventBuffer.get(channel);
166
+ if (buffered?.length) {
167
+ eventBuffer.delete(channel);
168
+ buffered.forEach((p) => cb(p));
169
+ }
170
+ return () => set.delete(cb);
171
+ },
172
+ dispose() {
173
+ off();
174
+ for (const s of calls.values()) clearTimeout(s.timer);
175
+ calls.clear();
176
+ events.clear();
177
+ eventBuffer.clear();
178
+ }
179
+ };
180
+ return new Proxy(base, {
181
+ get(target, prop) {
182
+ if (prop in target) return target[prop];
183
+ return (args) => makeCall(prop, args);
184
+ }
185
+ });
186
+ }
187
+
188
+ // src/platform.ts
189
+ function getRuntime() {
190
+ return typeof window !== "undefined" ? window.__garret : void 0;
191
+ }
192
+ function getHostTransport() {
193
+ return getRuntime()?.hostTransport ?? null;
194
+ }
195
+ function getInstanceId() {
196
+ return getRuntime()?.instanceId ?? "dev";
197
+ }
198
+ function nope() {
199
+ throw new GarretError("UNAVAILABLE", "not running inside Garret");
200
+ }
201
+ var stubStorage = { get: nope, set: nope, delete: nope, keys: nope, clear: nope };
202
+ var stubSecrets = { get: nope, set: nope, delete: nope };
203
+ function getGarret() {
204
+ const rt = getRuntime();
205
+ if (rt) return rt;
206
+ return {
207
+ inGarret: false,
208
+ storage: stubStorage,
209
+ instanceStorage: stubStorage,
210
+ secrets: stubSecrets,
211
+ shared: { storage: stubStorage, secrets: stubSecrets },
212
+ fetch: async () => {
213
+ throw new GarretError("UNAVAILABLE", "g.fetch is only available inside Garret");
214
+ },
215
+ service: () => ({ status: nope, query: nope }),
216
+ notify: () => {
217
+ },
218
+ openExternal: async () => false,
219
+ clipboard: { readText: nope, writeText: nope },
220
+ active: true,
221
+ onActiveChange: () => () => {
222
+ },
223
+ onOpenSettings: () => () => {
224
+ },
225
+ surfaces: { open: nope, onClosed: () => () => {
226
+ } },
227
+ window: { setAspectRatio: () => {
228
+ }, resize: () => {
229
+ }, close: () => {
230
+ } },
231
+ onReady: (cb) => {
232
+ cb({});
233
+ return () => {
234
+ };
235
+ }
236
+ };
237
+ }
238
+ function EmptyState({ children }) {
239
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "gx-empty", children });
240
+ }
241
+ function ErrorState({ children }) {
242
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "gx-error", children });
243
+ }
244
+ function Scroll({ children }) {
245
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "gx-scroll", children });
246
+ }
247
+ function Item({
248
+ leading,
249
+ trailing,
250
+ onClick,
251
+ onContextMenu,
252
+ children
253
+ }) {
254
+ const cls = `gx-item${onClick ? " gx-item--interactive" : ""}`;
255
+ const inner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
256
+ leading,
257
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "gx-item-content", children }),
258
+ trailing
259
+ ] });
260
+ return onClick ? /* @__PURE__ */ jsxRuntime.jsx("button", { className: cls, onClick, onContextMenu, children: inner }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls, onContextMenu, children: inner });
261
+ }
262
+ function Accordion({
263
+ title,
264
+ aside,
265
+ defaultOpen = true,
266
+ children
267
+ }) {
268
+ const [open, setOpen] = react.useState(defaultOpen);
269
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
270
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "gx-accordion-head", onClick: () => setOpen((o) => !o), children: [
271
+ /* @__PURE__ */ jsxRuntime.jsx(
272
+ "svg",
273
+ {
274
+ width: "11",
275
+ height: "11",
276
+ viewBox: "0 0 24 24",
277
+ fill: "none",
278
+ stroke: "currentColor",
279
+ strokeWidth: 2.5,
280
+ strokeLinecap: "round",
281
+ strokeLinejoin: "round",
282
+ style: { color: "var(--gx-text-3)", flexShrink: 0, transform: open ? "rotate(90deg)" : "", transition: "transform .12s" },
283
+ children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 6 15 12 9 18" })
284
+ }
285
+ ),
286
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "gx-accordion-title", children: title }),
287
+ aside != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "gx-accordion-aside", children: aside })
288
+ ] }),
289
+ open && /* @__PURE__ */ jsxRuntime.jsx("div", { children })
290
+ ] });
291
+ }
292
+ function Badge({ tone = "neutral", children }) {
293
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `gx-badge gx-badge--${tone}`, children });
294
+ }
295
+ function Dot({ tone = "neutral", title }) {
296
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `gx-dot gx-dot--${tone}`, title });
297
+ }
298
+ function SettingsPanel({ onDone, children }) {
299
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gx-form", children: [
300
+ children,
301
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gx-form-footer", children: [
302
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "gx-form-note", children: "Changes save automatically" }),
303
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "gx-btn", onClick: onDone, children: "Done" })
304
+ ] })
305
+ ] });
306
+ }
307
+ function FieldGroup({ label, children }) {
308
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gx-group-wrap", children: [
309
+ label != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "gx-group-label", children: label }),
310
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "gx-group", children })
311
+ ] });
312
+ }
313
+ function Field({ label, children }) {
314
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gx-field", children: [
315
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "gx-field-label", children: label }),
316
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "gx-field-control", children })
317
+ ] });
318
+ }
319
+ function TextInput({
320
+ value,
321
+ placeholder,
322
+ secret,
323
+ onCommit
324
+ }) {
325
+ return /* @__PURE__ */ jsxRuntime.jsx(
326
+ "input",
327
+ {
328
+ className: "gx-input",
329
+ type: secret ? "password" : "text",
330
+ defaultValue: value,
331
+ placeholder,
332
+ onBlur: (e) => onCommit(e.target.value),
333
+ onKeyDown: (e) => e.key === "Enter" && onCommit(e.target.value)
334
+ }
335
+ );
336
+ }
337
+ function NumberInput({ value, onCommit }) {
338
+ return /* @__PURE__ */ jsxRuntime.jsx(
339
+ "input",
340
+ {
341
+ className: "gx-input",
342
+ type: "number",
343
+ defaultValue: value == null ? "" : String(value),
344
+ onBlur: (e) => onCommit(Number(e.target.value) || 0)
345
+ }
346
+ );
347
+ }
348
+ function Select({
349
+ value,
350
+ options,
351
+ onChange
352
+ }) {
353
+ return /* @__PURE__ */ jsxRuntime.jsx("select", { className: "gx-select", value, onChange: (e) => onChange(e.target.value), children: options.map(([v, label]) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: v, children: label }, v)) });
354
+ }
355
+ function Switch({ on, onChange }) {
356
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { className: `gx-switch${on ? " gx-switch--on" : ""}`, role: "switch", "aria-checked": on, onClick: () => onChange(!on), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "gx-switch-knob" }) });
357
+ }
358
+
359
+ // src/react.ts
360
+ var singleton;
361
+ function hostClient() {
362
+ if (!singleton) {
363
+ const transport = getHostTransport();
364
+ if (!transport) throw new GarretError("UNAVAILABLE", "this extension has no host process");
365
+ singleton = createHostClient(transport, { instanceId: getInstanceId() });
366
+ }
367
+ return singleton;
368
+ }
369
+ function useHost() {
370
+ return react.useMemo(() => hostClient(), []);
371
+ }
372
+ function useGarret() {
373
+ return getGarret();
374
+ }
375
+ function useHostEvent(channel, handler, deps = []) {
376
+ react.useEffect(() => {
377
+ const off = hostClient().on(channel, handler);
378
+ return off;
379
+ }, [channel, ...deps]);
380
+ }
381
+ function useStream(factory, deps = [], opts) {
382
+ const enabled = opts?.enabled ?? true;
383
+ const [chunks, setChunks] = react.useState([]);
384
+ const [result, setResult] = react.useState(void 0);
385
+ const [error, setError] = react.useState(void 0);
386
+ const [status, setStatus] = react.useState("idle");
387
+ const ref = react.useRef(null);
388
+ react.useEffect(() => {
389
+ if (!enabled) {
390
+ setStatus("idle");
391
+ return;
392
+ }
393
+ setChunks([]);
394
+ setResult(void 0);
395
+ setError(void 0);
396
+ setStatus("streaming");
397
+ const call = factory();
398
+ ref.current = call;
399
+ call.onData((c) => setChunks((xs) => [...xs, c])).onEnd((r) => {
400
+ setResult(r);
401
+ setStatus("done");
402
+ }).onError((e) => {
403
+ setError(e);
404
+ setStatus("error");
405
+ });
406
+ return () => call.cancel();
407
+ }, [enabled, ...deps]);
408
+ const cancel = react.useCallback(() => ref.current?.cancel(), []);
409
+ return { chunks, result, error, status, cancel };
410
+ }
411
+ function useConfig() {
412
+ const rt = getRuntime();
413
+ const [cfg, setCfg] = react.useState(rt?.config.get() ?? {});
414
+ react.useEffect(() => rt?.config.subscribe((c) => setCfg(c)), [rt]);
415
+ const patch = react.useCallback((p) => rt?.config.set(p, false), [rt]);
416
+ const replace = react.useCallback((v) => rt?.config.set(v, true), [rt]);
417
+ return [cfg, patch, replace];
418
+ }
419
+ function useActive() {
420
+ const g = getGarret();
421
+ const [active, setActive] = react.useState(g.active);
422
+ react.useEffect(() => g.onActiveChange(setActive), [g]);
423
+ return active;
424
+ }
425
+ function useOpenSettings(cb) {
426
+ const g = getGarret();
427
+ const ref = react.useRef(cb);
428
+ ref.current = cb;
429
+ react.useEffect(() => g.onOpenSettings(() => ref.current()), [g]);
430
+ }
431
+ function useInstanceConfig(defaults) {
432
+ const g = getGarret();
433
+ const [cfg, setCfg] = react.useState(defaults);
434
+ const [loaded, setLoaded] = react.useState(false);
435
+ react.useEffect(() => {
436
+ let alive = true;
437
+ const unsub = g.onReady(() => {
438
+ void (async () => {
439
+ const saved = {};
440
+ await Promise.all(
441
+ Object.keys(defaults).map(async (k) => {
442
+ const v = await g.instanceStorage.get(k);
443
+ if (v !== void 0) saved[k] = v;
444
+ })
445
+ );
446
+ if (alive) {
447
+ setCfg({ ...defaults, ...saved });
448
+ setLoaded(true);
449
+ }
450
+ })();
451
+ });
452
+ return () => {
453
+ alive = false;
454
+ unsub();
455
+ };
456
+ }, []);
457
+ const set = react.useCallback(
458
+ (patch) => {
459
+ setCfg((c) => ({ ...c, ...patch }));
460
+ for (const [k, v] of Object.entries(patch)) void g.instanceStorage.set(k, v);
461
+ },
462
+ [g]
463
+ );
464
+ return { cfg, set, loaded };
465
+ }
466
+ function useProps() {
467
+ const g = getGarret();
468
+ const [props, setProps] = react.useState({});
469
+ react.useEffect(() => g.onReady((p) => setProps(p)), [g]);
470
+ return props;
471
+ }
472
+
473
+ exports.Accordion = Accordion;
474
+ exports.Badge = Badge;
475
+ exports.Dot = Dot;
476
+ exports.EmptyState = EmptyState;
477
+ exports.ErrorState = ErrorState;
478
+ exports.Field = Field;
479
+ exports.FieldGroup = FieldGroup;
480
+ exports.GarretError = GarretError;
481
+ exports.Item = Item;
482
+ exports.NumberInput = NumberInput;
483
+ exports.Scroll = Scroll;
484
+ exports.Select = Select;
485
+ exports.SettingsPanel = SettingsPanel;
486
+ exports.Switch = Switch;
487
+ exports.TextInput = TextInput;
488
+ exports.useActive = useActive;
489
+ exports.useConfig = useConfig;
490
+ exports.useGarret = useGarret;
491
+ exports.useHost = useHost;
492
+ exports.useHostEvent = useHostEvent;
493
+ exports.useInstanceConfig = useInstanceConfig;
494
+ exports.useOpenSettings = useOpenSettings;
495
+ exports.useProps = useProps;
496
+ exports.useStream = useStream;
@@ -0,0 +1,128 @@
1
+ import { G as GarretError, E as EventMap, H as HostClient, f as StreamCall } from './types-BxSYAH_H.cjs';
2
+ import { G as GarretPlatform } from './platform-DnI7gJTx.cjs';
3
+ export { S as SurfaceApi, a as SurfaceHandle, b as SurfaceOpenOptions, W as WindowControls } from './platform-DnI7gJTx.cjs';
4
+ import { ReactNode } from 'react';
5
+ import './protocol-Do0BJdeE.cjs';
6
+
7
+ /**
8
+ * Garret widget design system — GENERIC React building blocks that emit the classes the app's shared
9
+ * theme styles (`<link rel="stylesheet" href="~theme.css">`). No widget-specific components: these are
10
+ * primitives (rows, badges, accordions, a settings-form kit) that consumers compose into their own UI.
11
+ * Import from `@garretapp/sdk/react`.
12
+ */
13
+ type Tone = 'neutral' | 'accent' | 'success' | 'warning' | 'danger';
14
+ /** Centered muted message (supports rich children). */
15
+ declare function EmptyState({ children }: {
16
+ children: ReactNode;
17
+ }): JSX.Element;
18
+ /** Error message (in the danger color). */
19
+ declare function ErrorState({ children }: {
20
+ children: ReactNode;
21
+ }): JSX.Element;
22
+ /** A scrolling content region. */
23
+ declare function Scroll({ children }: {
24
+ children: ReactNode;
25
+ }): JSX.Element;
26
+ /** A generic list row: optional `leading` / `trailing` slots around the content. Interactive (hover +
27
+ * pointer) when `onClick` is given. Renders a <button> if clickable, else a <div>. */
28
+ declare function Item({ leading, trailing, onClick, onContextMenu, children }: {
29
+ leading?: ReactNode;
30
+ trailing?: ReactNode;
31
+ onClick?: () => void;
32
+ onContextMenu?: (e: React.MouseEvent) => void;
33
+ children: ReactNode;
34
+ }): JSX.Element;
35
+ /** A collapsible section: a header (title + optional `aside`, e.g. a count) and a rotating chevron. */
36
+ declare function Accordion({ title, aside, defaultOpen, children }: {
37
+ title: ReactNode;
38
+ aside?: ReactNode;
39
+ defaultOpen?: boolean;
40
+ children: ReactNode;
41
+ }): JSX.Element;
42
+ /** A small pill; `tone` sets the color. */
43
+ declare function Badge({ tone, children }: {
44
+ tone?: Tone;
45
+ children: ReactNode;
46
+ }): JSX.Element;
47
+ /** A small status dot; same tones. */
48
+ declare function Dot({ tone, title }: {
49
+ tone?: Tone;
50
+ title?: string;
51
+ }): JSX.Element;
52
+ /** The container a widget renders when the host opens its settings (see `useOpenSettings`). Adds a
53
+ * footer with a Done button. */
54
+ declare function SettingsPanel({ onDone, children }: {
55
+ onDone: () => void;
56
+ children: ReactNode;
57
+ }): JSX.Element;
58
+ /** An inset grouped container (System-Settings style). Group related Fields; `label` is optional. */
59
+ declare function FieldGroup({ label, children }: {
60
+ label?: ReactNode;
61
+ children: ReactNode;
62
+ }): JSX.Element;
63
+ declare function Field({ label, children }: {
64
+ label: ReactNode;
65
+ children: ReactNode;
66
+ }): JSX.Element;
67
+ /** Uncontrolled text input — commits on blur / Enter (so typing doesn't churn state per keystroke). */
68
+ declare function TextInput({ value, placeholder, secret, onCommit }: {
69
+ value?: string;
70
+ placeholder?: string;
71
+ secret?: boolean;
72
+ onCommit: (v: string) => void;
73
+ }): JSX.Element;
74
+ declare function NumberInput({ value, onCommit }: {
75
+ value?: number;
76
+ onCommit: (v: number) => void;
77
+ }): JSX.Element;
78
+ declare function Select({ value, options, onChange }: {
79
+ value: string;
80
+ options: [value: string, label: string][];
81
+ onChange: (v: string) => void;
82
+ }): JSX.Element;
83
+ declare function Switch({ on, onChange }: {
84
+ on: boolean;
85
+ onChange: (v: boolean) => void;
86
+ }): JSX.Element;
87
+
88
+ /** Typed proxy of your host's methods. Stream-vs-Promise is inferred from each method's `Api` return
89
+ * type — nothing to configure. */
90
+ declare function useHost<Api, Events extends EventMap = EventMap>(): HostClient<Api>;
91
+ /** Platform capabilities (storage/secrets/fetch/service/notify/…). Available in both tiers. */
92
+ declare function useGarret(): GarretPlatform;
93
+ /** Subscribe to a typed host event. `useHostEvent<Events, 'changed'>('changed', p => …)`. */
94
+ declare function useHostEvent<E extends EventMap, K extends keyof E & string>(channel: K, handler: (payload: E[K]) => void, deps?: unknown[]): void;
95
+ type StreamStatus = 'idle' | 'streaming' | 'done' | 'error';
96
+ interface UseStreamResult<Chunk, Result> {
97
+ chunks: Chunk[];
98
+ result: Result | undefined;
99
+ error: GarretError | undefined;
100
+ status: StreamStatus;
101
+ cancel: () => void;
102
+ }
103
+ /** Consume a stream in React without manual effect+cleanup. `deps` auto-cancel + restart; pass
104
+ * `{ enabled: false }` to defer (e.g. until the user triggers a run). */
105
+ declare function useStream<Chunk, Result = void>(factory: () => StreamCall<Chunk, Result>, deps?: unknown[], opts?: {
106
+ enabled?: boolean;
107
+ }): UseStreamResult<Chunk, Result>;
108
+ /** This placement's settings. `patch` shallow-merges; `replace` overwrites. */
109
+ declare function useConfig<T>(): [T, (patch: Partial<T>) => void, (value: T) => void];
110
+ /** Board activity — `false` when ambient/idle. Gate polling / rAF / animations on it. */
111
+ declare function useActive(): boolean;
112
+ /** Run `cb` when the host (frame ⋯→Settings) asks this widget to reveal its own config UI. */
113
+ declare function useOpenSettings(cb: () => void): void;
114
+ /** Per-placement config backed by `g.instanceStorage` (isolated per widget instance). Loads once the
115
+ * runtime binds (calls before bind reject); `set` writes through to storage + state. `loaded` gates
116
+ * the first data fetch so you don't fetch with defaults before the saved config arrives. */
117
+ declare function useInstanceConfig<T extends Record<string, unknown>>(defaults: T): {
118
+ cfg: T;
119
+ set: (patch: Partial<T>) => void;
120
+ loaded: boolean;
121
+ };
122
+
123
+ /** Launch props for a spawned surface window (`g.surfaces.open(..., { props })`). `{}` for the board
124
+ * surface. Delivered via `onReady`'s callback (a contextBridge getter would be frozen at exposure
125
+ * time), so this re-renders when the runtime binds. The `T` is an unchecked cast — validate it yourself. */
126
+ declare function useProps<T = Record<string, unknown>>(): T;
127
+
128
+ export { Accordion, Badge, Dot, EmptyState, ErrorState, Field, FieldGroup, GarretError, GarretPlatform, Item, NumberInput, Scroll, Select, SettingsPanel, type StreamStatus, Switch, TextInput, type Tone, type UseStreamResult, useActive, useConfig, useGarret, useHost, useHostEvent, useInstanceConfig, useOpenSettings, useProps, useStream };