paperclip-plugin-claude-accounts 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/manifest.js CHANGED
@@ -2,7 +2,7 @@
2
2
  var manifest = {
3
3
  id: "neotalk.claude-autoauth",
4
4
  apiVersion: 1,
5
- version: "0.3.0",
5
+ version: "0.4.0",
6
6
  displayName: "Claude Accounts",
7
7
  description: "Each team member connects their own Claude account from the browser (no shell), picks which agents use it, and it stays signed in across restarts.",
8
8
  author: "Victor <victor@neotalk.com.br>",
package/dist/ui/index.js CHANGED
@@ -2,9 +2,11 @@
2
2
  import { useCallback, useEffect, useRef, useState } from "react";
3
3
  import {
4
4
  useHostContext,
5
+ useHostLocation,
5
6
  useHostNavigation,
6
7
  usePluginAction,
7
- usePluginData
8
+ usePluginData,
9
+ usePluginToast
8
10
  } from "@paperclipai/plugin-sdk/ui";
9
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
12
  async function api(method, url, body) {
@@ -44,19 +46,169 @@ async function setAgentConfigDir(agentId, dir) {
44
46
  else delete env.CLAUDE_CONFIG_DIR;
45
47
  await api("PATCH", `/agents/${agentId}`, { adapterConfig: { env } });
46
48
  }
47
- var S = {
48
- card: { border: "1px solid var(--border, #8884)", borderRadius: 10, padding: 16, marginBottom: 16, maxWidth: 760 },
49
- row: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" },
50
- muted: { opacity: 0.7, fontSize: 13 },
51
- input: { flex: 1, minWidth: 220, padding: "8px 10px", borderRadius: 6, border: "1px solid var(--border, #8886)", background: "transparent", color: "inherit", fontFamily: "monospace" },
52
- btn: { padding: "7px 12px", borderRadius: 6, border: "1px solid var(--border, #8886)", background: "transparent", color: "inherit", cursor: "pointer" },
53
- primary: { padding: "7px 14px", borderRadius: 6, border: "none", background: "#d97757", color: "#fff", cursor: "pointer", fontWeight: 600 },
54
- ok: { color: "#2e9d5b", fontWeight: 600 },
55
- bad: { color: "#d14343", fontWeight: 600 },
56
- code: { fontFamily: "monospace", fontSize: 12, wordBreak: "break-all" }
49
+ async function myClaudeConnections(companyId) {
50
+ const r = await api("GET", `/companies/${companyId}/ai-connections`);
51
+ return r.connections.filter(
52
+ (c) => c.provider === "anthropic" && c.method === "subscription" && c.ownership === "personal" && c.ownerUserId === r.currentUserId
53
+ );
54
+ }
55
+ async function connectToPaperclip(companyId, fill, record, opts = {}) {
56
+ const intent = {
57
+ provider: "anthropic",
58
+ method: "subscription",
59
+ name: opts.name ?? "My Claude",
60
+ ownership: "personal",
61
+ agentIds: [],
62
+ allAgents: false,
63
+ ...opts.connectionId ? { connectionId: opts.connectionId } : {}
64
+ };
65
+ const attempt = await api("POST", `/companies/${companyId}/ai-connections/local/attempts`, {
66
+ ...intent,
67
+ restart: true
68
+ });
69
+ const filled = await fill({ command: attempt.command });
70
+ if (!filled.filled) {
71
+ await api("DELETE", `/companies/${companyId}/ai-connections/local/attempts/${attempt.sessionId}`).catch(() => {
72
+ });
73
+ return { connectionId: "", needsSignIn: true };
74
+ }
75
+ const saved = await api("POST", `/companies/${companyId}/ai-connections/local`, {
76
+ ...intent,
77
+ localSessionId: attempt.sessionId
78
+ });
79
+ await record({ companyId, connectionId: saved.connectionId, name: intent.name });
80
+ return { connectionId: saved.connectionId };
81
+ }
82
+ var errText = (e) => String(e?.message ?? e);
83
+ var cx = (...c) => c.filter(Boolean).join(" ");
84
+ var BTN_BASE = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium disabled:pointer-events-none disabled:opacity-50 shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50";
85
+ var BTN_VARIANT = {
86
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
87
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
88
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
89
+ destructive: "bg-destructive text-white hover:bg-destructive/90 dark:bg-destructive/60"
90
+ };
91
+ var BTN_SIZE = { default: "h-10 px-4 py-2", sm: "h-9 rounded-md gap-1.5 px-3" };
92
+ function Button(p) {
93
+ return /* @__PURE__ */ jsx(
94
+ "button",
95
+ {
96
+ type: "button",
97
+ "data-slot": "button",
98
+ disabled: p.disabled,
99
+ onClick: p.onClick,
100
+ className: cx(BTN_BASE, BTN_VARIANT[p.variant ?? "default"], BTN_SIZE[p.size ?? "sm"], p.className),
101
+ children: p.children
102
+ }
103
+ );
104
+ }
105
+ function Input(p) {
106
+ return /* @__PURE__ */ jsx(
107
+ "input",
108
+ {
109
+ "data-slot": "input",
110
+ type: p.type ?? "text",
111
+ value: p.value,
112
+ autoFocus: p.autoFocus,
113
+ placeholder: p.placeholder,
114
+ onChange: (e) => p.onChange(e.target.value),
115
+ className: "placeholder:text-muted-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs outline-none md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 font-mono"
116
+ }
117
+ );
118
+ }
119
+ function Badge({ tone, children }) {
120
+ const tones = {
121
+ ok: "border-green-500/30 bg-green-500/10 text-green-700 dark:text-green-400",
122
+ warn: "border-amber-400 text-amber-600",
123
+ bad: "border-destructive/30 bg-destructive/10 text-destructive",
124
+ muted: "border-border text-muted-foreground"
125
+ };
126
+ return /* @__PURE__ */ jsx(
127
+ "span",
128
+ {
129
+ "data-slot": "badge",
130
+ className: cx("inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 gap-1", tones[tone]),
131
+ children
132
+ }
133
+ );
134
+ }
135
+ var ICONS = {
136
+ key: /* @__PURE__ */ jsxs(Fragment, { children: [
137
+ /* @__PURE__ */ jsx("path", { d: "M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z" }),
138
+ /* @__PURE__ */ jsx("circle", { cx: "16.5", cy: "7.5", r: ".5", fill: "currentColor" })
139
+ ] }),
140
+ check: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }),
141
+ external: /* @__PURE__ */ jsxs(Fragment, { children: [
142
+ /* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
143
+ /* @__PURE__ */ jsx("path", { d: "M10 14 21 3" }),
144
+ /* @__PURE__ */ jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" })
145
+ ] }),
146
+ loader: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }),
147
+ chevron: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }),
148
+ bot: /* @__PURE__ */ jsxs(Fragment, { children: [
149
+ /* @__PURE__ */ jsx("path", { d: "M12 8V4H8" }),
150
+ /* @__PURE__ */ jsx("rect", { width: "16", height: "12", x: "4", y: "8", rx: "2" }),
151
+ /* @__PURE__ */ jsx("path", { d: "M2 14h2" }),
152
+ /* @__PURE__ */ jsx("path", { d: "M20 14h2" }),
153
+ /* @__PURE__ */ jsx("path", { d: "M15 13v2" }),
154
+ /* @__PURE__ */ jsx("path", { d: "M9 13v2" })
155
+ ] })
57
156
  };
