@tangle-network/sandbox-ui 0.16.3 → 0.17.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/auth.d.ts CHANGED
@@ -1 +1,30 @@
1
1
  export { AuthHeader, AuthHeaderProps, GitHubLoginButton, GitHubLoginButtonProps, LoginLayout, LoginLayoutProps, SessionUser, UserMenu, UserMenuProps } from '@tangle-network/ui/auth';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
+ import { ButtonProps } from '@tangle-network/ui/primitives';
4
+
5
+ interface TangleLoginButtonProps extends Omit<ButtonProps, "onClick"> {
6
+ /**
7
+ * Consumer-app endpoint that starts the cross-site SSO flow. The
8
+ * consumer's server is expected to redirect this request to the
9
+ * platform's `/cross-site/authorize` URL (built via
10
+ * `PlatformAuthClient.authorizeUrl` from
11
+ * `@tangle-network/agent-runtime/platform`). Defaults to
12
+ * `/auth/tangle`.
13
+ */
14
+ authUrl?: string;
15
+ /** Product variant for styling. */
16
+ variant?: "sandbox" | "default" | "outline";
17
+ }
18
+ /**
19
+ * "Login with Tangle" button — kicks off the cross-site SSO bridge.
20
+ * Server-side, the consumer app should:
21
+ * 1. Generate a `state` for CSRF.
22
+ * 2. Persist `state` in a session cookie or signed JWT.
23
+ * 3. 302-redirect to `PlatformAuthClient.authorizeUrl({ state, redirectUri })`.
24
+ *
25
+ * This component itself only triggers the redirect to `authUrl`; the
26
+ * server owns the platform URL construction.
27
+ */
28
+ declare function TangleLoginButton({ authUrl, variant, className, children, ...props }: TangleLoginButtonProps): react_jsx_runtime.JSX.Element;
29
+
30
+ export { TangleLoginButton, type TangleLoginButtonProps };
package/dist/auth.js CHANGED
@@ -2,11 +2,13 @@ import {
2
2
  AuthHeader,
3
3
  GitHubLoginButton,
4
4
  LoginLayout,
5
+ TangleLoginButton,
5
6
  UserMenu
6
- } from "./chunk-NJNME4J4.js";
7
+ } from "./chunk-IOB2PW5Z.js";
7
8
  export {
8
9
  AuthHeader,
9
10
  GitHubLoginButton,
10
11
  LoginLayout,
12
+ TangleLoginButton,
11
13
  UserMenu
12
14
  };
@@ -0,0 +1,55 @@
1
+ // src/auth/index.ts
2
+ import {
3
+ AuthHeader,
4
+ GitHubLoginButton,
5
+ LoginLayout,
6
+ UserMenu
7
+ } from "@tangle-network/ui/auth";
8
+
9
+ // src/auth/tangle-login-button.tsx
10
+ import { Button } from "@tangle-network/ui/primitives";
11
+ import { cn } from "@tangle-network/ui/utils";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ function TangleMark({ className }) {
14
+ return /* @__PURE__ */ jsx(
15
+ "svg",
16
+ {
17
+ className,
18
+ viewBox: "0 0 24 24",
19
+ fill: "currentColor",
20
+ "aria-hidden": "true",
21
+ children: /* @__PURE__ */ jsx("path", { d: "M4 5h16v3.2h-6.4V19h-3.2V8.2H4V5z" })
22
+ }
23
+ );
24
+ }
25
+ function TangleLoginButton({
26
+ authUrl = "/auth/tangle",
27
+ variant = "default",
28
+ className,
29
+ children,
30
+ ...props
31
+ }) {
32
+ return /* @__PURE__ */ jsxs(
33
+ Button,
34
+ {
35
+ variant,
36
+ className: cn("gap-2", className),
37
+ onClick: () => {
38
+ window.location.href = authUrl;
39
+ },
40
+ ...props,
41
+ children: [
42
+ /* @__PURE__ */ jsx(TangleMark, { className: "h-5 w-5" }),
43
+ children ?? "Sign in with Tangle"
44
+ ]
45
+ }
46
+ );
47
+ }
48
+
49
+ export {
50
+ TangleLoginButton,
51
+ AuthHeader,
52
+ GitHubLoginButton,
53
+ LoginLayout,
54
+ UserMenu
55
+ };
package/dist/globals.css CHANGED
@@ -1020,6 +1020,13 @@
1020
1020
  .gap-px {
1021
1021
  gap: 1px;
1022
1022
  }
