@korso/shepherd-ui 0.3.1 → 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.
Files changed (38) hide show
  1. package/LICENSE +661 -0
  2. package/dist/ShepherdRoot.js +13 -12
  3. package/dist/client.d.ts +6 -0
  4. package/dist/client.js +14 -0
  5. package/dist/components/Dashboard.d.ts +7 -1
  6. package/dist/components/Dashboard.js +2 -2
  7. package/dist/config/ConfigPanel.d.ts +14 -0
  8. package/dist/config/ConfigPanel.js +16 -0
  9. package/dist/config/ConnectAgent.d.ts +1 -2
  10. package/dist/config/ConnectAgent.js +6 -12
  11. package/dist/config/GeneralSettings.d.ts +8 -0
  12. package/dist/config/GeneralSettings.js +27 -0
  13. package/dist/config/Invites.d.ts +7 -0
  14. package/dist/config/Invites.js +84 -0
  15. package/dist/config/Members.d.ts +1 -3
  16. package/dist/config/Members.js +4 -23
  17. package/dist/config/WorkspaceSwitcher.d.ts +14 -0
  18. package/dist/config/WorkspaceSwitcher.js +108 -0
  19. package/dist/config/index.d.ts +8 -2
  20. package/dist/config/index.js +6 -3
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +1 -1
  23. package/dist/lib/Dashboard-C48qY8R2.js +1412 -0
  24. package/dist/lib/index.d.ts +63 -22
  25. package/dist/lib/index.js +552 -302
  26. package/dist/lib/selfhost.js +1 -1
  27. package/dist/lib/styles.css +128 -18
  28. package/dist/preview.js +40 -4
  29. package/dist/selfhost/assets/index-CK7qrsNo.css +1 -0
  30. package/dist/selfhost/assets/index-adPvx18g.js +40 -0
  31. package/dist/selfhost/index.html +2 -2
  32. package/dist/test/mockClient.js +3 -0
  33. package/package.json +2 -2
  34. package/dist/config/Workspaces.d.ts +0 -15
  35. package/dist/config/Workspaces.js +0 -116
  36. package/dist/lib/Dashboard-Bgi50wCo.js +0 -1396
  37. package/dist/selfhost/assets/index-D0C3m6st.js +0 -40
  38. package/dist/selfhost/assets/index-rNR1Acph.css +0 -1