58
- function Btn(p) {
59
- return /* @__PURE__ */ jsx("button", { style: { ...p.primary ? S.primary : S.btn, opacity: p.disabled ? 0.5 : 1 }, disabled: p.disabled, onClick: p.onClick, children: p.children });
157
+ function Icon({ name, className }) {
158
+ return /* @__PURE__ */ jsx(
159
+ "svg",
160
+ {
161
+ viewBox: "0 0 24 24",
162
+ fill: "none",
163
+ stroke: "currentColor",
164
+ strokeWidth: "2",
165
+ strokeLinecap: "round",
166
+ strokeLinejoin: "round",
167
+ "aria-hidden": "true",
168
+ className: cx("h-4 w-4 shrink-0", className),
169
+ children: ICONS[name]
170
+ }
171
+ );
172
+ }
173
+ var Spinner = () => /* @__PURE__ */ jsx(Icon, { name: "loader", className: "animate-spin" });
174
+ function Step({ n, done, title, description, children }) {
175
+ return /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
176
+ /* @__PURE__ */ jsx(
177
+ "div",
178
+ {
179
+ className: cx(
180
+ "flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold",
181
+ done ? "bg-primary text-primary-foreground" : "border border-border text-muted-foreground"
182
+ ),
183
+ children: done ? /* @__PURE__ */ jsx(Icon, { name: "check", className: "h-3.5 w-3.5" }) : n
184
+ }
185
+ ),
186
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
187
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
188
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: title }),
189
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description })
190
+ ] }),
191
+ children
192
+ ] })
193
+ ] });
194
+ }
195
+ function Disclosure({ title, children }) {
196
+ const [open, setOpen] = useState(false);
197
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
198
+ /* @__PURE__ */ jsxs(
199
+ "button",
200
+ {
201
+ type: "button",
202
+ onClick: () => setOpen((v) => !v),
203
+ className: "flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground",
204
+ children: [
205
+ /* @__PURE__ */ jsx(Icon, { name: "chevron", className: cx("h-3.5 w-3.5 transition-transform", open && "rotate-90") }),
206
+ title
207
+ ]
208
+ }
209
+ ),
210
+ open && children
211
+ ] });
60
212
  }
61
213
  function useMyAccount() {
62
214
  const me = usePluginAction("me");
@@ -67,7 +219,7 @@ function useMyAccount() {
67
219
  setAccount(await me());
68
220
  setError(null);
69
221
  } catch (e) {
70
- setError(String(e.message ?? e));
222
+ setError(errText(e));
71
223
  }
72
224
  }, [me]);
73
225
  useEffect(() => {
@@ -75,298 +227,280 @@ function useMyAccount() {
75
227
  }, [refresh]);
76
228
  return { account, setAccount, error, refresh };
77
229
  }
