@stigmer/react 0.0.36

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.
Files changed (190) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +248 -0
  3. package/agent/components/AgentCard.d.ts +9 -0
  4. package/agent/components/AgentCard.d.ts.map +1 -0
  5. package/agent/components/AgentCard.js +26 -0
  6. package/agent/components/AgentCard.js.map +1 -0
  7. package/agent/components/AgentOverview.d.ts +7 -0
  8. package/agent/components/AgentOverview.d.ts.map +1 -0
  9. package/agent/components/AgentOverview.js +36 -0
  10. package/agent/components/AgentOverview.js.map +1 -0
  11. package/agent/components/AgentPicker.d.ts +17 -0
  12. package/agent/components/AgentPicker.d.ts.map +1 -0
  13. package/agent/components/AgentPicker.js +86 -0
  14. package/agent/components/AgentPicker.js.map +1 -0
  15. package/agent/hooks/useAgentSearch.d.ts +28 -0
  16. package/agent/hooks/useAgentSearch.d.ts.map +1 -0
  17. package/agent/hooks/useAgentSearch.js +63 -0
  18. package/agent/hooks/useAgentSearch.js.map +1 -0
  19. package/agent/index.d.ts +9 -0
  20. package/agent/index.d.ts.map +1 -0
  21. package/agent/index.js +7 -0
  22. package/agent/index.js.map +1 -0
  23. package/agent-execution/components/ApprovalControls.d.ts +10 -0
  24. package/agent-execution/components/ApprovalControls.d.ts.map +1 -0
  25. package/agent-execution/components/ApprovalControls.js +19 -0
  26. package/agent-execution/components/ApprovalControls.js.map +1 -0
  27. package/agent-execution/components/ExecutionStatus.d.ts +8 -0
  28. package/agent-execution/components/ExecutionStatus.d.ts.map +1 -0
  29. package/agent-execution/components/ExecutionStatus.js +14 -0
  30. package/agent-execution/components/ExecutionStatus.js.map +1 -0
  31. package/agent-execution/components/ExecutionStream.d.ts +16 -0
  32. package/agent-execution/components/ExecutionStream.d.ts.map +1 -0
  33. package/agent-execution/components/ExecutionStream.js +39 -0
  34. package/agent-execution/components/ExecutionStream.js.map +1 -0
  35. package/agent-execution/components/MessageEntry.d.ts +17 -0
  36. package/agent-execution/components/MessageEntry.d.ts.map +1 -0
  37. package/agent-execution/components/MessageEntry.js +36 -0
  38. package/agent-execution/components/MessageEntry.js.map +1 -0
  39. package/agent-execution/components/MessageInput.d.ts +10 -0
  40. package/agent-execution/components/MessageInput.d.ts.map +1 -0
  41. package/agent-execution/components/MessageInput.js +27 -0
  42. package/agent-execution/components/MessageInput.js.map +1 -0
  43. package/agent-execution/components/OutputBlock.d.ts +9 -0
  44. package/agent-execution/components/OutputBlock.d.ts.map +1 -0
  45. package/agent-execution/components/OutputBlock.js +15 -0
  46. package/agent-execution/components/OutputBlock.js.map +1 -0
  47. package/agent-execution/components/SubAgentCard.d.ts +11 -0
  48. package/agent-execution/components/SubAgentCard.d.ts.map +1 -0
  49. package/agent-execution/components/SubAgentCard.js +19 -0
  50. package/agent-execution/components/SubAgentCard.js.map +1 -0
  51. package/agent-execution/components/ToolCallCard.d.ts +11 -0
  52. package/agent-execution/components/ToolCallCard.d.ts.map +1 -0
  53. package/agent-execution/components/ToolCallCard.js +25 -0
  54. package/agent-execution/components/ToolCallCard.js.map +1 -0
  55. package/agent-execution/helpers.d.ts +35 -0
  56. package/agent-execution/helpers.d.ts.map +1 -0
  57. package/agent-execution/helpers.js +157 -0
  58. package/agent-execution/helpers.js.map +1 -0
  59. package/agent-execution/hooks/useAgentExecution.d.ts +21 -0
  60. package/agent-execution/hooks/useAgentExecution.d.ts.map +1 -0
  61. package/agent-execution/hooks/useAgentExecution.js +99 -0
  62. package/agent-execution/hooks/useAgentExecution.js.map +1 -0
  63. package/agent-execution/hooks/useApproval.d.ts +12 -0
  64. package/agent-execution/hooks/useApproval.d.ts.map +1 -0
  65. package/agent-execution/hooks/useApproval.js +32 -0
  66. package/agent-execution/hooks/useApproval.js.map +1 -0
  67. package/agent-execution/index.d.ts +14 -0
  68. package/agent-execution/index.d.ts.map +1 -0
  69. package/agent-execution/index.js +15 -0
  70. package/agent-execution/index.js.map +1 -0
  71. package/catalog/components/ResourceSearchCard.d.ts +23 -0
  72. package/catalog/components/ResourceSearchCard.d.ts.map +1 -0
  73. package/catalog/components/ResourceSearchCard.js +36 -0
  74. package/catalog/components/ResourceSearchCard.js.map +1 -0
  75. package/catalog/index.d.ts +4 -0
  76. package/catalog/index.d.ts.map +1 -0
  77. package/catalog/index.js +5 -0
  78. package/catalog/index.js.map +1 -0
  79. package/catalog/internal/time.d.ts +13 -0
  80. package/catalog/internal/time.d.ts.map +1 -0
  81. package/catalog/internal/time.js +41 -0
  82. package/catalog/internal/time.js.map +1 -0
  83. package/context.d.ts +12 -0
  84. package/context.d.ts.map +1 -0
  85. package/context.js +13 -0
  86. package/context.js.map +1 -0
  87. package/hooks.d.ts +19 -0
  88. package/hooks.d.ts.map +1 -0
  89. package/hooks.js +28 -0
  90. package/hooks.js.map +1 -0
  91. package/index.d.ts +4 -0
  92. package/index.d.ts.map +1 -0
  93. package/index.js +6 -0
  94. package/index.js.map +1 -0
  95. package/internal/badge.d.ts +8 -0
  96. package/internal/badge.d.ts.map +1 -0
  97. package/internal/badge.js +34 -0
  98. package/internal/badge.js.map +1 -0
  99. package/internal/button.d.ts +9 -0
  100. package/internal/button.d.ts.map +1 -0
  101. package/internal/button.js +36 -0
  102. package/internal/button.js.map +1 -0
  103. package/internal/collapsible.d.ts +6 -0
  104. package/internal/collapsible.d.ts.map +1 -0
  105. package/internal/collapsible.js +14 -0
  106. package/internal/collapsible.js.map +1 -0
  107. package/internal/section.d.ts +8 -0
  108. package/internal/section.d.ts.map +1 -0
  109. package/internal/section.js +6 -0
  110. package/internal/section.js.map +1 -0
  111. package/internal/textarea.d.ts +4 -0
  112. package/internal/textarea.d.ts.map +1 -0
  113. package/internal/textarea.js +9 -0
  114. package/internal/textarea.js.map +1 -0
  115. package/mcp-server/hooks/useMcpServerSearch.d.ts +25 -0
  116. package/mcp-server/hooks/useMcpServerSearch.d.ts.map +1 -0
  117. package/mcp-server/hooks/useMcpServerSearch.js +57 -0
  118. package/mcp-server/hooks/useMcpServerSearch.js.map +1 -0
  119. package/mcp-server/index.d.ts +3 -0
  120. package/mcp-server/index.d.ts.map +1 -0
  121. package/mcp-server/index.js +3 -0
  122. package/mcp-server/index.js.map +1 -0
  123. package/package.json +75 -0
  124. package/provider.d.ts +55 -0
  125. package/provider.d.ts.map +1 -0
  126. package/provider.js +34 -0
  127. package/provider.js.map +1 -0
  128. package/session/components/AgentSessionHistory.d.ts +8 -0
  129. package/session/components/AgentSessionHistory.d.ts.map +1 -0
  130. package/session/components/AgentSessionHistory.js +11 -0
  131. package/session/components/AgentSessionHistory.js.map +1 -0
  132. package/session/components/SessionCard.d.ts +8 -0
  133. package/session/components/SessionCard.d.ts.map +1 -0
  134. package/session/components/SessionCard.js +57 -0
  135. package/session/components/SessionCard.js.map +1 -0
  136. package/session/hooks/useAgentSessionList.d.ts +21 -0
  137. package/session/hooks/useAgentSessionList.d.ts.map +1 -0
  138. package/session/hooks/useAgentSessionList.js +90 -0
  139. package/session/hooks/useAgentSessionList.js.map +1 -0
  140. package/session/index.d.ts +7 -0
  141. package/session/index.d.ts.map +1 -0
  142. package/session/index.js +6 -0
  143. package/session/index.js.map +1 -0
  144. package/skill/hooks/useSkillSearch.d.ts +25 -0
  145. package/skill/hooks/useSkillSearch.d.ts.map +1 -0
  146. package/skill/hooks/useSkillSearch.js +57 -0
  147. package/skill/hooks/useSkillSearch.js.map +1 -0
  148. package/skill/index.d.ts +3 -0
  149. package/skill/index.d.ts.map +1 -0
  150. package/skill/index.js +3 -0
  151. package/skill/index.js.map +1 -0
  152. package/src/agent/components/AgentCard.tsx +125 -0
  153. package/src/agent/components/AgentOverview.tsx +209 -0
  154. package/src/agent/components/AgentPicker.tsx +255 -0
  155. package/src/agent/hooks/useAgentSearch.ts +94 -0
  156. package/src/agent/index.ts +17 -0
  157. package/src/agent-execution/components/ApprovalControls.tsx +99 -0
  158. package/src/agent-execution/components/ExecutionStatus.tsx +33 -0
  159. package/src/agent-execution/components/ExecutionStream.tsx +148 -0
  160. package/src/agent-execution/components/MessageEntry.tsx +125 -0
  161. package/src/agent-execution/components/MessageInput.tsx +70 -0
  162. package/src/agent-execution/components/OutputBlock.tsx +43 -0
  163. package/src/agent-execution/components/SubAgentCard.tsx +138 -0
  164. package/src/agent-execution/components/ToolCallCard.tsx +153 -0
  165. package/src/agent-execution/helpers.ts +193 -0
  166. package/src/agent-execution/hooks/useAgentExecution.ts +147 -0
  167. package/src/agent-execution/hooks/useApproval.ts +56 -0
  168. package/src/agent-execution/index.ts +46 -0
  169. package/src/catalog/components/ResourceSearchCard.tsx +137 -0
  170. package/src/catalog/index.ts +6 -0
  171. package/src/catalog/internal/time.ts +40 -0
  172. package/src/context.ts +15 -0
  173. package/src/hooks.ts +32 -0
  174. package/src/index.ts +6 -0
  175. package/src/internal/badge.tsx +52 -0
  176. package/src/internal/button.tsx +60 -0
  177. package/src/internal/collapsible.tsx +21 -0
  178. package/src/internal/section.tsx +18 -0
  179. package/src/internal/textarea.tsx +23 -0
  180. package/src/mcp-server/hooks/useMcpServerSearch.ts +79 -0
  181. package/src/mcp-server/index.ts +6 -0
  182. package/src/provider.tsx +73 -0
  183. package/src/session/components/AgentSessionHistory.tsx +109 -0
  184. package/src/session/components/SessionCard.tsx +113 -0
  185. package/src/session/hooks/useAgentSessionList.ts +117 -0
  186. package/src/session/index.ts +13 -0
  187. package/src/skill/hooks/useSkillSearch.ts +79 -0
  188. package/src/skill/index.ts +6 -0
  189. package/src/styles.css +72 -0
  190. package/styles.css +2 -0
