@tangle-network/sandbox-ui 0.84.0 → 0.85.1

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.
@@ -1,5 +1,27 @@
1
1
  // src/hooks/use-session-stream.ts
2
2
  import { useCallback, useEffect, useRef, useState } from "react";
3
+
4
+ // src/hooks/wire-encoding.ts
5
+ function utf8ToLatin1(text) {
6
+ return encodeURIComponent(text).replace(
7
+ /%([0-9A-F]{2})/g,
8
+ (_, hex) => String.fromCharCode(Number.parseInt(hex, 16))
9
+ );
10
+ }
11
+ function encodeTextForWire(text) {
12
+ if (typeof btoa === "function") {
13
+ return btoa(utf8ToLatin1(text));
14
+ }
15
+ const bufferCtor = globalThis.Buffer;
16
+ if (bufferCtor) {
17
+ return bufferCtor.from(text, "utf8").toString("base64");
18
+ }
19
+ throw new Error(
20
+ "encodeTextForWire: no base64 encoder available (btoa and Buffer both undefined)"
21
+ );
22
+ }
23
+
24
+ // src/hooks/use-session-stream.ts
3
25
  function mapApiMessage(msg, counterRef) {
4
26
  const created = msg.info.timestamp ? new Date(msg.info.timestamp).getTime() : Date.now();
5
27
  const message = {
@@ -235,7 +257,9 @@ function useSessionStream({
235
257
  await fetchJson(url, token, {
236
258
  method: "POST",
237
259
  body: JSON.stringify({
238
- parts: [{ type: "text", text }],
260
+ // The sidecar base64-decodes every text part at the route boundary;
261
+ // a raw-UTF-8 part is rejected 400 (or mis-decoded). See wire-encoding.ts.
262
+ parts: [{ type: "text", text: encodeTextForWire(text) }],
239
263
  ...options?.agent ? { agent: options.agent } : {},
240
264
  ...options?.model ? { model: options.model } : {},
241
265
  ...options?.system ? { system: options.system } : {},
@@ -0,0 +1,80 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ /** One invokable action a connector exposes (the platform's hub tool shape, decoupled from any client). */
5
+ interface ConnectorAction {
6
+ path: string;
7
+ title?: string;
8
+ description?: string;
9
+ inputSchema?: unknown;
10
+ outputSchema?: unknown;
11
+ risk?: "read" | "write" | "destructive" | "unknown";
12
+ }
13
+ /** One row in the connector catalog browse list. */
14
+ interface ConnectorCatalogEntry {
15
+ providerId: string;
16
+ title: string;
17
+ category?: string | null;
18
+ categoryLabel?: string | null;
19
+ authKind?: string | null;
20
+ authKindLabel?: string | null;
21
+ actionCount?: number | null;
22
+ triggerCount?: number | null;
23
+ connected?: boolean;
24
+ }
25
+
26
+ interface ConnectorActionListProps {
27
+ actions: ConnectorAction[];
28
+ /** Connector display title; a leading "<title>: " prefix is stripped from action titles. */
29
+ providerTitle: string;
30
+ /** Renders the "Build with assistant" button when provided; called with the action. */
31
+ onBuildWithAssistant?: (action: ConnectorAction) => void;
32
+ /** True when the action list may be truncated at the fetch limit — the count renders "N+". */
33
+ maybeTruncated?: boolean;
34
+ className?: string;
35
+ }
36
+ /**
37
+ * Searchable list of a connector's invokable actions. Each row expands to show
38
+ * the action's path, input/output schemas, and copy/build affordances. Purely
39
+ * presentational — the host supplies the actions and the assistant hook.
40
+ */
41
+ declare function ConnectorActionList({ actions, providerTitle, onBuildWithAssistant, maybeTruncated, className, }: ConnectorActionListProps): react_jsx_runtime.JSX.Element;
42
+
43
+ interface ConnectorCatalogListProps {
44
+ entries: ConnectorCatalogEntry[];
45
+ /** Resolve the href for a connector's detail surface (rendered on each row's <a>). */
46
+ getConnectorHref: (providerId: string) => string;
47
+ /** SPA navigation: when provided, row clicks preventDefault and call this instead of following the href. */
48
+ onOpenConnector?: (providerId: string) => void;
49
+ /** Host-owned brand icon per provider (e.g. platform's HubProviderIcon); fallback is a neutral chip. */
50
+ renderIcon?: (providerId: string) => ReactNode;
51
+ /** "Request an integration" affordance, receives the current search text; button hidden when absent. */
52
+ onRequestIntegration?: (prefill: string) => void;
53
+ className?: string;
54
+ }
55
+ /**
56
+ * Searchable/filterable browse list over every connector the host exposes.
57
+ * Each row links into the connector's detail surface; connect/disconnect and
58
+ * data fetching stay with the host — this list is for discovery.
59
+ */
60
+ declare function ConnectorCatalogList({ entries, getConnectorHref, onOpenConnector, renderIcon, onRequestIntegration, className, }: ConnectorCatalogListProps): react_jsx_runtime.JSX.Element;
61
+
62
+ interface SchemaTableProps {
63
+ schema: unknown;
64
+ /** Toggle/table caption, e.g. "Input" or "Output". */
65
+ label: string;
66
+ }
67
+ declare function SchemaTable({ schema, label }: SchemaTableProps): react_jsx_runtime.JSX.Element;
68
+
69
+ /**
70
+ * Mechanical `integration.invoke` step-YAML stub for a hub action, derived
71
+ * entirely from the action's JSON input schema: the step shape the workflow
72
+ * language expects, with the action's required fields (or every top-level
73
+ * field when the schema declares none required) stubbed by type. A copy-paste
74
+ * seed for hand-authoring a workflow — not a validated document.
75
+ */
76
+ /** The `- integration.invoke:` step block for an action, ready to paste into a
77
+ * workflow's `do:` list. */
78
+ declare function integrationStepYaml(path: string, inputSchema: unknown): string;
79
+
80
+ export { type ConnectorAction, ConnectorActionList, type ConnectorActionListProps, type ConnectorCatalogEntry, ConnectorCatalogList, type ConnectorCatalogListProps, SchemaTable, type SchemaTableProps, integrationStepYaml };
@@ -0,0 +1,682 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-EI44GEQ5.js";
4
+ import "./chunk-U67V476Y.js";
5
+
6
+ // src/connectors/action-list.tsx
7
+ import { Badge, Button, Input } from "@tangle-network/ui/primitives";
8
+ import { Check, ChevronDown, ChevronRight, Copy, Search } from "lucide-react";
9
+ import { useEffect, useMemo, useRef, useState as useState2 } from "react";
10
+
11
+ // src/connectors/integration-step-yaml.ts
12
+ function asSchemaObject(value) {
13
+ return typeof value === "object" && value !== null ? value : null;
14
+ }
15
+ function placeholderFor(propSchema) {
16
+ const schema = asSchemaObject(propSchema);
17
+ if (!schema) return '""';
18
+ if (Array.isArray(schema.enum)) {
19
+ const firstString = schema.enum.find(
20
+ (value) => typeof value === "string"
21
+ );
22
+ if (firstString !== void 0) return JSON.stringify(firstString);
23
+ }
24
+ const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
25
+ switch (type) {
26
+ case "number":
27
+ case "integer":
28
+ return "0";
29
+ case "boolean":
30
+ return "false";
31
+ case "array":
32
+ return "[]";
33
+ case "object":
34
+ return "{}";
35
+ default:
36
+ return '""';
37
+ }
38
+ }
39
+ var YAML_RESERVED_SCALAR = /^(?:true|false|null|yes|no|on|off|~)$/i;
40
+ function isBareYamlScalar(value) {
41
+ return /^[A-Za-z0-9_.-]+$/.test(value) && /[A-Za-z]/.test(value) && !YAML_RESERVED_SCALAR.test(value);
42
+ }
43
+ function yamlKey(key) {
44
+ return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key) && !YAML_RESERVED_SCALAR.test(key) ? key : JSON.stringify(key);
45
+ }
46
+ function stubFields(inputSchema) {
47
+ const schema = asSchemaObject(inputSchema);
48
+ const properties = asSchemaObject(schema?.properties);
49
+ if (!properties) return [];
50
+ const names = Object.keys(properties);
51
+ const required = Array.isArray(schema?.required) ? schema.required.filter(
52
+ (name) => typeof name === "string" && names.includes(name)
53
+ ) : [];
54
+ const chosen = required.length > 0 ? required : names;
55
+ return chosen.map((name) => [
56
+ name,
57
+ properties[name]
58
+ ]);
59
+ }
60
+ function integrationStepYaml(path, inputSchema) {
61
+ const fields = stubFields(inputSchema);
62
+ const pathScalar = isBareYamlScalar(path) ? path : JSON.stringify(path);
63
+ const lines = [
64
+ "- integration.invoke:",
65
+ ` path: ${pathScalar}`,
66
+ ...fields.length === 0 ? [" input: {}"] : [
67
+ " input:",
68
+ ...fields.map(
69
+ ([name, propSchema]) => ` ${yamlKey(name)}: ${placeholderFor(propSchema)}`
70
+ )
71
+ ]
72
+ ];
73
+ return `${lines.join("\n")}
74
+ `;
75
+ }
76
+
77
+ // src/connectors/schema-table.tsx
78
+ import { useState } from "react";
79
+ import { jsx, jsxs } from "react/jsx-runtime";
80
+ var MAX_DEPTH = 3;
81
+ function asNode(value) {
82
+ return typeof value === "object" && value !== null ? value : null;
83
+ }
84
+ function typeLabel(node) {
85
+ const type = Array.isArray(node.type) ? node.type.join(" | ") : node.type;
86
+ if (type === "array") {
87
+ const items = asNode(node.items);
88
+ const itemType = items ? Array.isArray(items.type) ? items.type.join(" | ") : items.type : void 0;
89
+ return typeof itemType === "string" ? `array<${itemType}>` : "array";
90
+ }
91
+ if (typeof type === "string") return type;
92
+ if (Array.isArray(node.enum)) return "enum";
93
+ return "any";
94
+ }
95
+ function collectRows(schema, depth, prefix, out) {
96
+ const properties = asNode(schema.properties);
97
+ if (!properties) return;
98
+ const required = new Set(
99
+ Array.isArray(schema.required) ? schema.required.filter((n) => typeof n === "string") : []
100
+ );
101
+ for (const [name, rawProp] of Object.entries(
102
+ properties
103
+ )) {
104
+ const prop = asNode(rawProp) ?? {};
105
+ const enumValues = Array.isArray(prop.enum) ? prop.enum.map((v) => JSON.stringify(v)).filter((v) => typeof v === "string") : null;
106
+ out.push({
107
+ name: prefix ? `${prefix}.${name}` : name,
108
+ depth,
109
+ type: typeLabel(prop),
110
+ required: required.has(name),
111
+ description: typeof prop.description === "string" ? prop.description : "",
112
+ enumValues
113
+ });
114
+ if (depth + 1 >= MAX_DEPTH) continue;
115
+ const child = typeLabel(prop) === "object" || asNode(prop.properties) ? prop : asNode(prop.items)?.properties ?? null ? asNode(prop.items) : null;
116
+ if (child) {
117
+ collectRows(child, depth + 1, prefix ? `${prefix}.${name}` : name, out);
118
+ }
119
+ }
120
+ }
121
+ function schemaRows(schema) {
122
+ const node = asNode(schema);
123
+ if (!node || !asNode(node.properties)) return null;
124
+ const rows = [];
125
+ collectRows(node, 0, "", rows);
126
+ return rows;
127
+ }
128
+ function safeJsonStringify(value) {
129
+ try {
130
+ return JSON.stringify(value, null, 2) ?? "null";
131
+ } catch {
132
+ return "(schema cannot be shown as JSON)";
133
+ }
134
+ }
135
+ function SchemaTable({ schema, label }) {
136
+ const [showRaw, setShowRaw] = useState(false);
137
+ const rows = schemaRows(schema);
138
+ const rawJson = safeJsonStringify(schema);
139
+ return /* @__PURE__ */ jsxs("div", { children: [
140
+ /* @__PURE__ */ jsxs("div", { className: "mb-1.5 flex items-center justify-between", children: [
141
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: label }),
142
+ rows !== null && rows.length > 0 && /* @__PURE__ */ jsx(
143
+ "button",
144
+ {
145
+ type: "button",
146
+ onClick: () => setShowRaw((v) => !v),
147
+ className: "text-primary text-xs transition-colors hover:underline",
148
+ children: showRaw ? "Field view" : "Raw JSON"
149
+ }
150
+ )
151
+ ] }),
152
+ rows === null || rows.length === 0 || showRaw ? rows !== null && rows.length === 0 && !showRaw ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "No fields." }) : /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded-lg bg-surface-container-high p-3 text-xs leading-relaxed", children: rawJson }) : /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg border border-border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
153
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-border border-b bg-surface-container-high", children: [
154
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-1.5 text-left font-medium text-muted-foreground text-xs", children: "Field" }),
155
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-1.5 text-left font-medium text-muted-foreground text-xs", children: "Type" }),
156
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-1.5 text-left font-medium text-muted-foreground text-xs", children: "Description" })
157
+ ] }) }),
158
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs(
159
+ "tr",
160
+ {
161
+ className: "border-border border-b last:border-b-0",
162
+ children: [
163
+ /* @__PURE__ */ jsxs(
164
+ "td",
165
+ {
166
+ className: "whitespace-nowrap px-3 py-1.5 align-top font-mono text-foreground text-xs",
167
+ style: { paddingLeft: `${0.75 + row.depth * 1}rem` },
168
+ children: [
169
+ row.name.split(".").at(-1),
170
+ row.required && /* @__PURE__ */ jsxs("span", { className: "ml-0.5 text-destructive", title: "Required", children: [
171
+ "*",
172
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "required" })
173
+ ] })
174
+ ]
175
+ }
176
+ ),
177
+ /* @__PURE__ */ jsx("td", { className: "whitespace-nowrap px-3 py-1.5 align-top font-mono text-muted-foreground text-xs", children: row.type }),
178
+ /* @__PURE__ */ jsxs("td", { className: "px-3 py-1.5 align-top text-muted-foreground text-xs", children: [
179
+ row.description,
180
+ row.enumValues && /* @__PURE__ */ jsxs("span", { className: row.description ? "ml-1" : "", children: [
181
+ "One of: ",
182
+ row.enumValues.join(", ")
183
+ ] })
184
+ ] })
185
+ ]
186
+ },
187
+ row.name
188
+ )) })
189
+ ] }) })
190
+ ] });
191
+ }
192
+
193
+ // src/connectors/action-list.tsx
194
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
195
+ var RISK_BADGES = {
196
+ read: { label: "read", variant: "success" },
197
+ write: { label: "write", variant: "warning" },
198
+ destructive: { label: "destructive", variant: "destructive" },
199
+ unknown: { label: "unclassified", variant: "outline" }
200
+ };
201
+ function RiskBadge({ risk }) {
202
+ const badge = RISK_BADGES[risk ?? "unknown"] ?? RISK_BADGES.unknown;
203
+ return /* @__PURE__ */ jsx2(Badge, { variant: badge.variant, children: badge.label });
204
+ }
205
+ function CopyButton({
206
+ text,
207
+ label,
208
+ title
209
+ }) {
210
+ const [state, setState] = useState2("idle");
211
+ const timerRef = useRef(null);
212
+ useEffect(
213
+ () => () => {
214
+ if (timerRef.current) clearTimeout(timerRef.current);
215
+ },
216
+ []
217
+ );
218
+ const copy = async () => {
219
+ let next;
220
+ try {
221
+ await navigator.clipboard.writeText(text);
222
+ next = "copied";
223
+ } catch (err) {
224
+ console.warn("Clipboard write failed:", err);
225
+ next = "failed";
226
+ }
227
+ setState(next);
228
+ if (timerRef.current) clearTimeout(timerRef.current);
229
+ timerRef.current = setTimeout(() => setState("idle"), 2e3);
230
+ };
231
+ return /* @__PURE__ */ jsxs2(Button, { type: "button", variant: "outline", size: "sm", onClick: copy, title, children: [
232
+ state === "copied" ? /* @__PURE__ */ jsx2(Check, { className: "h-3.5 w-3.5 text-[var(--surface-success-text)]" }) : /* @__PURE__ */ jsx2(Copy, { className: "h-3.5 w-3.5" }),
233
+ state === "copied" ? "Copied" : state === "failed" ? "Copy failed" : label
234
+ ] });
235
+ }
236
+ function actionDisplayTitle(action, providerTitle) {
237
+ const title = action.title ?? action.path;
238
+ const prefix = `${providerTitle.toLowerCase()}: `;
239
+ return title.toLowerCase().startsWith(prefix) ? title.slice(prefix.length) : title;
240
+ }
241
+ function ActionRow({
242
+ action,
243
+ title,
244
+ expanded,
245
+ onToggle,
246
+ onBuildWithAssistant
247
+ }) {
248
+ return /* @__PURE__ */ jsxs2("li", { children: [
249
+ /* @__PURE__ */ jsxs2(
250
+ "button",
251
+ {
252
+ type: "button",
253
+ onClick: onToggle,
254
+ "aria-expanded": expanded,
255
+ className: "flex w-full items-center justify-between gap-4 p-4 text-left transition-colors hover:bg-surface-container-high",
256
+ children: [
257
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 items-center gap-3", children: [
258
+ expanded ? /* @__PURE__ */ jsx2(
259
+ ChevronDown,
260
+ {
261
+ "aria-hidden": "true",
262
+ className: "h-4 w-4 shrink-0 text-muted-foreground"
263
+ }
264
+ ) : /* @__PURE__ */ jsx2(
265
+ ChevronRight,
266
+ {
267
+ "aria-hidden": "true",
268
+ className: "h-4 w-4 shrink-0 text-muted-foreground"
269
+ }
270
+ ),
271
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0", children: [
272
+ /* @__PURE__ */ jsx2("p", { className: "font-medium text-foreground", children: title }),
273
+ !expanded && action.description && /* @__PURE__ */ jsx2("p", { className: "truncate text-muted-foreground text-sm", children: action.description })
274
+ ] })
275
+ ] }),
276
+ /* @__PURE__ */ jsx2(RiskBadge, { risk: action.risk })
277
+ ]
278
+ }
279
+ ),
280
+ expanded && /* @__PURE__ */ jsxs2("div", { className: "space-y-4 border-border border-t bg-surface-container-high/40 p-4 pl-11", children: [
281
+ action.description && /* @__PURE__ */ jsx2("p", { className: "text-muted-foreground text-sm", children: action.description }),
282
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-2", children: [
283
+ /* @__PURE__ */ jsx2("code", { className: "rounded bg-surface-container-high px-2 py-1 font-mono text-sm", children: action.path }),
284
+ /* @__PURE__ */ jsx2(CopyButton, { text: action.path, label: "Copy path" })
285
+ ] }),
286
+ /* @__PURE__ */ jsx2(SchemaTable, { schema: action.inputSchema, label: "Input" }),
287
+ action.outputSchema != null && /* @__PURE__ */ jsxs2("details", { children: [
288
+ /* @__PURE__ */ jsxs2("summary", { className: "cursor-pointer text-muted-foreground text-sm transition-colors hover:text-foreground", children: [
289
+ "Output shape (for",
290
+ " ",
291
+ /* @__PURE__ */ jsx2("code", { className: "rounded bg-surface-container-high px-1 py-0.5 font-mono text-xs", children: "${steps[N]\u2026}" }),
292
+ " ",
293
+ "expressions)"
294
+ ] }),
295
+ /* @__PURE__ */ jsx2("div", { className: "mt-2", children: /* @__PURE__ */ jsx2(SchemaTable, { schema: action.outputSchema, label: "Output" }) })
296
+ ] }),
297
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-2", children: [
298
+ /* @__PURE__ */ jsx2(
299
+ CopyButton,
300
+ {
301
+ text: integrationStepYaml(action.path, action.inputSchema),
302
+ label: "Copy step YAML",
303
+ title: "A ready-to-paste integration.invoke step for a workflow definition's do: list"
304
+ }
305
+ ),
306
+ onBuildWithAssistant && /* @__PURE__ */ jsx2(
307
+ Button,
308
+ {
309
+ type: "button",
310
+ size: "sm",
311
+ onClick: () => onBuildWithAssistant(action),
312
+ children: "Build with assistant"
313
+ }
314
+ )
315
+ ] })
316
+ ] })
317
+ ] });
318
+ }
319
+ function ConnectorActionList({
320
+ actions,
321
+ providerTitle,
322
+ onBuildWithAssistant,
323
+ maybeTruncated,
324
+ className
325
+ }) {
326
+ const [filter, setFilter] = useState2("");
327
+ const [expandedPath, setExpandedPath] = useState2(null);
328
+ const decorated = useMemo(
329
+ () => actions.map((action) => ({
330
+ action,
331
+ title: actionDisplayTitle(action, providerTitle)
332
+ })).sort((a, b) => a.title.localeCompare(b.title)),
333
+ [actions, providerTitle]
334
+ );
335
+ const filtered = useMemo(() => {
336
+ const q = filter.trim().toLowerCase();
337
+ if (!q) return decorated;
338
+ return decorated.filter(
339
+ ({ action, title }) => title.toLowerCase().includes(q) || action.path.toLowerCase().includes(q) || (action.description ?? "").toLowerCase().includes(q)
340
+ );
341
+ }, [decorated, filter]);
342
+ if (actions.length === 0) {
343
+ return /* @__PURE__ */ jsx2(
344
+ "section",
345
+ {
346
+ className: cn("rounded-lg border border-border bg-card p-6", className),
347
+ children: /* @__PURE__ */ jsx2("p", { className: "text-center text-muted-foreground text-sm", children: "This connector doesn't expose any invokable actions yet." })
348
+ }
349
+ );
350
+ }
351
+ return /* @__PURE__ */ jsxs2(
352
+ "section",
353
+ {
354
+ className: cn(
355
+ "overflow-hidden rounded-lg border border-border bg-card",
356
+ className
357
+ ),
358
+ children: [
359
+ /* @__PURE__ */ jsx2("div", { className: "border-border border-b p-4", children: /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-3", children: [
360
+ /* @__PURE__ */ jsxs2("div", { className: "relative min-w-[12rem] flex-1", children: [
361
+ /* @__PURE__ */ jsx2(
362
+ Search,
363
+ {
364
+ "aria-hidden": "true",
365
+ className: "pointer-events-none absolute top-1/2 left-2.5 h-4 w-4 -translate-y-1/2 text-muted-foreground"
366
+ }
367
+ ),
368
+ /* @__PURE__ */ jsx2(
369
+ Input,
370
+ {
371
+ type: "search",
372
+ value: filter,
373
+ onChange: (e) => setFilter(e.target.value),
374
+ placeholder: "Filter actions",
375
+ "aria-label": "Filter actions",
376
+ className: "pl-8"
377
+ }
378
+ )
379
+ ] }),
380
+ /* @__PURE__ */ jsxs2(
381
+ "span",
382
+ {
383
+ role: "status",
384
+ "aria-live": "polite",
385
+ className: "text-muted-foreground text-sm",
386
+ children: [
387
+ filtered.length,
388
+ " of ",
389
+ actions.length,
390
+ maybeTruncated ? "+" : "",
391
+ " actions"
392
+ ]
393
+ }
394
+ )
395
+ ] }) }),
396
+ filtered.length === 0 ? /* @__PURE__ */ jsxs2("div", { className: "p-6 text-center", children: [
397
+ /* @__PURE__ */ jsx2("p", { className: "text-muted-foreground text-sm", children: "No actions match your filter." }),
398
+ /* @__PURE__ */ jsx2(
399
+ Button,
400
+ {
401
+ type: "button",
402
+ variant: "outline",
403
+ size: "sm",
404
+ onClick: () => setFilter(""),
405
+ className: "mt-4",
406
+ children: "Clear filter"
407
+ }
408
+ )
409
+ ] }) : /* @__PURE__ */ jsx2("ul", { className: "divide-y divide-border", children: filtered.map(({ action, title }) => /* @__PURE__ */ jsx2(
410
+ ActionRow,
411
+ {
412
+ action,
413
+ title,
414
+ expanded: expandedPath === action.path,
415
+ onToggle: () => setExpandedPath(
416
+ (current) => current === action.path ? null : action.path
417
+ ),
418
+ onBuildWithAssistant
419
+ },
420
+ action.path
421
+ )) })
422
+ ]
423
+ }
424
+ );
425
+ }
426
+
427
+ // src/connectors/catalog-list.tsx
428
+ import { Button as Button2, EmptyState, Input as Input2 } from "@tangle-network/ui/primitives";
429
+ import { Blocks, ChevronRight as ChevronRight2, Search as Search2 } from "lucide-react";
430
+ import { useEffect as useEffect2, useMemo as useMemo2, useState as useState3 } from "react";
431
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
432
+ function optionsFor(entries, pick) {
433
+ const map = /* @__PURE__ */ new Map();
434
+ for (const entry of entries) {
435
+ const { value, label } = pick(entry);
436
+ if (value) map.set(value, label ?? value);
437
+ }
438
+ return [...map.entries()].map(([value, label]) => ({ value, label })).sort((a, b) => a.label.localeCompare(b.label));
439
+ }
440
+ function rowDetail(entry) {
441
+ const parts = [];
442
+ if (entry.category) parts.push(entry.categoryLabel ?? entry.category);
443
+ if (entry.authKind) parts.push(entry.authKindLabel ?? entry.authKind);
444
+ if (entry.actionCount != null) {
445
+ parts.push(
446
+ `${entry.actionCount} ${entry.actionCount === 1 ? "action" : "actions"}`
447
+ );
448
+ }
449
+ if (entry.triggerCount != null && entry.triggerCount > 0) {
450
+ parts.push(
451
+ `${entry.triggerCount} trigger ${entry.triggerCount === 1 ? "event" : "events"}`
452
+ );
453
+ }
454
+ return parts.join(" \xB7 ");
455
+ }
456
+ function FallbackIcon() {
457
+ return /* @__PURE__ */ jsx3("div", { className: "flex size-9 shrink-0 items-center justify-center rounded-lg bg-surface-container-high", children: /* @__PURE__ */ jsx3(Blocks, { className: "size-5 text-muted-foreground" }) });
458
+ }
459
+ function FilterSelect({
460
+ value,
461
+ onChange,
462
+ options,
463
+ allLabel,
464
+ ariaLabel
465
+ }) {
466
+ return /* @__PURE__ */ jsxs3(
467
+ "select",
468
+ {
469
+ value,
470
+ onChange: (e) => onChange(e.target.value),
471
+ "aria-label": ariaLabel,
472
+ className: "rounded-lg border border-border bg-card px-3 py-2 text-foreground text-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
473
+ children: [
474
+ /* @__PURE__ */ jsx3("option", { value: "", children: allLabel }),
475
+ options.map((option) => /* @__PURE__ */ jsx3("option", { value: option.value, children: option.label }, option.value))
476
+ ]
477
+ }
478
+ );
479
+ }
480
+ function ConnectorCatalogList({
481
+ entries,
482
+ getConnectorHref,
483
+ onOpenConnector,
484
+ renderIcon,
485
+ onRequestIntegration,
486
+ className
487
+ }) {
488
+ const [search, setSearch] = useState3("");
489
+ const [categoryFilter, setCategoryFilter] = useState3("");
490
+ const [authFilter, setAuthFilter] = useState3("");
491
+ const categoryOptions = useMemo2(
492
+ () => optionsFor(entries, (e) => ({
493
+ value: e.category,
494
+ label: e.categoryLabel
495
+ })),
496
+ [entries]
497
+ );
498
+ const authOptions = useMemo2(
499
+ () => optionsFor(entries, (e) => ({
500
+ value: e.authKind,
501
+ label: e.authKindLabel
502
+ })),
503
+ [entries]
504
+ );
505
+ const filtered = useMemo2(() => {
506
+ const q = search.trim().toLowerCase();
507
+ return entries.filter((entry) => {
508
+ if (categoryFilter && entry.category !== categoryFilter) return false;
509
+ if (authFilter && entry.authKind !== authFilter) return false;
510
+ if (!q) return true;
511
+ return entry.title.toLowerCase().includes(q) || entry.providerId.toLowerCase().includes(q);
512
+ }).sort((a, b) => a.title.localeCompare(b.title));
513
+ }, [entries, search, categoryFilter, authFilter]);
514
+ const clearFilters = () => {
515
+ setSearch("");
516
+ setCategoryFilter("");
517
+ setAuthFilter("");
518
+ };
519
+ useEffect2(() => {
520
+ if (categoryFilter && !categoryOptions.some((o) => o.value === categoryFilter)) {
521
+ setCategoryFilter("");
522
+ }
523
+ }, [categoryOptions, categoryFilter]);
524
+ useEffect2(() => {
525
+ if (authFilter && !authOptions.some((o) => o.value === authFilter)) {
526
+ setAuthFilter("");
527
+ }
528
+ }, [authOptions, authFilter]);
529
+ return /* @__PURE__ */ jsxs3(
530
+ "section",
531
+ {
532
+ className: cn(
533
+ "overflow-hidden rounded-lg border border-border bg-card",
534
+ className
535
+ ),
536
+ children: [
537
+ entries.length > 0 && /* @__PURE__ */ jsx3("div", { className: "border-border border-b p-4", children: /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-3", children: [
538
+ /* @__PURE__ */ jsxs3("div", { className: "relative min-w-[15rem] flex-1", children: [
539
+ /* @__PURE__ */ jsx3(
540
+ Search2,
541
+ {
542
+ "aria-hidden": "true",
543
+ className: "pointer-events-none absolute top-1/2 left-2.5 h-4 w-4 -translate-y-1/2 text-muted-foreground"
544
+ }
545
+ ),
546
+ /* @__PURE__ */ jsx3(
547
+ Input2,
548
+ {
549
+ type: "search",
550
+ value: search,
551
+ onChange: (e) => setSearch(e.target.value),
552
+ placeholder: "Search connectors",
553
+ "aria-label": "Search connectors",
554
+ className: "pl-8"
555
+ }
556
+ )
557
+ ] }),
558
+ categoryOptions.length > 1 && /* @__PURE__ */ jsx3(
559
+ FilterSelect,
560
+ {
561
+ value: categoryFilter,
562
+ onChange: setCategoryFilter,
563
+ options: categoryOptions,
564
+ allLabel: "All categories",
565
+ ariaLabel: "Filter by category"
566
+ }
567
+ ),
568
+ authOptions.length > 1 && /* @__PURE__ */ jsx3(
569
+ FilterSelect,
570
+ {
571
+ value: authFilter,
572
+ onChange: setAuthFilter,
573
+ options: authOptions,
574
+ allLabel: "All auth",
575
+ ariaLabel: "Filter by auth type"
576
+ }
577
+ ),
578
+ /* @__PURE__ */ jsxs3(
579
+ "span",
580
+ {
581
+ role: "status",
582
+ "aria-live": "polite",
583
+ className: "text-muted-foreground text-sm",
584
+ children: [
585
+ filtered.length,
586
+ " of ",
587
+ entries.length,
588
+ " connectors"
589
+ ]
590
+ }
591
+ ),
592
+ onRequestIntegration && /* @__PURE__ */ jsx3(
593
+ "button",
594
+ {
595
+ type: "button",
596
+ onClick: () => onRequestIntegration(search.trim()),
597
+ className: "text-primary text-sm transition-colors hover:underline",
598
+ children: "Request an integration"
599
+ }
600
+ )
601
+ ] }) }),
602
+ entries.length === 0 ? /* @__PURE__ */ jsx3(
603
+ EmptyState,
604
+ {
605
+ title: "No connectors",
606
+ description: "No connectors are available yet."
607
+ }
608
+ ) : filtered.length === 0 ? /* @__PURE__ */ jsx3(
609
+ EmptyState,
610
+ {
611
+ title: "No matches",
612
+ description: "No connectors match your filters.",
613
+ action: /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
614
+ /* @__PURE__ */ jsx3(
615
+ Button2,
616
+ {
617
+ type: "button",
618
+ variant: "outline",
619
+ size: "sm",
620
+ onClick: clearFilters,
621
+ children: "Clear filters"
622
+ }
623
+ ),
624
+ onRequestIntegration && /* @__PURE__ */ jsxs3(
625
+ Button2,
626
+ {
627
+ type: "button",
628
+ size: "sm",
629
+ onClick: () => onRequestIntegration(search.trim()),
630
+ children: [
631
+ "Request ",
632
+ search.trim() ? `"${search.trim()}"` : "an integration"
633
+ ]
634
+ }
635
+ )
636
+ ] })
637
+ }
638
+ ) : /* @__PURE__ */ jsx3("ul", { className: "divide-y divide-border", children: filtered.map((entry) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs3(
639
+ "a",
640
+ {
641
+ href: getConnectorHref(entry.providerId),
642
+ onClick: (e) => {
643
+ if (onOpenConnector && e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey) {
644
+ e.preventDefault();
645
+ onOpenConnector(entry.providerId);
646
+ }
647
+ },
648
+ className: "flex items-center justify-between gap-4 p-4 transition-colors hover:bg-surface-container-high",
649
+ children: [
650
+ /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-3", children: [
651
+ renderIcon ? renderIcon(entry.providerId) : /* @__PURE__ */ jsx3(FallbackIcon, {}),
652
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
653
+ /* @__PURE__ */ jsx3("p", { className: "font-medium text-foreground", children: entry.title }),
654
+ /* @__PURE__ */ jsx3("p", { className: "truncate text-muted-foreground text-sm", children: rowDetail(entry) })
655
+ ] })
656
+ ] }),
657
+ /* @__PURE__ */ jsxs3("div", { className: "flex shrink-0 items-center gap-3", children: [
658
+ entry.connected && /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground text-sm", children: [
659
+ /* @__PURE__ */ jsx3("span", { className: "size-2 rounded-full bg-[var(--surface-success-text)]" }),
660
+ "Connected"
661
+ ] }),
662
+ /* @__PURE__ */ jsx3(
663
+ ChevronRight2,
664
+ {
665
+ "aria-hidden": "true",
666
+ className: "h-4 w-4 text-muted-foreground"
667
+ }
668
+ )
669
+ ] })
670
+ ]
671
+ }
672
+ ) }, entry.providerId)) })
673
+ ]
674
+ }
675
+ );
676
+ }
677
+ export {
678
+ ConnectorActionList,
679
+ ConnectorCatalogList,
680
+ SchemaTable,
681
+ integrationStepYaml
682
+ };
package/dist/globals.css CHANGED
@@ -1289,6 +1289,9 @@
1289
1289
  .min-w-\[12rem\] {
1290
1290
  min-width: 12rem;
1291
1291
  }
