@korso/shepherd-ui 0.11.0 → 0.11.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/README.md CHANGED
@@ -1,50 +1,50 @@
1
- # @korso/shepherd-ui
2
-
3
- The Shepherd dashboard: an **auth-agnostic** React app (Vite) that renders the
4
- workspace landscape (crew, territory, active/done work, chat) and issues operator
5
- actions against the hub. "Auth-agnostic" means the core holds no notion of who
6
- the user is or how they authenticated — it renders coordination state and issues
7
- operator actions, leaving authentication to the layer in front of it.
8
-
9
- ## Two build outputs
10
-
11
- One source tree, two Vite builds (`npm run build` runs both):
12
-
13
- - **`dist/lib`** (`build:lib`) — a component library for **Korso** to embed in
14
- the hosted product. Exposes `<Dashboard/>`, `createShepherdClient`, the React
15
- context (`ShepherdClientProvider` / `useShepherdClient`), and types via the `.`
16
- export. It is **token-blind**: auth lives in the BFF in front of it. The
17
- stylesheet is opt-in (`@korso/shepherd-ui/styles.css`), not auto-imported.
18
- - **`dist/selfhost`** (`build:app`) — a self-contained SPA the **hub serves**
19
- verbatim via `@fastify/static` (the hub resolves `../../ui/dist/selfhost/`).
20
- It bundles its own CSS and mounts the token-gated root.
21
-
22
- Both `dist/` outputs are gitignored; `npm run build` (root) and the hub Docker
23
- image regenerate them.
24
-
25
- ## Two mounts
26
-
27
- - **Self-host (the Hub).** The Hub serves `<SelfHostApp/>` (the `./selfhost`
28
- export, mounted by `src/main.tsx`). This is the **one** place a team token is
29
- handled — the named exception to the auth-agnostic core: it prompts for the
30
- `TEAM_TOKEN`, persists it, builds a same-origin client that sends
31
- `Authorization: Bearer <token>`, and re-gates on a 401. The token gate lives
32
- **only** here, never in the `.` export or `<Dashboard/>`.
33
- - **Hosted (Korso).** Korso imports `<Dashboard/>` + `createShepherdClient` from
34
- the `.` export and supplies its own auth layer (the BFF). No token gate.
35
- Hosted shells that embed `<ShepherdRoot/>` may pass `onLogout`; Shepherd renders
36
- the Sign out action at the bottom of Config, while the host callback performs
37
- the actual session cleanup and navigation (cookies, OIDC, BFF state, etc.).
38
-
39
- ## Development
40
-
41
- ```
42
- npm -w @korso/shepherd-ui run dev # Vite dev server for the self-host SPA
43
- ```
44
-
45
- Other scripts: `build` (both outputs), `build:lib`, `build:app`, `type-check`
46
- (`tsc --noEmit`), `test` (Vitest, jsdom).
47
-
48
- ## Phase note
49
-
50
- Korso-side consumption of the `dist/lib` library is **finalized in Phase 5**.
1
+ # @korso/shepherd-ui
2
+
3
+ The Shepherd dashboard: an **auth-agnostic** React app (Vite) that renders the
4
+ workspace landscape (crew, territory, active/done work, chat) and issues operator
5
+ actions against the hub. "Auth-agnostic" means the core holds no notion of who
6
+ the user is or how they authenticated — it renders coordination state and issues
7
+ operator actions, leaving authentication to the layer in front of it.
8
+
9
+ ## Two build outputs
10
+
11
+ One source tree, two Vite builds (`npm run build` runs both):
12
+
13
+ - **`dist/lib`** (`build:lib`) — a component library for **Korso** to embed in
14
+ the hosted product. Exposes `<Dashboard/>`, `createShepherdClient`, the React
15
+ context (`ShepherdClientProvider` / `useShepherdClient`), and types via the `.`
16
+ export. It is **token-blind**: auth lives in the BFF in front of it. The
17
+ stylesheet is opt-in (`@korso/shepherd-ui/styles.css`), not auto-imported.
18
+ - **`dist/selfhost`** (`build:app`) — a self-contained SPA the **hub serves**
19
+ verbatim via `@fastify/static` (the hub resolves `../../ui/dist/selfhost/`).
20
+ It bundles its own CSS and mounts the token-gated root.
21
+
22
+ Both `dist/` outputs are gitignored; `npm run build` (root) and the hub Docker
23
+ image regenerate them.
24
+
25
+ ## Two mounts
26
+
27
+ - **Self-host (the Hub).** The Hub serves `<SelfHostApp/>` (the `./selfhost`
28
+ export, mounted by `src/main.tsx`). This is the **one** place a team token is
29
+ handled — the named exception to the auth-agnostic core: it prompts for the
30
+ `TEAM_TOKEN`, persists it, builds a same-origin client that sends
31
+ `Authorization: Bearer <token>`, and re-gates on a 401. The token gate lives
32
+ **only** here, never in the `.` export or `<Dashboard/>`.
33
+ - **Hosted (Korso).** Korso imports `<Dashboard/>` + `createShepherdClient` from
34
+ the `.` export and supplies its own auth layer (the BFF). No token gate.
35
+ Hosted shells that embed `<ShepherdRoot/>` may pass `onLogout`; Shepherd renders
36
+ the Sign out action at the bottom of Config, while the host callback performs
37
+ the actual session cleanup and navigation (cookies, OIDC, BFF state, etc.).
38
+
39
+ ## Development
40
+
41
+ ```
42
+ npm -w @korso/shepherd-ui run dev # Vite dev server for the self-host SPA
43
+ ```
44
+
45
+ Other scripts: `build` (both outputs), `build:lib`, `build:app`, `type-check`
46
+ (`tsc --noEmit`), `test` (Vitest, jsdom).
47
+
48
+ ## Phase note
49
+
50
+ Korso-side consumption of the `dist/lib` library is **finalized in Phase 5**.
@@ -48,7 +48,7 @@ export function Chat({ announcements, selectedRepo, nowMs }) {
48
48
  chatRef.current.scrollTop = chatRef.current.scrollHeight;
49
49
  }
