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 +1 -1
- package/dist/ui/index.js +444 -274
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
59
|
-
return /* @__PURE__ */ jsx(
|
|
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(
|
|
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
|
|
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
|
|
246
|
+
const [url, setUrl] = useState(account.login?.url ?? null);
|
|
85
247
|
const [code, setCode] = useState("");
|
|
86
248
|
const [paste, setPaste] = useState("");
|
|
87
|
-
const [
|
|
88
|
-
const [
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
257
|
+
setError(errText(e));
|
|
97
258
|
} finally {
|
|
98
|
-
setBusy(
|
|
259
|
+
setBusy(null);
|
|
99
260
|
}
|
|
100
261
|
};
|
|
101
|
-
|
|
102
|
-
return /* @__PURE__ */ jsxs(
|
|
103
|
-
/* @__PURE__ */
|
|
104
|
-
|
|
105
|
-
/* @__PURE__ */
|
|
106
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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: "
|
|
277
|
+
children: "Sign out"
|
|
119
278
|
}
|
|
120
|
-
)
|
|
121
|
-
] }) : url ? /* @__PURE__ */ jsxs(
|
|
122
|
-
/* @__PURE__ */ jsxs("p", {
|
|
123
|
-
|
|
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
|
-
"
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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", {
|
|
134
|
-
/* @__PURE__ */ jsx(
|
|
135
|
-
/* @__PURE__ */
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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(
|
|
164
|
-
/* @__PURE__ */
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
"
|
|
184
|
-
/* @__PURE__ */ jsx("code", {
|
|
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", {
|
|
192
|
-
/* @__PURE__ */ jsx(
|
|
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
|
-
|
|
351
|
+
Button,
|
|
195
352
|
{
|
|
196
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
|
477
|
+
function AgentsSection({ companyId, account }) {
|
|
237
478
|
const [agents, setAgents] = useState(null);
|
|
238
|
-
const [
|
|
479
|
+
const [error, setError] = useState(null);
|
|
239
480
|
const load = useCallback(() => {
|
|
240
|
-
listClaudeAgents(companyId).then(setAgents).catch((e) =>
|
|
481
|
+
listClaudeAgents(companyId).then(setAgents).catch((e) => setError(errText(e)));
|
|
241
482
|
}, [companyId]);
|
|
242
483
|
useEffect(load, [load]);
|
|
243
|
-
return /* @__PURE__ */ jsxs("
|
|
244
|
-
/* @__PURE__ */
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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", {
|
|
405
|
-
/* @__PURE__ */
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
/* @__PURE__ */ jsx("
|
|
411
|
-
|
|
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
|
-
|
|
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
|
|
583
|
+
const nav = useHostNavigation();
|
|
584
|
+
const { account } = useMyAccount();
|
|
423
585
|
const [agent, setAgent] = useState(null);
|
|
424
|
-
const [
|
|
586
|
+
const [error, setError] = useState(null);
|
|
425
587
|
const load = useCallback(() => {
|
|
426
|
-
api("GET", `/agents/${context.entityId}`).then(setAgent).catch((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 (
|
|
432
|
-
if (!agent || !account)
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
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
|
+
"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",
|