1292
+ .min-w-\[15rem\] {
1293
+ min-width: 15rem;
1294
+ }
1292
1295
  .min-w-\[20px\] {
1293
1296
  min-width: 20px;
1294
1297
  }
@@ -2475,6 +2478,12 @@
2475
2478
  .bg-surface-container-high {
2476
2479
  background-color: var(--color-surface-container-high);
2477
2480
  }
2481
+ .bg-surface-container-high\/40 {
2482
+ background-color: var(--color-surface-container-high);
2483
+ @supports (color: color-mix(in lab, red, red)) {
2484
+ background-color: color-mix(in oklab, var(--color-surface-container-high) 40%, transparent);
2485
+ }
2486
+ }
2478
2487
  .bg-surface-container-high\/60 {
2479
2488
  background-color: var(--color-surface-container-high);
2480
2489
  @supports (color: color-mix(in lab, red, red)) {
@@ -2826,6 +2835,9 @@
2826
2835
  .pl-9 {
2827
2836
  padding-left: calc(var(--spacing) * 9);
2828
2837
  }
2838
+ .pl-11 {
2839
+ padding-left: calc(var(--spacing) * 11);
2840
+ }
2829
2841
  .text-center {
2830
2842
  text-align: center;
2831
2843
  }
@@ -2841,6 +2853,9 @@
2841
2853
  .align-text-bottom {
2842
2854
  vertical-align: text-bottom;
2843
2855
  }
2856
+ .align-top {
2857
+ vertical-align: top;
2858
+ }
2844
2859
  .font-mono {
2845
2860
  font-family: var(--font-mono);
2846
2861
  }
@@ -3821,6 +3836,12 @@
3821
3836
  border-width: 0px;
3822
3837
  }
3823
3838
  }