78
- function ConnectCard({ account, onChange }) {
230
+ function useClaudeConnections(companyId) {
231
+ const [conns, setConns] = useState(null);
232
+ const load = useCallback(() => {
233
+ if (!companyId) return;
234
+ myClaudeConnections(companyId).then(setConns).catch(() => setConns([]));
235
+ }, [companyId]);
236
+ useEffect(load, [load]);
237
+ return { conns, load, existing: conns?.find((c) => c.isDefault) ?? conns?.[0] };
238
+ }
239
+ function SignInStep({ account, onChange }) {
240
+ const toast = usePluginToast();
79
241
  const loginStart = usePluginAction("login-start");
80
242
  const loginCode = usePluginAction("login-code");
81
243
  const loginCancel = usePluginAction("login-cancel");
82
244
  const saveToken = usePluginAction("save-token");
83
245
  const disconnect = usePluginAction("disconnect");
84
- const [url, setUrl] = useState(account?.login?.url ?? null);
246
+ const [url, setUrl] = useState(account.login?.url ?? null);
85
247
  const [code, setCode] = useState("");
86
248
  const [paste, setPaste] = useState("");
87
- const [showPaste, setShowPaste] = useState(false);
88
- const [busy, setBusy] = useState(false);
89
- const [msg, setMsg] = useState(null);
90
- const run = async (fn) => {
91
- setBusy(true);
92
- setMsg(null);
249
+ const [busy, setBusy] = useState(null);
250
+ const [error, setError] = useState(null);
251
+ const run = async (key, fn) => {
252
+ setBusy(key);
253
+ setError(null);
93
254
  try {
94
255
  await fn();
95
256
  } catch (e) {
96
- setMsg({ ok: false, text: String(e.message ?? e) });
257
+ setError(errText(e));
97
258
  } finally {
98
- setBusy(false);
259
+ setBusy(null);
99
260
  }
100
261
  };
101
- if (!account) return /* @__PURE__ */ jsx("div", { style: S.card, children: "Loading\u2026" });
102
- return /* @__PURE__ */ jsxs("div", { style: S.card, children: [
103
- /* @__PURE__ */ jsx("h3", { style: { marginTop: 0 }, children: "My Claude account" }),
104
- account.connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
105
- /* @__PURE__ */ jsxs("div", { style: S.row, children: [
106
- /* @__PURE__ */ jsx("span", { style: S.ok, children: "\u25CF Connected" }),
107
- /* @__PURE__ */ jsx("span", { style: S.muted, children: account.hasRefreshToken ? "renews automatically" : `token valid until ${account.expiresAt?.slice(0, 10) ?? "?"}` })
108
- ] }),
109
- /* @__PURE__ */ jsx("div", { style: { ...S.row, marginTop: 12 }, children: /* @__PURE__ */ jsx(
110
- Btn,
262
+ const description = account.connected ? account.hasRefreshToken ? "Signed in. Your login renews automatically." : `Signed in with a long-lived token, valid until ${account.expiresAt?.slice(0, 10) ?? "\u2014"}.` : "Use your own Claude Pro or Max subscription. You approve it on claude.ai, no terminal needed.";
263
+ return /* @__PURE__ */ jsxs(Step, { n: 1, done: account.connected, title: "Sign in to Claude", description, children: [
264
+ account.connected ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
265
+ /* @__PURE__ */ jsx(Badge, { tone: "ok", children: "Signed in" }),
266
+ /* @__PURE__ */ jsx(
267
+ Button,
111
268
  {
112
- disabled: busy,
113
- onClick: () => run(async () => {
114
- if (!confirm("Disconnect your Claude account? Agents using it will stop working.")) return;
269
+ variant: "ghost",
270
+ disabled: !!busy,
271
+ onClick: () => run("disconnect", async () => {
272
+ if (!confirm("Sign out of Claude? Agents using your account will stop working until you sign in again.")) return;
115
273
  await disconnect();
116
274
  onChange(null);
275
+ toast({ title: "Signed out of Claude", tone: "info" });
117
276
  }),
118
- children: "Disconnect"
277
+ children: "Sign out"
119
278
  }
120
- ) })
121
- ] }) : url ? /* @__PURE__ */ jsxs(Fragment, { children: [
122
- /* @__PURE__ */ jsxs("p", { style: { marginTop: 0 }, children: [
123
- /* @__PURE__ */ jsx("b", { children: "1." }),
124
- " ",
125
- /* @__PURE__ */ jsx("a", { href: url, target: "_blank", rel: "noreferrer", children: "Open the Claude sign-in page \u2197" }),
279
+ )
280
+ ] }) : url ? /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border border-border bg-muted/40 p-4", children: [
281
+ /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
282
+ "Approve the request on claude.ai, then paste the code it shows you.",
126
283
  " ",
127
- "and approve."
128
- ] }),
129
- /* @__PURE__ */ jsxs("p", { children: [
130
- /* @__PURE__ */ jsx("b", { children: "2." }),
131
- " Paste the code that Claude shows you:"
284
+ /* @__PURE__ */ jsxs("a", { href: url, target: "_blank", rel: "noreferrer", className: "inline-flex items-center gap-1 font-medium text-primary underline-offset-4 hover:underline", children: [
285
+ "Open claude.ai ",
286
+ /* @__PURE__ */ jsx(Icon, { name: "external", className: "h-3.5 w-3.5" })
287
+ ] })
132
288
  ] }),
133
- /* @__PURE__ */ jsxs("div", { style: S.row, children: [
134
- /* @__PURE__ */ jsx("input", { style: S.input, value: code, onChange: (e) => setCode(e.target.value), placeholder: "code#state", autoFocus: true }),
135
- /* @__PURE__ */ jsx(
136
- Btn,
137
- {
138
- primary: true,
139
- disabled: busy || !code.trim(),
140
- onClick: () => run(async () => {
141
- const r = await loginCode({ code });
142
- setMsg({ ok: r.ok, text: r.message });
143
- if (r.ok) {
289
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
290
+ /* @__PURE__ */ jsx(Input, { value: code, onChange: setCode, placeholder: "Paste code", autoFocus: true }),
291
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
292
+ /* @__PURE__ */ jsxs(
293
+ Button,
294
+ {
295
+ disabled: !!busy || !code.trim(),
296
+ onClick: () => run("code", async () => {
297
+ const r = await loginCode({ code });
298
+ if (!r.ok) throw new Error(r.message);
144
299
  setUrl(null);
145
300
  onChange(r.status);
146
- }
147
- }),
148
- children: busy ? "Connecting\u2026" : "Connect"
149
- }
150
- ),
151
- /* @__PURE__ */ jsx(
152
- Btn,
153
- {
154
- disabled: busy,
155
- onClick: () => run(async () => {
156
- await loginCancel();
157
- setUrl(null);
158
- }),
159
- children: "Cancel"
160
- }
161
- )
301
+ toast({ title: "Signed in to Claude", tone: "success" });
302
+ }),
303
+ children: [
304
+ busy === "code" && /* @__PURE__ */ jsx(Spinner, {}),
305
+ busy === "code" ? "Checking\u2026" : "Continue"
306
+ ]
307
+ }
308
+ ),
309
+ /* @__PURE__ */ jsx(
310
+ Button,
311
+ {
312
+ variant: "ghost",
313
+ disabled: !!busy,
314
+ onClick: () => run("cancel", async () => {
315
+ await loginCancel();
316
+ setUrl(null);
317
+ setCode("");
318
+ }),
319
+ children: "Cancel"
320
+ }
321
+ )
322
+ ] })
162
323
  ] })
163
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
164
- /* @__PURE__ */ jsx("p", { style: { marginTop: 0 }, children: "Connect your own Claude Pro/Max account. Your agents will use it, and it stays signed in." }),
165
- /* @__PURE__ */ jsxs("div", { style: S.row, children: [
166
- /* @__PURE__ */ jsx(
167
- Btn,
168
- {
169
- primary: true,
170
- disabled: busy,
171
- onClick: () => run(async () => {
172
- const r = await loginStart();
173
- setUrl(r.url);
174
- window.open(r.url, "_blank", "noopener");
175
- }),
176
- children: busy ? "Starting\u2026" : "Sign in with Claude"
177
- }
178
- ),
179
- /* @__PURE__ */ jsx(Btn, { onClick: () => setShowPaste((v) => !v), children: "Paste a token instead" })
180
- ] }),
181
- showPaste && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
182
- /* @__PURE__ */ jsxs("p", { style: S.muted, children: [
183
- "On your computer run ",
184
- /* @__PURE__ */ jsx("code", { children: "claude setup-token" }),
185
- " and paste the ",
186
- /* @__PURE__ */ jsx("code", { children: "sk-ant-oat\u2026" }),
187
- " token, or paste the contents of ",
188
- /* @__PURE__ */ jsx("code", { children: "~/.claude/.credentials.json" }),
324
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
325
+ /* @__PURE__ */ jsxs(
326
+ Button,
327
+ {
328
+ disabled: !!busy,
329
+ onClick: () => run("start", async () => {
330
+ const r = await loginStart();
331
+ setUrl(r.url);
332
+ window.open(r.url, "_blank", "noopener");
333
+ }),
334
+ children: [
335
+ busy === "start" ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsx(Icon, { name: "external" }),
336
+ busy === "start" ? "Opening Claude\u2026" : "Sign in with Claude"
337
+ ]
338
+ }
339
+ ),
340
+ /* @__PURE__ */ jsx(Disclosure, { title: "Use a token instead", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
341
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
342
+ "Run ",
343
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs", children: "claude setup-token" }),
344
+ " on your computer and paste the token, or paste the contents of ",
345
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs", children: "~/.claude/.credentials.json" }),
189
346
  "."
190
347
  ] }),
191
- /* @__PURE__ */ jsxs("div", { style: S.row, children: [
192
- /* @__PURE__ */ jsx("input", { style: S.input, type: "password", value: paste, onChange: (e) => setPaste(e.target.value), placeholder: "sk-ant-oat01-\u2026" }),
348
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
349
+ /* @__PURE__ */ jsx(Input, { type: "password", value: paste, onChange: setPaste, placeholder: "sk-ant-oat01-\u2026" }),
193
350
  /* @__PURE__ */ jsx(
194
- Btn,
351
+ Button,
195
352
  {
196
- primary: true,
197
- disabled: busy || !paste.trim(),
198
- onClick: () => run(async () => {
353
+ variant: "outline",
354
+ disabled: !!busy || !paste.trim(),
355
+ onClick: () => run("paste", async () => {
199
356
  onChange(await saveToken({ value: paste }));
200
357
  setPaste("");
201
- setShowPaste(false);
358
+ toast({ title: "Claude token saved", tone: "success" });
202
359
  }),
203
- children: "Save"
360
+ children: "Save token"
204
361
  }
205
362
  )
206
363
  ] })
207
- ] })
364
+ ] }) })
208
365
  ] }),
