@korso/shepherd-ui 0.4.0 → 0.5.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/client.js CHANGED
@@ -177,6 +177,20 @@ export function createShepherdClient(config) {
177
177
  revokeToken(workspaceId, tokenId) {
178
178
  return request("DELETE", `/workspaces/${enc(workspaceId)}/tokens/${enc(tokenId)}`);
179
179
  },
180
+ mintAccountToken(body) {
181
+ return request("POST", "/tokens", {
182
+ body,
183
+ schema: MintTokenResponse,
184
+ });
185
+ },
186
+ listAccountTokens() {
187
+ return request("GET", "/tokens", {
188
+ schema: ListTokensResponse,
189
+ });
190
+ },
191
+ revokeAccountToken(id) {
192
+ return request("DELETE", `/tokens/${enc(id)}`);
193
+ },
180
194
  createInvite(workspaceId, body) {
181
195
  return request("POST", `/workspaces/${enc(workspaceId)}/invites`, {
182
196
  body,
@@ -12,5 +12,5 @@ const SECTIONS = [
12
12
  export function ConfigPanel({ workspace, hubUrl, membersRefreshKey, onMembersChanged, onLeft, }) {
13
13
  const [section, setSection] = useState("general");
14
14
  const isAdmin = workspace.role === "admin";
15
- return (_jsxs("div", { className: "config-layout", children: [_jsx("nav", { className: "config-nav", "aria-label": "Configuration sections", children: SECTIONS.map(({ id, label }) => (_jsx("button", { type: "button", className: "config-nav__item" + (section === id ? " config-nav__item--on" : ""), "aria-current": section === id ? "page" : undefined, onClick: () => setSection(id), children: label }, id))) }), _jsxs("div", { className: "config-panel", children: [section === "general" && (_jsx(GeneralSettings, { workspace: workspace, onLeft: onLeft })), section === "members" && (_jsxs(_Fragment, { children: [_jsx(Members, { workspaceId: workspace.id, refreshKey: membersRefreshKey, canRemove: isAdmin }), isAdmin && (_jsx(Invites, { workspaceId: workspace.id, onMembersChanged: onMembersChanged }))] })), section === "agent" && _jsx(ConnectAgent, { workspaceId: workspace.id, hubUrl: hubUrl })] })] }));
15
+ return (_jsxs("div", { className: "config-layout", children: [_jsx("nav", { className: "config-nav", "aria-label": "Configuration sections", children: SECTIONS.map(({ id, label }) => (_jsx("button", { type: "button", className: "config-nav__item" + (section === id ? " config-nav__item--on" : ""), "aria-current": section === id ? "page" : undefined, onClick: () => setSection(id), children: label }, id))) }), _jsxs("div", { className: "config-panel", children: [section === "general" && (_jsx(GeneralSettings, { workspace: workspace, onLeft: onLeft })), section === "members" && (_jsxs(_Fragment, { children: [_jsx(Members, { workspaceId: workspace.id, refreshKey: membersRefreshKey, canRemove: isAdmin }), isAdmin && (_jsx(Invites, { workspaceId: workspace.id, onMembersChanged: onMembersChanged }))] })), section === "agent" && _jsx(ConnectAgent, { hubUrl: hubUrl })] })] }));
16
16
  }
@@ -1,5 +1,4 @@
1
1
  export interface ConnectAgentProps {
2
- workspaceId: string;
3
2
  /**
4
3
  * The DIRECT Hub URL the agent connects to (public Cloud Run URL when hosted).
5
4
  * Defaults to the dashboard client's baseUrl, which is correct for self-host
@@ -7,4 +6,4 @@ export interface ConnectAgentProps {
7
6
  */
8
7
  hubUrl?: string;
9
8
  }
10
- export declare function ConnectAgent({ workspaceId, hubUrl }: ConnectAgentProps): import("react").JSX.Element;
9
+ export declare function ConnectAgent({ hubUrl }: ConnectAgentProps): import("react").JSX.Element;
@@ -45,7 +45,7 @@ function installCommand(tool, hubUrl, token) {
45
45
  " -- npx -y @korso/shepherd",
46
46
  ].join("\n");
47
47
  }
48
- export function ConnectAgent({ workspaceId, hubUrl }) {
48
+ export function ConnectAgent({ hubUrl }) {
49
49
  const client = useShepherdClient();
50
50
  const directHubUrl = hubUrl ?? client.baseUrl;
51
51
  const headingId = useId();
@@ -66,7 +66,7 @@ export function ConnectAgent({ workspaceId, hubUrl }) {
66
66
  const [revokingId, setRevokingId] = useState(null);
67
67
  async function loadTokens() {
68
68
  try {
69
- const res = await client.listTokens(workspaceId);
69
+ const res = await client.listAccountTokens();
70
70
  setTokens(res.tokens);
71
71
  }
72
72
  catch (err) {
@@ -80,18 +80,12 @@ export function ConnectAgent({ workspaceId, hubUrl }) {
80
80
  setLoading(true);
81
81
  void loadTokens();
82
82
  // eslint-disable-next-line react-hooks/exhaustive-deps
83
- }, [client, workspaceId]);
84
- // The component is re-rendered (not remounted) across a workspace switch, so
85
- // clear the once-shown raw token so it never leaks into another workspace's
86
- // install command (mirrors Workspaces' setInvite(null) reset).
87
- useEffect(() => {
88
- setRawToken(null);
89
- }, [workspaceId]);
83
+ }, [client]);
90
84
  async function generate() {
91
85
  setBusy(true);
92
86
  setError(null);
93
87
  try {
94
- const res = await client.mintToken(workspaceId, name.trim() ? { name: name.trim() } : {});
88
+ const res = await client.mintAccountToken(name.trim() ? { name: name.trim() } : {});
95
89
  setRawToken(res.token);
96
90
  setName("");
97
91
  await loadTokens();
@@ -110,7 +104,7 @@ export function ConnectAgent({ workspaceId, hubUrl }) {
110
104
  setError(null);
111
105
  setStatus(null);
112
106
  try {
113
- await client.revokeToken(workspaceId, tokenId);
107
+ await client.revokeAccountToken(tokenId);
114
108
  setTokens((prev) => prev.filter((t) => t.id !== tokenId));
115
109
  setStatus("Token revoked");
116
110
  }
@@ -127,7 +121,7 @@ export function ConnectAgent({ workspaceId, hubUrl }) {
127
121
  setCopied(true);
128
122
  setTimeout(() => setCopied(false), 2000);
129
123
  }
130
- return (_jsxs("section", { className: "shepherd-connect-agent", "aria-labelledby": headingId, children: [_jsxs("div", { className: "card-head", children: [_jsx("h3", { id: headingId, children: "Connect your agent" }), _jsx("p", { className: "card-sub", children: "Generate a token, then paste the command into your coding tool." })] }), _jsxs("div", { className: "card-body", children: [_jsxs("div", { className: "field", children: [_jsx("label", { htmlFor: "connect-tool", children: "Tool" }), _jsx("select", { id: "connect-tool", value: tool, onChange: (e) => setTool(e.target.value), children: TOOLS.map((t) => (_jsx("option", { value: t.id, children: t.label }, t.id))) })] }), _jsxs("div", { className: "field generate", children: [_jsx("label", { htmlFor: "token-name", children: "Token name (optional)" }), _jsxs("div", { className: "field__row", children: [_jsx("input", { id: "token-name", type: "text", value: name, onChange: (e) => setName(e.target.value), placeholder: "e.g. laptop" }), _jsx("button", { type: "button", onClick: () => void generate(), disabled: busy, children: "Generate token" })] })] }), error && _jsx("p", { role: "alert", children: error }), status && _jsx("p", { role: "status", children: status }), rawToken && (_jsx("p", { className: "token-once", role: "status", children: "Copy this token now \u2014 it won't be shown again." })), _jsxs("div", { className: "install-command", children: [_jsx("pre", { "data-testid": "install-command", children: command }), _jsx("button", { type: "button", className: "install-command__copy", "aria-label": copied ? "Copied" : "Copy command", title: copied ? "Copied" : "Copy", onClick: () => void copyCommand(), children: copied ? (_jsx("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) })) : (_jsxs("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }), _jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] })) })] }), _jsx("h4", { children: "Existing tokens" }), loading ? (_jsx("p", { role: "status", children: "Loading\u2026" })) : tokens.length === 0 ? (_jsx("p", { children: "No tokens yet." })) : (_jsx("ul", { children: tokens.map((t) => {
124
+ return (_jsxs("section", { className: "shepherd-connect-agent", "aria-labelledby": headingId, children: [_jsxs("div", { className: "card-head", children: [_jsx("h3", { id: headingId, children: "Connect your agent" }), _jsx("p", { className: "card-sub", children: "Generate a token \u2014 it works across all your workspaces \u2014 then paste the command into your coding tool." }), _jsx("p", { className: "card-sub", children: "The first time the agent opens a repo, it'll ask which workspace to coordinate that repo with." })] }), _jsxs("div", { className: "card-body", children: [_jsxs("div", { className: "field", children: [_jsx("label", { htmlFor: "connect-tool", children: "Tool" }), _jsx("select", { id: "connect-tool", value: tool, onChange: (e) => setTool(e.target.value), children: TOOLS.map((t) => (_jsx("option", { value: t.id, children: t.label }, t.id))) })] }), _jsxs("div", { className: "field generate", children: [_jsx("label", { htmlFor: "token-name", children: "Token name (optional)" }), _jsxs("div", { className: "field__row", children: [_jsx("input", { id: "token-name", type: "text", value: name, onChange: (e) => setName(e.target.value), placeholder: "e.g. laptop" }), _jsx("button", { type: "button", onClick: () => void generate(), disabled: busy, children: "Generate token" })] })] }), error && _jsx("p", { role: "alert", children: error }), status && _jsx("p", { role: "status", children: status }), rawToken && (_jsx("p", { className: "token-once", role: "status", children: "Copy this token now \u2014 it won't be shown again." })), _jsxs("div", { className: "install-command", children: [_jsx("pre", { "data-testid": "install-command", children: command }), _jsx("button", { type: "button", className: "install-command__copy", "aria-label": copied ? "Copied" : "Copy command", title: copied ? "Copied" : "Copy", onClick: () => void copyCommand(), children: copied ? (_jsx("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) })) : (_jsxs("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }), _jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] })) })] }), _jsx("h4", { children: "Existing tokens" }), loading ? (_jsx("p", { role: "status", children: "Loading\u2026" })) : tokens.length === 0 ? (_jsx("p", { children: "No tokens yet." })) : (_jsx("ul", { children: tokens.map((t) => {
131
125
  const name = t.name ?? t.id;
132
126
  return (_jsxs("li", { children: [_jsx("span", { children: name }), _jsx("span", { className: "token-meta", children: tokenMeta(t, Date.now()) }), t.revokedAt ? (_jsx("span", { className: "revoked", children: "revoked" })) : (_jsx("button", { type: "button", "aria-label": `Revoke token ${name}`, onClick: () => void revoke(t.id), disabled: revokingId === t.id, children: "Revoke" }))] }, t.id));
133
127
  }) }))] })] }));