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.
Files changed (75) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  4. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  5. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  11. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/index.html +1 -1
  20. package/.next/standalone/.next/server/app/index.rsc +2 -2
  21. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  22. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  24. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  25. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  26. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  28. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-ao6a_._.js +1 -1
  29. 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
  30. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__134_q7l._.js → [root-of-the-server]__12ahxbl._.js} +2 -2
  31. package/.next/standalone/.next/server/chunks/ssr/_0pqaawz._.js +2 -2
  32. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0rc1vh5._.js → node_modules__pnpm_02gx8km._.js} +1 -1
  33. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0t4unpg._.js → node_modules__pnpm_02mew~s._.js} +2 -2
  34. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_12-ni1n._.js → node_modules__pnpm_03ij5.g._.js} +1 -1
  35. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_12~a4c_._.js → node_modules__pnpm_03v21p-._.js} +2 -2
  36. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0f~_mm3._.js → node_modules__pnpm_05qsply._.js} +2 -2
  37. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0alwr5e._.js → node_modules__pnpm_06pgebo._.js} +2 -2
  38. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_02zxe7o._.js → node_modules__pnpm_08ibh0w._.js} +2 -2
  39. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0hr027i._.js → node_modules__pnpm_099cv.b._.js} +1 -1
  40. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0znncy9._.js → node_modules__pnpm_0cwpt9p._.js} +1 -1
  41. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-pisfi._.js → node_modules__pnpm_0f9fpy5._.js} +1 -1
  42. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0zhbnni._.js → node_modules__pnpm_0f_uzgy._.js} +1 -1
  43. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-w-d-.._.js → node_modules__pnpm_0fixtms._.js} +1 -1
  44. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_03z0vkh._.js → node_modules__pnpm_0gofu~q._.js} +1 -1
  45. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_10i6lh6._.js → node_modules__pnpm_0iojvye._.js} +2 -2
  46. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-hfev1._.js → node_modules__pnpm_0li2vzs._.js} +1 -1
  47. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_13kfy_z._.js → node_modules__pnpm_0m5-azh._.js} +1 -1
  48. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_071f6vl._.js → node_modules__pnpm_0n1kfcp._.js} +1 -1
  49. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_07uh2kc._.js → node_modules__pnpm_0rebktf._.js} +1 -1
  50. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0okw1r.._.js → node_modules__pnpm_0tkiej3._.js} +2 -2
  51. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0xuz.8f._.js → node_modules__pnpm_0v_nwsz._.js} +1 -1
  52. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0zb6573._.js → node_modules__pnpm_0wffzo0._.js} +1 -1
  53. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0d7jxsf._.js → node_modules__pnpm_0wouz19._.js} +1 -1
  54. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0egenev._.js → node_modules__pnpm_0xxwmv2._.js} +1 -1
  55. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0j9bq9s._.js → node_modules__pnpm_0zzzsrj._.js} +1 -1
  56. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0irsd.b._.js → node_modules__pnpm_0~.6dgb._.js} +1 -1
  57. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0erwv_m._.js → node_modules__pnpm_0~2~c30._.js} +1 -1
  58. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0y9k6h~._.js → node_modules__pnpm_10413dq._.js} +1 -1
  59. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  60. package/.next/standalone/.next/server/middleware-manifest.json +1 -1
  61. package/.next/standalone/.next/server/pages/404.html +1 -1
  62. package/.next/standalone/.next/server/pages/500.html +1 -1
  63. package/.next/standalone/.next/static/chunks/{14luv~jq7e6-n.js → 17~r3.8awh3qg.js} +2 -2
  64. package/.next/standalone/agents/bootstrap-prompt.md +1 -1
  65. package/.next/standalone/agents/wiki-viewer-skill/SKILL.md +1 -1
  66. package/.next/standalone/package.json +1 -1
  67. package/.next/standalone/src/app/api/system/auth-settings/route.ts +1 -0
  68. package/.next/standalone/src/components/ai-panel/ai-panel.tsx +70 -124
  69. package/.next/standalone/src/components/ai-panel/token-section.tsx +8 -7
  70. package/.next/standalone/src/components/auth-settings-sheet.tsx +81 -22
  71. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +1 -1
  73. /package/.next/standalone/.next/static/{19j4-_brJLoulXAMekpM2 → _avhhNXTnEvzPnQc1sIDF}/_buildManifest.js +0 -0
  74. /package/.next/standalone/.next/static/{19j4-_brJLoulXAMekpM2 → _avhhNXTnEvzPnQc1sIDF}/_clientMiddlewareManifest.js +0 -0
  75. /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`) handles tier routing automatically.
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` — it reads `X-Collab-State` automatically and routes to the correct tier. Set env vars `WIKI_VIEWER_URL`, `WIKI_VIEWER_TOKEN`, `WIKI_VIEWER_AGENT_ID`.
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,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-viewer",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Local file viewer and wiki — view markdown, HTML, PDFs, notebooks, office docs, and more from any directory",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,6 +31,7 @@ export async function GET(request: Request) {
31
31
  allowedDomains: effective.domains,
32
32
  source: usingConfig ? "config" : "env",
33
33
  envFallbackActive: !usingConfig,
34
+ rateLimit: Number(process.env.AGENT_RATE_LIMIT) || 60,
34
35
  });