209
- msg && /* @__PURE__ */ jsx("p", { style: msg.ok ? S.ok : S.bad, children: msg.text })
366
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: error })
210
367
  ] });
211
368
  }
369
+ function ConnectionStep({ companyId, account }) {
370
+ const toast = usePluginToast();
371
+ const fill = usePluginAction("native-fill");
372
+ const record = usePluginAction("native-record");
373
+ const { existing, load } = useClaudeConnections(companyId);
374
+ const [cmd, setCmd] = useState("");
375
+ const [busy, setBusy] = useState(null);
376
+ const [error, setError] = useState(null);
377
+ const run = async (key, fn) => {
378
+ setBusy(key);
379
+ setError(null);
380
+ try {
381
+ await fn();
382
+ load();
383
+ } catch (e) {
384
+ setError(errText(e));
385
+ } finally {
386
+ setBusy(null);
387
+ }
388
+ };
389
+ const connected = existing?.status === "connected";
390
+ return /* @__PURE__ */ jsxs(
391
+ Step,
392
+ {
393
+ n: 2,
394
+ done: !!connected,
395
+ title: "Use it in Paperclip",
396
+ description: "Saves your Claude as your personal AI connection. Agents you're responsible for use it, and it's kept fresh automatically.",
397
+ children: [
398
+ existing && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
399
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: existing.name }),
400
+ /* @__PURE__ */ jsx(Badge, { tone: connected ? "ok" : "warn", children: connected ? "Connected" : existing.status.replace(/_/g, " ") }),
401
+ existing.isDefault && /* @__PURE__ */ jsx(Badge, { tone: "muted", children: "Default" })
402
+ ] }),
403
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
404
+ /* @__PURE__ */ jsxs(
405
+ Button,
406
+ {
407
+ variant: existing ? "outline" : "default",
408
+ disabled: !!busy || !account.connected,
409
+ onClick: () => run("connect", async () => {
410
+ const r = await connectToPaperclip(companyId, fill, record, existing ? { connectionId: existing.id, name: existing.name } : {});
411
+ if (r.needsSignIn) throw new Error("Sign in to Claude first.");
412
+ toast({ title: existing ? "Claude reconnected" : "Claude connected to Paperclip", tone: "success" });
413
+ }),
414
+ children: [
415
+ busy === "connect" && /* @__PURE__ */ jsx(Spinner, {}),
416
+ busy === "connect" ? "Connecting\u2026" : existing ? "Reconnect" : "Connect to Paperclip"
417
+ ]
418
+ }
419
+ ),
420
+ !account.connected && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Finish step 1 first." })
421
+ ] }),
422
+ /* @__PURE__ */ jsx(Disclosure, { title: "Paperclip showed me a terminal command", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
423
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
424
+ "Paste the ",
425
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs", children: "export CLAUDE_CONFIG_DIR=\u2026 claude auth login" }),
426
+ " command here. The plugin does it for you. Then click ",
427
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Connect" }),
428
+ " in Paperclip's dialog."
429
+ ] }),
430
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
431
+ /* @__PURE__ */ jsx(Input, { value: cmd, onChange: setCmd, placeholder: "(export CLAUDE_CONFIG_DIR='\u2026/ai-local-logins/\u2026' && \u2026)" }),
432
+ /* @__PURE__ */ jsx(
433
+ Button,
434
+ {
435
+ variant: "outline",
436
+ disabled: !!busy || !cmd.trim() || !account.connected,
437
+ onClick: () => run("fill", async () => {
438
+ const r = await fill({ command: cmd });
439
+ if (!r.filled) throw new Error("Sign in to Claude first.");
440
+ setCmd("");
441
+ toast({ title: "Done. Click Connect in Paperclip's dialog", tone: "success" });
442
+ }),
443
+ children: "Do it for me"
444
+ }
445
+ )
446
+ ] })
447
+ ] }) }),
448
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: error })
449
+ ]
450
+ }
451
+ );
452
+ }
212
453
  function AgentToggle({ agent, account, onDone }) {
454
+ const toast = usePluginToast();
213
455
  const [busy, setBusy] = useState(false);
214
- const [err, setErr] = useState(null);
215
- const dir = agentConfigDir(agent);
216
- const mine = dir === account.claudeConfigDir;
217
- const bound = !!agent.runtimeConfig?.aiConnection;
456
+ const mine = agentConfigDir(agent) === account.claudeConfigDir;
218
457
  const set = async (value) => {
219
458
  setBusy(true);
220
- setErr(null);
221
459
  try {
222
460
  await setAgentConfigDir(agent.id, value);
461
+ toast({ title: value ? `${agent.name} now uses your Claude` : `${agent.name} no longer uses your Claude`, tone: "success" });
223
462
  onDone();
224
463
  } catch (e) {
225
- setErr(String(e.message ?? e));
464
+ toast({ title: "Couldn't update agent", body: errText(e), tone: "error" });
226
465
  } finally {
227
466
  setBusy(false);
228
467
  }
229
468
  };
230
- return /* @__PURE__ */ jsxs(Fragment, { children: [
231
- mine ? /* @__PURE__ */ jsx(Btn, { disabled: busy, onClick: () => set(null), children: "Stop using my Claude" }) : /* @__PURE__ */ jsx(Btn, { primary: true, disabled: busy || !account.connected, onClick: () => set(account.claudeConfigDir), children: "Use my Claude" }),
232
- bound && /* @__PURE__ */ jsx("span", { style: { ...S.muted, color: "#c48a00" }, children: "\u26A0 This agent has a Paperclip \u201CAI connection\u201D selected, which overrides the folder. Switch it to the local login in the agent's settings." }),
233
- err && /* @__PURE__ */ jsx("span", { style: S.bad, children: err })
469
+ return mine ? /* @__PURE__ */ jsxs(Button, { variant: "ghost", disabled: busy, onClick: () => set(null), children: [
470
+ busy && /* @__PURE__ */ jsx(Spinner, {}),
471
+ "Stop using"
472
+ ] }) : /* @__PURE__ */ jsxs(Button, { variant: "outline", disabled: busy || !account.connected, onClick: () => set(account.claudeConfigDir), children: [
473
+ busy && /* @__PURE__ */ jsx(Spinner, {}),
474
+ "Use my Claude"
234
475
  ] });
235
476
  }
236
- function AgentsCard({ companyId, account }) {
477
+ function AgentsSection({ companyId, account }) {
237
478
  const [agents, setAgents] = useState(null);
238
- const [err, setErr] = useState(null);
479
+ const [error, setError] = useState(null);
239
480
  const load = useCallback(() => {
240
- listClaudeAgents(companyId).then(setAgents).catch((e) => setErr(String(e.message ?? e)));
481
+ listClaudeAgents(companyId).then(setAgents).catch((e) => setError(errText(e)));
241
482
  }, [companyId]);
242
483
  useEffect(load, [load]);
243
- return /* @__PURE__ */ jsxs("div", { style: S.card, children: [
244
- /* @__PURE__ */ jsx("h3", { style: { marginTop: 0 }, children: "Agents" }),
245
- /* @__PURE__ */ jsx("p", { style: S.muted, children: "Choose which Claude agents run on your account. Each person can connect their own." }),
246
- err && /* @__PURE__ */ jsx("p", { style: S.bad, children: err }),
247
- !agents ? /* @__PURE__ */ jsx("p", { children: "Loading\u2026" }) : agents.length === 0 ? /* @__PURE__ */ jsx("p", { style: S.muted, children: "No Claude (claude_local) agents in this company." }) : agents.map((a) => {
484
+ return /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
485
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
486
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: "Run agents directly on your login" }),
487
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "For agents that must keep working while you're away. They renew the login on the server, no browser needed. Agents with a Paperclip AI connection selected keep using that instead." })
488
+ ] }),
489
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
490
+ !agents ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Loading agents\u2026" }) : agents.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No Claude Code agents in this company." }) : /* @__PURE__ */ jsx("div", { className: "divide-y divide-border rounded-lg border border-border", children: agents.map((a) => {
248
491
  const dir = agentConfigDir(a);
249
- return /* @__PURE__ */ jsxs("div", { style: { ...S.row, padding: "8px 0", borderTop: "1px solid var(--border, #8882)" }, children: [
250
- /* @__PURE__ */ jsx("span", { style: { minWidth: 160, fontWeight: 600 }, children: a.name }),
251
- /* @__PURE__ */ jsx("span", { style: { ...S.muted, minWidth: 130 }, children: dir === account.claudeConfigDir ? "uses your Claude" : dir ? "uses another account" : "server login" }),
492
+ const mine = dir === account.claudeConfigDir;
493
+ const bound = !!a.runtimeConfig?.aiConnection;
494
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
495
+ /* @__PURE__ */ jsx(Icon, { name: "bot", className: "text-muted-foreground" }),
496
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
497
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: a.name }),
498
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: bound ? "Uses a Paperclip AI connection" : mine ? "Your Claude" : dir ? "Another account" : "Server login" })
499
+ ] }),
500
+ mine && /* @__PURE__ */ jsx(Badge, { tone: "ok", children: "Yours" }),
252
501
  /* @__PURE__ */ jsx(AgentToggle, { agent: a, account, onDone: load })
253
502
  ] }, a.id);
