wiki-viewer 1.5.0 → 1.5.2
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-ao6a_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{0~w0_katex_dist_katex_mjs_055boxx._.js → 0~w0_katex_dist_katex_mjs_00650~e._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__134_q7l._.js → [root-of-the-server]__12ahxbl._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0pqaawz._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0rc1vh5._.js → node_modules__pnpm_02gx8km._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0t4unpg._.js → node_modules__pnpm_02mew~s._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_12-ni1n._.js → node_modules__pnpm_03ij5.g._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_12~a4c_._.js → node_modules__pnpm_03v21p-._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0f~_mm3._.js → node_modules__pnpm_05qsply._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0alwr5e._.js → node_modules__pnpm_06pgebo._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_02zxe7o._.js → node_modules__pnpm_08ibh0w._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0hr027i._.js → node_modules__pnpm_099cv.b._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0znncy9._.js → node_modules__pnpm_0cwpt9p._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-pisfi._.js → node_modules__pnpm_0f9fpy5._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0zhbnni._.js → node_modules__pnpm_0f_uzgy._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-w-d-.._.js → node_modules__pnpm_0fixtms._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_03z0vkh._.js → node_modules__pnpm_0gofu~q._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_10i6lh6._.js → node_modules__pnpm_0iojvye._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-hfev1._.js → node_modules__pnpm_0li2vzs._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_13kfy_z._.js → node_modules__pnpm_0m5-azh._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_071f6vl._.js → node_modules__pnpm_0n1kfcp._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_07uh2kc._.js → node_modules__pnpm_0rebktf._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0okw1r.._.js → node_modules__pnpm_0tkiej3._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0xuz.8f._.js → node_modules__pnpm_0v_nwsz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0zb6573._.js → node_modules__pnpm_0wffzo0._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0d7jxsf._.js → node_modules__pnpm_0wouz19._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0egenev._.js → node_modules__pnpm_0xxwmv2._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0j9bq9s._.js → node_modules__pnpm_0zzzsrj._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0irsd.b._.js → node_modules__pnpm_0~.6dgb._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0erwv_m._.js → node_modules__pnpm_0~2~c30._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0y9k6h~._.js → node_modules__pnpm_10413dq._.js} +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/static/chunks/{14luv~jq7e6-n.js → 17~r3.8awh3qg.js} +2 -2
- package/.next/standalone/agents/bootstrap-prompt.md +1 -1
- package/.next/standalone/agents/wiki-viewer-skill/SKILL.md +1 -1
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/src/app/api/system/auth-settings/route.ts +1 -0
- package/.next/standalone/src/components/ai-panel/ai-panel.tsx +70 -124
- package/.next/standalone/src/components/ai-panel/token-section.tsx +8 -7
- package/.next/standalone/src/components/auth-settings-sheet.tsx +81 -22
- package/.next/standalone/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- /package/.next/standalone/.next/static/{19j4-_brJLoulXAMekpM2 → _avhhNXTnEvzPnQc1sIDF}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{19j4-_brJLoulXAMekpM2 → _avhhNXTnEvzPnQc1sIDF}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{19j4-_brJLoulXAMekpM2 → _avhhNXTnEvzPnQc1sIDF}/_ssgManifest.js +0 -0
|
@@ -9,4 +9,4 @@ You are connecting to a running wiki-viewer instance at $WIKI_URL (e.g. `http://
|
|
|
9
9
|
|
|
10
10
|
Set `basis` and `basisDetail` on every Tier-2 content op so the human can see where your changes came from. Prefer `suggestion.add` over direct block ops unless told otherwise.
|
|
11
11
|
|
|
12
|
-
MCP-capable agents: `npx wiki-viewer-mcp` (set `WIKI_VIEWER_URL`, `WIKI_VIEWER_TOKEN`, `WIKI_VIEWER_AGENT_ID`)
|
|
12
|
+
MCP-capable agents: `npx wiki-viewer-mcp` (set `WIKI_VIEWER_URL`, `WIKI_VIEWER_TOKEN`, `WIKI_VIEWER_AGENT_ID`) gives native **Tier-1 file tools** (read/write/edit/list/search/move/delete) and refuses to overwrite an `active` doc. It has **no Tier-2 collab tools** — to co-write a doc (suggestions/comments), call the Tier-2 HTTP endpoints directly. MCP = fast filework; Tier-2 HTTP = reviewable collaboration.
|
|
@@ -395,7 +395,7 @@ Every `GET /api/agent/fs/file/<path>` (and every Tier-2 snapshot read) returns `
|
|
|
395
395
|
Switch to Tier-2 (block-ops) using the URL in the response body.
|
|
396
396
|
```
|
|
397
397
|
|
|
398
|
-
**MCP-capable agents:** run `npx wiki-viewer-mcp` —
|
|
398
|
+
**MCP-capable agents:** run `npx wiki-viewer-mcp` (set `WIKI_VIEWER_URL`, `WIKI_VIEWER_TOKEN`, `WIKI_VIEWER_AGENT_ID`). It exposes **Tier-1 file tools only** — `read_file`, `write_file`, `edit_file`, `list_directory`, `search`, `move_file`, `delete_file` — and reads `X-Collab-State` so it won't clobber an active doc: it blocks the write and tells you to use the collaborative path instead. The MCP shim has **no Tier-2 (block-ops / suggestion / comment) tools** — to co-write a doc, use the Tier-2 HTTP endpoints described above directly (the skill instructions cover them). Use the MCP for fast filework; use Tier-2 HTTP for reviewable doc collaboration.
|
|
399
399
|
|
|
400
400
|
---
|
|
401
401
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useCallback, useRef, useState } from "react";
|
|
4
|
-
import { X, Copy, Check, Bot, Wifi,
|
|
4
|
+
import { X, Copy, Check, Bot, Wifi, ExternalLink } from "lucide-react";
|
|
5
5
|
import { Button } from "@/components/ui/button";
|
|
6
6
|
import { useAIPanelStore } from "@/stores/ai-panel-store";
|
|
7
7
|
import { TokenSection } from "./token-section";
|
|
8
8
|
import { ActivityRow } from "./activity-row";
|
|
9
|
-
import { authClient } from "@/lib/auth/client";
|
|
10
9
|
const SKILL_CLI = "npx skills add anh-chu/wiki-viewer/agents/wiki-viewer-skill";
|
|
11
10
|
|
|
12
11
|
function useCopyCurl(currentPath: string | null) {
|
|
@@ -55,7 +54,6 @@ export function AIPanel({ currentPath }: { currentPath?: string | null }) {
|
|
|
55
54
|
const close = useAIPanelStore((s) => s.close);
|
|
56
55
|
const activity = useAIPanelStore((s) => s.activity);
|
|
57
56
|
const connections = useAIPanelStore((s) => s.connections);
|
|
58
|
-
const rateLimit = useAIPanelStore((s) => s.rateLimit);
|
|
59
57
|
const loadActivity = useAIPanelStore((s) => s.loadActivity);
|
|
60
58
|
const { copied, copy } = useCopyCurl(currentPath ?? null);
|
|
61
59
|
const [bootstrapPrompt, setBootstrapPrompt] = useState<string>("");
|
|
@@ -172,95 +170,84 @@ export function AIPanel({ currentPath }: { currentPath?: string | null }) {
|
|
|
172
170
|
|
|
173
171
|
{/* Scrollable body */}
|
|
174
172
|
<div className="flex-1 overflow-y-auto space-y-5 px-4 py-4">
|
|
175
|
-
|
|
173
|
+
{/* What this does — one line */}
|
|
174
|
+
<section className="rounded-md border border-border bg-muted/30 p-3">
|
|
175
|
+
<p className="text-sm font-medium text-foreground">
|
|
176
|
+
Work with this wiki using AI
|
|
177
|
+
</p>
|
|
178
|
+
<p className="mt-1 text-xs leading-relaxed text-muted-foreground">
|
|
179
|
+
Connect Claude, Cursor, ChatGPT, and more. Pick by what you want —
|
|
180
|
+
each option gives a copy-paste setup. You approve every assistant
|
|
181
|
+
in <span className="font-medium text-foreground/80">Agents</span> below before it gets access.
|
|
182
|
+
</p>
|
|
183
|
+
</section>
|
|
176
184
|
|
|
177
|
-
{/*
|
|
178
|
-
<section className="space-y-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
{/* Choose by goal */}
|
|
186
|
+
<section className="space-y-3">
|
|
187
|
+
{/* Goal 1: collaborate (one-off instructions + permanent skill) */}
|
|
188
|
+
<div className="rounded-md border border-border bg-muted/40 p-3 space-y-1.5">
|
|
189
|
+
<p className="text-xs font-semibold text-foreground">
|
|
190
|
+
Co-write docs with you
|
|
191
|
+
</p>
|
|
192
|
+
<p className="text-[10px] text-muted-foreground/70">
|
|
193
|
+
Edits arrive as suggestions with comments you accept or revert.
|
|
194
|
+
No MCP needed.
|
|
195
|
+
</p>
|
|
196
|
+
<Button size="sm" variant="outline" className="h-7 w-full text-xs gap-1.5" onClick={() => void bootstrapCopy.copy()} disabled={!bootstrapPrompt}>
|
|
197
|
+
{bootstrapCopy.copied
|
|
198
|
+
? <><Check className="h-3.5 w-3.5 text-green-500" /> Copied!</>
|
|
199
|
+
: <><Copy className="h-3.5 w-3.5" /> Paste into any chatbot</>}
|
|
200
|
+
</Button>
|
|
201
|
+
<p className="text-[10px] text-muted-foreground/50 pt-1">
|
|
202
|
+
Or install once so it sticks{" "}
|
|
203
|
+
<span className="text-muted-foreground/40">· Claude, OpenCode</span>:
|
|
204
|
+
</p>
|
|
183
205
|
<div className="flex items-center gap-2">
|
|
184
|
-
<code className="flex-1 text-
|
|
185
|
-
{
|
|
206
|
+
<code className="flex-1 text-[10px] font-mono text-foreground/80 truncate bg-muted rounded px-1 py-0.5">
|
|
207
|
+
{SKILL_CLI}
|
|
186
208
|
</code>
|
|
187
|
-
<Button
|
|
188
|
-
|
|
189
|
-
variant="ghost"
|
|
190
|
-
className="h-6 w-6 p-0 shrink-0"
|
|
191
|
-
title="Copy curl example"
|
|
192
|
-
onClick={() => void copy()}
|
|
193
|
-
>
|
|
194
|
-
{copied ? (
|
|
195
|
-
<Check className="h-3.5 w-3.5 text-green-500" />
|
|
196
|
-
) : (
|
|
197
|
-
<Copy className="h-3.5 w-3.5" />
|
|
198
|
-
)}
|
|
209
|
+
<Button size="sm" variant="ghost" className="h-6 w-6 p-0 shrink-0" title="Copy this command" onClick={() => void skillCli.copy()}>
|
|
210
|
+
{skillCli.copied ? <Check className="h-3.5 w-3.5 text-green-500" /> : <Copy className="h-3.5 w-3.5" />}
|
|
199
211
|
</Button>
|
|
200
212
|
</div>
|
|
201
|
-
<p className="text-[10px] text-muted-foreground/70">
|
|
202
|
-
Copy icon copies a registration-flow <code className="bg-muted px-0.5 rounded">curl</code> trace for the current file.
|
|
203
|
-
</p>
|
|
204
213
|
</div>
|
|
205
|
-
</section>
|
|
206
214
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
<
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
<
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
<Button size="sm" variant="ghost" className="h-6 w-6 p-0 shrink-0" title="Copy register command" onClick={() => void mcpRegister.copy()}>
|
|
223
|
-
{mcpRegister.copied ? <Check className="h-3.5 w-3.5 text-green-500" /> : <Copy className="h-3.5 w-3.5" />}
|
|
224
|
-
</Button>
|
|
225
|
-
</div>
|
|
226
|
-
<p className="text-[10px] text-muted-foreground/50 pt-1">2. Approve below, then add to your <code className="bg-muted px-0.5 rounded">mcp.json</code>:</p>
|
|
227
|
-
<Button size="sm" variant="outline" className="h-7 w-full text-xs gap-1.5" onClick={() => void mcpJson.copy()}>
|
|
228
|
-
{mcpJson.copied
|
|
229
|
-
? <><Check className="h-3.5 w-3.5 text-green-500" /> Copied!</>
|
|
230
|
-
: <><Copy className="h-3.5 w-3.5" /> Copy mcp.json</>}
|
|
231
|
-
</Button>
|
|
232
|
-
</div>
|
|
233
|
-
<div className="h-px bg-border/60" />
|
|
234
|
-
<div className="space-y-1">
|
|
235
|
-
<p className="text-[10px] text-muted-foreground/70">Agent Skills standard:</p>
|
|
236
|
-
<div className="flex items-center gap-2">
|
|
237
|
-
<code className="flex-1 text-[10px] font-mono text-foreground/80 truncate bg-muted rounded px-1 py-0.5">
|
|
238
|
-
{SKILL_CLI}
|
|
239
|
-
</code>
|
|
240
|
-
<Button size="sm" variant="ghost" className="h-6 w-6 p-0 shrink-0" title="Copy CLI command" onClick={() => void skillCli.copy()}>
|
|
241
|
-
{skillCli.copied ? <Check className="h-3.5 w-3.5 text-green-500" /> : <Copy className="h-3.5 w-3.5" />}
|
|
242
|
-
</Button>
|
|
243
|
-
</div>
|
|
244
|
-
</div>
|
|
245
|
-
<div className="space-y-1">
|
|
246
|
-
<p className="text-[10px] text-muted-foreground/70">Any agent (paste into chat):</p>
|
|
247
|
-
<Button size="sm" variant="outline" className="h-7 w-full text-xs gap-1.5" onClick={() => void bootstrapCopy.copy()} disabled={!bootstrapPrompt}>
|
|
248
|
-
{bootstrapCopy.copied
|
|
249
|
-
? <><Check className="h-3.5 w-3.5 text-green-500" /> Copied!</>
|
|
250
|
-
: <><Copy className="h-3.5 w-3.5" /> Copy bootstrap prompt</>}
|
|
215
|
+
{/* Goal 2: raw filework */}
|
|
216
|
+
<div className="rounded-md border border-border bg-muted/40 p-3 space-y-1.5">
|
|
217
|
+
<p className="text-xs font-semibold text-foreground">
|
|
218
|
+
Edit files directly{" "}
|
|
219
|
+
<span className="font-normal text-muted-foreground/60">· Claude Code, Cursor, Codex</span>
|
|
220
|
+
</p>
|
|
221
|
+
<p className="text-[10px] text-muted-foreground/70">
|
|
222
|
+
Native read / search / edit tools over MCP. Run to request access:
|
|
223
|
+
</p>
|
|
224
|
+
<div className="flex items-center gap-2">
|
|
225
|
+
<code className="flex-1 text-[10px] font-mono text-foreground/80 truncate bg-muted rounded px-1 py-0.5">
|
|
226
|
+
{getMcpRegister()}
|
|
227
|
+
</code>
|
|
228
|
+
<Button size="sm" variant="ghost" className="h-6 w-6 p-0 shrink-0" title="Copy this command" onClick={() => void mcpRegister.copy()}>
|
|
229
|
+
{mcpRegister.copied ? <Check className="h-3.5 w-3.5 text-green-500" /> : <Copy className="h-3.5 w-3.5" />}
|
|
251
230
|
</Button>
|
|
252
231
|
</div>
|
|
253
|
-
<
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
232
|
+
<p className="text-[10px] text-muted-foreground/50">Approve in <span className="font-medium text-foreground/70">Agents</span>, then add to your tool:</p>
|
|
233
|
+
<Button size="sm" variant="outline" className="h-7 w-full text-xs gap-1.5" onClick={() => void mcpJson.copy()}>
|
|
234
|
+
{mcpJson.copied
|
|
235
|
+
? <><Check className="h-3.5 w-3.5 text-green-500" /> Copied!</>
|
|
236
|
+
: <><Copy className="h-3.5 w-3.5" /> Copy config (mcp.json)</>}
|
|
237
|
+
</Button>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div className="flex flex-wrap items-center gap-x-3 gap-y-1">
|
|
241
|
+
<span className="text-[10px] text-muted-foreground/50">For developers:</span>
|
|
242
|
+
<button type="button" onClick={() => void copy()} className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
|
|
243
|
+
{copied ? <Check className="h-3 w-3 text-green-500" /> : <Copy className="h-3 w-3" />} curl example
|
|
244
|
+
</button>
|
|
245
|
+
<a href="/api/agents/skill" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
|
|
246
|
+
<ExternalLink className="h-3 w-3" /> skill
|
|
247
|
+
</a>
|
|
248
|
+
<a href="/api/agents/install" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
|
|
249
|
+
<ExternalLink className="h-3 w-3" /> install JSON
|
|
250
|
+
</a>
|
|
264
251
|
</div>
|
|
265
252
|
</section>
|
|
266
253
|
|
|
@@ -312,24 +299,6 @@ export function AIPanel({ currentPath }: { currentPath?: string | null }) {
|
|
|
312
299
|
</div>
|
|
313
300
|
</section>
|
|
314
301
|
|
|
315
|
-
{/* Settings */}
|
|
316
|
-
<section className="space-y-2">
|
|
317
|
-
<h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
|
|
318
|
-
Settings
|
|
319
|
-
</h3>
|
|
320
|
-
<div className="rounded-md border border-border bg-muted/40 p-3 space-y-1.5">
|
|
321
|
-
<div className="flex items-center justify-between text-xs">
|
|
322
|
-
<span className="text-muted-foreground">Rate limit</span>
|
|
323
|
-
<span className="font-mono">
|
|
324
|
-
{rateLimit !== null ? `${rateLimit} ops/min` : "--"}
|
|
325
|
-
</span>
|
|
326
|
-
</div>
|
|
327
|
-
<p className="text-[10px] text-muted-foreground/60">
|
|
328
|
-
Override with <code className="bg-muted px-0.5 rounded">AGENT_RATE_LIMIT</code> env var.
|
|
329
|
-
</p>
|
|
330
|
-
</div>
|
|
331
|
-
</section>
|
|
332
|
-
|
|
333
302
|
{/* Docs link */}
|
|
334
303
|
<section>
|
|
335
304
|
<a
|
|
@@ -346,26 +315,3 @@ export function AIPanel({ currentPath }: { currentPath?: string | null }) {
|
|
|
346
315
|
</>
|
|
347
316
|
);
|
|
348
317
|
}
|
|
349
|
-
|
|
350
|
-
function SignedInUser() {
|
|
351
|
-
const { data: session } = authClient.useSession();
|
|
352
|
-
if (!session?.user) return null;
|
|
353
|
-
return (
|
|
354
|
-
<div className="flex items-center justify-between rounded-md border border-border bg-muted/40 p-3">
|
|
355
|
-
<div className="text-sm min-w-0">
|
|
356
|
-
<div className="font-medium truncate">{session.user.name}</div>
|
|
357
|
-
<div className="text-xs text-muted-foreground truncate">{session.user.email}</div>
|
|
358
|
-
</div>
|
|
359
|
-
<button
|
|
360
|
-
type="button"
|
|
361
|
-
onClick={async () => {
|
|
362
|
-
await authClient.signOut();
|
|
363
|
-
window.location.href = "/signin";
|
|
364
|
-
}}
|
|
365
|
-
className="ml-2 shrink-0 text-xs px-2 py-1 rounded border border-border hover:bg-accent"
|
|
366
|
-
>
|
|
367
|
-
Sign out
|
|
368
|
-
</button>
|
|
369
|
-
</div>
|
|
370
|
-
);
|
|
371
|
-
}
|
|
@@ -251,7 +251,7 @@ export function TokenSection() {
|
|
|
251
251
|
<section className="space-y-2">
|
|
252
252
|
<div className="flex items-center justify-between">
|
|
253
253
|
<h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
|
|
254
|
-
Agents
|
|
254
|
+
Agents · approve & manage access
|
|
255
255
|
</h3>
|
|
256
256
|
<Button
|
|
257
257
|
size="sm"
|
|
@@ -269,7 +269,7 @@ export function TokenSection() {
|
|
|
269
269
|
<div className="rounded-md border border-amber-500/40 bg-amber-50/10 p-2">
|
|
270
270
|
<p className="text-[10px] font-semibold uppercase tracking-widest text-amber-600/80 mb-1 flex items-center gap-1">
|
|
271
271
|
<Clock className="h-3 w-3" />
|
|
272
|
-
|
|
272
|
+
Waiting for your approval ({pending.length})
|
|
273
273
|
</p>
|
|
274
274
|
<ul>
|
|
275
275
|
{pending.map((reg) => (
|
|
@@ -288,9 +288,10 @@ export function TokenSection() {
|
|
|
288
288
|
<div className="rounded-md border border-border bg-muted/40 p-2">
|
|
289
289
|
{agents.length === 0 ? (
|
|
290
290
|
<p className="text-xs text-muted-foreground/60 py-1">
|
|
291
|
-
No
|
|
291
|
+
No assistants connected yet.{" "}
|
|
292
292
|
<span className="text-muted-foreground/40">
|
|
293
|
-
|
|
293
|
+
Connect one above; once it requests access it’ll appear here
|
|
294
|
+
for you to approve.
|
|
294
295
|
</span>
|
|
295
296
|
</p>
|
|
296
297
|
) : (
|
|
@@ -303,9 +304,9 @@ export function TokenSection() {
|
|
|
303
304
|
</div>
|
|
304
305
|
|
|
305
306
|
<p className="text-[10px] text-muted-foreground/60 leading-relaxed">
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
307
|
+
Each assistant gets its own access that you can revoke anytime. New
|
|
308
|
+
requests appear above for your approval before they can read or edit
|
|
309
|
+
anything.
|
|
309
310
|
</p>
|
|
310
311
|
</section>
|
|
311
312
|
);
|
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useState } from "react";
|
|
4
|
-
import { AlertCircle, Loader2 } from "lucide-react";
|
|
4
|
+
import { AlertCircle, Loader2, X } from "lucide-react";
|
|
5
5
|
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
|
-
import {
|
|
8
|
-
Sheet,
|
|
9
|
-
SheetContent,
|
|
10
|
-
SheetDescription,
|
|
11
|
-
SheetFooter,
|
|
12
|
-
SheetHeader,
|
|
13
|
-
SheetTitle,
|
|
14
|
-
} from "@/components/ui/sheet";
|
|
7
|
+
import { Sheet, SheetContent, SheetClose } from "@/components/ui/sheet";
|
|
15
8
|
import { showError } from "@/lib/toast";
|
|
9
|
+
import { authClient } from "@/lib/auth/client";
|
|
16
10
|
|
|
17
11
|
interface AuthSettings {
|
|
18
12
|
allowedEmails: string[];
|
|
19
13
|
allowedDomains: string[];
|
|
20
14
|
source: "config" | "env";
|
|
21
15
|
envFallbackActive: boolean;
|
|
16
|
+
rateLimit?: number;
|
|
22
17
|
}
|
|
23
18
|
|
|
24
19
|
function listToText(list: string[]): string {
|
|
@@ -45,6 +40,8 @@ export function AuthSettingsSheet({
|
|
|
45
40
|
const [source, setSource] = useState<"config" | "env">("env");
|
|
46
41
|
const [emailsText, setEmailsText] = useState("");
|
|
47
42
|
const [domainsText, setDomainsText] = useState("");
|
|
43
|
+
const [rateLimit, setRateLimit] = useState<number | null>(null);
|
|
44
|
+
const { data: session } = authClient.useSession();
|
|
48
45
|
|
|
49
46
|
const load = useCallback(async () => {
|
|
50
47
|
setLoading(true);
|
|
@@ -56,6 +53,7 @@ export function AuthSettingsSheet({
|
|
|
56
53
|
setEmailsText(listToText(data.allowedEmails));
|
|
57
54
|
setDomainsText(listToText(data.allowedDomains));
|
|
58
55
|
setSource(data.source);
|
|
56
|
+
setRateLimit(typeof data.rateLimit === "number" ? data.rateLimit : null);
|
|
59
57
|
} catch (e) {
|
|
60
58
|
setError(e instanceof Error ? e.message : "Failed to load");
|
|
61
59
|
} finally {
|
|
@@ -101,22 +99,63 @@ export function AuthSettingsSheet({
|
|
|
101
99
|
|
|
102
100
|
return (
|
|
103
101
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
104
|
-
<SheetContent
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
<SheetContent
|
|
103
|
+
side="right"
|
|
104
|
+
className="w-80 sm:max-w-md flex flex-col border-l border-border p-0"
|
|
105
|
+
>
|
|
106
|
+
{/* Header — matches AI panel chrome */}
|
|
107
|
+
<div className="flex items-center justify-between border-b border-border px-4 py-3 shrink-0">
|
|
108
|
+
<span className="text-sm font-semibold">Settings</span>
|
|
109
|
+
<SheetClose className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent focus:outline-none focus:ring-2 focus:ring-ring">
|
|
110
|
+
<X className="h-3.5 w-3.5" />
|
|
111
|
+
<span className="sr-only">Close</span>
|
|
112
|
+
</SheetClose>
|
|
113
|
+
</div>
|
|
113
114
|
|
|
114
115
|
{loading ? (
|
|
115
116
|
<div className="flex flex-1 items-center justify-center">
|
|
116
117
|
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
|
117
118
|
</div>
|
|
118
119
|
) : (
|
|
119
|
-
<div className="flex-1 space-y-5 overflow-auto py-4">
|
|
120
|
+
<div className="flex-1 space-y-5 overflow-y-auto px-4 py-4">
|
|
121
|
+
{/* Account */}
|
|
122
|
+
{session?.user && (
|
|
123
|
+
<section className="space-y-2">
|
|
124
|
+
<h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
|
|
125
|
+
Account
|
|
126
|
+
</h3>
|
|
127
|
+
<div className="flex items-center justify-between rounded-md border border-border bg-muted/40 p-3">
|
|
128
|
+
<div className="text-sm min-w-0">
|
|
129
|
+
<div className="font-medium truncate">{session.user.name}</div>
|
|
130
|
+
<div className="text-xs text-muted-foreground truncate">{session.user.email}</div>
|
|
131
|
+
</div>
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
onClick={async () => {
|
|
135
|
+
await authClient.signOut();
|
|
136
|
+
window.location.href = "/signin";
|
|
137
|
+
}}
|
|
138
|
+
className="ml-2 shrink-0 text-xs px-2 py-1 rounded border border-border hover:bg-accent"
|
|
139
|
+
>
|
|
140
|
+
Sign out
|
|
141
|
+
</button>
|
|
142
|
+
</div>
|
|
143
|
+
</section>
|
|
144
|
+
)}
|
|
145
|
+
|
|
146
|
+
{/* Signup allowlist */}
|
|
147
|
+
<section className="space-y-2">
|
|
148
|
+
<h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
|
|
149
|
+
Signup allowlist
|
|
150
|
+
</h3>
|
|
151
|
+
<p className="text-xs leading-relaxed text-muted-foreground">
|
|
152
|
+
Control who can create an account. Leave both lists empty to
|
|
153
|
+
allow any email. Saving here overrides the{" "}
|
|
154
|
+
<code className="bg-muted px-0.5 rounded">AUTH_ALLOWED_EMAILS</code>{" "}
|
|
155
|
+
and{" "}
|
|
156
|
+
<code className="bg-muted px-0.5 rounded">AUTH_ALLOWED_DOMAIN</code>{" "}
|
|
157
|
+
environment variables.
|
|
158
|
+
</p>
|
|
120
159
|
{source === "env" && (
|
|
121
160
|
<div className="flex items-start gap-2 rounded-md bg-muted px-3 py-2 text-xs text-muted-foreground">
|
|
122
161
|
<AlertCircle className="h-3.5 w-3.5 shrink-0 mt-0.5" />
|
|
@@ -187,22 +226,42 @@ export function AuthSettingsSheet({
|
|
|
187
226
|
{error}
|
|
188
227
|
</div>
|
|
189
228
|
)}
|
|
229
|
+
</section>
|
|
230
|
+
|
|
231
|
+
{/* Agent rate limit */}
|
|
232
|
+
<section className="space-y-2">
|
|
233
|
+
<h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
|
|
234
|
+
Agent rate limit
|
|
235
|
+
</h3>
|
|
236
|
+
<div className="rounded-md border border-border bg-muted/40 p-3 space-y-1.5">
|
|
237
|
+
<div className="flex items-center justify-between text-xs">
|
|
238
|
+
<span className="text-muted-foreground">Per agent</span>
|
|
239
|
+
<span className="font-mono">
|
|
240
|
+
{rateLimit !== null ? `${rateLimit} ops/min` : "--"}
|
|
241
|
+
</span>
|
|
242
|
+
</div>
|
|
243
|
+
<p className="text-[10px] text-muted-foreground/60">
|
|
244
|
+
Override with <code className="bg-muted px-0.5 rounded">AGENT_RATE_LIMIT</code> env var.
|
|
245
|
+
</p>
|
|
246
|
+
</div>
|
|
247
|
+
</section>
|
|
190
248
|
</div>
|
|
191
249
|
)}
|
|
192
250
|
|
|
193
|
-
<
|
|
251
|
+
<div className="flex items-center justify-end gap-2 border-t border-border px-4 py-3 shrink-0">
|
|
194
252
|
<Button
|
|
253
|
+
size="sm"
|
|
195
254
|
variant="ghost"
|
|
196
255
|
onClick={() => onOpenChange(false)}
|
|
197
256
|
disabled={saving}
|
|
198
257
|
>
|
|
199
258
|
Cancel
|
|
200
259
|
</Button>
|
|
201
|
-
<Button onClick={handleSave} disabled={saving || loading} className="gap-1.5">
|
|
260
|
+
<Button size="sm" onClick={handleSave} disabled={saving || loading} className="gap-1.5">
|
|
202
261
|
{saving && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
|
|
203
262
|
Save
|
|
204
263
|
</Button>
|
|
205
|
-
</
|
|
264
|
+
</div>
|
|
206
265
|
</SheetContent>
|
|
207
266
|
</Sheet>
|
|
208
267
|
);
|