@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 +57 -0
- package/dist/chunk-ENIDFSMM.js +29 -0
- package/dist/chunk-RZJO3X6O.js +9 -0
- package/dist/chunk-ZJYHRC5C.js +206 -0
- package/dist/host.cjs +263 -0
- package/dist/host.d.cts +54 -0
- package/dist/host.d.ts +54 -0
- package/dist/host.js +246 -0
- package/dist/index.cjs +77 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +37 -0
- package/dist/platform-CZHtdUK0.d.ts +154 -0
- package/dist/platform-DnI7gJTx.d.cts +154 -0
- package/dist/protocol-Do0BJdeE.d.cts +64 -0
- package/dist/protocol-Do0BJdeE.d.ts +64 -0
- package/dist/react.cjs +496 -0
- package/dist/react.d.cts +128 -0
- package/dist/react.d.ts +128 -0
- package/dist/react.js +242 -0
- package/dist/types-BxSYAH_H.d.cts +88 -0
- package/dist/types-BxSYAH_H.d.ts +88 -0
- package/dist/ui.cjs +249 -0
- package/dist/ui.d.cts +23 -0
- package/dist/ui.d.ts +23 -0
- package/dist/ui.js +3 -0
- package/package.json +37 -0
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { G as GarretError, E as EventMap, H as HostClient, f as StreamCall } from './types-BxSYAH_H.js';
|
|
2
|
+
import { G as GarretPlatform } from './platform-CZHtdUK0.js';
|
|
3
|
+
export { S as SurfaceApi, a as SurfaceHandle, b as SurfaceOpenOptions, W as WindowControls } from './platform-CZHtdUK0.js';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
import './protocol-Do0BJdeE.js';
|
|
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 };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { getGarret, getRuntime, getHostTransport, createHostClient, getInstanceId } from './chunk-ZJYHRC5C.js';
|
|
2
|
+
import { GarretError } from './chunk-ENIDFSMM.js';
|
|
3
|
+
export { GarretError } from './chunk-ENIDFSMM.js';
|
|
4
|
+
import { useState, useMemo, useEffect, useRef, useCallback } from 'react';
|
|
5
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function EmptyState({ children }) {
|
|
8
|
+
return /* @__PURE__ */ jsx("div", { className: "gx-empty", children });
|
|
9
|
+
}
|
|
10
|
+
function ErrorState({ children }) {
|
|
11
|
+
return /* @__PURE__ */ jsx("div", { className: "gx-error", children });
|
|
12
|
+
}
|
|
13
|
+
function Scroll({ children }) {
|
|
14
|
+
return /* @__PURE__ */ jsx("div", { className: "gx-scroll", children });
|
|
15
|
+
}
|
|
16
|
+
function Item({
|
|
17
|
+
leading,
|
|
18
|
+
trailing,
|
|
19
|
+
onClick,
|
|
20
|
+
onContextMenu,
|
|
21
|
+
children
|
|
22
|
+
}) {
|
|
23
|
+
const cls = `gx-item${onClick ? " gx-item--interactive" : ""}`;
|
|
24
|
+
const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
25
|
+
leading,
|
|
26
|
+
/* @__PURE__ */ jsx("span", { className: "gx-item-content", children }),
|
|
27
|
+
trailing
|
|
28
|
+
] });
|
|
29
|
+
return onClick ? /* @__PURE__ */ jsx("button", { className: cls, onClick, onContextMenu, children: inner }) : /* @__PURE__ */ jsx("div", { className: cls, onContextMenu, children: inner });
|
|
30
|
+
}
|
|
31
|
+
function Accordion({
|
|
32
|
+
title,
|
|
33
|
+
aside,
|
|
34
|
+
defaultOpen = true,
|
|
35
|
+
children
|
|
36
|
+
}) {
|
|
37
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
38
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
39
|
+
/* @__PURE__ */ jsxs("button", { className: "gx-accordion-head", onClick: () => setOpen((o) => !o), children: [
|
|
40
|
+
/* @__PURE__ */ jsx(
|
|
41
|
+
"svg",
|
|
42
|
+
{
|
|
43
|
+
width: "11",
|
|
44
|
+
height: "11",
|
|
45
|
+
viewBox: "0 0 24 24",
|
|
46
|
+
fill: "none",
|
|
47
|
+
stroke: "currentColor",
|
|
48
|
+
strokeWidth: 2.5,
|
|
49
|
+
strokeLinecap: "round",
|
|
50
|
+
strokeLinejoin: "round",
|
|
51
|
+
style: { color: "var(--gx-text-3)", flexShrink: 0, transform: open ? "rotate(90deg)" : "", transition: "transform .12s" },
|
|
52
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "9 6 15 12 9 18" })
|
|
53
|
+
}
|
|
54
|
+
),
|
|
55
|
+
/* @__PURE__ */ jsx("span", { className: "gx-accordion-title", children: title }),
|
|
56
|
+
aside != null && /* @__PURE__ */ jsx("span", { className: "gx-accordion-aside", children: aside })
|
|
57
|
+
] }),
|
|
58
|
+
open && /* @__PURE__ */ jsx("div", { children })
|
|
59
|
+
] });
|
|
60
|
+
}
|
|
61
|
+
function Badge({ tone = "neutral", children }) {
|
|
62
|
+
return /* @__PURE__ */ jsx("span", { className: `gx-badge gx-badge--${tone}`, children });
|
|
63
|
+
}
|
|
64
|
+
function Dot({ tone = "neutral", title }) {
|
|
65
|
+
return /* @__PURE__ */ jsx("span", { className: `gx-dot gx-dot--${tone}`, title });
|
|
66
|
+
}
|
|
67
|
+
function SettingsPanel({ onDone, children }) {
|
|
68
|
+
return /* @__PURE__ */ jsxs("div", { className: "gx-form", children: [
|
|
69
|
+
children,
|
|
70
|
+
/* @__PURE__ */ jsxs("div", { className: "gx-form-footer", children: [
|
|
71
|
+
/* @__PURE__ */ jsx("span", { className: "gx-form-note", children: "Changes save automatically" }),
|
|
72
|
+
/* @__PURE__ */ jsx("button", { className: "gx-btn", onClick: onDone, children: "Done" })
|
|
73
|
+
] })
|
|
74
|
+
] });
|
|
75
|
+
}
|
|
76
|
+
function FieldGroup({ label, children }) {
|
|
77
|
+
return /* @__PURE__ */ jsxs("div", { className: "gx-group-wrap", children: [
|
|
78
|
+
label != null && /* @__PURE__ */ jsx("span", { className: "gx-group-label", children: label }),
|
|
79
|
+
/* @__PURE__ */ jsx("div", { className: "gx-group", children })
|
|
80
|
+
] });
|
|
81
|
+
}
|
|
82
|
+
function Field({ label, children }) {
|
|
83
|
+
return /* @__PURE__ */ jsxs("div", { className: "gx-field", children: [
|
|
84
|
+
/* @__PURE__ */ jsx("label", { className: "gx-field-label", children: label }),
|
|
85
|
+
/* @__PURE__ */ jsx("div", { className: "gx-field-control", children })
|
|
86
|
+
] });
|
|
87
|
+
}
|
|
88
|
+
function TextInput({
|
|
89
|
+
value,
|
|
90
|
+
placeholder,
|
|
91
|
+
secret,
|
|
92
|
+
onCommit
|
|
93
|
+
}) {
|
|
94
|
+
return /* @__PURE__ */ jsx(
|
|
95
|
+
"input",
|
|
96
|
+
{
|
|
97
|
+
className: "gx-input",
|
|
98
|
+
type: secret ? "password" : "text",
|
|
99
|
+
defaultValue: value,
|
|
100
|
+
placeholder,
|
|
101
|
+
onBlur: (e) => onCommit(e.target.value),
|
|
102
|
+
onKeyDown: (e) => e.key === "Enter" && onCommit(e.target.value)
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
function NumberInput({ value, onCommit }) {
|
|
107
|
+
return /* @__PURE__ */ jsx(
|
|
108
|
+
"input",
|
|
109
|
+
{
|
|
110
|
+
className: "gx-input",
|
|
111
|
+
type: "number",
|
|
112
|
+
defaultValue: value == null ? "" : String(value),
|
|
113
|
+
onBlur: (e) => onCommit(Number(e.target.value) || 0)
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
function Select({
|
|
118
|
+
value,
|
|
119
|
+
options,
|
|
120
|
+
onChange
|
|
121
|
+
}) {
|
|
122
|
+
return /* @__PURE__ */ jsx("select", { className: "gx-select", value, onChange: (e) => onChange(e.target.value), children: options.map(([v, label]) => /* @__PURE__ */ jsx("option", { value: v, children: label }, v)) });
|
|
123
|
+
}
|
|
124
|
+
function Switch({ on, onChange }) {
|
|
125
|
+
return /* @__PURE__ */ jsx("button", { className: `gx-switch${on ? " gx-switch--on" : ""}`, role: "switch", "aria-checked": on, onClick: () => onChange(!on), children: /* @__PURE__ */ jsx("span", { className: "gx-switch-knob" }) });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/react.ts
|
|
129
|
+
var singleton;
|
|
130
|
+
function hostClient() {
|
|
131
|
+
if (!singleton) {
|
|
132
|
+
const transport = getHostTransport();
|
|
133
|
+
if (!transport) throw new GarretError("UNAVAILABLE", "this extension has no host process");
|
|
134
|
+
singleton = createHostClient(transport, { instanceId: getInstanceId() });
|
|
135
|
+
}
|
|
136
|
+
return singleton;
|
|
137
|
+
}
|
|
138
|
+
function useHost() {
|
|
139
|
+
return useMemo(() => hostClient(), []);
|
|
140
|
+
}
|
|
141
|
+
function useGarret() {
|
|
142
|
+
return getGarret();
|
|
143
|
+
}
|
|
144
|
+
function useHostEvent(channel, handler, deps = []) {
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
const off = hostClient().on(channel, handler);
|
|
147
|
+
return off;
|
|
148
|
+
}, [channel, ...deps]);
|
|
149
|
+
}
|
|
150
|
+
function useStream(factory, deps = [], opts) {
|
|
151
|
+
const enabled = opts?.enabled ?? true;
|
|
152
|
+
const [chunks, setChunks] = useState([]);
|
|
153
|
+
const [result, setResult] = useState(void 0);
|
|
154
|
+
const [error, setError] = useState(void 0);
|
|
155
|
+
const [status, setStatus] = useState("idle");
|
|
156
|
+
const ref = useRef(null);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (!enabled) {
|
|
159
|
+
setStatus("idle");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
setChunks([]);
|
|
163
|
+
setResult(void 0);
|
|
164
|
+
setError(void 0);
|
|
165
|
+
setStatus("streaming");
|
|
166
|
+
const call = factory();
|
|
167
|
+
ref.current = call;
|
|
168
|
+
call.onData((c) => setChunks((xs) => [...xs, c])).onEnd((r) => {
|
|
169
|
+
setResult(r);
|
|
170
|
+
setStatus("done");
|
|
171
|
+
}).onError((e) => {
|
|
172
|
+
setError(e);
|
|
173
|
+
setStatus("error");
|
|
174
|
+
});
|
|
175
|
+
return () => call.cancel();
|
|
176
|
+
}, [enabled, ...deps]);
|
|
177
|
+
const cancel = useCallback(() => ref.current?.cancel(), []);
|
|
178
|
+
return { chunks, result, error, status, cancel };
|
|
179
|
+
}
|
|
180
|
+
function useConfig() {
|
|
181
|
+
const rt = getRuntime();
|
|
182
|
+
const [cfg, setCfg] = useState(rt?.config.get() ?? {});
|
|
183
|
+
useEffect(() => rt?.config.subscribe((c) => setCfg(c)), [rt]);
|
|
184
|
+
const patch = useCallback((p) => rt?.config.set(p, false), [rt]);
|
|
185
|
+
const replace = useCallback((v) => rt?.config.set(v, true), [rt]);
|
|
186
|
+
return [cfg, patch, replace];
|
|
187
|
+
}
|
|
188
|
+
function useActive() {
|
|
189
|
+
const g = getGarret();
|
|
190
|
+
const [active, setActive] = useState(g.active);
|
|
191
|
+
useEffect(() => g.onActiveChange(setActive), [g]);
|
|
192
|
+
return active;
|
|
193
|
+
}
|
|
194
|
+
function useOpenSettings(cb) {
|
|
195
|
+
const g = getGarret();
|
|
196
|
+
const ref = useRef(cb);
|
|
197
|
+
ref.current = cb;
|
|
198
|
+
useEffect(() => g.onOpenSettings(() => ref.current()), [g]);
|
|
199
|
+
}
|
|
200
|
+
function useInstanceConfig(defaults) {
|
|
201
|
+
const g = getGarret();
|
|
202
|
+
const [cfg, setCfg] = useState(defaults);
|
|
203
|
+
const [loaded, setLoaded] = useState(false);
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
let alive = true;
|
|
206
|
+
const unsub = g.onReady(() => {
|
|
207
|
+
void (async () => {
|
|
208
|
+
const saved = {};
|
|
209
|
+
await Promise.all(
|
|
210
|
+
Object.keys(defaults).map(async (k) => {
|
|
211
|
+
const v = await g.instanceStorage.get(k);
|
|
212
|
+
if (v !== void 0) saved[k] = v;
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
if (alive) {
|
|
216
|
+
setCfg({ ...defaults, ...saved });
|
|
217
|
+
setLoaded(true);
|
|
218
|
+
}
|
|
219
|
+
})();
|
|
220
|
+
});
|
|
221
|
+
return () => {
|
|
222
|
+
alive = false;
|
|
223
|
+
unsub();
|
|
224
|
+
};
|
|
225
|
+
}, []);
|
|
226
|
+
const set = useCallback(
|
|
227
|
+
(patch) => {
|
|
228
|
+
setCfg((c) => ({ ...c, ...patch }));
|
|
229
|
+
for (const [k, v] of Object.entries(patch)) void g.instanceStorage.set(k, v);
|
|
230
|
+
},
|
|
231
|
+
[g]
|
|
232
|
+
);
|
|
233
|
+
return { cfg, set, loaded };
|
|
234
|
+
}
|
|
235
|
+
function useProps() {
|
|
236
|
+
const g = getGarret();
|
|
237
|
+
const [props, setProps] = useState({});
|
|
238
|
+
useEffect(() => g.onReady((p) => setProps(p)), [g]);
|
|
239
|
+
return props;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export { Accordion, Badge, Dot, EmptyState, ErrorState, Field, FieldGroup, Item, NumberInput, Scroll, Select, SettingsPanel, Switch, TextInput, useActive, useConfig, useGarret, useHost, useHostEvent, useInstanceConfig, useOpenSettings, useProps, useStream };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** Typed errors that travel across the bridge (see docs/garret.html §Errors). */
|
|
2
|
+
type GarretErrorCode = 'BINARY_NOT_FOUND' | 'NOT_FOUND' | 'PERMISSION' | 'UNAVAILABLE' | 'BAD_ARGS' | 'TIMEOUT' | 'CANCELLED' | 'NETWORK' | 'INTERNAL';
|
|
3
|
+
interface GarretErrorOptions {
|
|
4
|
+
/** e.g. an install command for BINARY_NOT_FOUND. */
|
|
5
|
+
hint?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The single error type crossing the bridge. `code` lets the UI branch (offer `brew install` on
|
|
9
|
+
* BINARY_NOT_FOUND, an auth prompt on PERMISSION, …). Thrown host-side, re-thrown client-side.
|
|
10
|
+
*/
|
|
11
|
+
declare class GarretError extends Error {
|
|
12
|
+
readonly code: GarretErrorCode;
|
|
13
|
+
readonly hint?: string;
|
|
14
|
+
constructor(code: GarretErrorCode, message: string, options?: GarretErrorOptions);
|
|
15
|
+
}
|
|
16
|
+
/** Reconstruct a GarretError from a wire error frame (client side). */
|
|
17
|
+
declare function garretErrorFromWire(code: string, message: string, hint?: string): GarretError;
|
|
18
|
+
|
|
19
|
+
declare const STREAM_BRAND: unique symbol;
|
|
20
|
+
/**
|
|
21
|
+
* Declare a streaming method in your `Api`: its return type is `Stream<Chunk, Result>`. The host
|
|
22
|
+
* produces it with `ctx.stream(...)`; the UI consumes it as a {@link StreamCall}. `Result` defaults
|
|
23
|
+
* to `void` (bounded streams may return a final value, e.g. an exit code).
|
|
24
|
+
*/
|
|
25
|
+
interface Stream<Chunk, Result = void> {
|
|
26
|
+
readonly [STREAM_BRAND]: [Chunk, Result];
|
|
27
|
+
}
|
|
28
|
+
/** The UI-side handle for a streaming method. Deliberately NOT thenable (can't await an endless
|
|
29
|
+
* stream). Chainable. */
|
|
30
|
+
interface StreamCall<Chunk, Result = void> {
|
|
31
|
+
onData(cb: (chunk: Chunk) => void): StreamCall<Chunk, Result>;
|
|
32
|
+
onEnd(cb: (result: Result) => void): StreamCall<Chunk, Result>;
|
|
33
|
+
onError(cb: (err: GarretError) => void): StreamCall<Chunk, Result>;
|
|
34
|
+
/** Abort the host's `signal` + kill any spawned children. */
|
|
35
|
+
cancel(): void;
|
|
36
|
+
/** BOUNDED streams only — resolves on end, rejects on error. Awaiting an endless stream hangs. */
|
|
37
|
+
result(): Promise<Result>;
|
|
38
|
+
}
|
|
39
|
+
/** Maps each Api method to its client form: `Stream<…>` → `StreamCall`, else `Promise`. */
|
|
40
|
+
type HostClient<Api> = {
|
|
41
|
+
[K in keyof Api]: Api[K] extends (...args: infer A) => infer R ? R extends Stream<infer C, infer Res> ? (...args: A) => StreamCall<C, Res> : (...args: A) => Promise<Awaited<R>> : never;
|
|
42
|
+
};
|
|
43
|
+
/** Event payload map: `{ changed: { dir: string } }` → typed `on('changed', p => …)`. */
|
|
44
|
+
type EventMap = Record<string, unknown>;
|
|
45
|
+
/** Declared in the manifest; drives the tier + the main-process broker's allowlist. System caps
|
|
46
|
+
* (`process`/`fs`/`native`) and wildcard network require a host (full-access tier). */
|
|
47
|
+
type Capability = 'storage' | 'secrets' | 'notify' | 'clipboard' | 'openExternal' | `network:${string}` | `service:${string}` | 'process' | 'fs' | 'native';
|
|
48
|
+
type ConfigFieldType = 'string' | 'number' | 'boolean';
|
|
49
|
+
interface ConfigField {
|
|
50
|
+
type: ConfigFieldType;
|
|
51
|
+
label: string;
|
|
52
|
+
default: string | number | boolean;
|
|
53
|
+
min?: number;
|
|
54
|
+
max?: number;
|
|
55
|
+
}
|
|
56
|
+
type ConfigSchema = Record<string, ConfigField>;
|
|
57
|
+
interface Manifest {
|
|
58
|
+
/** lowercase [a-z0-9._-], no "..". Directory + origin. */
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
version: string;
|
|
62
|
+
apiVersion?: number;
|
|
63
|
+
description?: string;
|
|
64
|
+
/** lucide icon name. */
|
|
65
|
+
icon?: string;
|
|
66
|
+
/** bundled preview image path. */
|
|
67
|
+
preview?: string;
|
|
68
|
+
/** built UI dir (contains index.html). Required. */
|
|
69
|
+
ui: string;
|
|
70
|
+
/** built Node host entry. Presence + a system capability ⇒ full-access tier. */
|
|
71
|
+
host?: string;
|
|
72
|
+
capabilities?: Capability[];
|
|
73
|
+
defaultSize?: {
|
|
74
|
+
w: number;
|
|
75
|
+
h: number;
|
|
76
|
+
};
|
|
77
|
+
minSize?: {
|
|
78
|
+
w: number;
|
|
79
|
+
h: number;
|
|
80
|
+
};
|
|
81
|
+
config?: ConfigSchema;
|
|
82
|
+
}
|
|
83
|
+
/** Identity helper for `garret.manifest.ts` — pure passthrough, just for autocomplete + checking. */
|
|
84
|
+
declare function defineManifest(manifest: Manifest): Manifest;
|
|
85
|
+
/** Identity helper for a config schema — infers the settings value type for `useConfig<T>()`. */
|
|
86
|
+
declare function defineConfig<T extends ConfigSchema>(schema: T): T;
|
|
87
|
+
|
|
88
|
+
export { type Capability as C, type EventMap as E, GarretError as G, type HostClient as H, type Manifest as M, type Stream as S, type ConfigField as a, type ConfigFieldType as b, type ConfigSchema as c, type GarretErrorCode as d, type GarretErrorOptions as e, type StreamCall as f, defineConfig as g, defineManifest as h, garretErrorFromWire as i };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** Typed errors that travel across the bridge (see docs/garret.html §Errors). */
|
|
2
|
+
type GarretErrorCode = 'BINARY_NOT_FOUND' | 'NOT_FOUND' | 'PERMISSION' | 'UNAVAILABLE' | 'BAD_ARGS' | 'TIMEOUT' | 'CANCELLED' | 'NETWORK' | 'INTERNAL';
|
|
3
|
+
interface GarretErrorOptions {
|
|
4
|
+
/** e.g. an install command for BINARY_NOT_FOUND. */
|
|
5
|
+
hint?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The single error type crossing the bridge. `code` lets the UI branch (offer `brew install` on
|
|
9
|
+
* BINARY_NOT_FOUND, an auth prompt on PERMISSION, …). Thrown host-side, re-thrown client-side.
|
|
10
|
+
*/
|
|
11
|
+
declare class GarretError extends Error {
|
|
12
|
+
readonly code: GarretErrorCode;
|
|
13
|
+
readonly hint?: string;
|
|
14
|
+
constructor(code: GarretErrorCode, message: string, options?: GarretErrorOptions);
|
|
15
|
+
}
|
|
16
|
+
/** Reconstruct a GarretError from a wire error frame (client side). */
|
|
17
|
+
declare function garretErrorFromWire(code: string, message: string, hint?: string): GarretError;
|
|
18
|
+
|
|
19
|
+
declare const STREAM_BRAND: unique symbol;
|
|
20
|
+
/**
|
|
21
|
+
* Declare a streaming method in your `Api`: its return type is `Stream<Chunk, Result>`. The host
|
|
22
|
+
* produces it with `ctx.stream(...)`; the UI consumes it as a {@link StreamCall}. `Result` defaults
|
|
23
|
+
* to `void` (bounded streams may return a final value, e.g. an exit code).
|
|
24
|
+
*/
|
|
25
|
+
interface Stream<Chunk, Result = void> {
|
|
26
|
+
readonly [STREAM_BRAND]: [Chunk, Result];
|
|
27
|
+
}
|
|
28
|
+
/** The UI-side handle for a streaming method. Deliberately NOT thenable (can't await an endless
|
|
29
|
+
* stream). Chainable. */
|
|
30
|
+
interface StreamCall<Chunk, Result = void> {
|
|
31
|
+
onData(cb: (chunk: Chunk) => void): StreamCall<Chunk, Result>;
|
|
32
|
+
onEnd(cb: (result: Result) => void): StreamCall<Chunk, Result>;
|
|
33
|
+
onError(cb: (err: GarretError) => void): StreamCall<Chunk, Result>;
|
|
34
|
+
/** Abort the host's `signal` + kill any spawned children. */
|
|
35
|
+
cancel(): void;
|
|
36
|
+
/** BOUNDED streams only — resolves on end, rejects on error. Awaiting an endless stream hangs. */
|
|
37
|
+
result(): Promise<Result>;
|
|
38
|
+
}
|
|
39
|
+
/** Maps each Api method to its client form: `Stream<…>` → `StreamCall`, else `Promise`. */
|
|
40
|
+
type HostClient<Api> = {
|
|
41
|
+
[K in keyof Api]: Api[K] extends (...args: infer A) => infer R ? R extends Stream<infer C, infer Res> ? (...args: A) => StreamCall<C, Res> : (...args: A) => Promise<Awaited<R>> : never;
|
|
42
|
+
};
|
|
43
|
+
/** Event payload map: `{ changed: { dir: string } }` → typed `on('changed', p => …)`. */
|
|
44
|
+
type EventMap = Record<string, unknown>;
|
|
45
|
+
/** Declared in the manifest; drives the tier + the main-process broker's allowlist. System caps
|
|
46
|
+
* (`process`/`fs`/`native`) and wildcard network require a host (full-access tier). */
|
|
47
|
+
type Capability = 'storage' | 'secrets' | 'notify' | 'clipboard' | 'openExternal' | `network:${string}` | `service:${string}` | 'process' | 'fs' | 'native';
|
|
48
|
+
type ConfigFieldType = 'string' | 'number' | 'boolean';
|
|
49
|
+
interface ConfigField {
|
|
50
|
+
type: ConfigFieldType;
|
|
51
|
+
label: string;
|
|
52
|
+
default: string | number | boolean;
|
|
53
|
+
min?: number;
|
|
54
|
+
max?: number;
|
|
55
|
+
}
|
|
56
|
+
type ConfigSchema = Record<string, ConfigField>;
|
|
57
|
+
interface Manifest {
|
|
58
|
+
/** lowercase [a-z0-9._-], no "..". Directory + origin. */
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
version: string;
|
|
62
|
+
apiVersion?: number;
|
|
63
|
+
description?: string;
|
|
64
|
+
/** lucide icon name. */
|
|
65
|
+
icon?: string;
|
|
66
|
+
/** bundled preview image path. */
|
|
67
|
+
preview?: string;
|
|
68
|
+
/** built UI dir (contains index.html). Required. */
|
|
69
|
+
ui: string;
|
|
70
|
+
/** built Node host entry. Presence + a system capability ⇒ full-access tier. */
|
|
71
|
+
host?: string;
|
|
72
|
+
capabilities?: Capability[];
|
|
73
|
+
defaultSize?: {
|
|
74
|
+
w: number;
|
|
75
|
+
h: number;
|
|
76
|
+
};
|
|
77
|
+
minSize?: {
|
|
78
|
+
w: number;
|
|
79
|
+
h: number;
|
|
80
|
+
};
|
|
81
|
+
config?: ConfigSchema;
|
|
82
|
+
}
|
|
83
|
+
/** Identity helper for `garret.manifest.ts` — pure passthrough, just for autocomplete + checking. */
|
|
84
|
+
declare function defineManifest(manifest: Manifest): Manifest;
|
|
85
|
+
/** Identity helper for a config schema — infers the settings value type for `useConfig<T>()`. */
|
|
86
|
+
declare function defineConfig<T extends ConfigSchema>(schema: T): T;
|
|
87
|
+
|
|
88
|
+
export { type Capability as C, type EventMap as E, GarretError as G, type HostClient as H, type Manifest as M, type Stream as S, type ConfigField as a, type ConfigFieldType as b, type ConfigSchema as c, type GarretErrorCode as d, type GarretErrorOptions as e, type StreamCall as f, defineConfig as g, defineManifest as h, garretErrorFromWire as i };
|