254
- })
255
- ] });
256
- }
257
- async function myClaudeConnections(companyId) {
258
- const r = await api("GET", `/companies/${companyId}/ai-connections`);
259
- return r.connections.filter(
260
- (c) => c.provider === "anthropic" && c.method === "subscription" && c.ownership === "personal" && c.ownerUserId === r.currentUserId
261
- );
262
- }
263
- async function connectToPaperclip(companyId, fill, record, opts = {}) {
264
- const intent = {
265
- provider: "anthropic",
266
- method: "subscription",
267
- name: opts.name ?? "My Claude",
268
- ownership: "personal",
269
- agentIds: [],
270
- allAgents: false,
271
- ...opts.connectionId ? { connectionId: opts.connectionId } : {}
272
- };
273
- const attempt = await api("POST", `/companies/${companyId}/ai-connections/local/attempts`, {
274
- ...intent,
275
- restart: true
276
- });
277
- const filled = await fill({ command: attempt.command });
278
- if (!filled.filled) {
279
- await api("DELETE", `/companies/${companyId}/ai-connections/local/attempts/${attempt.sessionId}`).catch(() => {
280
- });
281
- return { connectionId: "", needsSignIn: true };
282
- }
283
- const saved = await api("POST", `/companies/${companyId}/ai-connections/local`, {
284
- ...intent,
285
- localSessionId: attempt.sessionId
286
- });
287
- await record({ companyId, connectionId: saved.connectionId, name: intent.name });
288
- return { connectionId: saved.connectionId };
289
- }
290
- function PaperclipConnectionCard({ companyId, account }) {
291
- const fill = usePluginAction("native-fill");
292
- const record = usePluginAction("native-record");
293
- const [conns, setConns] = useState(null);
294
- const [cmd, setCmd] = useState("");
295
- const [busy, setBusy] = useState(false);
296
- const [msg, setMsg] = useState(null);
297
- const load = useCallback(() => {
298
- myClaudeConnections(companyId).then(setConns).catch(() => setConns([]));
299
- }, [companyId]);
300
- useEffect(load, [load]);
301
- const run = async (fn) => {
302
- setBusy(true);
303
- setMsg(null);
304
- try {
305
- setMsg({ ok: true, text: await fn() });
306
- load();
307
- } catch (e) {
308
- setMsg({ ok: false, text: String(e.message ?? e) });
309
- } finally {
310
- setBusy(false);
311
- }
312
- };
313
- const existing = conns?.find((c) => c.isDefault) ?? conns?.[0];
314
- return /* @__PURE__ */ jsxs("div", { style: S.card, children: [
315
- /* @__PURE__ */ jsx("h3", { style: { marginTop: 0 }, children: "Paperclip AI connection" }),
316
- /* @__PURE__ */ jsx("p", { style: S.muted, children: "Saves your Claude as your personal Paperclip AI connection, with no terminal. Agents set to \u201Cresponsible user\u201D use it. The plugin renews it before it expires." }),
317
- existing && /* @__PURE__ */ jsxs("p", { children: [
318
- /* @__PURE__ */ jsxs("span", { style: existing.status === "connected" ? S.ok : S.bad, children: [
319
- "\u25CF ",
320
- existing.name
321
- ] }),
322
- " ",
323
- /* @__PURE__ */ jsxs("span", { style: S.muted, children: [
324
- existing.status,
325
- existing.isDefault ? " \xB7 default" : ""
326
- ] })
327
- ] }),
328
- /* @__PURE__ */ jsxs("div", { style: S.row, children: [
329
- /* @__PURE__ */ jsx(
330
- Btn,
331
- {
332
- primary: true,
333
- disabled: busy || !account.connected,
334
- onClick: () => run(async () => {
335
- const r = await connectToPaperclip(companyId, fill, record, existing ? { connectionId: existing.id, name: existing.name } : {});
336
- return r.needsSignIn ? "Sign in with Claude above first." : existing ? "Reconnected \u2713" : "Connected \u2713";
337
- }),
338
- children: busy ? "Connecting\u2026" : existing ? "Reconnect now" : "Connect to Paperclip"
339
- }
340
- ),
341
- !account.connected && /* @__PURE__ */ jsx("span", { style: S.muted, children: "Sign in with Claude above first." })
342
- ] }),
343
- /* @__PURE__ */ jsxs("details", { style: { marginTop: 14 }, children: [
344
- /* @__PURE__ */ jsx("summary", { style: { cursor: "pointer" }, children: "Paperclip showed me a terminal command" }),
345
- /* @__PURE__ */ jsxs("p", { style: S.muted, children: [
346
- "Paste it here. It looks like ",
347
- /* @__PURE__ */ jsx("code", { style: S.code, children: "(export CLAUDE_CONFIG_DIR='\u2026/ai-local-logins/\u2026' && \u2026 claude auth login)" }),
348
- ". The plugin does that step for you. Then click ",
349
- /* @__PURE__ */ jsx("b", { children: "Connect" }),
350
- " in Paperclip's dialog."
351
- ] }),
352
- /* @__PURE__ */ jsxs("div", { style: S.row, children: [
353
- /* @__PURE__ */ jsx("input", { style: S.input, value: cmd, onChange: (e) => setCmd(e.target.value), placeholder: "(export CLAUDE_CONFIG_DIR=\u2026" }),
354
- /* @__PURE__ */ jsx(
355
- Btn,
356
- {
357
- disabled: busy || !cmd.trim(),
358
- onClick: () => run(async () => {
359
- const r = await fill({ command: cmd });
360
- if (!r.filled) throw new Error("Sign in with Claude above first, then paste again.");
361
- setCmd("");
362
- return "Done. Now click Connect in Paperclip's dialog.";
363
- }),
364
- children: "Do it for me"
365
- }
366
- )
367
- ] })
368
- ] }),
369
- msg && /* @__PURE__ */ jsx("p", { style: msg.ok ? S.ok : S.bad, children: msg.text })
503
+ }) })
370
504
  ] });