3839
+ .last\:border-b-0 {
3840
+ &:last-child {
3841
+ border-bottom-style: var(--tw-border-style);
3842
+ border-bottom-width: 0px;
3843
+ }
3844
+ }
3824
3845
  .focus-within\:border-\[var\(--border-accent-hover\)\] {
3825
3846
  &:focus-within {
3826
3847
  border-color: var(--border-accent-hover);
package/dist/hooks.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  import {
22
22
  useSessionStream,
23
23
  useSidecarAuth
24
- } from "./chunk-MEDE37J5.js";
24
+ } from "./chunk-QORIZARI.js";
25
25
  import {
26
26
  usePtySession
27
27
  } from "./chunk-77WVVJA4.js";
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  import {
22
22
  useSessionStream,
23
23
  useSidecarAuth
24
- } from "./chunk-MEDE37J5.js";
24
+ } from "./chunk-QORIZARI.js";
25
25
  import {
26
26
  activeSessionsAtom,
27
27
  addMessage,
@@ -214,10 +214,6 @@ import {
214
214
  parseInsufficientBalance,
215
215
  useSidebar
216
216
  } from "./chunk-H237JF3H.js";
217
- import "./chunk-VIREDUBF.js";
218
- import {
219
- usePtySession
220
- } from "./chunk-77WVVJA4.js";
221
217
  import {
222
218
  BillingDashboard,
223
219
  InfoPanel,
@@ -226,6 +222,10 @@ import {
226
222
  UsageChart,
227
223
  formatPrice
228
224
  } from "./chunk-OMUIPJQZ.js";
225
+ import "./chunk-VIREDUBF.js";
226
+ import {
227
+ usePtySession
228
+ } from "./chunk-77WVVJA4.js";
229
229
  import {
230
230
  Avatar,
231
231
  AvatarFallback,
package/dist/sdk-hooks.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  useSessionStream,
3
3
  useSidecarAuth
4
- } from "./chunk-MEDE37J5.js";
4
+ } from "./chunk-QORIZARI.js";
5
5
  import "./chunk-U67V476Y.js";
6
6
 
7
7
  // src/sdk-hooks.ts
package/dist/styles.css CHANGED
@@ -1289,6 +1289,9 @@
1289
1289
  .min-w-\[12rem\] {
1290
1290
  min-width: 12rem;
1291
1291
  }
1292
+ .min-w-\[15rem\] {
1293
+ min-width: 15rem;
1294
+ }
1292
1295
  .min-w-\[20px\] {
1293
1296
  min-width: 20px;
1294
1297
  }
@@ -2475,6 +2478,12 @@
2475
2478
  .bg-surface-container-high {
2476
2479
  background-color: var(--color-surface-container-high);
2477
2480
  }
2481
+ .bg-surface-container-high\/40 {
2482
+ background-color: var(--color-surface-container-high);
2483
+ @supports (color: color-mix(in lab, red, red)) {
2484
+ background-color: color-mix(in oklab, var(--color-surface-container-high) 40%, transparent);
2485
+ }
2486
+ }
2478
2487
  .bg-surface-container-high\/60 {
2479
2488
  background-color: var(--color-surface-container-high);
2480
2489
  @supports (color: color-mix(in lab, red, red)) {
@@ -2826,6 +2835,9 @@
2826
2835
  .pl-9 {
2827
2836
  padding-left: calc(var(--spacing) * 9);
2828
2837
  }
2838
+ .pl-11 {
2839
+ padding-left: calc(var(--spacing) * 11);
2840
+ }
2829
2841
  .text-center {
2830
2842
  text-align: center;
2831
2843
  }
@@ -2841,6 +2853,9 @@
2841
2853
  .align-text-bottom {
2842
2854
  vertical-align: text-bottom;
2843
2855
  }
2856
+ .align-top {
2857
+ vertical-align: top;
2858
+ }
2844
2859
  .font-mono {
2845
2860
  font-family: var(--font-mono);
2846
2861
  }
@@ -3821,6 +3836,12 @@
3821
3836
  border-width: 0px;
3822
3837
  }
3823
3838
  }
3839
+ .last\:border-b-0 {
3840
+ &:last-child {
3841
+ border-bottom-style: var(--tw-border-style);
3842
+ border-bottom-width: 0px;
3843
+ }
3844
+ }
3824
3845
  .focus-within\:border-\[var\(--border-accent-hover\)\] {
3825
3846
  &:focus-within {
3826
3847
  border-color: var(--border-accent-hover);
package/dist/workbench.js CHANGED
@@ -15,9 +15,9 @@ import {
15
15
  } from "./chunk-HCOZO53G.js";
16
16
  import "./chunk-3J6FG3FJ.js";
17
17
  import "./chunk-H237JF3H.js";
18
+ import "./chunk-OMUIPJQZ.js";
18
19
  import "./chunk-VIREDUBF.js";
19
20
  import "./chunk-77WVVJA4.js";
20
- import "./chunk-OMUIPJQZ.js";
21
21
  import "./chunk-4ZZUMXPX.js";
22
22
  import "./chunk-2T6KBMLN.js";
23
23
  import "./chunk-R4AR3TKR.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.84.0",
3
+ "version": "0.85.1",
4
4
  "description": "Unified UI component library for Tangle Sandbox \u2014 primitives, chat, dashboard, terminal, editor, and workspace components",
5
5
  "repository": {
6
6
  "type": "git",
@@ -74,6 +74,10 @@
74
74
  "import": "./dist/integrations.js",
75
75
  "types": "./dist/integrations.d.ts"
76
76
  },
77
+ "./connectors": {
78
+ "import": "./dist/connectors.js",
79
+ "types": "./dist/connectors.d.ts"
80
+ },
77
81
  "./pages": {
78
82
  "import": "./dist/pages.js",
79
83
  "types": "./dist/pages.d.ts"