50
50
  }, [messages.length]);
51
- return (_jsx("div", { id: "chat", className: "chat", ref: chatRef, children: messages.length === 0 ? (_jsx("div", { className: "empty", children: "No announcements yet \u2014 agents will post here as they coordinate." })) : (messages.map((a, i) => {
51
+ return (_jsx("div", { id: "chat", className: "chat", ref: chatRef, children: messages.length === 0 ? (_jsx("div", { className: "empty", children: "No announcements yet. Agents will post here as they coordinate." })) : (messages.map((a, i) => {
52
52
  const targeted = a.targetAgentName !== null || a.toAdmin;
53
53
  const className = "msg" +
54
54
  (targeted ? " msg--targeted" : "") +
@@ -142,5 +142,5 @@ export function Composer({ agents, selectedRepo, workspaceId, onSent }) {
142
142
  accept(name);
143
143
  }, children: [_jsx("div", { className: "ma", style: { background: colorForName(name) }, children: initialsFor(name) }), _jsx("span", { children: name })] }, name))) }), _jsxs("form", { id: "chat-form", className: "chat-form", onSubmit: (e) => {
144
144
  void onSubmit(e);
145
- }, children: [_jsx("input", { id: "chat-input", className: "chat-input", type: "text", autoComplete: "off", "aria-label": "Message the team", placeholder: "Message the team\u2026 use @name to direct it", role: "combobox", "aria-expanded": popOpen, "aria-controls": "mention-pop", "aria-autocomplete": "list", "aria-activedescendant": popOpen ? `mention-opt-${active}` : undefined, ref: inputRef, value: value, onChange: (e) => onInput(e.target.value), onKeyDown: onKeyDown }), _jsx("button", { id: "chat-send", className: "chat-send", type: "submit", disabled: sending, children: "Send" })] }), failed ? (_jsx("div", { className: "chat__note", role: "status", children: "send failed \u2014 retry" })) : null] }));
145
+ }, children: [_jsx("input", { id: "chat-input", className: "chat-input", type: "text", autoComplete: "off", "aria-label": "Message the team", placeholder: "Message the team\u2026 use @name to direct it", role: "combobox", "aria-expanded": popOpen, "aria-controls": "mention-pop", "aria-autocomplete": "list", "aria-activedescendant": popOpen ? `mention-opt-${active}` : undefined, ref: inputRef, value: value, onChange: (e) => onInput(e.target.value), onKeyDown: onKeyDown }), _jsx("button", { id: "chat-send", className: "chat-send", type: "submit", disabled: sending, children: "Send" })] }), failed ? (_jsx("div", { className: "chat__note", role: "status", children: "send failed, retry" })) : null] }));
146
146
  }
@@ -36,7 +36,7 @@ export function DoneList({ tasks, nowMs, selectedRepo, doneShown, onLoadMore, })
36
36
  lastDay = day;
37
37
  const dropped = t.status === "dropped";
38
38
  const meta = dropped
39
- ? `went offline ${formatRelative(t.endedAt ?? t.createdAt, nowMs)} no done signal`
39
+ ? `went offline ${formatRelative(t.endedAt ?? t.createdAt, nowMs)}, no done signal`
40
40
  : `finished ${formatRelative(t.endedAt ?? t.createdAt, nowMs)}${formatActiveDuration(t.createdAt, t.endedAt)
41
41
  ? " · " + formatActiveDuration(t.createdAt, t.endedAt)
42
42
  : ""}`;