371
505
  }
372
506
  function useNativeKeepAlive(companyId) {
@@ -398,48 +532,84 @@ function useNativeKeepAlive(companyId) {
398
532
  return () => clearInterval(t);
399
533
  }, [companyId, keepalive, fill, record]);
400
534
  }
535
+ var PAGE_ROUTE = "/claude-accounts";
401
536
  function ClaudeAccountsPage(_props) {
402
537
  const { companyId } = useHostContext();
403
538
  const { account, setAccount, error, refresh } = useMyAccount();
404
- return /* @__PURE__ */ jsxs("div", { style: { padding: 24 }, children: [
405
- /* @__PURE__ */ jsx("h2", { style: { marginTop: 0 }, children: "Claude accounts" }),
406
- error && /* @__PURE__ */ jsx("p", { style: S.bad, children: error }),
407
- /* @__PURE__ */ jsx(ConnectCard, { account, onChange: (a) => a ? setAccount(a) : void refresh() }),
408
- account && companyId && /* @__PURE__ */ jsx(PaperclipConnectionCard, { companyId, account }),
409
- account && companyId && /* @__PURE__ */ jsxs("details", { children: [
410
- /* @__PURE__ */ jsx("summary", { style: { cursor: "pointer", marginBottom: 12 }, children: "Advanced: run specific agents directly on my login folder" }),
411
- /* @__PURE__ */ jsx(AgentsCard, { companyId, account })
539
+ return /* @__PURE__ */ jsxs("div", { className: "max-w-3xl space-y-6 pb-12", children: [
540
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
541
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
542
+ /* @__PURE__ */ jsx(Icon, { name: "key", className: "h-6 w-6 text-muted-foreground" }),
543
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "Claude accounts" })
544
+ ] }),
545
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Connect your own Claude subscription once. It stays signed in, even across restarts." })
546
+ ] }),
547
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }),
548
+ !account ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
549
+ /* @__PURE__ */ jsx(Spinner, {}),
550
+ " Loading\u2026"
551
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
552
+ /* @__PURE__ */ jsxs("section", { className: "space-y-6 rounded-lg border border-border p-4", children: [
553
+ /* @__PURE__ */ jsx(SignInStep, { account, onChange: (a) => a ? setAccount(a) : void refresh() }),
554
+ companyId && /* @__PURE__ */ jsx(ConnectionStep, { companyId, account })
555
+ ] }),
556
+ companyId && /* @__PURE__ */ jsx(Disclosure, { title: "Advanced", children: /* @__PURE__ */ jsx(AgentsSection, { companyId, account }) })
412
557
  ] })