@@ -0,0 +1,137 @@
1
+ import type { ReactNode } from "react";
2
+ import type { SearchResult } from "@stigmer/protos/ai/stigmer/search/v1/io_pb";
3
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
4
+ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
5
+ import { Bot, FileCode2, Server, Globe, Box } from "lucide-react";
6
+
7
+ import { cn } from "@stigmer/theme";
8
+
9
+ import { Badge } from "../../internal/badge";
10
+ import { formatRelativeTime, toDate } from "../internal/time";
11
+
12
+ const KIND_ICON: Record<number, ReactNode> = {
13
+ [ApiResourceKind.agent]: <Bot className="text-muted-foreground size-4" />,
14
+ [ApiResourceKind.skill]: <FileCode2 className="text-muted-foreground size-4" />,
15
+ [ApiResourceKind.mcp_server]: <Server className="text-muted-foreground size-4" />,
16
+ };
17
+
18
+ const FALLBACK_ICON = <Box className="text-muted-foreground size-4" />;
19
+
20
+ export interface ResourceSearchCardProps {
21
+ /** The search result to display. */
22
+ result: SearchResult;
23
+ /** When provided, the card renders as an `<a>` element with this URL. */
24
+ href?: string;
25
+ /** Click handler for SPA navigation or custom actions. */
26
+ onClick?: () => void;
27
+ /** Override the auto-detected icon derived from `result.kind`. */
28
+ icon?: ReactNode;
29
+ /** Additional CSS class names on the root element. */
30
+ className?: string;
31
+ }
32
+
33
+ /**
34
+ * A card component for displaying any Stigmer resource search result
35
+ * (agent, skill, MCP server) in a grid catalog layout.
36
+ *
37
+ * Auto-selects the icon based on `result.kind`. Renders as an `<a>` tag
38
+ * when `href` is provided, otherwise as a `<div>`.
39
+ */
40
+ export function ResourceSearchCard({
41
+ result,
42
+ href,
43
+ onClick,
44
+ icon,
45
+ className,
46
+ }: ResourceSearchCardProps) {
47
+ const isPublic =
48
+ result.visibility === ApiResourceVisibility.visibility_public;
49
+
50
+ const resolvedIcon = icon ?? KIND_ICON[result.kind] ?? FALLBACK_ICON;
51
+
52
+ const Tag = href ? "a" : "div";
53
+ const interactive = !!(onClick || href);
54
+
55
+ return (
56
+ <Tag
57
+ href={href}
58
+ onClick={onClick}
59
+ role={interactive ? "button" : undefined}
60
+ tabIndex={interactive ? 0 : undefined}
61
+ onKeyDown={
62
+ interactive
63
+ ? (e: React.KeyboardEvent) => {
64
+ if (onClick && (e.key === "Enter" || e.key === " ")) {
65
+ e.preventDefault();
66
+ onClick();
67
+ }
68
+ }
69
+ : undefined
70
+ }
71
+ className={cn(
72
+ "bg-card text-card-foreground flex flex-col rounded-xl border p-4",
73
+ interactive &&
74
+ "hover:bg-accent/50 focus-visible:ring-ring cursor-pointer transition-colors focus-visible:ring-2 focus-visible:outline-none",
75
+ className,
76
+ )}
77
+ >
78
+ {/* Header: icon + name + visibility */}
79
+ <div className="flex items-start gap-3">
80
+ <div className="bg-muted flex size-9 shrink-0 items-center justify-center rounded-lg">
81
+ {resolvedIcon}
82
+ </div>
83
+ <div className="min-w-0 flex-1">
84
+ <div className="flex items-center gap-2">
85
+ <span className="truncate text-sm font-semibold">
86
+ {result.name}
87
+ </span>
88
+ {isPublic && (
89
+ <Badge variant="outline" className="shrink-0 text-[10px]">
90
+ <Globe className="size-2.5" />
91
+ Public
92
+ </Badge>
93
+ )}
94
+ </div>
95
+ <p className="text-muted-foreground truncate font-mono text-[11px]">
96
+ {result.qualifiedSlug}
97
+ </p>
98
+ </div>
99
+ </div>
100
+
101
+ {/* Description */}
102
+ {result.description && (
103
+ <p className="text-muted-foreground mt-2 line-clamp-2 text-xs leading-relaxed">
104
+ {result.description}
105
+ </p>
106
+ )}
107
+
108
+ {/* Footer: tags + timestamp */}
109
+ <div className="mt-3 flex items-end justify-between gap-2">
110
+ {result.tags.length > 0 ? (
111
+ <div className="flex min-w-0 flex-wrap gap-1">
112
+ {result.tags.slice(0, 3).map((tag) => (
113
+ <Badge key={tag} variant="secondary" className="text-[10px]">
114
+ {tag}
115
+ </Badge>
116
+ ))}
117
+ {result.tags.length > 3 && (
118
+ <span className="text-muted-foreground self-center text-[10px]">
119
+ +{result.tags.length - 3}
120
+ </span>
121
+ )}
122
+ </div>
123
+ ) : (
124
+ <div />
125
+ )}
126
+ {result.createdAt && (
127
+ <time
128
+ dateTime={toDate(result.createdAt)?.toISOString() ?? ""}
129
+ className="text-muted-foreground shrink-0 text-[10px]"
130
+ >
131
+ {formatRelativeTime(result.createdAt)}
132
+ </time>
133
+ )}
134
+ </div>
135
+ </Tag>
136
+ );
137
+ }
@@ -0,0 +1,6 @@
1
+ // Components
2
+ export { ResourceSearchCard } from "./components/ResourceSearchCard";
3
+ export type { ResourceSearchCardProps } from "./components/ResourceSearchCard";
4
+
5
+ // Utilities
6
+ export { formatRelativeTime, toDate } from "./internal/time";
@@ -0,0 +1,40 @@
1
+ import type { Timestamp } from "@bufbuild/protobuf/wkt";
2
+
3
+ /**
4
+ * Convert a protobuf Timestamp to a JS Date.
5
+ * Returns null if the timestamp is undefined or has no seconds.
6
+ */
7
+ export function toDate(ts: Timestamp | undefined): Date | null {
8
+ if (!ts) return null;
9
+ const seconds = Number(ts.seconds);
10
+ if (!seconds && seconds !== 0) return null;
11
+ return new Date(seconds * 1000 + Math.floor(ts.nanos / 1_000_000));
12
+ }
13
+
14
+ /**
15
+ * Format a protobuf Timestamp as a human-readable relative time string.
16
+ *
17
+ * Examples: "just now", "2m ago", "3h ago", "yesterday", "Mar 12"
18
+ */
19
+ export function formatRelativeTime(ts: Timestamp | undefined): string {
20
+ const date = toDate(ts);
21
+ if (!date) return "";
22
+
23
+ const now = Date.now();
24
+ const diffMs = now - date.getTime();
25
+
26
+ if (diffMs < 0) return "just now";
27
+ if (diffMs < 60_000) return "just now";
28
+
29
+ const minutes = Math.floor(diffMs / 60_000);
30
+ if (minutes < 60) return `${minutes}m ago`;
31
+
32
+ const hours = Math.floor(minutes / 60);
33
+ if (hours < 24) return `${hours}h ago`;
34
+
35
+ const days = Math.floor(hours / 24);
36
+ if (days === 1) return "yesterday";
37
+ if (days < 7) return `${days}d ago`;
38
+
39
+ return date.toLocaleDateString(undefined, { month: "short", day: "numeric" });
40
+ }
package/src/context.ts ADDED
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ import { createContext } from "react";
4
+ import type { Stigmer } from "@stigmer/sdk";
5
+
6
+ /**
7
+ * React context for the Stigmer SDK client instance.
8
+ *
9
+ * Separated from the provider component to prevent circular imports:
10
+ * both the provider and consumer hooks import from this file.
11
+ *
12
+ * The context value is `null` when no provider is mounted. Consumer hooks
13
+ * throw in this case to surface wiring mistakes during development.
14
+ */
15
+ export const StigmerContext = createContext<Stigmer | null>(null);
package/src/hooks.ts ADDED
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ import { useContext } from "react";
4
+ import type { Stigmer } from "@stigmer/sdk";
5
+ import { StigmerContext } from "./context";
6
+
7
+ /**
8
+ * Access the {@link Stigmer} SDK client from the nearest
9
+ * {@link StigmerProvider}.
10
+ *
11
+ * Throws if called outside a provider — this surfaces wiring mistakes
12
+ * immediately during development rather than producing silent `null`
13
+ * failures at runtime.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * function AgentDetail({ id }: { id: string }) {
18
+ * const stigmer = useStigmer();
19
+ * const agent = await stigmer.agent.get(id);
20
+ * }
21
+ * ```
22
+ */
23
+ export function useStigmer(): Stigmer {
24
+ const client = useContext(StigmerContext);
25
+ if (!client) {
26
+ throw new Error(
27
+ "useStigmer must be used within <StigmerProvider>. " +
28
+ "Wrap your component tree with <StigmerProvider client={stigmerClient}>.",
29
+ );
30
+ }
31
+ return client;
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ // Provider and context
2
+ export { StigmerProvider, type StigmerProviderProps } from "./provider";
3
+ export { StigmerContext } from "./context";
4
+
5
+ // Hooks
6
+ export { useStigmer } from "./hooks";
@@ -0,0 +1,52 @@
1
+ import { mergeProps } from "@base-ui/react/merge-props";
2
+ import { useRender } from "@base-ui/react/use-render";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@stigmer/theme";
6
+
7
+ const badgeVariants = cva(
8
+ "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
13
+ secondary:
14
+ "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
15
+ destructive:
16
+ "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
17
+ outline:
18
+ "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
19
+ ghost:
20
+ "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ },
27
+ },
28
+ );
29
+
30
+ function Badge({
31
+ className,
32
+ variant = "default",
33
+ render,
34
+ ...props
35
+ }: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
36
+ return useRender({
37
+ defaultTagName: "span",
38
+ props: mergeProps<"span">(
39
+ {
40
+ className: cn(badgeVariants({ variant }), className),
41
+ },
42
+ props,
43
+ ),
44
+ render,
45
+ state: {
46
+ slot: "badge",
47
+ variant,
48
+ },
49
+ });
50
+ }
51
+
52
+ export { Badge, badgeVariants };
@@ -0,0 +1,60 @@
1
+ "use client";
2
+
3
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+
6
+ import { cn } from "@stigmer/theme";
7
+
8
+ const buttonVariants = cva(
9
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
14
+ outline:
15
+ "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
16
+ secondary:
17
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
18
+ ghost:
19
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
20
+ destructive:
21
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
22
+ link: "text-primary underline-offset-4 hover:underline",
23
+ },
24
+ size: {
25
+ default:
26
+ "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
27
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
28
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
29
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
30
+ icon: "size-8",
31
+ "icon-xs":
32
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
33
+ "icon-sm":
34
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
35
+ "icon-lg": "size-9",
36
+ },
37
+ },
38
+ defaultVariants: {
39
+ variant: "default",
40
+ size: "default",
41
+ },
42
+ },
43
+ );
44
+
45
+ function Button({
46
+ className,
47
+ variant = "default",
48
+ size = "default",
49
+ ...props
50
+ }: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
51
+ return (
52
+ <ButtonPrimitive
53
+ data-slot="button"
54
+ className={cn(buttonVariants({ variant, size, className }))}
55
+ {...props}
56
+ />
57
+ );
58
+ }
59
+
60
+ export { Button, buttonVariants };
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible";
4
+
5
+ function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
6
+ return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
7
+ }
8
+
9
+ function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
10
+ return (
11
+ <CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
12
+ );
13
+ }
14
+
15
+ function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
16
+ return (
17
+ <CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
18
+ );
19
+ }
20
+
21
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };
@@ -0,0 +1,18 @@
1
+ interface SectionProps {
2
+ title: string;
3
+ children: React.ReactNode;
4
+ }
5
+
6
+ function Section({ title, children }: SectionProps) {
7
+ return (
8
+ <section>
9
+ <h3 className="text-muted-foreground mb-3 text-sm font-semibold tracking-wider uppercase">
10
+ {title}
11
+ </h3>
12
+ {children}
13
+ </section>
14
+ );
15
+ }
16
+
17
+ export { Section };
18
+ export type { SectionProps };
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@stigmer/theme";
4
+
5
+ const Textarea = React.forwardRef<
6
+ HTMLTextAreaElement,
7
+ React.ComponentProps<"textarea">
8
+ >(({ className, ...props }, ref) => {
9
+ return (
10
+ <textarea
11
+ ref={ref}
12
+ data-slot="textarea"
13
+ className={cn(
14
+ "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 disabled:bg-input/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 flex field-sizing-content min-h-16 w-full rounded-lg border bg-transparent px-2.5 py-2 text-base transition-colors outline-none focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 md:text-sm",
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ );
20
+ });
21
+ Textarea.displayName = "Textarea";
22
+
23
+ export { Textarea };
@@ -0,0 +1,79 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { useStigmer } from "../../hooks";
5
+ import type { SearchResult } from "@stigmer/protos/ai/stigmer/search/v1/io_pb";
6
+
7
+ const DEBOUNCE_MS = 300;
8
+ const DEFAULT_PAGE_SIZE = 20;
9
+
10
+ export interface UseMcpServerSearchOptions {
11
+ org: string;
12
+ debounceMs?: number;
13
+ pageSize?: number;
14
+ }
15
+
16
+ export interface UseMcpServerSearchReturn {
17
+ query: string;
18
+ setQuery: (query: string) => void;
19
+ results: SearchResult[];
20
+ isLoading: boolean;
21
+ error: string | null;
22
+ }
23
+
24
+ /**
25
+ * Debounced MCP server search scoped to a specific organization.
26
+ *
27
+ * On mount fetches all accessible MCP servers (empty query). Typing fires at
28
+ * most one request per debounce interval. Stale responses from out-of-order
29
+ * requests are discarded.
30
+ *
31
+ * Returns full {@link SearchResult} objects suitable for use with
32
+ * `ResourceSearchCard`.
33
+ */
34
+ export function useMcpServerSearch(options: UseMcpServerSearchOptions): UseMcpServerSearchReturn {
35
+ const { org, debounceMs = DEBOUNCE_MS, pageSize = DEFAULT_PAGE_SIZE } = options;
36
+ const stigmer = useStigmer();
37
+
38
+ const [query, setQuery] = useState("");
39
+ const [results, setResults] = useState<SearchResult[]>([]);
40
+ const [isLoading, setIsLoading] = useState(false);
41
+ const [error, setError] = useState<string | null>(null);
42
+
43
+ const latestRequestId = useRef(0);
44
+ const debouncedQuery = useDebouncedValue(query, debounceMs);
45
+
46
+ useEffect(() => {
47
+ const requestId = ++latestRequestId.current;
48
+ setIsLoading(true);
49
+ setError(null);
50
+
51
+ stigmer.mcpServer
52
+ .list({ query: debouncedQuery, org, page: { num: 1, size: pageSize } })
53
+ .then((response) => {
54
+ if (requestId !== latestRequestId.current) return;
55
+ setResults(response.entries);
56
+ })
57
+ .catch((err: unknown) => {
58
+ if (requestId !== latestRequestId.current) return;
59
+ setError(err instanceof Error ? err.message : "Search failed");
60
+ })
61
+ .finally(() => {
62
+ if (requestId !== latestRequestId.current) return;
63
+ setIsLoading(false);
64
+ });
65
+ }, [debouncedQuery, org, pageSize, stigmer]);
66
+
67
+ return { query, setQuery, results, isLoading, error };
68
+ }
69
+
70
+ function useDebouncedValue<T>(value: T, delayMs: number): T {
71
+ const [debounced, setDebounced] = useState(value);
72
+
73
+ useEffect(() => {
74
+ const timer = setTimeout(() => setDebounced(value), delayMs);
75
+ return () => clearTimeout(timer);
76
+ }, [value, delayMs]);
77
+
78
+ return debounced;
79
+ }
@@ -0,0 +1,6 @@
1
+ // Hooks
2
+ export { useMcpServerSearch } from "./hooks/useMcpServerSearch";
3
+ export type {
4
+ UseMcpServerSearchOptions,
5
+ UseMcpServerSearchReturn,
6
+ } from "./hooks/useMcpServerSearch";
@@ -0,0 +1,73 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+ import type { Stigmer } from "@stigmer/sdk";
5
+ import { cn, resolvePresetClass } from "@stigmer/theme";
6
+ import type { ThemePresetId } from "@stigmer/theme";
7
+ import { StigmerContext } from "./context";
8
+
9
+ export interface StigmerProviderProps {
10
+ /** A configured {@link Stigmer} client instance. */
11
+ readonly client: Stigmer;
12
+ readonly children: ReactNode;
13
+ /**
14
+ * Built-in theme preset to apply.
15
+ *
16
+ * Maps to a CSS class on the scoping container so the preset's
17
+ * design tokens take effect for all descendant Stigmer components.
18
+ * Omit (or pass `"default"`) to use the base Stigmer palette.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * <StigmerProvider client={client} preset="corporate">
23
+ * <ChatWidget />
24
+ * </StigmerProvider>
25
+ * ```
26
+ */
27
+ readonly preset?: ThemePresetId;
28
+ /**
29
+ * Additional CSS class names applied to the scoping container element.
30
+ * The container always includes the `stgm` class for style isolation.
31
+ */
32
+ readonly className?: string;
33
+ }
34
+
35
+ /**
36
+ * React provider that distributes a {@link Stigmer} SDK client to
37
+ * descendant components via {@link StigmerContext}.
38
+ *
39
+ * Renders a `<div class="stgm">` container that scopes Stigmer's
40
+ * CSS reset and design tokens. External consumers importing
41
+ * `@stigmer/react/styles.css` get isolated styles that do not
42
+ * leak into the host application.
43
+ *
44
+ * Pass {@link StigmerProviderProps.preset | preset} to apply a
45
+ * built-in theme, or use {@link StigmerProviderProps.className | className}
46
+ * for custom styling.
47
+ *
48
+ * @example
49
+ * ```tsx
50
+ * const client = useMemo(
51
+ * () => new Stigmer({ baseUrl, getAccessToken }),
52
+ * [getAccessToken],
53
+ * );
54
+ *
55
+ * <StigmerProvider client={client} preset="fintech">
56
+ * <App />
57
+ * </StigmerProvider>
58
+ * ```
59
+ */
60
+ export function StigmerProvider({
61
+ client,
62
+ children,
63
+ preset,
64
+ className,
65
+ }: StigmerProviderProps) {
66
+ const presetClass = preset ? resolvePresetClass(preset) : "";
67
+
68
+ return (
69
+ <StigmerContext.Provider value={client}>
70
+ <div className={cn("stgm", presetClass, className)}>{children}</div>
71
+ </StigmerContext.Provider>
72
+ );
73
+ }