@@ -24,5 +24,5 @@ export function ConfirmDeleteWorkspace({ workspaceName, busy, error, onConfirm,
24
24
  onCancel();
25
25
  }, children: _jsxs("div", { className: "shepherd-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": headingId, "aria-describedby": descId,
26
26
  // Clicks inside the dialog must not bubble to the backdrop's cancel.
27
- onClick: (e) => e.stopPropagation(), children: [_jsx("h3", { id: headingId, children: "Delete workspace" }), _jsxs("p", { id: descId, className: "shepherd-modal__body", children: ["This permanently deletes ", _jsx("b", { children: workspaceName }), " and all of its data \u2014 agents, sessions, tasks, announcements, change history, tokens, invites, and members. This cannot be undone."] }), _jsxs("label", { htmlFor: `${headingId}-input`, className: "shepherd-modal__label", children: ["Type ", _jsx("b", { children: workspaceName }), " to confirm"] }), _jsx("input", { id: `${headingId}-input`, ref: inputRef, type: "text", value: typed, autoComplete: "off", disabled: busy, onChange: (e) => setTyped(e.target.value) }), error && _jsx("p", { role: "alert", children: error }), _jsxs("div", { className: "shepherd-modal__actions", children: [_jsx("button", { type: "button", onClick: onCancel, disabled: busy, children: "Cancel" }), _jsx("button", { type: "button", className: "danger", onClick: onConfirm, disabled: !confirmed || busy, children: busy ? "Deleting…" : "Delete workspace" })] })] }) }));
27
+ onClick: (e) => e.stopPropagation(), children: [_jsx("h3", { id: headingId, children: "Delete workspace" }), _jsxs("p", { id: descId, className: "shepherd-modal__body", children: ["This permanently deletes ", _jsx("b", { children: workspaceName }), " and all of its data: agents, sessions, tasks, announcements, change history, tokens, invites, and members. This cannot be undone."] }), _jsxs("label", { htmlFor: `${headingId}-input`, className: "shepherd-modal__label", children: ["Type ", _jsx("b", { children: workspaceName }), " to confirm"] }), _jsx("input", { id: `${headingId}-input`, ref: inputRef, type: "text", value: typed, autoComplete: "off", disabled: busy, onChange: (e) => setTyped(e.target.value) }), error && _jsx("p", { role: "alert", children: error }), _jsxs("div", { className: "shepherd-modal__actions", children: [_jsx("button", { type: "button", onClick: onCancel, disabled: busy, children: "Cancel" }), _jsx("button", { type: "button", className: "danger", onClick: onConfirm, disabled: !confirmed || busy, children: busy ? "Deleting…" : "Delete workspace" })] })] }) }));
28
28
  }
@@ -206,12 +206,12 @@ export function ConnectAgent({ hubUrl }) {
206
206
  setCopied(true);
207
207
  setTimeout(() => setCopied(false), 2000);
208
208
  }
209
- 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 changes files in a repo, Shepherd asks which workspace to coordinate that repo with \u2014 once per repo, right in your 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), onKeyDown: (e) => {
209
+ 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; it works across all your workspaces, so paste the command into your coding tool." }), _jsx("p", { className: "card-sub", children: "The first time the agent changes files in a repo, Shepherd asks which workspace to coordinate that repo with, once per repo, right in your 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), onKeyDown: (e) => {
210
210
  if (e.key === "Enter" && !busy) {
211
211
  e.preventDefault();
212
212
  void generate();
213
213
  }
214
- }, 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" })] })) })] }), hook && (_jsxs("div", { className: "hook-setup", children: [_jsxs("p", { className: "card-sub", children: ["Message delivery sets itself up: the first time the agent runs, Shepherd adds its inbox hook to ", _jsx("code", { children: hook.target }), " ", "automatically \u2014 it delivers teammate announcements and reminds the agent to link each repo before its first write. Happens at most once; set ", _jsx("code", { children: "SHEPHERD_NO_AUTO_HOOKS=1" }), " to opt out."] }), hook.snippet && (_jsxs("details", { className: "hook-reference", children: [_jsx("summary", { children: "What gets added (manual equivalent)" }), _jsx("div", { className: "install-command", children: _jsx("pre", { "data-testid": "hook-snippet", children: hook.snippet }) })] }))] })), _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) => {
214
+ }, 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; 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" })] })) })] }), hook && (_jsxs("div", { className: "hook-setup", children: [_jsxs("p", { className: "card-sub", children: ["Message delivery sets itself up: the first time the agent runs, Shepherd adds its inbox hook to ", _jsx("code", { children: hook.target }), " ", "automatically. It delivers teammate announcements and reminds the agent to link each repo before its first write. This happens at most once; set", " ", _jsx("code", { children: "SHEPHERD_NO_AUTO_HOOKS=1" }), " to opt out."] }), hook.snippet && (_jsxs("details", { className: "hook-reference", children: [_jsx("summary", { children: "What gets added (manual equivalent)" }), _jsx("div", { className: "install-command", children: _jsx("pre", { "data-testid": "hook-snippet", children: hook.snippet }) })] }))] })), _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) => {
215
215
  const name = t.name ?? t.id;
216
216
  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));
217
217
  }) }))] })] }));