413
558
  ] });
414
559
  }
415
560
  function ClaudeAccountsSidebar(_props) {
416
561
  const nav = useHostNavigation();
562
+ const { pathname } = useHostLocation();
417
563
  const { companyId } = useHostContext();
418
564
  useNativeKeepAlive(companyId);
419
- return /* @__PURE__ */ jsx("a", { ...nav.linkProps("/claude-accounts"), children: "Claude accounts" });
565
+ const active = pathname === nav.resolveHref(PAGE_ROUTE);
566
+ return /* @__PURE__ */ jsxs(
567
+ "a",
568
+ {
569
+ ...nav.linkProps(PAGE_ROUTE),
570
+ "aria-current": active ? "page" : void 0,
571
+ className: cx(
572
+ "flex items-center gap-2.5 mx-2 rounded-lg px-2 py-1.5 pointer-coarse:py-1 text-(length:--text-compact) font-medium transition-colors",
573
+ active ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
574
+ ),
575
+ children: [
576
+ /* @__PURE__ */ jsx("span", { "data-slot": "sidebar-nav-icon", className: "relative shrink-0", children: /* @__PURE__ */ jsx(Icon, { name: "key" }) }),
577
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: "Claude accounts" })
578
+ ]
579
+ }
580
+ );
420
581
  }