package/dist/lib/index.js CHANGED
@@ -1,444 +1,694 @@
1
- import { u as U, d as N, f as F, D as W } from "./Dashboard-Bgi50wCo.js";
2
- import { E as ie, S as ae, a as le, c as re } from "./Dashboard-Bgi50wCo.js";
3
- import { jsxs as a, jsx as e } from "react/jsx-runtime";
4
- import { useId as B, useState as i, useEffect as $, useCallback as D } from "react";
5
- function K({
6
- workspaces: l,
1
+ import { u as A, d as I, f as O, D as G } from "./Dashboard-C48qY8R2.js";
2
+ import { E as ce, S as de, a as ue, c as he } from "./Dashboard-C48qY8R2.js";
3
+ import { jsxs as n, jsx as e, Fragment as F } from "react/jsx-runtime";
4
+ import { useId as B, useRef as P, useState as a, useEffect as U, useCallback as K } from "react";
5
+ function D(i) {
6
+ return (i.trim()[0] ?? "?").toUpperCase();
7
+ }
8
+ function Y({
9
+ workspaces: i,
7
10
  selected: t,
8
- onChanged: r,
9
- onSelect: b,
10
- onMembersChanged: d
11
+ onSelect: d,
12
+ onChanged: v,
13
+ onMembersChanged: o
11
14
  }) {
12
- const y = U(), C = B(), [g, k] = i(""), [m, u] = i(""), [o, w] = i(null), [h, s] = i(null), [p, v] = i(!1), [S, R] = i(""), [x, E] = i(null), [c, f] = i(!1), _ = (t == null ? void 0 : t.role) === "admin";
13
- async function I() {
14
- if (!(!g.trim() || p)) {
15
- v(!0), s(null);
16
- try {
17
- await y.createWorkspace({ name: g.trim() }), k(""), r();
18
- } catch (n) {
19
- s(N(n));
20
- } finally {
21
- v(!1);
22
- }
23
- }
15
+ const h = A(), m = B(), p = P(null), [l, y] = a(!1), [b, w] = a("menu"), [g, N] = a(""), [u, C] = a(""), [E, r] = a(null), [k, _] = a(!1);
16
+ function S() {
17
+ y(!1), w("menu"), N(""), C(""), r(null);
24
18
  }
25
- async function L() {
26
- if (!(!m.trim() || p)) {
27
- v(!0), s(null);
28
- try {
29
- await y.redeemInvite(m.trim()), u(""), r(), d == null || d();
30
- } catch (n) {
31
- s(N(n));
32
- } finally {
33
- v(!1);
34
- }
19
+ U(() => {
20
+ if (!l) return;
21
+ function s($) {
22
+ p.current && !p.current.contains($.target) && S();
35
23
  }
36
- }
37
- async function H() {
38
- if (!(!t || p)) {
39
- v(!0), s(null);
40
- try {
41
- const n = await y.createInvite(t.id, {});
42
- w(n), d == null || d();
43
- } catch (n) {
44
- s(N(n));
45
- } finally {
46
- v(!1);
47
- }
24
+ function R($) {
25
+ $.key === "Escape" && S();
48
26
  }
49
- }
50
- async function O() {
51
- if (!(!t || !S.trim() || c)) {
52
- f(!0), E(null);
27
+ return document.addEventListener("mousedown", s), document.addEventListener("keydown", R), () => {
28
+ document.removeEventListener("mousedown", s), document.removeEventListener("keydown", R);
29
+ };
30
+ }, [l]);
31
+ async function x() {
32
+ if (!(!g.trim() || k)) {
33
+ _(!0), r(null);
53
34
  try {
54
- const n = await y.inviteByEmail(t.id, S.trim());
55
- E(`Invite sent to ${n.email}.`), R(""), d == null || d();
56
- } catch (n) {
57
- E(N(n));
35
+ const s = await h.createWorkspace({ name: g.trim() });
36
+ d(s.id), v(), S();
37
+ } catch (s) {
38
+ r(I(s));
58
39
  } finally {
59
- f(!1);
40
+ _(!1);
60
41
  }
61
42
  }
62
43
  }
63
- async function A() {
64
- if (!(!t || !o || p)) {
65
- v(!0), s(null);
44
+ async function L() {
45
+ if (!(!u.trim() || k)) {
46
+ _(!0), r(null);
66
47
  try {
67
- await y.revokeInvite(t.id, o.code), w(null);
68
- } catch (n) {
69
- s(N(n));
48
+ const s = await h.redeemInvite(u.trim());
49
+ d(s.workspace.id), v(), o == null || o(), S();
50
+ } catch (s) {
51
+ r(I(s));
70
52
  } finally {
71
- v(!1);
53
+ _(!1);
72
54
  }
73
55
  }
74
56
  }
75
- $(() => {
76
- w(null), E(null);
77
- }, [t == null ? void 0 : t.id]);
78
- const j = o ? `${window.location.origin}/shepherd/join/${encodeURIComponent(o.code)}` : null;
79
- return /* @__PURE__ */ a("section", { className: "shepherd-workspaces", "aria-labelledby": C, children: [
80
- /* @__PURE__ */ a("div", { className: "card-head", children: [
81
- /* @__PURE__ */ e("h3", { id: C, children: "Workspace management" }),
82
- /* @__PURE__ */ e("p", { className: "card-sub", children: "Create a workspace to get started, or join an existing one with an invite code." })
83
- ] }),
84
- /* @__PURE__ */ a("div", { className: "card-body", children: [
85
- h && /* @__PURE__ */ e("p", { role: "alert", children: h }),
86
- l.length > 1 && b && /* @__PURE__ */ a("div", { className: "field switcher", children: [
87
- /* @__PURE__ */ e("label", { htmlFor: "ws-switcher", children: "Active workspace" }),
88
- /* @__PURE__ */ e(
89
- "select",
90
- {
91
- id: "ws-switcher",
92
- value: (t == null ? void 0 : t.id) ?? "",
93
- onChange: (n) => b(n.target.value),
94
- children: l.map((n) => /* @__PURE__ */ e("option", { value: n.id, children: n.name }, n.id))
95
- }
96
- )
97
- ] }),
98
- /* @__PURE__ */ a("div", { className: "field create", children: [
99
- /* @__PURE__ */ e("label", { htmlFor: "ws-name", children: "Workspace name" }),
100
- /* @__PURE__ */ e("p", { className: "helper", children: "Start a fresh workspace — you'll be its admin." }),
101
- /* @__PURE__ */ a("div", { className: "field__row", children: [
57
+ const f = t !== null, T = t ? t.name : "Get started";
58
+ return /* @__PURE__ */ n("div", { className: "ws-switcher", ref: p, children: [
59
+ /* @__PURE__ */ n(
60
+ "button",
61
+ {
62
+ type: "button",
63
+ className: "ws-trig" + (f ? "" : " ws-trig--empty"),
64
+ "aria-haspopup": "menu",
65
+ "aria-expanded": l,
66
+ "aria-controls": l ? m : void 0,
67
+ onClick: () => l ? S() : y(!0),
68
+ children: [
69
+ f ? /* @__PURE__ */ e("span", { className: "ws-avatar", "aria-hidden": "true", children: D(T) }) : /* @__PURE__ */ e("span", { className: "ws-plus", "aria-hidden": "true", children: "+" }),
70
+ /* @__PURE__ */ e("span", { className: "ws-name", children: T }),
71
+ /* @__PURE__ */ e("span", { className: "ws-caret", "aria-hidden": "true", children: "▾" })
72
+ ]
73
+ }
74
+ ),
75
+ l && /* @__PURE__ */ n("div", { className: "ws-menu", id: m, role: "menu", children: [
76
+ b === "menu" && /* @__PURE__ */ n(F, { children: [
77
+ /* @__PURE__ */ e("p", { className: "ws-menu__head", children: f ? "Switch workspace" : "Your workspaces" }),
78
+ i.length === 0 ? /* @__PURE__ */ e("p", { className: "ws-menu__none", children: "None yet" }) : /* @__PURE__ */ e("ul", { className: "ws-menu__list", children: i.map((s) => {
79
+ const R = s.id === (t == null ? void 0 : t.id);
80
+ return /* @__PURE__ */ e("li", { children: /* @__PURE__ */ n(
81
+ "button",
82
+ {
83
+ type: "button",
84
+ role: "menuitemradio",
85
+ "aria-checked": R,
86
+ className: "ws-mi" + (R ? " ws-mi--on" : ""),
87
+ onClick: () => {
88
+ d(s.id), S();
89
+ },
90
+ children: [
91
+ /* @__PURE__ */ e("span", { className: "ws-avatar ws-avatar--sm", "aria-hidden": "true", children: D(s.name) }),
92
+ /* @__PURE__ */ e("span", { className: "ws-mi__name", children: s.name }),
93
+ R && /* @__PURE__ */ e("span", { className: "ws-mi__check", "aria-hidden": "true", children: "✓" })
94
+ ]
95
+ }
96
+ ) }, s.id);
97
+ }) }),
98
+ /* @__PURE__ */ n("div", { className: "ws-menu__actions", children: [
102
99
  /* @__PURE__ */ e(
103
- "input",
100
+ "button",
104
101
  {
105
- id: "ws-name",
106
- type: "text",
107
- placeholder: "e.g. Acme Engineering",
108
- value: g,
109
- onChange: (n) => k(n.target.value)
102
+ type: "button",
103
+ role: "menuitem",
104
+ className: "ws-action",
105
+ onClick: () => {
106
+ r(null), w("create");
107
+ },
108
+ children: "+ Create workspace"
110
109
  }
111
110
  ),
112
- /* @__PURE__ */ e("button", { type: "button", onClick: () => void I(), disabled: p, children: "Create workspace" })
113
- ] })
114
- ] }),
115
- /* @__PURE__ */ a("div", { className: "field join", children: [
116
- /* @__PURE__ */ e("label", { htmlFor: "invite-code", children: "Invite code" }),
117
- /* @__PURE__ */ e("p", { className: "helper", children: "Paste an invite code someone shared to join their workspace." }),
118
- /* @__PURE__ */ a("div", { className: "field__row", children: [
119
111
  /* @__PURE__ */ e(
120
- "input",
112
+ "button",
121
113
  {
122
- id: "invite-code",
123
- type: "text",
124
- placeholder: "Invite code",
125
- value: m,
126
- onChange: (n) => u(n.target.value)
114
+ type: "button",
115
+ role: "menuitem",
116
+ className: "ws-action",
117
+ onClick: () => {
118
+ r(null), w("join");
119
+ },
120
+ children: "⎘ Join with a code"
127
121
  }
128
- ),
129
- /* @__PURE__ */ e("button", { type: "button", onClick: () => void L(), disabled: p, children: "Join" })
122
+ )
130
123
  ] })
131
124
  ] }),
132
- _ && t && /* @__PURE__ */ a("div", { className: "admin", children: [
133
- /* @__PURE__ */ a("div", { className: "invites", children: [
134
- /* @__PURE__ */ e("button", { type: "button", onClick: () => void H(), disabled: p, children: "Create invite" }),
135
- o && /* @__PURE__ */ a("div", { className: "invite-result", children: [
136
- /* @__PURE__ */ a("div", { className: "invite-result__row", children: [
137
- /* @__PURE__ */ e("code", { children: o.code }),
138
- /* @__PURE__ */ e("span", { className: "invite-uses", children: o.maxUses === null ? `${o.useCount} uses` : `${o.useCount} / ${o.maxUses} uses` }),
139
- /* @__PURE__ */ e("button", { type: "button", onClick: () => void A(), disabled: p, children: "Revoke invite" })
140
- ] }),
141
- /* @__PURE__ */ e("a", { href: j ?? void 0, children: j })
142
- ] })
143
- ] }),
144
- /* @__PURE__ */ a("div", { className: "field email-invite", children: [
145
- /* @__PURE__ */ e("label", { htmlFor: "invite-email", children: "Email invite" }),
146
- /* @__PURE__ */ e("p", { className: "helper", children: "Send a one-time-use join link directly to someone's inbox." }),
147
- /* @__PURE__ */ a("div", { className: "field__row", children: [
125
+ b === "create" && /* @__PURE__ */ n(
126
+ "form",
127
+ {
128
+ className: "ws-form",
129
+ onSubmit: (s) => {
130
+ s.preventDefault(), x();
131
+ },
132
+ children: [
133
+ /* @__PURE__ */ e("label", { htmlFor: `${m}-name`, children: "New workspace name" }),
148
134
  /* @__PURE__ */ e(
149
135
  "input",
150
136
  {
151
- id: "invite-email",
152
- type: "email",
153
- placeholder: "name@example.com",
154
- value: S,
155
- onChange: (n) => R(n.target.value)
137
+ id: `${m}-name`,
138
+ type: "text",
139
+ placeholder: "e.g. Acme Engineering",
140
+ value: g,
141
+ autoFocus: !0,
142
+ onChange: (s) => N(s.target.value)
156
143
  }
157
144
  ),
158
- /* @__PURE__ */ e("button", { type: "button", onClick: () => void O(), disabled: c, children: "Send invite" })
159
- ] }),
160
- x && /* @__PURE__ */ e("p", { className: "email-invite__status", children: x })
161
- ] })
145
+ /* @__PURE__ */ n("div", { className: "ws-form__row", children: [
146
+ /* @__PURE__ */ e("button", { type: "button", className: "ws-form__back", onClick: () => w("menu"), children: "Back" }),
147
+ /* @__PURE__ */ e("button", { type: "submit", disabled: k || !g.trim(), children: "Create" })
148
+ ] })
149
+ ]
150
+ }
151
+ ),
152
+ b === "join" && /* @__PURE__ */ n(
153
+ "form",
154
+ {
155
+ className: "ws-form",
156
+ onSubmit: (s) => {
157
+ s.preventDefault(), L();
158
+ },
159
+ children: [
160
+ /* @__PURE__ */ e("label", { htmlFor: `${m}-code`, children: "Invite code" }),
161
+ /* @__PURE__ */ e(
162
+ "input",
163
+ {
164
+ id: `${m}-code`,
165
+ type: "text",
166
+ placeholder: "Paste an invite code",
167
+ value: u,
168
+ autoFocus: !0,
169
+ onChange: (s) => C(s.target.value)
170
+ }
171
+ ),
172
+ /* @__PURE__ */ n("div", { className: "ws-form__row", children: [
173
+ /* @__PURE__ */ e("button", { type: "button", className: "ws-form__back", onClick: () => w("menu"), children: "Back" }),
174
+ /* @__PURE__ */ e("button", { type: "submit", disabled: k || !u.trim(), children: "Join" })
175
+ ] })
176
+ ]
177
+ }
178
+ ),
179
+ E && /* @__PURE__ */ e("p", { className: "ws-menu__error", role: "alert", children: E })
180
+ ] })
181
+ ] });
182
+ }
183
+ function J({ workspace: i, onLeft: t }) {
184
+ const d = A(), v = B(), [o, h] = a(null), [m, p] = a(!1);
185
+ async function l() {
186
+ if (!m) {
187
+ p(!0), h(null);
188
+ try {
189
+ await d.leave(i.id), t == null || t();
190
+ } catch (y) {
191
+ h(I(y));
192
+ } finally {
193
+ p(!1);
194
+ }
195
+ }
196
+ }
197
+ return /* @__PURE__ */ n("section", { className: "shepherd-general", "aria-labelledby": v, children: [
198
+ /* @__PURE__ */ n("div", { className: "card-head", children: [
199
+ /* @__PURE__ */ e("h3", { id: v, children: "General" }),
200
+ /* @__PURE__ */ e("p", { className: "card-sub", children: "Your active workspace and its settings." })
201
+ ] }),
202
+ /* @__PURE__ */ n("div", { className: "card-body", children: [
203
+ o && /* @__PURE__ */ e("p", { role: "alert", children: o }),
204
+ /* @__PURE__ */ n("div", { className: "field", children: [
205
+ /* @__PURE__ */ e("label", { children: "Workspace name" }),
206
+ /* @__PURE__ */ e("p", { className: "readonly-value", children: i.name })
207
+ ] }),
208
+ /* @__PURE__ */ n("div", { className: "field", children: [
209
+ /* @__PURE__ */ e("label", { children: "Your role" }),
210
+ /* @__PURE__ */ e("p", { className: "readonly-value", children: i.role })
211
+ ] }),
212
+ /* @__PURE__ */ n("div", { className: "field leave", children: [
213
+ /* @__PURE__ */ e("label", { children: "Leave workspace" }),
214
+ /* @__PURE__ */ e("p", { className: "helper", children: "Remove yourself from this workspace. You'll need a new invite to rejoin." }),
215
+ /* @__PURE__ */ e("button", { type: "button", className: "danger", onClick: () => void l(), disabled: m, children: "Leave workspace" })
162
216
  ] })
163
217
  ] })
164
218
  ] });
165
219
  }
166
- function G({ workspaceId: l, refreshKey: t = 0, canRemove: r = !1, onLeft: b }) {
167
- const d = U(), y = B(), [C, g] = i([]), [k, m] = i(null), [u, o] = i(null), [w, h] = i(!0), [s, p] = i(null), [v, S] = i(!1);
168
- async function R({ keepError: c = !1 } = {}) {
169
- c || m(null);
220
+ function V({ workspaceId: i, refreshKey: t = 0, canRemove: d = !1 }) {
221
+ const v = A(), o = B(), [h, m] = a([]), [p, l] = a(null), [y, b] = a(null), [w, g] = a(!0), [N, u] = a(null);
222
+ async function C({ keepError: r = !1 } = {}) {
223
+ r || l(null);
170
224
  try {
171
- const f = await d.listMembers(l);
172
- g(f.members);
173
- } catch (f) {
174
- m(N(f));
225
+ const k = await v.listMembers(i);
226
+ m(k.members);
227
+ } catch (k) {
228
+ l(I(k));
175
229
  } finally {
176
- h(!1);
230
+ g(!1);
177
231
  }
178
232
  }
179
- $(() => {
180
- h(!0), R();
181
- }, [d, l, t]);
182
- async function x(c, f) {
183
- if (!s) {
184
- p(c), m(null), o(null), g((_) => _.filter((I) => I.accountId !== c));
233
+ U(() => {
234
+ g(!0), C();
235
+ }, [v, i, t]);
236
+ async function E(r, k) {
237
+ if (!N) {
238
+ u(r), l(null), b(null), m((_) => _.filter((S) => S.accountId !== r));
185
239
  try {
186
- await d.removeMember(l, c), o(`Removed ${f}`);
240
+ await v.removeMember(i, r), b(`Removed ${k}`);
187
241
  } catch (_) {
188
- m(N(_)), R({ keepError: !0 });
242
+ l(I(_)), C({ keepError: !0 });
243
+ } finally {
244
+ u(null);
245
+ }
246
+ }
247
+ }
248
+ return /* @__PURE__ */ n("section", { className: "shepherd-members", "aria-labelledby": o, children: [
249
+ /* @__PURE__ */ e("div", { className: "card-head", children: /* @__PURE__ */ e("h3", { id: o, children: "Members" }) }),
250
+ /* @__PURE__ */ n("div", { className: "card-body", children: [
251
+ p && /* @__PURE__ */ e("p", { role: "alert", children: p }),
252
+ y && /* @__PURE__ */ e("p", { role: "status", children: y }),
253
+ w ? /* @__PURE__ */ e("p", { role: "status", children: "Loading…" }) : h.length === 0 ? /* @__PURE__ */ e("p", { children: "No members." }) : /* @__PURE__ */ e("ul", { children: h.map((r) => {
254
+ const k = r.displayName ?? r.githubLogin ?? r.accountId;
255
+ return /* @__PURE__ */ n("li", { children: [
256
+ /* @__PURE__ */ e("span", { children: k }),
257
+ /* @__PURE__ */ e("span", { className: "role", children: r.role }),
258
+ d && /* @__PURE__ */ e(
259
+ "button",
260
+ {
261
+ type: "button",
262
+ "aria-label": `Remove ${k}`,
263
+ onClick: () => void E(r.accountId, k),
264
+ disabled: N === r.accountId,
265
+ children: "Remove"
266
+ }
267
+ )
268
+ ] }, r.accountId);
269
+ }) })
270
+ ] })
271
+ ] });
272
+ }
273
+ function X({ workspaceId: i, onMembersChanged: t }) {
274
+ const d = A(), v = B(), [o, h] = a(null), [m, p] = a(null), [l, y] = a(!1), [b, w] = a(""), [g, N] = a(null), [u, C] = a(!1), [E, r] = a(null);
275
+ U(() => {
276
+ h(null), N(null);
277
+ }, [i]);
278
+ async function k() {
279
+ if (!l) {
280
+ y(!0), p(null);
281
+ try {
282
+ const f = await d.createInvite(i, {});
283
+ h(f), t == null || t();
284
+ } catch (f) {
285
+ p(I(f));
189
286
  } finally {
190
- p(null);
287
+ y(!1);
191
288
  }
192
289
  }
193
290
  }
194
- async function E() {
195
- if (!v) {
196
- S(!0), m(null), o(null);
291
+ async function _() {
292
+ if (!(!o || l)) {
293
+ y(!0), p(null);
197
294
  try {
198
- await d.leave(l), o("Left workspace"), b == null || b();
199
- } catch (c) {
200
- m(N(c));
295
+ await d.revokeInvite(i, o.code), h(null);
296
+ } catch (f) {
297
+ p(I(f));
201
298
  } finally {
202
- S(!1);
299
+ y(!1);
203
300
  }
204
301
  }
205
302
  }
206
- return /* @__PURE__ */ a("section", { className: "shepherd-members", "aria-labelledby": y, children: [
207
- /* @__PURE__ */ e("div", { className: "card-head", children: /* @__PURE__ */ e("h3", { id: y, children: "Members" }) }),
208
- /* @__PURE__ */ a("div", { className: "card-body", children: [
209
- k && /* @__PURE__ */ e("p", { role: "alert", children: k }),
210
- u && /* @__PURE__ */ e("p", { role: "status", children: u }),
211
- w ? /* @__PURE__ */ e("p", { role: "status", children: "Loading…" }) : C.length === 0 ? /* @__PURE__ */ e("p", { children: "No members." }) : /* @__PURE__ */ e("ul", { children: C.map((c) => {
212
- const f = c.displayName ?? c.githubLogin ?? c.accountId;
213
- return /* @__PURE__ */ a("li", { children: [
214
- /* @__PURE__ */ e("span", { children: f }),
215
- /* @__PURE__ */ e("span", { className: "role", children: c.role }),
216
- r && /* @__PURE__ */ e(
217
- "button",
303
+ async function S() {
304
+ if (!(!b.trim() || u)) {
305
+ C(!0), N(null);
306
+ try {
307
+ const f = await d.inviteByEmail(i, b.trim());
308
+ N(`Invite sent to ${f.email}.`), w(""), t == null || t();
309
+ } catch (f) {
310
+ N(I(f));
311
+ } finally {
312
+ C(!1);
313
+ }
314
+ }
315
+ }
316
+ const x = o ? `${window.location.origin}/shepherd/join/${encodeURIComponent(o.code)}` : null;
317
+ async function L(f, T) {
318
+ await navigator.clipboard.writeText(T), r(f), setTimeout(() => r((s) => s === f ? null : s), 2e3);
319
+ }
320
+ return /* @__PURE__ */ n("section", { className: "shepherd-invites", "aria-labelledby": v, children: [
321
+ /* @__PURE__ */ n("div", { className: "card-head", children: [
322
+ /* @__PURE__ */ e("h3", { id: v, children: "Invite people" }),
323
+ /* @__PURE__ */ e("p", { className: "card-sub", children: "Share a code, or send a one-time join link by email." })
324
+ ] }),
325
+ /* @__PURE__ */ n("div", { className: "card-body", children: [
326
+ m && /* @__PURE__ */ e("p", { role: "alert", children: m }),
327
+ /* @__PURE__ */ n("div", { className: "field invites", children: [
328
+ /* @__PURE__ */ e("label", { children: "Invite by code" }),
329
+ /* @__PURE__ */ e("p", { className: "helper", children: "Anyone with the code can join this workspace." }),
330
+ o && /* @__PURE__ */ n("div", { className: "invite-result", children: [
331
+ /* @__PURE__ */ n("div", { className: "invite-result__head", children: [
332
+ /* @__PURE__ */ e("span", { className: "invite-result__eyebrow", children: "Invite code" }),
333
+ /* @__PURE__ */ e("span", { className: "invite-uses", children: o.maxUses === null ? `${o.useCount} uses` : `${o.useCount} / ${o.maxUses} uses` }),
334
+ /* @__PURE__ */ e(
335
+ "button",
336
+ {
337
+ type: "button",
338
+ className: "link-btn",
339
+ "aria-label": "Revoke invite",
340
+ onClick: () => void _(),
341
+ disabled: l,
342
+ children: "Revoke"
343
+ }
344
+ )
345
+ ] }),
346
+ /* @__PURE__ */ n("div", { className: "invite-result__copyrow", children: [
347
+ /* @__PURE__ */ e("code", { children: o.code }),
348
+ /* @__PURE__ */ e(
349
+ "button",
350
+ {
351
+ type: "button",
352
+ className: "copy-btn",
353
+ onClick: () => void L("code", o.code),
354
+ children: E === "code" ? "Copied" : "Copy"
355
+ }
356
+ )
357
+ ] }),
358
+ x && /* @__PURE__ */ n("div", { className: "invite-result__copyrow invite-result__copyrow--link", children: [
359
+ /* @__PURE__ */ e("a", { href: x, children: x }),
360
+ /* @__PURE__ */ e(
361
+ "button",
362
+ {
363
+ type: "button",
364
+ className: "copy-btn",
365
+ onClick: () => void L("link", x),
366
+ children: E === "link" ? "Copied" : "Copy link"
367
+ }
368
+ )
369
+ ] })
370
+ ] }),
371
+ /* @__PURE__ */ e("button", { type: "button", onClick: () => void k(), disabled: l, children: "Create invite" })
372
+ ] }),
373
+ /* @__PURE__ */ n("div", { className: "field email-invite", children: [
374
+ /* @__PURE__ */ e("label", { htmlFor: "invite-email", children: "Invite by email" }),
375
+ /* @__PURE__ */ e("p", { className: "helper", children: "Send a one-time-use join link directly to someone's inbox." }),
376
+ /* @__PURE__ */ n("div", { className: "field__row", children: [
377
+ /* @__PURE__ */ e(
378
+ "input",
218
379
  {
219
- type: "button",
220
- "aria-label": `Remove ${f}`,
221
- onClick: () => void x(c.accountId, f),
222
- disabled: s === c.accountId,
223
- children: "Remove"
380
+ id: "invite-email",
381
+ type: "email",
382
+ placeholder: "name@example.com",
383
+ value: b,
384
+ onChange: (f) => w(f.target.value)
224
385
  }
225
- )
226
- ] }, c.accountId);
227
- }) }),
228
- /* @__PURE__ */ e("button", { type: "button", onClick: () => void E(), disabled: v, children: "Leave workspace" })
386
+ ),
387
+ /* @__PURE__ */ e("button", { type: "button", onClick: () => void S(), disabled: u, children: "Send invite" })
388
+ ] }),
389
+ g && /* @__PURE__ */ e("p", { className: "email-invite__status", children: g })
390
+ ] })
229
391
  ] })
230
392
  ] });
231
393
  }
232
- const J = [
394
+ const q = [
233
395
  { id: "claude", label: "Claude Code" },
234
396
  { id: "codex", label: "Codex" },
235
397
  { id: "pi", label: "Pi" },
236
398
  { id: "generic", label: "Generic (JSON)" }
237
- ], X = "shp_<paste-after-generating>", q = {
399
+ ], z = "shp_<paste-after-generating>", Q = {
238
400
  claude: "claude mcp add shepherd -s user \\",
239
401
  codex: "codex mcp add shepherd \\",
240
402
  pi: "pi mcp add shepherd \\"
241
403
  };
242
- function z(l, t) {
243
- const r = `created ${F(l.createdAt, t)}`, b = l.lastUsedAt ? `last used ${F(l.lastUsedAt, t)}` : "never used";
244
- return `${r} · ${b}`;
404
+ function Z(i, t) {
405
+ const d = `created ${O(i.createdAt, t)}`, v = i.lastUsedAt ? `last used ${O(i.lastUsedAt, t)}` : "never used";
406
+ return `${d} · ${v}`;
245
407
  }
246
- function Q(l, t, r) {
247
- return l === "generic" ? JSON.stringify(
408
+ function M(i, t, d) {
409
+ return i === "generic" ? JSON.stringify(
248
410
  {
249
411
  mcpServers: {
250
412
  shepherd: {
251
413
  command: "npx",
252
414
  args: ["-y", "@korso/shepherd"],
253
- env: { HUB_URL: t, SHEPHERD_TOKEN: r }
415
+ env: { HUB_URL: t, SHEPHERD_TOKEN: d }
254
416
  }
255
417
  }
256
418
  },
257
419
  null,
258
420
  2
259
421
  ) : [
260
- q[l],
422
+ Q[i],
261
423
  ` -e HUB_URL=${t} \\`,
262
- ` -e SHEPHERD_TOKEN=${r} \\`,
424
+ ` -e SHEPHERD_TOKEN=${d} \\`,
263
425
  " -- npx -y @korso/shepherd"
264
426
  ].join(`
265
427
  `);
266
428
  }
267
- function V({ workspaceId: l, hubUrl: t }) {
268
- const r = U(), b = t ?? r.baseUrl, d = B(), [y, C] = i("claude"), [g, k] = i(""), [m, u] = i(null), [o, w] = i([]), [h, s] = i(null), [p, v] = i(null), [S, R] = i(!1), [x, E] = i(!1), [c, f] = i(!0), [_, I] = i(null);
269
- async function L() {
429
+ function ee({ hubUrl: i }) {
430
+ const t = A(), d = i ?? t.baseUrl, v = B(), [o, h] = a("claude"), [m, p] = a(""), [l, y] = a(null), [b, w] = a([]), [g, N] = a(null), [u, C] = a(null), [E, r] = a(!1), [k, _] = a(!1), [S, x] = a(!0), [L, f] = a(null);
431
+ async function T() {
270
432
  try {
271
- const n = await r.listTokens(l);
272
- w(n.tokens);
273
- } catch (n) {
274
- s(N(n));
433
+ const c = await t.listAccountTokens();
434
+ w(c.tokens);
435
+ } catch (c) {
436
+ N(I(c));
275
437
  } finally {
276
- f(!1);
438
+ x(!1);
277
439
  }
278
440
  }
279
- $(() => {
280
- f(!0), L();
281
- }, [r, l]), $(() => {
282
- u(null);
283
- }, [l]);
284
- async function H() {
285
- R(!0), s(null);
441
+ U(() => {
442
+ x(!0), T();
443
+ }, [t]);
444
+ async function s() {
445
+ r(!0), N(null);
286
446
  try {
287
- const n = await r.mintToken(
288
- l,
289
- g.trim() ? { name: g.trim() } : {}
447
+ const c = await t.mintAccountToken(
448
+ m.trim() ? { name: m.trim() } : {}
290
449
  );
291
- u(n.token), k(""), await L();
292
- } catch (n) {
293
- s(N(n));
450
+ y(c.token), p(""), await T();
451
+ } catch (c) {
452
+ N(I(c));
294
453
  } finally {
295
- R(!1);
454
+ r(!1);
296
455
  }
297
456
  }
298
- async function O(n) {
299
- if (!_) {
300
- I(n), s(null), v(null);
457
+ async function R(c) {
458
+ if (!L) {
459
+ f(c), N(null), C(null);
301
460
  try {
302
- await r.revokeToken(l, n), w((T) => T.filter((P) => P.id !== n)), v("Token revoked");
303
- } catch (T) {
304
- s(N(T));
461
+ await t.revokeAccountToken(c), w((j) => j.filter((W) => W.id !== c)), C("Token revoked");
462
+ } catch (j) {
463
+ N(I(j));
305
464
  } finally {
306
- I(null);
465
+ f(null);
307
466
  }
308
467
  }
309
468
  }
310
- const A = Q(y, b, m ?? X);
311
- async function j() {
312
- await navigator.clipboard.writeText(A), E(!0), setTimeout(() => E(!1), 2e3);
469
+ const $ = M(o, d, l ?? z);
470
+ async function H() {
471
+ await navigator.clipboard.writeText($), _(!0), setTimeout(() => _(!1), 2e3);
313
472
  }
314
- return /* @__PURE__ */ a("section", { className: "shepherd-connect-agent", "aria-labelledby": d, children: [
315
- /* @__PURE__ */ a("div", { className: "card-head", children: [
316
- /* @__PURE__ */ e("h3", { id: d, children: "Connect your agent" }),
317
- /* @__PURE__ */ e("p", { className: "card-sub", children: "Generate a token, then paste the command into your coding tool." })
473
+ return /* @__PURE__ */ n("section", { className: "shepherd-connect-agent", "aria-labelledby": v, children: [
474
+ /* @__PURE__ */ n("div", { className: "card-head", children: [
475
+ /* @__PURE__ */ e("h3", { id: v, children: "Connect your agent" }),
476
+ /* @__PURE__ */ e("p", { className: "card-sub", children: "Generate a token — it works across all your workspaces — then paste the command into your coding tool." }),
477
+ /* @__PURE__ */ e("p", { className: "card-sub", children: "The first time the agent opens a repo, it'll ask which workspace to coordinate that repo with." })
318
478
  ] }),
319
- /* @__PURE__ */ a("div", { className: "card-body", children: [
320
- /* @__PURE__ */ a("div", { className: "field", children: [
479
+ /* @__PURE__ */ n("div", { className: "card-body", children: [
480
+ /* @__PURE__ */ n("div", { className: "field", children: [
321
481
  /* @__PURE__ */ e("label", { htmlFor: "connect-tool", children: "Tool" }),
322
482
  /* @__PURE__ */ e(
323
483
  "select",
324
484
  {
325
485
  id: "connect-tool",
326
- value: y,
327
- onChange: (n) => C(n.target.value),
328
- children: J.map((n) => /* @__PURE__ */ e("option", { value: n.id, children: n.label }, n.id))
486
+ value: o,
487
+ onChange: (c) => h(c.target.value),
488
+ children: q.map((c) => /* @__PURE__ */ e("option", { value: c.id, children: c.label }, c.id))
329
489
  }
330
490
  )
331
491
  ] }),
332
- /* @__PURE__ */ a("div", { className: "field generate", children: [
492
+ /* @__PURE__ */ n("div", { className: "field generate", children: [
333
493
  /* @__PURE__ */ e("label", { htmlFor: "token-name", children: "Token name (optional)" }),
334
- /* @__PURE__ */ a("div", { className: "field__row", children: [
494
+ /* @__PURE__ */ n("div", { className: "field__row", children: [
335
495
  /* @__PURE__ */ e(
336
496
  "input",
337
497
  {
338
498
  id: "token-name",
339
499
  type: "text",
340
- value: g,
341
- onChange: (n) => k(n.target.value),
500
+ value: m,
501
+ onChange: (c) => p(c.target.value),
342
502
  placeholder: "e.g. laptop"
343
503
  }
344
504
  ),
345
- /* @__PURE__ */ e("button", { type: "button", onClick: () => void H(), disabled: S, children: "Generate token" })
505
+ /* @__PURE__ */ e("button", { type: "button", onClick: () => void s(), disabled: E, children: "Generate token" })
346
506
  ] })
347
507
  ] }),
348
- h && /* @__PURE__ */ e("p", { role: "alert", children: h }),
349
- p && /* @__PURE__ */ e("p", { role: "status", children: p }),
350
- m && /* @__PURE__ */ e("p", { className: "token-once", role: "status", children: "Copy this token now — it won't be shown again." }),
351
- /* @__PURE__ */ a("div", { className: "install-command", children: [
352
- /* @__PURE__ */ e("pre", { "data-testid": "install-command", children: A }),
353
- /* @__PURE__ */ e("button", { type: "button", className: "install-command__copy", onClick: () => void j(), children: x ? "Copied" : "Copy" })
508
+ g && /* @__PURE__ */ e("p", { role: "alert", children: g }),
509
+ u && /* @__PURE__ */ e("p", { role: "status", children: u }),
510
+ l && /* @__PURE__ */ e("p", { className: "token-once", role: "status", children: "Copy this token now — it won't be shown again." }),
511
+ /* @__PURE__ */ n("div", { className: "install-command", children: [
512
+ /* @__PURE__ */ e("pre", { "data-testid": "install-command", children: $ }),
513
+ /* @__PURE__ */ e(
514
+ "button",
515
+ {
516
+ type: "button",
517
+ className: "install-command__copy",
518
+ "aria-label": k ? "Copied" : "Copy command",
519
+ title: k ? "Copied" : "Copy",
520
+ onClick: () => void H(),
521
+ children: k ? /* @__PURE__ */ e(
522
+ "svg",
523
+ {
524
+ width: "15",
525
+ height: "15",
526
+ viewBox: "0 0 24 24",
527
+ fill: "none",
528
+ stroke: "currentColor",
529
+ strokeWidth: "2.5",
530
+ strokeLinecap: "round",
531
+ strokeLinejoin: "round",
532
+ "aria-hidden": "true",
533
+ children: /* @__PURE__ */ e("path", { d: "M20 6 9 17l-5-5" })
534
+ }
535
+ ) : /* @__PURE__ */ n(
536
+ "svg",
537
+ {
538
+ width: "15",
539
+ height: "15",
540
+ viewBox: "0 0 24 24",
541
+ fill: "none",
542
+ stroke: "currentColor",
543
+ strokeWidth: "2",
544
+ strokeLinecap: "round",
545
+ strokeLinejoin: "round",
546
+ "aria-hidden": "true",
547
+ children: [
548
+ /* @__PURE__ */ e("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
549
+ /* @__PURE__ */ e("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
550
+ ]
551
+ }
552
+ )
553
+ }
554
+ )
354
555
  ] }),
355
556
  /* @__PURE__ */ e("h4", { children: "Existing tokens" }),
356
- c ? /* @__PURE__ */ e("p", { role: "status", children: "Loading…" }) : o.length === 0 ? /* @__PURE__ */ e("p", { children: "No tokens yet." }) : /* @__PURE__ */ e("ul", { children: o.map((n) => {
357
- const T = n.name ?? n.id;
358
- return /* @__PURE__ */ a("li", { children: [
359
- /* @__PURE__ */ e("span", { children: T }),
360
- /* @__PURE__ */ e("span", { className: "token-meta", children: z(n, Date.now()) }),
361
- n.revokedAt ? /* @__PURE__ */ e("span", { className: "revoked", children: "revoked" }) : /* @__PURE__ */ e(
557
+ S ? /* @__PURE__ */ e("p", { role: "status", children: "Loading…" }) : b.length === 0 ? /* @__PURE__ */ e("p", { children: "No tokens yet." }) : /* @__PURE__ */ e("ul", { children: b.map((c) => {
558
+ const j = c.name ?? c.id;
559
+ return /* @__PURE__ */ n("li", { children: [
560
+ /* @__PURE__ */ e("span", { children: j }),
561
+ /* @__PURE__ */ e("span", { className: "token-meta", children: Z(c, Date.now()) }),
562
+ c.revokedAt ? /* @__PURE__ */ e("span", { className: "revoked", children: "revoked" }) : /* @__PURE__ */ e(
362
563
  "button",
363
564
  {
364
565
  type: "button",
365
- "aria-label": `Revoke token ${T}`,
366
- onClick: () => void O(n.id),
367
- disabled: _ === n.id,
566
+ "aria-label": `Revoke token ${j}`,
567
+ onClick: () => void R(c.id),
568
+ disabled: L === c.id,
368
569
  children: "Revoke"
369
570
  }
370
571
  )
371
- ] }, n.id);
572
+ ] }, c.id);
372
573
  }) })
373
574
  ] })
374
575
  ] });
375
576
  }
376
- function ee({ hubUrl: l }) {
377
- const t = U(), [r, b] = i({ status: "loading" }), [d, y] = i(null), [C, g] = i(0), k = D(async () => {
577
+ const ne = [
578
+ { id: "general", label: "General" },
579
+ { id: "members", label: "Members" },
580
+ { id: "agent", label: "Agent" }
581
+ ];
582
+ function te({
583
+ workspace: i,
584
+ hubUrl: t,
585
+ membersRefreshKey: d,
586
+ onMembersChanged: v,
587
+ onLeft: o
588
+ }) {
589
+ const [h, m] = a("general"), p = i.role === "admin";
590
+ return /* @__PURE__ */ n("div", { className: "config-layout", children: [
591
+ /* @__PURE__ */ e("nav", { className: "config-nav", "aria-label": "Configuration sections", children: ne.map(({ id: l, label: y }) => /* @__PURE__ */ e(
592
+ "button",
593
+ {
594
+ type: "button",
595
+ className: "config-nav__item" + (h === l ? " config-nav__item--on" : ""),
596
+ "aria-current": h === l ? "page" : void 0,
597
+ onClick: () => m(l),
598
+ children: y
599
+ },
600
+ l
601
+ )) }),
602
+ /* @__PURE__ */ n("div", { className: "config-panel", children: [
603
+ h === "general" && /* @__PURE__ */ e(J, { workspace: i, onLeft: o }),
604
+ h === "members" && /* @__PURE__ */ n(F, { children: [
605
+ /* @__PURE__ */ e(
606
+ V,
607
+ {
608
+ workspaceId: i.id,
609
+ refreshKey: d,
610
+ canRemove: p
611
+ }
612
+ ),
613
+ p && /* @__PURE__ */ e(X, { workspaceId: i.id, onMembersChanged: v })
614
+ ] }),
615
+ h === "agent" && /* @__PURE__ */ e(ee, { hubUrl: t })
616
+ ] })
617
+ ] });
618
+ }
619
+ function re({ hubUrl: i }) {
620
+ const t = A(), [d, v] = a({ status: "loading" }), [o, h] = a(null), [m, p] = a(0), l = K(async () => {
378
621
  try {
379
- const h = await t.listWorkspaces();
380
- b({ status: "ready", workspaces: h.workspaces }), y(
381
- (s) => {
382
- var p;
383
- return s && h.workspaces.some((v) => v.id === s) ? s : ((p = h.workspaces[0]) == null ? void 0 : p.id) ?? null;
622
+ const u = await t.listWorkspaces();
623
+ v({ status: "ready", workspaces: u.workspaces }), h(
624
+ (C) => {
625
+ var E;
626
+ return C && u.workspaces.some((r) => r.id === C) ? C : ((E = u.workspaces[0]) == null ? void 0 : E.id) ?? null;
384
627
  }
385
628
  );
386
- } catch (h) {
387
- b({ status: "error", message: N(h) });
629
+ } catch (u) {
630
+ v({ status: "error", message: I(u) });
388
631
  }
389
632
  }, [t]);
390
- $(() => {
391
- k();
392
- }, [k]);
393
- const m = r.status === "ready" ? r.workspaces : [], u = m.find((h) => h.id === d) ?? null;
394
- if (r.status === "loading")
633
+ U(() => {
634
+ l();
635
+ }, [l]);
636
+ const y = d.status === "ready" ? d.workspaces : [], b = y.find((u) => u.id === o) ?? null;
637
+ if (d.status === "loading")
395
638
  return /* @__PURE__ */ e("div", { className: "shepherd-root", children: /* @__PURE__ */ e("p", { role: "status", children: "Loading…" }) });
396
- if (r.status === "error")
397
- return /* @__PURE__ */ e("div", { className: "shepherd-root", children: /* @__PURE__ */ e("p", { role: "alert", children: r.message }) });
398
- const o = m.length > 0, w = /* @__PURE__ */ a("section", { className: "shepherd-config", "aria-labelledby": "config-heading", children: [
639
+ if (d.status === "error")
640
+ return /* @__PURE__ */ e("div", { className: "shepherd-root", children: /* @__PURE__ */ e("p", { role: "alert", children: d.message }) });
641
+ const w = y.length > 0, g = /* @__PURE__ */ e(
642
+ Y,
643
+ {
644
+ workspaces: y,
645
+ selected: b,
646
+ onSelect: h,
647
+ onChanged: () => {
648
+ l();
649
+ },
650
+ onMembersChanged: () => p((u) => u + 1)
651
+ }
652
+ ), N = /* @__PURE__ */ n("section", { className: "shepherd-config", "aria-labelledby": "config-heading", children: [
399
653
  /* @__PURE__ */ e("h2", { id: "config-heading", children: "Configuration" }),
400
- /* @__PURE__ */ e(
401
- K,
654
+ b ? /* @__PURE__ */ e(
655
+ te,
402
656
  {
403
- workspaces: m,
404
- selected: u,
405
- onChanged: () => {
406
- k();
407
- },
408
- onSelect: y,
409
- onMembersChanged: () => g((h) => h + 1)
657
+ workspace: b,
658
+ hubUrl: i,
659
+ membersRefreshKey: m,
660
+ onMembersChanged: () => p((u) => u + 1),
661
+ onLeft: () => void l()
410
662
  }
411
- ),
412
- u && /* @__PURE__ */ e(
413
- G,
414
- {
415
- workspaceId: u.id,
416
- refreshKey: C,
417
- canRemove: u.role === "admin",
418
- onLeft: () => void k()
419
- }
420
- ),
421
- u && /* @__PURE__ */ e(V, { workspaceId: u.id, hubUrl: l })
663
+ ) : /* @__PURE__ */ n("p", { className: "config-none", children: [
664
+ "You're not in a workspace yet. Use the ",
665
+ /* @__PURE__ */ e("b", { children: "Get started ▾" }),
666
+ " menu at the top to create one or join with an invite code."
667
+ ] })
422
668
  ] });
423
669
  return /* @__PURE__ */ e("div", { className: "shepherd-root", children: /* @__PURE__ */ e(
424
- W,
670
+ G,
425
671
  {
426
- workspaceId: u == null ? void 0 : u.id,
427
- config: w,
428
- hasWorkspace: o
672
+ workspaceId: b == null ? void 0 : b.id,
673
+ config: N,
674
+ switcher: g,
675
+ hasWorkspace: w
429
676
  }
430
677
  ) });
431
678
  }
432
679
  export {
433
- V as ConnectAgent,
434
- W as Dashboard,
435
- ie as EmptyState,
436
- G as Members,
437
- ae as ShepherdClientError,
438
- le as ShepherdClientProvider,
439
- ee as ShepherdRoot,
440
- K as Workspaces,
441
- re as createShepherdClient,
442
- N as describeError,
443
- U as useShepherdClient
680
+ te as ConfigPanel,
681
+ ee as ConnectAgent,
682
+ G as Dashboard,
683
+ ce as EmptyState,
684
+ J as GeneralSettings,
685
+ X as Invites,
686
+ V as Members,
687
+ de as ShepherdClientError,
688
+ ue as ShepherdClientProvider,
689
+ re as ShepherdRoot,
690
+ Y as WorkspaceSwitcher,
691
+ he as createShepherdClient,
692
+ I as describeError,
693
+ A as useShepherdClient
444
694
  };