1023
+ .space-y-0 {
1024
+ :where(& > :not(:last-child)) {
1025
+ --tw-space-y-reverse: 0;
1026
+ margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));
1027
+ margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)));
1028
+ }
1029
+ }
1023
1030
  .space-y-0\.5 {
1024
1031
  :where(& > :not(:last-child)) {
1025
1032
  --tw-space-y-reverse: 0;
@@ -1576,6 +1583,9 @@
1576
1583
  .py-5 {
1577
1584
  padding-block: calc(var(--spacing) * 5);
1578
1585
  }
1586
+ .py-6 {
1587
+ padding-block: calc(var(--spacing) * 6);
1588
+ }
1579
1589
  .py-8 {
1580
1590
  padding-block: calc(var(--spacing) * 8);
1581
1591
  }
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export { FileArtifactPane, FileArtifactPaneProps, FileNode, FilePreview, FilePre
10
10
  export { Backend, BackendConfig, BackendConfigProps, BackendSelector, BackendSelectorProps, BackendStatusData, ClusterStatusBar, ClusterStatusBarProps, ClusterStatusItem, CreditBalance, CreditBalanceProps, DashboardLayout, DashboardLayoutProps, DashboardProfile, DashboardSnapshotInfo, DashboardUser, ExposedPort, GitCommitData, GitPanel, GitPanelProps, GitStatusData, HARNESS_OPTIONS, HarnessPicker, HarnessPickerProps, HarnessType, InfoPanel, InfoPanelProps, Invoice, InvoiceTable, InvoiceTableProps, McpServer, NavItem, NetworkConfig, NetworkConfigData, NetworkConfigProps, NewSandboxCard, NewSandboxCardProps, PlanCardData, PlanCards, PlanCardsProps, PlanFeature, PortsList, PortsListProps, ProcessInfo, ProcessList, ProcessListProps, ProductVariant, ProfileAvatar, ProfileAvatarProps, ProfileComparison, ProfileComparisonProps, ProfileSelector, ProfileSelectorProps, PromoBanner, PromoBannerProps, RailButton, RailButtonProps, RailModeButton, RailModeButtonProps, RailSeparator, RailSeparatorProps, ResourceMeter, ResourceMeterProps, SIDEBAR_MOBILE_WIDTH, SIDEBAR_PANEL_WIDTH, SIDEBAR_RAIL_WIDTH, SIDEBAR_TOTAL_WIDTH, SandboxCard, SandboxCardData, SandboxCardProps, SandboxStatus, SandboxTable, SandboxTableProps, Sidebar, SidebarContent, SidebarContentProps, SidebarPanel, SidebarPanelContent, SidebarPanelContentProps, SidebarPanelHeader, SidebarPanelHeaderProps, SidebarPanelProps, SidebarProps, SidebarProvider, SidebarProviderProps, SidebarRail, SidebarRailFooter, SidebarRailFooterProps, SidebarRailHeader, SidebarRailHeaderProps, SidebarRailNav, SidebarRailNavProps, SidebarRailProps, SidebarUser, SnapshotList, SnapshotListProps, SystemLogsViewer, SystemLogsViewerProps, TeamRole, UsageSummary, UsageSummaryData, UsageSummaryProps, Variant, VariantList, VariantListProps, VariantOutcome, VariantStatus, canAdminSandbox, useSidebar } from './dashboard.js';
11
11
  export { B as BillingBalance, a as BillingDashboard, b as BillingDashboardProps, c as BillingSubscription, d as BillingUsage, M as ModelInfo, e as ModelPicker, f as ModelPickerProps, g as ModelPickerVariant, P as PricingPage, h as PricingPageProps, i as PricingTier, T as TemplateCard, j as TemplateCardData, k as TemplateCardProps, U as UsageChart, l as UsageChartProps, m as UsageDataPoint, n as canonicalModelId, o as formatContext, p as formatPrice, q as formatPricing } from './template-card-Dufxl4hV.js';
12
12
  export { AuthHeader, AuthHeaderProps, GitHubLoginButton, GitHubLoginButtonProps, LoginLayout, LoginLayoutProps, SessionUser, UserMenu, UserMenuProps } from '@tangle-network/ui/auth';
13
+ export { TangleLoginButton, TangleLoginButtonProps } from './auth.js';
13
14
  export { AgentStreamEvent, AppendUserMessageOptions, ApplySdkEventOptions, AuthUser, AutomationStreamEvent, BeginAssistantMessageOptions, BotStreamEvent, CompleteAssistantMessageOptions, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, TaskStreamEvent, TerminalStreamEvent, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, createAuthFetcher, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useLiveTime, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useToolCallStream } from '@tangle-network/ui/hooks';
14
15
  export { SandboxMetrics, SidecarMetricsPayload, UsePtySessionOptions, UsePtySessionReturn, UseSandboxMetricsOptions, UseSandboxMetricsResult, useCreateSession, useDeleteSession, usePtySession, useRenameSession, useSandboxMetrics, useSessions } from './hooks.js';
15
16
  export { SessionInfo, SidecarAuth, UseSessionStreamOptions, UseSessionStreamResult, UseSidecarAuthOptions, useSessionStream, useSidecarAuth } from './sdk-hooks.js';
package/dist/index.js CHANGED
@@ -1,3 +1,43 @@
1
+ import {
2
+ activeSessionsAtom,
3
+ addMessage,
4
+ addParts,
5
+ bumpActiveSessionActivity,
6
+ clearChat,
7
+ connectSession,
8
+ disconnectSession,
9
+ getActiveSession,
10
+ getAllActiveSessions,
11
+ getAllProjectActivity,
12
+ getSessionsByActivity,
13
+ getSessionsForNavbar,
14
+ getSessionsForProject,
15
+ getTotalRunningSessionCount,
16
+ hasBackgroundRunningSessions,
17
+ isStreamingAtom,
18
+ messagesAtom,
19
+ partMapAtom,
20
+ registerActiveSession,
21
+ resetActiveSessions,
22
+ sessionAtom,
23
+ setActiveSessionAttention,
24
+ setActiveSessionConnection,
25
+ setActiveSessionError,
26
+ setActiveSessionRunning,
27
+ setForegroundActiveSession,
28
+ unregisterActiveSession,
29
+ updateActiveSessionMeta,
30
+ updatePart,
31
+ useActiveSession,
32
+ useActiveSessions,
33
+ useActiveSessionsState,
34
+ useHasBackgroundRunningSessions,
35
+ useNavbarSessions,
36
+ useProjectActivity,
37
+ useProjectSessions,
38
+ useSessionsByActivity,
39
+ useTotalRunningSessions
40
+ } from "./chunk-WID73FPH.js";
1
41
  import "./chunk-2BUPSB7O.js";
2
42
  import {
3
43
  TOOL_CATEGORY_ICONS,
@@ -16,8 +56,9 @@ import {
16
56
  AuthHeader,
17
57
  GitHubLoginButton,
18
58
  LoginLayout,
59
+ TangleLoginButton,
19
60
  UserMenu
20
- } from "./chunk-NJNME4J4.js";
61
+ } from "./chunk-IOB2PW5Z.js";
21
62
  import {
22
63
  RealtimeSessionRegistry,
23
64
  createAuthFetcher,
@@ -45,46 +86,6 @@ import {
45
86
  useSessionStream,
46
87
  useSidecarAuth
47
88
  } from "./chunk-CMY7W45U.js";
48
- import {
49
- activeSessionsAtom,
50
- addMessage,
51
- addParts,
52
- bumpActiveSessionActivity,
53
- clearChat,
54
- connectSession,
55
- disconnectSession,
56
- getActiveSession,
57
- getAllActiveSessions,
58
- getAllProjectActivity,
59
- getSessionsByActivity,
60
- getSessionsForNavbar,
61
- getSessionsForProject,
62
- getTotalRunningSessionCount,
63
- hasBackgroundRunningSessions,
64
- isStreamingAtom,
65
- messagesAtom,
66
- partMapAtom,
67
- registerActiveSession,
68
- resetActiveSessions,
69
- sessionAtom,
70
- setActiveSessionAttention,
71
- setActiveSessionConnection,
72
- setActiveSessionError,
73
- setActiveSessionRunning,
74
- setForegroundActiveSession,
75
- unregisterActiveSession,
76
- updateActiveSessionMeta,
77
- updatePart,
78
- useActiveSession,
79
- useActiveSessions,
80
- useActiveSessionsState,
81
- useHasBackgroundRunningSessions,
82
- useNavbarSessions,
83
- useProjectActivity,
84
- useProjectSessions,
85
- useSessionsByActivity,
86
- useTotalRunningSessions
87
- } from "./chunk-WID73FPH.js";
88
89
  import {
89
90
  AgentTimeline,
90
91
  ChatContainer,
@@ -453,6 +454,7 @@ export {
453
454
  TabsList,
454
455
  TabsTrigger,
455
456
  TangleKnot,
457
+ TangleLoginButton,
456
458
  TaskBoard,
457
459
  TemplateCard,
458
460
  TerminalDisplay,
@@ -0,0 +1,114 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * Shapes for the integrations primitives. Mirrors the platform's
5
+ * `/v1/integrations/*` response shape so consumers can pipe payloads
6
+ * straight through. Defined here (rather than imported from
7
+ * `@tangle-network/agent-runtime/platform`) so the UI package stays
8
+ * leaf-level — no dependency on the server-side client.
9
+ */
10
+ interface IntegrationConnection {
11
+ id: string;
12
+ providerId: string;
13
+ connectorId: string;
14
+ status: "connected" | "pending" | "revoked" | "expired" | (string & {});
15
+ grantedScopes?: string[];
16
+ account?: {
17
+ identity?: string;
18
+ displayName?: string;
19
+ } & Record<string, unknown>;
20
+ expiresAt?: string | null;
21
+ createdAt?: string;
22
+ updatedAt?: string;
23
+ }
24
+ interface IntegrationConnector {
25
+ connectorId: string;
26
+ displayName?: string;
27
+ description?: string;
28
+ scopes?: string[];
29
+ }
30
+ interface IntegrationProvider {
31
+ providerId: string;
32
+ displayName?: string;
33
+ description?: string;
34
+ iconUrl?: string;
35
+ connectors?: IntegrationConnector[];
36
+ }
37
+ interface IntegrationHealth {
38
+ connectionId: string;
39
+ status: "ok" | "degraded" | "failing" | "unknown" | (string & {});
40
+ checkedAt?: string;
41
+ message?: string;
42
+ }
43
+
44
+ interface IntegrationsPanelProps {
45
+ catalog: IntegrationProvider[];
46
+ connections: IntegrationConnection[];
47
+ healthByConnectionId?: Record<string, IntegrationHealth>;
48
+ isLoading?: boolean;
49
+ error?: Error | null;
50
+ /**
51
+ * Invoked when the user clicks "Connect" on a catalog tile. The
52
+ * consumer should call its data hook's `connect(...)` action.
53
+ */
54
+ onConnect: (input: {
55
+ providerId: string;
56
+ connectorId: string;
57
+ }) => void | Promise<void>;
58
+ /** Invoked when the user clicks "Disconnect" on a live connection. */
59
+ onDisconnect: (connectionId: string) => void | Promise<void>;
60
+ /** Empty-state message when the catalog hasn't loaded any providers. */
61
+ emptyCatalogLabel?: string;
62
+ className?: string;
63
+ }
64
+ declare function IntegrationsPanel({ catalog, connections, healthByConnectionId, isLoading, error, onConnect, onDisconnect, emptyCatalogLabel, className, }: IntegrationsPanelProps): react_jsx_runtime.JSX.Element;
65
+
66
+ /**
67
+ * Endpoint contract expected on the consumer app's server (which
68
+ * wraps `PlatformHubClient` from `@tangle-network/agent-runtime/platform`):
69
+ *
70
+ * GET {base}/catalog
71
+ * → { catalog: { providers: IntegrationProvider[] } }
72
+ * GET {base}/connections
73
+ * → { connections: IntegrationConnection[] }
74
+ * GET {base}/healthchecks (optional)
75
+ * → { healthchecks: IntegrationHealth[] }
76
+ * POST {base}/auth/start
77
+ * body { providerId, connectorId, returnUrl, requestedScopes? }
78
+ * → { authorizationUrl: string }
79
+ * DELETE {base}/connections/{connectionId}
80
+ * → { connection: IntegrationConnection }
81
+ */
82
+ interface UseIntegrationsOptions {
83
+ /** Base URL where the consumer mounted the integrations endpoints. */
84
+ apiBaseUrl: string;
85
+ /** Custom fetch (tests / non-browser runtimes). */
86
+ fetchImpl?: typeof fetch;
87
+ /** Whether the initial load happens automatically on mount. */
88
+ autoLoad?: boolean;
89
+ }
90
+ interface UseIntegrationsResult {
91
+ catalog: IntegrationProvider[];
92
+ connections: IntegrationConnection[];
93
+ healthByConnectionId: Record<string, IntegrationHealth>;
94
+ isLoading: boolean;
95
+ error: Error | null;
96
+ refresh: () => Promise<void>;
97
+ /** Kick off OAuth — navigates the window on success. */
98
+ connect: (input: ConnectInput) => Promise<void>;
99
+ /** Revoke a connection by id; refreshes the connections list. */
100
+ disconnect: (connectionId: string) => Promise<void>;
101
+ }
102
+ interface ConnectInput {
103
+ providerId: string;
104
+ connectorId: string;
105
+ /**
106
+ * URL the platform redirects the user back to after OAuth. Must be
107
+ * allow-listed on the platform.
108
+ */
109
+ returnUrl: string;
110
+ requestedScopes?: string[];
111
+ }
112
+ declare function useIntegrations({ apiBaseUrl, fetchImpl, autoLoad, }: UseIntegrationsOptions): UseIntegrationsResult;
113
+
114
+ export { type ConnectInput, type IntegrationConnection, type IntegrationConnector, type IntegrationHealth, type IntegrationProvider, IntegrationsPanel, type IntegrationsPanelProps, type UseIntegrationsOptions, type UseIntegrationsResult, useIntegrations };
@@ -0,0 +1,245 @@
1
+ // src/integrations/integrations-panel.tsx
2
+ import * as React from "react";
3
+ import {
4
+ Badge,
5
+ Button,
6
+ Card,
7
+ CardContent,
8
+ CardHeader,
9
+ EmptyState
10
+ } from "@tangle-network/ui/primitives";
11
+ import { cn } from "@tangle-network/ui/utils";
12
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
+ function statusVariant(status) {
14
+ if (status === "connected" || status === "ok") return "default";
15
+ if (status === "pending") return "secondary";
16
+ if (status === "revoked" || status === "expired" || status === "failing")
17
+ return "destructive";
18
+ return "outline";
19
+ }
20
+ function defaultConnectorOf(provider) {
21
+ return provider.connectors?.[0]?.connectorId ?? provider.providerId;
22
+ }
23
+ function buildConnectionIndex(connections) {
24
+ const index = /* @__PURE__ */ new Map();
25
+ for (const conn of connections) {
26
+ if (conn.status === "revoked") continue;
27
+ index.set(`${conn.providerId}:${conn.connectorId}`, conn);
28
+ }
29
+ return index;
30
+ }
31
+ function IntegrationsPanel({
32
+ catalog,
33
+ connections,
34
+ healthByConnectionId,
35
+ isLoading,
36
+ error,
37
+ onConnect,
38
+ onDisconnect,
39
+ emptyCatalogLabel = "No integrations available yet.",
40
+ className
41
+ }) {
42
+ const connectionIndex = React.useMemo(
43
+ () => buildConnectionIndex(connections),
44
+ [connections]
45
+ );
46
+ if (error) {
47
+ return /* @__PURE__ */ jsx(Card, { className: cn("border-destructive/50", className), children: /* @__PURE__ */ jsx(CardContent, { className: "py-6", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive", children: [
48
+ "Failed to load integrations: ",
49
+ error.message
50
+ ] }) }) });
51
+ }
52
+ if (isLoading && catalog.length === 0) {
53
+ return /* @__PURE__ */ jsx("div", { className: cn("grid gap-3 sm:grid-cols-2 lg:grid-cols-3", className), children: [0, 1, 2, 3].map((i) => /* @__PURE__ */ jsxs(Card, { className: "animate-pulse", children: [
54
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx("div", { className: "h-5 w-32 rounded bg-muted" }) }),
55
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("div", { className: "h-4 w-full rounded bg-muted" }) })
56
+ ] }, i)) });
57
+ }
58
+ if (catalog.length === 0) {
59
+ return /* @__PURE__ */ jsx(
60
+ EmptyState,
61
+ {
62
+ title: "No integrations",
63
+ description: emptyCatalogLabel,
64
+ className
65
+ }
66
+ );
67
+ }
68
+ return /* @__PURE__ */ jsx("div", { className: cn("grid gap-3 sm:grid-cols-2 lg:grid-cols-3", className), children: catalog.map((provider) => {
69
+ const connectorId = defaultConnectorOf(provider);
70
+ const live = connectionIndex.get(`${provider.providerId}:${connectorId}`);
71
+ const health = live ? healthByConnectionId?.[live.id] : void 0;
72
+ const headline = provider.displayName ?? provider.providerId.replace(/[-_]/g, " ");
73
+ return /* @__PURE__ */ jsxs(
74
+ Card,
75
+ {
76
+ "data-testid": `integration-${provider.providerId}`,
77
+ children: [
78
+ /* @__PURE__ */ jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-2 space-y-0", children: [
79
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
80
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
81
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold capitalize text-foreground", children: headline }),
82
+ live ? /* @__PURE__ */ jsx(Badge, { variant: statusVariant(live.status), className: "text-xs", children: live.status }) : null
83
+ ] }),
84
+ provider.description ? /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-xs text-muted-foreground", children: provider.description }) : null
85
+ ] }),
86
+ health ? /* @__PURE__ */ jsx(
87
+ Badge,
88
+ {
89
+ variant: statusVariant(health.status),
90
+ className: "text-xs uppercase",
91
+ children: health.status
92
+ }
93
+ ) : null
94
+ ] }),
95
+ /* @__PURE__ */ jsx(CardContent, { className: "flex items-center justify-between gap-2", children: live ? /* @__PURE__ */ jsxs(Fragment, { children: [
96
+ /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: live.account?.displayName ?? live.account?.identity ?? "Connected" }),
97
+ /* @__PURE__ */ jsx(
98
+ Button,
99
+ {
100
+ size: "sm",
101
+ variant: "outline",
102
+ onClick: () => onDisconnect(live.id),
103
+ "data-testid": `disconnect-${provider.providerId}`,
104
+ children: "Disconnect"
105
+ }
106
+ )
107
+ ] }) : /* @__PURE__ */ jsx(
108
+ Button,
109
+ {
110
+ size: "sm",
111
+ className: "ml-auto",
112
+ onClick: () => onConnect({ providerId: provider.providerId, connectorId }),
113
+ "data-testid": `connect-${provider.providerId}`,
114
+ children: "Connect"
115
+ }
116
+ ) })
117
+ ]
118
+ },
119
+ `${provider.providerId}:${connectorId}`
120
+ );
121
+ }) });
122
+ }
123
+
124
+ // src/integrations/use-integrations.ts
125
+ import * as React2 from "react";
126
+ function unwrap(json) {
127
+ if (json && typeof json === "object" && "data" in json && json.data !== void 0) {
128
+ return json.data;
129
+ }
130
+ return json;
131
+ }
132
+ function useIntegrations({
133
+ apiBaseUrl,
134
+ fetchImpl,
135
+ autoLoad = true
136
+ }) {
137
+ const fetcher = fetchImpl ?? (typeof fetch === "function" ? fetch : null);
138
+ if (!fetcher) {
139
+ throw new Error("useIntegrations: fetch is not available in this environment");
140
+ }
141
+ const base = apiBaseUrl.replace(/\/+$/, "");
142
+ const [catalog, setCatalog] = React2.useState([]);
143
+ const [connections, setConnections] = React2.useState([]);
144
+ const [healthByConnectionId, setHealthByConnectionId] = React2.useState({});
145
+ const [isLoading, setIsLoading] = React2.useState(autoLoad);
146
+ const [error, setError] = React2.useState(null);
147
+ const refresh = React2.useCallback(async () => {
148
+ setIsLoading(true);
149
+ setError(null);
150
+ try {
151
+ const [catalogRes, connectionsRes] = await Promise.all([
152
+ fetcher(`${base}/catalog`, { credentials: "include" }),
153
+ fetcher(`${base}/connections`, { credentials: "include" })
154
+ ]);
155
+ if (!catalogRes.ok) {
156
+ throw new Error(`Failed to load integration catalog (${catalogRes.status})`);
157
+ }
158
+ if (!connectionsRes.ok) {
159
+ throw new Error(
160
+ `Failed to load integration connections (${connectionsRes.status})`
161
+ );
162
+ }
163
+ const catalogJson = unwrap(await catalogRes.json());
164
+ const providers = catalogJson?.catalog?.providers ?? catalogJson?.providers ?? [];
165
+ setCatalog(providers);
166
+ const connectionsJson = unwrap(
167
+ await connectionsRes.json()
168
+ );
169
+ setConnections(connectionsJson?.connections ?? []);
170
+ try {
171
+ const healthRes = await fetcher(`${base}/healthchecks`, {
172
+ credentials: "include"
173
+ });
174
+ if (healthRes.ok) {
175
+ const healthJson = unwrap(
176
+ await healthRes.json()
177
+ );
178
+ const map = {};
179
+ for (const h of healthJson?.healthchecks ?? []) {
180
+ map[h.connectionId] = h;
181
+ }
182
+ setHealthByConnectionId(map);
183
+ }
184
+ } catch {
185
+ }
186
+ } catch (err) {
187
+ setError(err instanceof Error ? err : new Error(String(err)));
188
+ } finally {
189
+ setIsLoading(false);
190
+ }
191
+ }, [base, fetcher]);
192
+ React2.useEffect(() => {
193
+ if (autoLoad) {
194
+ void refresh();
195
+ }
196
+ }, [autoLoad, refresh]);
197
+ const connect = React2.useCallback(
198
+ async (input) => {
199
+ const res = await fetcher(`${base}/auth/start`, {
200
+ method: "POST",
201
+ credentials: "include",
202
+ headers: { "content-type": "application/json" },
203
+ body: JSON.stringify(input)
204
+ });
205
+ if (!res.ok) {
206
+ const text = await res.text().catch(() => "");
207
+ throw new Error(`Failed to start OAuth (${res.status}): ${text}`);
208
+ }
209
+ const json = unwrap(await res.json());
210
+ if (!json?.authorizationUrl) {
211
+ throw new Error("Platform did not return an authorizationUrl");
212
+ }
213
+ window.location.href = json.authorizationUrl;
214
+ },
215
+ [base, fetcher]
216
+ );
217
+ const disconnect = React2.useCallback(
218
+ async (connectionId) => {
219
+ const res = await fetcher(
220
+ `${base}/connections/${encodeURIComponent(connectionId)}`,
221
+ { method: "DELETE", credentials: "include" }
222
+ );
223
+ if (!res.ok) {
224
+ const text = await res.text().catch(() => "");
225
+ throw new Error(`Failed to revoke connection (${res.status}): ${text}`);
226
+ }
227
+ await refresh();
228
+ },
229
+ [base, fetcher, refresh]
230
+ );
231
+ return {
232
+ catalog,
233
+ connections,
234
+ healthByConnectionId,
235
+ isLoading,
236
+ error,
237
+ refresh,
238
+ connect,
239
+ disconnect
240
+ };
241
+ }
242
+ export {
243
+ IntegrationsPanel,
244
+ useIntegrations
245
+ };
package/dist/openui.d.ts CHANGED
@@ -2,7 +2,6 @@ export { OpenUIAction, OpenUIActionsNode, OpenUIArtifactRenderer, OpenUIArtifact
2
2
  import '@tangle-network/ui/chat';
3
3
  import '@tangle-network/ui/run';
4
4
  import '@tangle-network/ui/files';
5
- import '@tangle-network/ui/auth';
6
5
  import '@tangle-network/ui/utils';
7
6
  import '@tangle-network/ui/editor';
8
7
  import '@tangle-network/ui/markdown';
package/dist/styles.css CHANGED
@@ -1020,6 +1020,13 @@
1020
1020
  .gap-px {
1021
1021
  gap: 1px;
1022
1022
  }
1023
+ .space-y-0 {
1024
+ :where(& > :not(:last-child)) {
1025
+ --tw-space-y-reverse: 0;
1026
+ margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));
1027
+ margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)));
1028
+ }
1029
+ }
1023
1030
  .space-y-0\.5 {
1024
1031
  :where(& > :not(:last-child)) {
1025
1032
  --tw-space-y-reverse: 0;
@@ -1576,6 +1583,9 @@
1576
1583
  .py-5 {
1577
1584
  padding-block: calc(var(--spacing) * 5);
1578
1585
  }
1586
+ .py-6 {
1587
+ padding-block: calc(var(--spacing) * 6);
1588
+ }
1579
1589
  .py-8 {
1580
1590
  padding-block: calc(var(--spacing) * 8);
1581
1591
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.16.3",
3
+ "version": "0.17.0",
4
4
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
5
5
  "repository": {
6
6
  "type": "git",
@@ -62,6 +62,10 @@
62
62
  "import": "./dist/auth.js",
63
63
  "types": "./dist/auth.d.ts"
64
64
  },
65
+ "./integrations": {
66
+ "import": "./dist/integrations.js",
67
+ "types": "./dist/integrations.d.ts"
68
+ },
65
69
  "./pages": {
66
70
  "import": "./dist/pages.js",
67
71
  "types": "./dist/pages.d.ts"
@@ -1,14 +0,0 @@
1
- // src/auth/index.ts
2
- import {
3
- AuthHeader,
4
- GitHubLoginButton,
5
- LoginLayout,
6
- UserMenu
7
- } from "@tangle-network/ui/auth";
8
-
9
- export {
10
- AuthHeader,
11
- GitHubLoginButton,
12
- LoginLayout,
13
- UserMenu
14
- };