421
582
  function AgentClaudeTab({ context }) {
422
- const { account, setAccount, refresh } = useMyAccount();
583
+ const nav = useHostNavigation();
584
+ const { account } = useMyAccount();
423
585
  const [agent, setAgent] = useState(null);
424
- const [err, setErr] = useState(null);
586
+ const [error, setError] = useState(null);
425
587
  const load = useCallback(() => {
426
- api("GET", `/agents/${context.entityId}`).then(setAgent).catch((e) => setErr(String(e.message ?? e)));
588
+ api("GET", `/agents/${context.entityId}`).then(setAgent).catch((e) => setError(errText(e)));
427
589
  }, [context.entityId]);
428
590
  useEffect(load, [load]);
429
591
  const dir = agent ? agentConfigDir(agent) : null;
430
592
  const owner = usePluginData("dir-owner", { dir: dir ?? "" });
431
- if (err) return /* @__PURE__ */ jsx("p", { style: S.bad, children: err });
432
- if (!agent || !account) return /* @__PURE__ */ jsx("p", { children: "Loading\u2026" });
433
- if (agent.adapterType !== "claude_local") return /* @__PURE__ */ jsx("p", { style: S.muted, children: "This agent does not use Claude Code." });
434
- const who = !dir ? "the server's shared login" : dir === account.claudeConfigDir ? "your Claude account" : owner.data?.managed ? "another team member's Claude account" : `a custom folder (${dir})`;
435
- return /* @__PURE__ */ jsxs("div", { style: { padding: 16 }, children: [
436
- /* @__PURE__ */ jsxs("p", { children: [
437
- "This agent signs in with ",
438
- /* @__PURE__ */ jsx("b", { children: who }),
439
- "."
593
+ if (error) return /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error });
594
+ if (!agent || !account)
595
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
596
+ /* @__PURE__ */ jsx(Spinner, {}),
597
+ " Loading\u2026"
598
+ ] });
599
+ if (agent.adapterType !== "claude_local") return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "This agent doesn't use Claude Code." });
600
+ const bound = !!agent.runtimeConfig?.aiConnection;
601
+ const mine = dir === account.claudeConfigDir;
602
+ const who = bound ? "its Paperclip AI connection" : !dir ? "the server login" : mine ? "your Claude account" : owner.data?.managed ? "a teammate's Claude account" : "a custom folder";
603
+ return /* @__PURE__ */ jsx("div", { className: "max-w-3xl space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4 rounded-lg border border-border p-4", children: [
604
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
605
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Claude login" }),
606
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
607
+ "This agent signs in with ",
608
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: who }),
609
+ "."
610
+ ] })
440
611
  ] }),
441
- !account.connected && /* @__PURE__ */ jsx(ConnectCard, { account, onChange: (a) => a ? setAccount(a) : void refresh() }),
442
- /* @__PURE__ */ jsx("div", { style: S.row, children: /* @__PURE__ */ jsx(
612
+ bound ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "To run it on your own login folder instead, switch it to the local login in the agent's configuration." }) : account.connected ? /* @__PURE__ */ jsx(
443
613
  AgentToggle,
444
614
  {
445
615
  agent,
@@ -449,8 +619,8 @@ function AgentClaudeTab({ context }) {
449
619
  owner.refresh();
450
620
  }
451
621
  }
452
- ) })
453
- ] });
622
+ ) : /* @__PURE__ */ jsx("a", { ...nav.linkProps(PAGE_ROUTE), className: "text-sm font-medium text-primary underline-offset-4 hover:underline", children: "Connect your Claude account first \u2192" })
623
+ ] }) });
454
624
  }
455
625
  export {
456
626
  AgentClaudeTab,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-plugin-claude-accounts",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Per-user Claude accounts for Paperclip: each member signs in to Claude from the browser, picks which agents use it, and it stays signed in. No shell, no re-auth.",
5
5
  "type": "module",
6
6
  "license": "MIT",