@@ -928,7 +928,7 @@ function Dt({
928
928
  c.map((u) => {
929
929
  const d = yt(u.endedAt ?? u.createdAt, n), g = d !== h ? d : null;
930
930
  h = d;
931
- const f = u.status === "dropped", k = f ? `went offline ${K(u.endedAt ?? u.createdAt, n)} no done signal` : `finished ${K(u.endedAt ?? u.createdAt, n)}${ke(u.createdAt, u.endedAt) ? " · " + ke(u.createdAt, u.endedAt) : ""}`;
931
+ const f = u.status === "dropped", k = f ? `went offline ${K(u.endedAt ?? u.createdAt, n)}, no done signal` : `finished ${K(u.endedAt ?? u.createdAt, n)}${ke(u.createdAt, u.endedAt) ? " · " + ke(u.createdAt, u.endedAt) : ""}`;
932
932
  return /* @__PURE__ */ m(Fe, { children: [
933
933
  g !== null && /* @__PURE__ */ r("div", { className: "day", children: g }),
934
934
  /* @__PURE__ */ m("div", { className: "task", children: [
@@ -971,7 +971,7 @@ function xt({ announcements: t, selectedRepo: n, nowMs: i }) {
971
971
  const l = [...t].filter((c) => M(c, n)).reverse();
972
972
  return Ue(() => {
973
973
  a.current && o.current && (o.current.scrollTop = o.current.scrollHeight);
974
- }, [l.length]), /* @__PURE__ */ r("div", { id: "chat", className: "chat", ref: o, children: l.length === 0 ? /* @__PURE__ */ r("div", { className: "empty", children: "No announcements yet agents will post here as they coordinate." }) : l.map((c, h) => {
974
+ }, [l.length]), /* @__PURE__ */ r("div", { id: "chat", className: "chat", ref: o, children: l.length === 0 ? /* @__PURE__ */ r("div", { className: "empty", children: "No announcements yet. Agents will post here as they coordinate." }) : l.map((c, h) => {
975
975
  const d = "msg" + (c.targetAgentName !== null || c.toAdmin ? " msg--targeted" : "") + (c.fromAdmin ? " msg--me" : "");
976
976
  return (
977
977
  // The feed has no stable id; index is acceptable because the list is
@@ -1130,7 +1130,7 @@ function Lt({ agents: t, selectedRepo: n, workspaceId: i, onSent: o }) {
1130
1130
  ]
1131
1131
  }
1132
1132
  ),
1133
- T ? /* @__PURE__ */ r("div", { className: "chat__note", role: "status", children: "send failed retry" }) : null
1133
+ T ? /* @__PURE__ */ r("div", { className: "chat__note", role: "status", children: "send failed, retry" }) : null
1134
1134
  ] });
1135
1135
  }
1136
1136
  function Ae({
package/dist/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { u as L, d as x, S as M, f as W, D as V } from "./Dashboard-YgxAVEUF.js";
2
- import { E as ye, a as be, c as ke } from "./Dashboard-YgxAVEUF.js";
1
+ import { u as L, d as x, S as M, f as W, D as V } from "./Dashboard-DNr0hdbW.js";
2
+ import { E as ye, a as be, c as ke } from "./Dashboard-DNr0hdbW.js";
3
3
  import { jsxs as n, jsx as e, Fragment as $ } from "react/jsx-runtime";
4
- import { useId as j, useRef as U, useState as r, useEffect as T, useCallback as G } from "react";
4
+ import { useId as j, useRef as B, useState as s, useEffect as T, useCallback as G } from "react";
5
5
  function F(t) {
6
6
  return (t.trim()[0] ?? "?").toUpperCase();
7
7
  }
@@ -9,10 +9,10 @@ function q({
9
9
  workspaces: t,
10
10
  selected: a,
11
11
  onSelect: h,
12
- onChanged: s,
12
+ onChanged: r,
13
13
  onMembersChanged: i
14
14
  }) {
15
- const p = L(), u = j(), c = U(null), [d, m] = r(!1), [y, l] = r("menu"), [N, g] = r(""), [_, b] = r(""), [C, o] = r(null), [k, E] = r(!1);
15
+ const p = L(), u = j(), c = B(null), [d, m] = s(!1), [y, l] = s("menu"), [N, g] = s(""), [_, b] = s(""), [C, o] = s(null), [k, E] = s(!1);
16
16
  function S() {
17
17
  m(!1), l("menu"), g(""), b(""), o(null);
18
18
  }
@@ -33,7 +33,7 @@ function q({
33
33
  E(!0), o(null);
34
34
  try {
35
35
  const v = await p.createWorkspace({ name: N.trim() });
36
- h(v.id), s(), S();
36
+ h(v.id), r(), S();
37
37
  } catch (v) {
38
38
  o(x(v));
39
39
  } finally {
@@ -46,7 +46,7 @@ function q({
46
46
  E(!0), o(null);
47
47
  try {
48
48
  const v = await p.redeemInvite(_.trim());
49
- h(v.workspace.id), s(), i == null || i(), S();
49
+ h(v.workspace.id), r(), i == null || i(), S();
50
50
  } catch (v) {
51
51
  o(x(v));
52
52
  } finally {
@@ -184,10 +184,10 @@ function X({
184
184
  workspaceName: t,
185
185
  busy: a,
186
186
  error: h,
187
- onConfirm: s,
187
+ onConfirm: r,
188
188
  onCancel: i
189
189
  }) {
190
- const p = j(), u = j(), [c, d] = r(""), m = U(null);
190
+ const p = j(), u = j(), [c, d] = s(""), m = B(null);
191
191
  T(() => {
192
192
  var l;
193
193
  (l = m.current) == null || l.focus();
@@ -219,7 +219,7 @@ function X({
219
219
  /* @__PURE__ */ n("p", { id: u, className: "shepherd-modal__body", children: [
220
220
  "This permanently deletes ",
221
221
  /* @__PURE__ */ e("b", { children: t }),
222
- " and all of its data agents, sessions, tasks, announcements, change history, tokens, invites, and members. This cannot be undone."
222
+ " and all of its data: agents, sessions, tasks, announcements, change history, tokens, invites, and members. This cannot be undone."
223
223
  ] }),
224
224
  /* @__PURE__ */ n("label", { htmlFor: `${p}-input`, className: "shepherd-modal__label", children: [
225
225
  "Type ",
@@ -246,7 +246,7 @@ function X({
246
246
  {
247
247
  type: "button",
248
248
  className: "danger",
249
- onClick: s,
249
+ onClick: r,
250
250
  disabled: !y || a,
251
251
  children: a ? "Deleting…" : "Delete workspace"
252
252
  }
@@ -259,12 +259,12 @@ function X({
259
259
  );
260
260
  }
261
261
  function z({ workspace: t, onLeft: a, onDeleted: h }) {
262
- const s = L(), i = j(), [p, u] = r(null), [c, d] = r(!1), [m, y] = r(!1), [l, N] = r(!1), [g, _] = r(!1), [b, C] = r(null), o = t.role === "admin";
262
+ const r = L(), i = j(), [p, u] = s(null), [c, d] = s(!1), [m, y] = s(!1), [l, N] = s(!1), [g, _] = s(!1), [b, C] = s(null), o = t.role === "admin";
263
263
  async function k() {
264
264
  if (!c) {
265
265
  d(!0), u(null), y(!1);
266
266
  try {
267
- await s.leave(t.id), a == null || a();
267
+ await r.leave(t.id), a == null || a();
268
268
  } catch (S) {
269
269
  u(x(S)), S instanceof M && S.status === 409 && y(!0);
270
270
  } finally {
@@ -276,7 +276,7 @@ function z({ workspace: t, onLeft: a, onDeleted: h }) {
276
276
  if (!g) {
277
277
  _(!0), C(null);
278
278
  try {
279
- await s.deleteWorkspace(t.id), N(!1), h == null || h();
279
+ await r.deleteWorkspace(t.id), N(!1), h == null || h();
280
280
  } catch (S) {
281
281
  C(x(S));
282
282
  } finally {
@@ -337,11 +337,11 @@ function z({ workspace: t, onLeft: a, onDeleted: h }) {
337
337
  ] });
338
338
  }
339
339
  function Q({ workspaceId: t, refreshKey: a = 0, canRemove: h = !1 }) {
340
- const s = L(), i = j(), [p, u] = r([]), [c, d] = r(null), [m, y] = r(null), [l, N] = r(!0), [g, _] = r(null);
340
+ const r = L(), i = j(), [p, u] = s([]), [c, d] = s(null), [m, y] = s(null), [l, N] = s(!0), [g, _] = s(null);
341
341
  async function b({ keepError: o = !1 } = {}) {
342
342
  o || d(null);
343
343
  try {
344
- const k = await s.listMembers(t);
344
+ const k = await r.listMembers(t);
345
345
  u(k.members);
346
346
  } catch (k) {
347
347
  d(x(k));
@@ -351,12 +351,12 @@ function Q({ workspaceId: t, refreshKey: a = 0, canRemove: h = !1 }) {
351
351
  }
352
352
  T(() => {
353
353
  N(!0), b();
354
- }, [s, t, a]);
354
+ }, [r, t, a]);
355
355
  async function C(o, k) {
356
356
  if (!g) {
357
357
  _(o), d(null), y(null), u((E) => E.filter((S) => S.accountId !== o));
358
358
  try {
359
- await s.removeMember(t, o), y(`Removed ${k}`);
359
+ await r.removeMember(t, o), y(`Removed ${k}`);
360
360
  } catch (E) {
361
361
  d(x(E)), b({ keepError: !0 });
362
362
  } finally {
@@ -390,7 +390,7 @@ function Q({ workspaceId: t, refreshKey: a = 0, canRemove: h = !1 }) {
390
390
  ] });
391
391
  }
392
392
  function Z({ workspaceId: t, onMembersChanged: a }) {
393
- const h = L(), s = j(), [i, p] = r(null), [u, c] = r(null), [d, m] = r(!1), [y, l] = r(""), [N, g] = r(null), [_, b] = r(!1), [C, o] = r(null);
393
+ const h = L(), r = j(), [i, p] = s(null), [u, c] = s(null), [d, m] = s(!1), [y, l] = s(""), [N, g] = s(null), [_, b] = s(!1), [C, o] = s(null);
394
394
  T(() => {
395
395
  p(null), g(null);
396
396
  }, [t]);
@@ -436,9 +436,9 @@ function Z({ workspaceId: t, onMembersChanged: a }) {
436
436
  async function A(w, O) {
437
437
  await navigator.clipboard.writeText(O), o(w), setTimeout(() => o((v) => v === w ? null : v), 2e3);
438
438
  }
439
- return /* @__PURE__ */ n("section", { className: "shepherd-invites", "aria-labelledby": s, children: [
439
+ return /* @__PURE__ */ n("section", { className: "shepherd-invites", "aria-labelledby": r, children: [
440
440
  /* @__PURE__ */ n("div", { className: "card-head", children: [
441
- /* @__PURE__ */ e("h3", { id: s, children: "Invite people" }),
441
+ /* @__PURE__ */ e("h3", { id: r, children: "Invite people" }),
442
442
  /* @__PURE__ */ e("p", { className: "card-sub", children: "Share a code, or send a one-time join link by email." })
443
443
  ] }),
444
444
  /* @__PURE__ */ n("div", { className: "card-body", children: [
@@ -522,16 +522,16 @@ const ee = [
522
522
  }, ae = {
523
523
  claude: "-e",
524
524
  codex: "--env"
525
- }, B = "npx -y --package=@korso/shepherd shepherd-inbox-hook";
525
+ }, H = "npx -y --package=@korso/shepherd shepherd-inbox-hook";
526
526
  function ie(t) {
527
527
  return t === "claude" ? {
528
528
  target: "~/.claude/settings.json",
529
529
  snippet: JSON.stringify(
530
530
  {
531
531
  hooks: {
532
- SessionStart: [{ hooks: [{ type: "command", command: B }] }],
532
+ SessionStart: [{ hooks: [{ type: "command", command: H }] }],
533
533
  PreToolUse: [
534
- { matcher: "*", hooks: [{ type: "command", command: B }] }
534
+ { matcher: "*", hooks: [{ type: "command", command: H }] }
535
535
  ]
536
536
  }
537
537
  },
@@ -557,7 +557,7 @@ function ie(t) {
557
557
  {
558
558
  version: 1,
559
559
  hooks: {
560
- beforeSubmitPrompt: [{ command: B }]
560
+ beforeSubmitPrompt: [{ command: H }]
561
561
  }
562
562
  },
563
563
  null,
@@ -565,11 +565,11 @@ function ie(t) {
565
565
  )
566
566
  } : null;
567
567
  }
568
- function re(t, a) {
569
- const h = `created ${W(t.createdAt, a)}`, s = t.lastUsedAt ? `last used ${W(t.lastUsedAt, a)}` : "never used";
570
- return `${h} · ${s}`;
568
+ function se(t, a) {
569
+ const h = `created ${W(t.createdAt, a)}`, r = t.lastUsedAt ? `last used ${W(t.lastUsedAt, a)}` : "never used";
570
+ return `${h} · ${r}`;
571
571
  }
572
- function se(t, a, h) {
572
+ function re(t, a, h) {
573
573
  if (t === "generic" || t === "pi" || t === "cursor") {
574
574
  const p = { HUB_URL: a, SHEPHERD_TOKEN: h };
575
575
  return t !== "generic" && (p.PROGRAM = t), JSON.stringify(
@@ -586,15 +586,15 @@ function se(t, a, h) {
586
586
  2
587
587
  );
588
588
  }
589
- const s = ae[t], i = [
589
+ const r = ae[t], i = [
590
590
  te[t],
591
- `${s} HUB_URL=${a}`,
592
- `${s} SHEPHERD_TOKEN=${h}`
591
+ `${r} HUB_URL=${a}`,
592
+ `${r} SHEPHERD_TOKEN=${h}`
593
593
  ];
594
- return t === "codex" && i.push(`${s} PROGRAM=codex`), i.push("-- npx -y --package=@korso/shepherd shepherd-mcp"), i.join(" ");
594
+ return t === "codex" && i.push(`${r} PROGRAM=codex`), i.push("-- npx -y --package=@korso/shepherd shepherd-mcp"), i.join(" ");
595
595
  }
596
596
  function le({ hubUrl: t }) {
597
- const a = L(), h = t ?? a.baseUrl, s = j(), [i, p] = r("claude"), [u, c] = r(""), [d, m] = r(null), [y, l] = r([]), [N, g] = r(null), [_, b] = r(null), [C, o] = r(!1), [k, E] = r(!1), [S, I] = r(!0), [A, w] = r(null);
597
+ const a = L(), h = t ?? a.baseUrl, r = j(), [i, p] = s("claude"), [u, c] = s(""), [d, m] = s(null), [y, l] = s([]), [N, g] = s(null), [_, b] = s(null), [C, o] = s(!1), [k, E] = s(!1), [S, I] = s(!0), [A, w] = s(null);
598
598
  async function O() {
599
599
  try {
600
600
  const f = await a.listAccountTokens();
@@ -633,15 +633,15 @@ function le({ hubUrl: t }) {
633
633
  }
634
634
  }
635
635
  }
636
- const D = se(i, h, d ?? ne), H = ie(i);
636
+ const D = re(i, h, d ?? ne), U = ie(i);
637
637
  async function J() {
638
638
  await navigator.clipboard.writeText(D), E(!0), setTimeout(() => E(!1), 2e3);
639
639
  }
640
- return /* @__PURE__ */ n("section", { className: "shepherd-connect-agent", "aria-labelledby": s, children: [
640
+ return /* @__PURE__ */ n("section", { className: "shepherd-connect-agent", "aria-labelledby": r, children: [
641
641
  /* @__PURE__ */ n("div", { className: "card-head", children: [
642
- /* @__PURE__ */ e("h3", { id: s, children: "Connect your agent" }),
643
- /* @__PURE__ */ e("p", { className: "card-sub", children: "Generate a token it works across all your workspaces then paste the command into your coding tool." }),
644
- /* @__PURE__ */ e("p", { className: "card-sub", children: "The first time the agent changes files in a repo, Shepherd asks which workspace to coordinate that repo with once per repo, right in your tool." })
642
+ /* @__PURE__ */ e("h3", { id: r, children: "Connect your agent" }),
643
+ /* @__PURE__ */ e("p", { className: "card-sub", children: "Generate a token; it works across all your workspaces, so paste the command into your coding tool." }),
644
+ /* @__PURE__ */ e("p", { className: "card-sub", children: "The first time the agent changes files in a repo, Shepherd asks which workspace to coordinate that repo with, once per repo, right in your tool." })
645
645
  ] }),
646
646
  /* @__PURE__ */ n("div", { className: "card-body", children: [
647
647
  /* @__PURE__ */ n("div", { className: "field", children: [
@@ -677,7 +677,7 @@ function le({ hubUrl: t }) {
677
677
  ] }),
678
678
  N && /* @__PURE__ */ e("p", { role: "alert", children: N }),
679
679
  _ && /* @__PURE__ */ e("p", { role: "status", children: _ }),
680
- d && /* @__PURE__ */ e("p", { className: "token-once", role: "status", children: "Copy this token now it won't be shown again." }),
680
+ d && /* @__PURE__ */ e("p", { className: "token-once", role: "status", children: "Copy this token now; it won't be shown again." }),
681
681
  /* @__PURE__ */ n("div", { className: "install-command", children: [
682
682
  /* @__PURE__ */ e("pre", { "data-testid": "install-command", children: D }),
683
683
  /* @__PURE__ */ e(
@@ -723,18 +723,19 @@ function le({ hubUrl: t }) {
723
723
  }
724
724
  )
725
725
  ] }),
726
- H && /* @__PURE__ */ n("div", { className: "hook-setup", children: [
726
+ U && /* @__PURE__ */ n("div", { className: "hook-setup", children: [
727
727
  /* @__PURE__ */ n("p", { className: "card-sub", children: [
728
728
  "Message delivery sets itself up: the first time the agent runs, Shepherd adds its inbox hook to ",
729
- /* @__PURE__ */ e("code", { children: H.target }),
729
+ /* @__PURE__ */ e("code", { children: U.target }),
730
+ " ",
731
+ "automatically. It delivers teammate announcements and reminds the agent to link each repo before its first write. This happens at most once; set",
730
732
  " ",
731
- "automatically — it delivers teammate announcements and reminds the agent to link each repo before its first write. Happens at most once; set ",
732
733
  /* @__PURE__ */ e("code", { children: "SHEPHERD_NO_AUTO_HOOKS=1" }),
733
734
  " to opt out."
734
735
  ] }),
735
- H.snippet && /* @__PURE__ */ n("details", { className: "hook-reference", children: [
736
+ U.snippet && /* @__PURE__ */ n("details", { className: "hook-reference", children: [
736
737
  /* @__PURE__ */ e("summary", { children: "What gets added (manual equivalent)" }),
737
- /* @__PURE__ */ e("div", { className: "install-command", children: /* @__PURE__ */ e("pre", { "data-testid": "hook-snippet", children: H.snippet }) })
738
+ /* @__PURE__ */ e("div", { className: "install-command", children: /* @__PURE__ */ e("pre", { "data-testid": "hook-snippet", children: U.snippet }) })
738
739
  ] })
739
740
  ] }),
740
741
  /* @__PURE__ */ e("h4", { children: "Existing tokens" }),
@@ -742,7 +743,7 @@ function le({ hubUrl: t }) {
742
743
  const P = f.name ?? f.id;
743
744
  return /* @__PURE__ */ n("li", { children: [
744
745
  /* @__PURE__ */ e("span", { children: P }),
745
- /* @__PURE__ */ e("span", { className: "token-meta", children: re(f, Date.now()) }),
746
+ /* @__PURE__ */ e("span", { className: "token-meta", children: se(f, Date.now()) }),
746
747
  f.revokedAt ? /* @__PURE__ */ e("span", { className: "revoked", children: "revoked" }) : /* @__PURE__ */ e(
747
748
  "button",
748
749
  {
@@ -777,12 +778,12 @@ function ce({
777
778
  workspace: t,
778
779
  hubUrl: a,
779
780
  membersRefreshKey: h,
780
- onMembersChanged: s,
781
+ onMembersChanged: r,
781
782
  onLeft: i,
782
783
  onDeleted: p,
783
784
  onLogout: u
784
785
  }) {
785
- const [c, d] = r("general"), m = t.role === "admin";
786
+ const [c, d] = s("general"), m = t.role === "admin";
786
787
  return /* @__PURE__ */ n($, { children: [
787
788
  /* @__PURE__ */ n("div", { className: "config-layout", children: [
788
789
  /* @__PURE__ */ e("nav", { className: "config-nav", "aria-label": "Configuration sections", children: oe.map(({ id: y, label: l }) => /* @__PURE__ */ e(
@@ -807,7 +808,7 @@ function ce({
807
808
  canRemove: m
808
809
  }
809
810
  ),
810
- m && /* @__PURE__ */ e(Z, { workspaceId: t.id, onMembersChanged: s })
811
+ m && /* @__PURE__ */ e(Z, { workspaceId: t.id, onMembersChanged: r })
811
812
  ] }),
812
813
  c === "agent" && /* @__PURE__ */ e(le, { hubUrl: a })
813
814
  ] })
@@ -816,7 +817,7 @@ function ce({
816
817
  ] });
817
818
  }
818
819
  function me({ hubUrl: t, onLogout: a }) {
819
- const h = L(), [s, i] = r({ status: "loading" }), [p, u] = r(null), [c, d] = r(0), m = G(async () => {
820
+ const h = L(), [r, i] = s({ status: "loading" }), [p, u] = s(null), [c, d] = s(0), m = G(async () => {
820
821
  try {
821
822
  const b = await h.listWorkspaces();
822
823
  i({ status: "ready", workspaces: b.workspaces }), u(
@@ -832,11 +833,11 @@ function me({ hubUrl: t, onLogout: a }) {
832
833
  T(() => {
833
834
  m();
834
835
  }, [m]);
835
- const y = s.status === "ready" ? s.workspaces : [], l = y.find((b) => b.id === p) ?? null;
836
- if (s.status === "loading")
836
+ const y = r.status === "ready" ? r.workspaces : [], l = y.find((b) => b.id === p) ?? null;
837
+ if (r.status === "loading")
837
838
  return /* @__PURE__ */ e("div", { className: "shepherd-root", children: /* @__PURE__ */ e("p", { role: "status", children: "Loading…" }) });
838
- if (s.status === "error")
839
- return /* @__PURE__ */ e("div", { className: "shepherd-root", children: /* @__PURE__ */ e("p", { role: "alert", children: s.message }) });
839
+ if (r.status === "error")
840
+ return /* @__PURE__ */ e("div", { className: "shepherd-root", children: /* @__PURE__ */ e("p", { role: "alert", children: r.message }) });
840
841
  const N = y.length > 0, g = /* @__PURE__ */ e(
841
842
  q,
842
843
  {
@@ -881,7 +882,7 @@ function me({ hubUrl: t, onLogout: a }) {
881
882
  ) });
882
883
  }
883
884
  function pe({ code: t, onJoined: a }) {
884
- const h = L(), s = j(), [i, p] = r({ status: "joining" }), u = U(a);
885
+ const h = L(), r = j(), [i, p] = s({ status: "joining" }), u = B(a);
885
886
  u.current = a;
886
887
  const c = G(async () => {
887
888
  p({ status: "joining" });
@@ -891,18 +892,18 @@ function pe({ code: t, onJoined: a }) {
891
892
  } catch (m) {
892
893
  p({ status: "error", message: x(m) });
893
894
  }
894
- }, [h, t]), d = U(!1);
895
+ }, [h, t]), d = B(!1);
895
896
  return T(() => {
896
897
  d.current || (d.current = !0, c());
897
- }, [c]), /* @__PURE__ */ e("section", { className: "shepherd-join", "aria-labelledby": s, children: /* @__PURE__ */ n("div", { className: "shepherd-join__card", children: [
898
+ }, [c]), /* @__PURE__ */ e("section", { className: "shepherd-join", "aria-labelledby": r, children: /* @__PURE__ */ n("div", { className: "shepherd-join__card", children: [
898
899
  /* @__PURE__ */ e("p", { className: "shepherd-join__brand", children: "Shepherd" }),
899
900
  i.status === "joining" && /* @__PURE__ */ n($, { children: [
900
- /* @__PURE__ */ e("h1", { id: s, children: "Joining workspace…" }),
901
+ /* @__PURE__ */ e("h1", { id: r, children: "Joining workspace…" }),
901
902
  /* @__PURE__ */ e("p", { className: "shepherd-join__copy", role: "status", children: "Hold on while we add you to the team." }),
902
903
  /* @__PURE__ */ e("div", { className: "shepherd-join__spinner", "aria-hidden": "true" })
903
904
  ] }),
904
905
  i.status === "joined" && /* @__PURE__ */ n($, { children: [
905
- /* @__PURE__ */ e("h1", { id: s, children: "You’re in" }),
906
+ /* @__PURE__ */ e("h1", { id: r, children: "You’re in" }),
906
907
  /* @__PURE__ */ n("p", { className: "shepherd-join__copy", role: "status", children: [
907
908
  "Joined ",
908
909
  /* @__PURE__ */ e("strong", { children: i.workspace.name }),
@@ -910,7 +911,7 @@ function pe({ code: t, onJoined: a }) {
910
911
  ] })
911
912
  ] }),
912
913
  i.status === "error" && /* @__PURE__ */ n($, { children: [
913
- /* @__PURE__ */ e("h1", { id: s, children: "Couldn’t join the workspace" }),
914
+ /* @__PURE__ */ e("h1", { id: r, children: "Couldn’t join the workspace" }),
914
915
  /* @__PURE__ */ e("p", { className: "shepherd-join__error", role: "alert", children: i.message }),
915
916
  /* @__PURE__ */ e(
916
917
  "button",
@@ -1,6 +1,6 @@
1
1
  import { jsx as e, jsxs as l } from "react/jsx-runtime";
2
2
  import { useState as s, useCallback as h, useMemo as d } from "react";
3
- import { c as m, a as u, D as p } from "./Dashboard-YgxAVEUF.js";
3
+ import { c as m, a as u, D as p } from "./Dashboard-DNr0hdbW.js";
4
4
  const n = "shepherd.token";
5
5
  function f() {
6
6
  try {