35
36
  }
36
37
 
@@ -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, Download, ExternalLink } from "lucide-react";
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
- <SignedInUser />
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
- {/* Bridge endpoint */}
178
- <section className="space-y-2">
179
- <h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
180
- Bridge Endpoint
181
- </h3>
182
- <div className="rounded-md border border-border bg-muted/40 p-3 space-y-2">
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-xs font-mono text-foreground/80 truncate">
185
- {origin}
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
- size="sm"
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
- {/* Install for AI agents */}
208
- <section className="space-y-2">
209
- <h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
210
- Install for AI Agents
211
- </h3>
212
- <div className="rounded-md border border-border bg-muted/40 p-3 space-y-3">
213
- <div className="space-y-1">
214
- <p className="text-[10px] text-muted-foreground/70">
215
- MCP server <span className="text-muted-foreground/50">(Claude Code, Cursor, Codex):</span>
216
- </p>
217
- <p className="text-[10px] text-muted-foreground/50">1. Register &amp; get a token:</p>
218
- <div className="flex items-center gap-2">
219
- <code className="flex-1 text-[10px] font-mono text-foreground/80 truncate bg-muted rounded px-1 py-0.5">
220
- {getMcpRegister()}
221
- </code>
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
- <div className="flex flex-wrap gap-x-3 gap-y-1 pt-1">
254
- <a href="/api/agents/skill.tar.gz" download className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
255
- <Download className="h-3 w-3" /> skill (.tar.gz)
256
- </a>
257
- <a href="/api/agents/skill" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
258
- <ExternalLink className="h-3 w-3" /> skill markdown
259
- </a>
260
- <a href="/api/agents/install" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-[10px] text-primary hover:underline">
261
- <ExternalLink className="h-3 w-3" /> install JSON
262
- </a>
263
- </div>
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 &amp; 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
- Pending ({pending.length})
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 registered agents.{" "}
291
+ No assistants connected yet.{" "}
292
292
  <span className="text-muted-foreground/40">
293
- Agents register via POST /api/agent/register.
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
- Agents authenticate via bearer token + X-Agent-Id header. Use{" "}
307
- <code className="bg-muted px-0.5 rounded">POST /api/agent/register</code> to start the
308
- TOFU flow.
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 side="right" className="w-full sm:max-w-md flex flex-col">
105
- <SheetHeader>
106
- <SheetTitle>Signup allowlist</SheetTitle>
107
- <SheetDescription>
108
- Control who can create an account. Leave both lists empty to allow
109
- any email. Saving here overrides the AUTH_ALLOWED_EMAILS and
110
- AUTH_ALLOWED_DOMAIN environment variables.
111
- </SheetDescription>
112
- </SheetHeader>
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
- <SheetFooter>
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
- </SheetFooter>
264
+ </div>
206
265
  </SheetContent>
207
266
  </Sheet>
208
267
  );