@korso/shepherd-ui 0.4.0 → 0.5.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.
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;
@@ -12,12 +12,12 @@ const TOOLS = [
12
12
  // The token placeholder shown before a real token is minted. Switching tools or
13
13
  // reading the command pre-mint shows this, never a real secret.
14
14
  const TOKEN_PLACEHOLDER = "shp_<paste-after-generating>";
15
- // The CLI tools differ only in the first line of the `mcp add` invocation; the
16
- // rest of the command (the env flags and the npx tail) is shared.
15
+ // The CLI tools differ only in the `mcp add` invocation; the rest of the
16
+ // command (the env flags and the npx tail) is shared.
17
17
  const CLI_PREFIX = {
18
- claude: "claude mcp add shepherd -s user \\",
19
- codex: "codex mcp add shepherd \\",
20
- pi: "pi mcp add shepherd \\",
18
+ claude: "claude mcp add shepherd -s user",
19
+ codex: "codex mcp add shepherd",
20
+ pi: "pi mcp add shepherd",
21
21
  };
22
22
  // "created 3d ago · never used" / "created 3d ago · last used 2h ago" — helps
23
23
  // an operator tell which tokens are still active before revoking one.
@@ -38,14 +38,17 @@ function installCommand(tool, hubUrl, token) {
38
38
  },
39
39
  }, null, 2);
40
40
  }
41
+ // A single line with no shell-specific continuation character (`\` in
42
+ // bash/zsh, backtick in PowerShell, `^` in cmd) so the command pastes
43
+ // cleanly into any terminal.
41
44
  return [
42
45
  CLI_PREFIX[tool],
43
- ` -e HUB_URL=${hubUrl} \\`,
44
- ` -e SHEPHERD_TOKEN=${token} \\`,
45
- " -- npx -y @korso/shepherd",
46
- ].join("\n");
46
+ `-e HUB_URL=${hubUrl}`,
47
+ `-e SHEPHERD_TOKEN=${token}`,
48
+ "-- npx -y @korso/shepherd",
49
+ ].join(" ");
47
50
  }
48
- export function ConnectAgent({ workspaceId, hubUrl }) {
51
+ export function ConnectAgent({ hubUrl }) {
49
52
  const client = useShepherdClient();
50
53
  const directHubUrl = hubUrl ?? client.baseUrl;
51
54
  const headingId = useId();
@@ -66,7 +69,7 @@ export function ConnectAgent({ workspaceId, hubUrl }) {
66
69
  const [revokingId, setRevokingId] = useState(null);
67
70
  async function loadTokens() {
68
71
  try {
69
- const res = await client.listTokens(workspaceId);
72
+ const res = await client.listAccountTokens();
70
73
  setTokens(res.tokens);
71
74
  }
72
75
  catch (err) {
@@ -80,18 +83,12 @@ export function ConnectAgent({ workspaceId, hubUrl }) {
80
83
  setLoading(true);
81
84
  void loadTokens();
82
85
  // 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]);
86
+ }, [client]);
90
87
  async function generate() {
91
88
  setBusy(true);
92
89
  setError(null);
93
90
  try {
94
- const res = await client.mintToken(workspaceId, name.trim() ? { name: name.trim() } : {});
91
+ const res = await client.mintAccountToken(name.trim() ? { name: name.trim() } : {});
95
92
  setRawToken(res.token);
96
93
  setName("");
97
94
  await loadTokens();
@@ -110,7 +107,7 @@ export function ConnectAgent({ workspaceId, hubUrl }) {
110
107
  setError(null);
111
108
  setStatus(null);
112
109
  try {
113
- await client.revokeToken(workspaceId, tokenId);
110
+ await client.revokeAccountToken(tokenId);
114
111
  setTokens((prev) => prev.filter((t) => t.id !== tokenId));
115
112
  setStatus("Token revoked");
116
113
  }
@@ -127,7 +124,7 @@ export function ConnectAgent({ workspaceId, hubUrl }) {
127
124
  setCopied(true);
128
125
  setTimeout(() => setCopied(false), 2000);
129
126
  }
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) => {
127
+ 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
128
  const name = t.name ?? t.id;
132
129
  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
130
  }) }))] })] }));
@@ -58,7 +58,7 @@ export function Members({ workspaceId, refreshKey = 0, canRemove = false }) {
58
58
  }
59
59
  }
60
60
  return (_jsxs("section", { className: "shepherd-members", "aria-labelledby": headingId, children: [_jsx("div", { className: "card-head", children: _jsx("h3", { id: headingId, children: "Members" }) }), _jsxs("div", { className: "card-body", children: [error && _jsx("p", { role: "alert", children: error }), status && _jsx("p", { role: "status", children: status }), loading ? (_jsx("p", { role: "status", children: "Loading\u2026" })) : members.length === 0 ? (_jsx("p", { children: "No members." })) : (_jsx("ul", { children: members.map((m) => {
61
- const display = m.displayName ?? m.githubLogin ?? m.accountId;
61
+ const display = m.displayName ?? m.githubLogin ?? m.email ?? m.accountId;
62
62
  return (_jsxs("li", { children: [_jsx("span", { children: display }), _jsx("span", { className: "role", children: m.role }), canRemove && (_jsx("button", { type: "button", "aria-label": `Remove ${display}`, onClick: () => void remove(m.accountId, display), disabled: removingId === m.accountId, children: "Remove" }))] }, m.accountId));
63
63
  }) }))] })] }));
64
64
  }