wiki-viewer 1.4.1 → 1.4.3
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/prerender-manifest.json +3 -27
- package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- 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/page.js +4 -4
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +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/agent/activity/route.js +5 -5
- package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js +6 -6
- package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js +6 -6
- package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js +6 -6
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js +4 -4
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js +4 -4
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js +5 -5
- package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js +4 -4
- package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/internal/span/route.js +8 -8
- package/.next/standalone/.next/server/app/api/agent/internal/span/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/settings/route.js +7 -7
- package/.next/standalone/.next/server/app/api/agent/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js +7 -7
- package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/[...all]/route.js +3 -3
- package/.next/standalone/.next/server/app/api/auth/[...all]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/auth-config/route.js +5 -5
- package/.next/standalone/.next/server/app/api/system/auth-config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/auth-settings/route.js +4 -4
- package/.next/standalone/.next/server/app/api/system/auth-settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/browse/route.js +6 -6
- package/.next/standalone/.next/server/app/api/system/browse/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/clear-root/route.js +5 -5
- package/.next/standalone/.next/server/app/api/system/clear-root/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/config/route.js +5 -5
- package/.next/standalone/.next/server/app/api/system/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/pins/route.js +4 -4
- package/.next/standalone/.next/server/app/api/system/pins/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/reveal/route.js +4 -4
- package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/root-status/route.js +4 -4
- package/.next/standalone/.next/server/app/api/system/root-status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/set-root/route.js +5 -5
- package/.next/standalone/.next/server/app/api/system/set-root/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/app/route.js +6 -6
- package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/content/route.js +7 -7
- package/.next/standalone/.next/server/app/api/wiki/content/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/folder/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/move/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/move/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/new-file/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/page/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/page/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/slugs/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/slugs/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/upload/route.js +5 -5
- package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/watch/route.js +6 -6
- package/.next/standalone/.next/server/app/api/wiki/watch/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 +4 -4
- 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/app/signin/page.js +9 -4
- package/.next/standalone/.next/server/app/signin/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/signin/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__03h3~3e._.js → [root-of-the-server]__04udrya._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__04g0j8i._.js → [root-of-the-server]__0kdn1le._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0d89o98._.js → src_lib_auth_server_ts_00f4e0h._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/0biy_@better-auth_core_dist_121m_lh._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/0dhv_better-auth_dist_adapters_kysely-adapter_index_mjs_08ymlcb._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/0dti_kysely_dist_esm_0-t3o9q._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/0dti_kysely_dist_esm_01._58q._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/0p91_next_0f40gy0._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/{0p91_next_dist_0~2d1tl._.js → 0p91_next_dist_10ud_sa._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/0sag_@better-auth_memory-adapter_dist_index_mjs_07-_ka6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_bun-sqlite-dialect-DzNwOpKv_mjs_0lruvb8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_d1-sqlite-dialect-C2B7YsIT_mjs_03n6~tc._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_index_mjs_0gi5w.w._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_node-sqlite-dialect_mjs_0l4.y~0._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__026s8~z._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06mzg2t._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0btcd4o._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0c~og-9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__134_q7l._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0pqaawz._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_051tcva._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0o~d81.._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_app_signin_00yk0vm._.js +452 -0
- package/.next/standalone/.next/server/chunks/ssr/src_app_signin_signin-form_tsx_0eznlbd._.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/09-jtayuhrq~b.js +1 -0
- package/.next/standalone/.next/static/chunks/0uv7ozzpiyp2_.js +1 -0
- package/.next/standalone/.next/static/chunks/135c~rq_ux73w.js +1 -0
- package/.next/standalone/docs/agent-fs-plan.md +230 -0
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/src/app/signin/page.tsx +10 -225
- package/.next/standalone/src/app/signin/signin-form.tsx +238 -0
- package/.next/standalone/src/lib/auth/server.ts +14 -7
- package/.next/standalone/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/.next/standalone/.next/server/app/signin.html +0 -1
- package/.next/standalone/.next/server/app/signin.meta +0 -15
- package/.next/standalone/.next/server/app/signin.rsc +0 -23
- package/.next/standalone/.next/server/app/signin.segments/_full.segment.rsc +0 -23
- package/.next/standalone/.next/server/app/signin.segments/_head.segment.rsc +0 -6
- package/.next/standalone/.next/server/app/signin.segments/_index.segment.rsc +0 -7
- package/.next/standalone/.next/server/app/signin.segments/_tree.segment.rsc +0 -3
- package/.next/standalone/.next/server/app/signin.segments/signin/__PAGE__.segment.rsc +0 -9
- package/.next/standalone/.next/server/app/signin.segments/signin.segment.rsc +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__088hqzy._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0c147fo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0z.1v1z._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12zp2dm._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0exqvyf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_app_signin_page_tsx_1143y49._.js +0 -3
- package/.next/standalone/.next/static/chunks/0-_39e4awbk8y.js +0 -1
- package/.next/standalone/.next/static/chunks/02bxx~2cg~s6~.js +0 -1
- package/.next/standalone/.next/static/chunks/0mj_f8ncf2tks.js +0 -1
- /package/.next/standalone/.next/static/{bNZLQMXPxlDzQicfJAh2f → kzOuSAQArtvD7uiAJ6ZLh}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{bNZLQMXPxlDzQicfJAh2f → kzOuSAQArtvD7uiAJ6ZLh}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{bNZLQMXPxlDzQicfJAh2f → kzOuSAQArtvD7uiAJ6ZLh}/_ssgManifest.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,419564,e=>{"use strict";var t=e.i(44501),s=e.i(101685),r=e.i(199075);e.s(["default",0,function({initialPasswordAuth:e,initialGoogle:n}){let[i,a]=(0,s.useState)("signin"),[o,l]=(0,s.useState)(""),[d,c]=(0,s.useState)(""),[u,m]=(0,s.useState)(""),[g,x]=(0,s.useState)(null),[p,f]=(0,s.useState)(!1),[h,b]=(0,s.useState)(n),[j,y]=(0,s.useState)(e),[w,v]=(0,s.useState)("/");async function N(e){e.preventDefault(),x(null),f(!0);try{if("signup"===i){let e=await r.authClient.signUp.email({email:o,password:d,name:u,callbackURL:w});e.error?x(e.error.message??"Sign-up failed"):window.location.href=w}else{let e=await r.authClient.signIn.email({email:o,password:d,callbackURL:w});if(e.error){let t=e.error.status;429===t?x("Too many sign-in attempts. Wait a minute and try again."):x(e.error.message??"Sign-in failed")}else window.location.href=w}}catch(e){x(e instanceof Error?e.message:"Unexpected error")}finally{f(!1)}}async function C(){x(null),f(!0);try{await r.authClient.signIn.social({provider:"google",callbackURL:w})}catch(e){x(e instanceof Error?e.message:"Google sign-in failed"),f(!1)}}return(0,s.useEffect)(()=>{fetch("/api/system/auth-config",{credentials:"include"}).then(e=>e.json()).then(e=>{e&&"object"==typeof e&&(b(!!e.google),y(!1!==e.passwordAuth))}).catch(()=>{});let e=new URLSearchParams(window.location.search).get("next");e&&e.startsWith("/")&&v(e)},[]),(0,t.jsx)("div",{className:"min-h-screen flex items-center justify-center bg-background",children:(0,t.jsxs)("div",{className:"w-full max-w-sm rounded-lg border border-border bg-card p-8 shadow-sm",children:[(0,t.jsx)("h1",{className:"mb-6 text-xl font-semibold text-foreground",children:"Sign in to wiki-viewer"}),h&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("button",{type:"button",onClick:C,disabled:p,className:"flex w-full items-center justify-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium hover:bg-accent disabled:opacity-50",children:[(0,t.jsxs)("svg",{viewBox:"0 0 24 24",className:"h-4 w-4","aria-hidden":"true",children:[(0,t.jsx)("path",{fill:"#4285F4",d:"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"}),(0,t.jsx)("path",{fill:"#34A853",d:"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"}),(0,t.jsx)("path",{fill:"#FBBC05",d:"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z"}),(0,t.jsx)("path",{fill:"#EA4335",d:"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"})]}),"Continue with Google"]}),j&&(0,t.jsxs)("div",{className:"my-4 flex items-center gap-3",children:[(0,t.jsx)("hr",{className:"flex-1 border-border"}),(0,t.jsx)("span",{className:"text-xs text-muted-foreground",children:"or"}),(0,t.jsx)("hr",{className:"flex-1 border-border"})]})]}),!j&&!h&&(0,t.jsx)("p",{className:"rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive",children:"No sign-in method is configured. Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET, or enable email/password auth."}),j&&(0,t.jsxs)("form",{onSubmit:N,className:"space-y-4",children:["signup"===i&&(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"mb-1 block text-sm font-medium text-foreground",htmlFor:"name",children:"Name"}),(0,t.jsx)("input",{id:"name",type:"text",autoComplete:"name",value:u,onChange:e=>m(e.target.value),required:!0,className:"w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"mb-1 block text-sm font-medium text-foreground",htmlFor:"email",children:"Email"}),(0,t.jsx)("input",{id:"email",type:"email",autoComplete:"email",value:o,onChange:e=>l(e.target.value),required:!0,className:"w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"mb-1 block text-sm font-medium text-foreground",htmlFor:"password",children:"Password"}),(0,t.jsx)("input",{id:"password",type:"password",autoComplete:"signup"===i?"new-password":"current-password",value:d,onChange:e=>c(e.target.value),required:!0,minLength:8,className:"w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"})]}),g&&(0,t.jsx)("p",{className:"rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive",children:g}),(0,t.jsx)("button",{type:"submit",disabled:p,className:"w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50",children:p?"signup"===i?"Creating account...":"Signing in...":"signup"===i?"Create account":"Sign in"})]}),j&&(0,t.jsxs)("p",{className:"mt-4 text-center text-sm text-muted-foreground",children:["signin"===i?"No account?":"Already have an account?"," ",(0,t.jsx)("button",{type:"button",onClick:()=>{a("signin"===i?"signup":"signin"),x(null)},className:"font-medium text-foreground underline-offset-4 hover:underline",children:"signin"===i?"Sign up":"Sign in"})]})]})})}])}]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,824627,t=>{"use strict";var e=function(t,r){return(e=Object.setPrototypeOf||({__proto__:[]})instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])})(t,r)},r=function(){return(r=Object.assign||function(t){for(var e,r=1,n=arguments.length;r<n;r++)for(var o in e=arguments[r])Object.prototype.hasOwnProperty.call(e,o)&&(t[o]=e[o]);return t}).apply(this,arguments)};"function"==typeof SuppressedError&&SuppressedError,t.s(["__assign",()=>r,"__awaiter",0,function(t,e,r,n){return new(r||(r=Promise))(function(o,c){function s(t){try{i(n.next(t))}catch(t){c(t)}}function a(t){try{i(n.throw(t))}catch(t){c(t)}}function i(t){var e;t.done?o(t.value):((e=t.value)instanceof r?e:new r(function(t){t(e)})).then(s,a)}i((n=n.apply(t,e||[])).next())})},"__extends",0,function(t,r){if("function"!=typeof r&&null!==r)throw TypeError("Class extends value "+String(r)+" is not a constructor or null");function n(){this.constructor=t}e(t,r),t.prototype=null===r?Object.create(r):(n.prototype=r.prototype,new n)},"__generator",0,function(t,e){var r,n,o,c={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},s=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return s.next=a(0),s.throw=a(1),s.return=a(2),"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(a){return function(i){var l=[a,i];if(r)throw TypeError("Generator is already executing.");for(;s&&(s=0,l[0]&&(c=0)),c;)try{if(r=1,n&&(o=2&l[0]?n.return:l[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,l[1])).done)return o;switch(n=0,o&&(l=[2&l[0],o.value]),l[0]){case 0:case 1:o=l;break;case 4:return c.label++,{value:l[1],done:!1};case 5:c.label++,n=l[1],l=[0];continue;case 7:l=c.ops.pop(),c.trys.pop();continue;default:if(!(o=(o=c.trys).length>0&&o[o.length-1])&&(6===l[0]||2===l[0])){c=0;continue}if(3===l[0]&&(!o||l[1]>o[0]&&l[1]<o[3])){c.label=l[1];break}if(6===l[0]&&c.label<o[1]){c.label=o[1],o=l;break}if(o&&c.label<o[2]){c.label=o[2],c.ops.push(l);break}o[2]&&c.ops.pop(),c.trys.pop();continue}l=e.call(t,c)}catch(t){l=[6,t],n=0}finally{r=o=0}if(5&l[0])throw l[1];return{value:l[0]?l[1]:void 0,done:!0}}}},"__rest",0,function(t,e){var r={};for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&0>e.indexOf(n)&&(r[n]=t[n]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(t);o<n.length;o++)0>e.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(t,n[o])&&(r[n[o]]=t[n[o]]);return r},"__spreadArray",0,function(t,e,r){if(r||2==arguments.length)for(var n,o=0,c=e.length;o<c;o++)!n&&o in e||(n||(n=Array.prototype.slice.call(e,0,o)),n[o]=e[o]);return t.concat(n||Array.prototype.slice.call(e))}])},890793,t=>{t.v(e=>Promise.all(["static/chunks/14n5cp8rr-k_b.js","static/chunks/0~fvcxnmv6g_x.js","static/chunks/1674m~gp3w-8h.js","static/chunks/0~ikzdp3hga8n.js","static/chunks/0q0.5-57agpll.js","static/chunks/0q_xpywc4mr6r.js","static/chunks/0rldt2m_ic3fr.js","static/chunks/105.j2~dvdym3.js","static/chunks/0l4qtf8hgz.11.js"].map(e=>t.l(e))).then(()=>e(229414)))},410052,t=>{t.v(e=>Promise.all(["static/chunks/0pc3jws56z~cg.js","static/chunks/144e01xuvhx~x.js","static/chunks/0vp2gi~fq70je.js"].map(e=>t.l(e))).then(()=>e(116818)))},355350,t=>{t.v(e=>Promise.all(["static/chunks/0pc3jws56z~cg.js","static/chunks/09-jtayuhrq~b.js","static/chunks/144e01xuvhx~x.js"].map(e=>t.l(e))).then(()=>e(203010)))},432302,t=>{t.v(e=>Promise.all(["static/chunks/0pc3jws56z~cg.js","static/chunks/0qvlcjvhvpaey.js"].map(e=>t.l(e))).then(()=>e(924659)))}]);
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Agent Filesystem Plan — "Two tiers, one spine"
|
|
2
|
+
|
|
3
|
+
**Status:** Proposed (co-designed; ready for sign-off).
|
|
4
|
+
**Goal:** Let a remote AI agent work with files in a wiki-viewer instance "barely worse than local filesystem", for **all file types**, without disturbing the existing markdown collaboration feature.
|
|
5
|
+
**Principle:** Elegant, minimalistic, boring on purpose. The bytes API is strict and observable; the collab tier stays special. No CRDT pivot. No exec. No PATCH. No JSON-RPC.
|
|
6
|
+
|
|
7
|
+
> Supersedes the unbuilt `docs/agent-collab-v2-plan.md` Yjs/Hocuspocus/Postgres direction, which is treated as discarded. App today = `better-auth` + `better-sqlite3`, file-on-disk-is-truth.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. The model: two provenance tiers, one shared spine
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌────────────────────────────────────────────┐
|
|
15
|
+
│ SHARED SPINE │
|
|
16
|
+
│ discovery (/api/agents/install) │
|
|
17
|
+
│ TOFU register → approve → one-shot token │
|
|
18
|
+
│ Authorization: Bearer + X-Agent-Id │
|
|
19
|
+
│ enforceScope(path-glob, op) │
|
|
20
|
+
│ withFileMutex (in-proc + proper-lockfile) │
|
|
21
|
+
│ safeRootPath (traversal/symlink guard) │
|
|
22
|
+
│ event log + audit │
|
|
23
|
+
└───────────────┬───────────────┬────────────┘
|
|
24
|
+
│ │
|
|
25
|
+
┌──────────────────▼──┐ ┌─────▼───────────────────────┐
|
|
26
|
+
│ TIER 1 — RAW FS │ │ TIER 2 — COLLAB (existing) │
|
|
27
|
+
│ all file types │ │ markdown only │
|
|
28
|
+
│ read/write/ls/ │ │ block-ops + proof-spans │
|
|
29
|
+
│ move/delete/search │ │ comments / suggestions │
|
|
30
|
+
│ boring bytes │ │ reviewable prose, accept/ │
|
|
31
|
+
│ light audit │ │ revert provenance │
|
|
32
|
+
└─────────────────────┘ └─────────────────────────────┘
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- **Tier 1 (Raw FS, NEW):** bytes on disk, every file type, fast agent tooling. Light audit (event + sha). The "do filework" tier.
|
|
36
|
+
- **Tier 2 (Collab, EXISTING, UNCHANGED):** markdown review layer — block-ops, `<proof-span>` marks, comments, suggestions. The "reviewable prose" tier.
|
|
37
|
+
- **Do NOT** fold block-ops into the fs endpoint as a content-type. Collab is a review/provenance _workflow_, not file IO. Different invariants. Keep them separate; share the spine only.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 2. Tier 1 — Raw FS API (v1, minimal)
|
|
42
|
+
|
|
43
|
+
All routes reuse `checkAuth` + `enforceScope` + `withFileMutex` + `safeRootPath`. All reject paths under `.proof/`, the lock dir, and the app db/config. Scope is enforced on **every** surface (reads, listings, search results, move endpoints).
|
|
44
|
+
|
|
45
|
+
| Method | Route | Purpose |
|
|
46
|
+
| -------- | ----------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
47
|
+
| `GET` | `/api/agent/fs/file/<path>` | Read file. Supports `Range`. Returns raw bytes. |
|
|
48
|
+
| `PUT` | `/api/agent/fs/file/<path>` | Atomic whole-file write. `If-Match: <sha256>` optional (REQUIRED for `.md`). |
|
|
49
|
+
| `DELETE` | `/api/agent/fs/file/<path>` | Delete file (+ sidecar if `.md`). |
|
|
50
|
+
| `GET` | `/api/agent/fs/ls/<path>?recursive&limit&depth` | Directory listing. Scope-filtered. |
|
|
51
|
+
| `POST` | `/api/agent/fs/move` | `{from, to, ifMatch?}`. Moves sidecar for `.md`. |
|
|
52
|
+
| `POST` | `/api/agent/fs/search` | `{kind:"grep"\|"glob", query, path?, glob?, limit?}`. Server-side. |
|
|
53
|
+
|
|
54
|
+
**No PATCH. No exec. No batch. No separate grep/glob.** Add later only if profiling proves need.
|
|
55
|
+
|
|
56
|
+
### 2.1 Read (`GET .../file/<path>`)
|
|
57
|
+
|
|
58
|
+
- Returns **raw bytes**, not a JSON wrapper.
|
|
59
|
+
- Metadata in headers: `ETag: "<sha256>"`, `X-File-Size`, `X-File-Mtime`, `Content-Type`.
|
|
60
|
+
- Supports HTTP `Range` for big/binary files.
|
|
61
|
+
|
|
62
|
+
### 2.2 Write (`PUT .../file/<path>`)
|
|
63
|
+
|
|
64
|
+
- Atomic: write temp in **same dir** → `fsync` → `rename`. (`fsync` dir optional but documented.)
|
|
65
|
+
- Preserve mode of existing file. Do **not** create parent dirs unless `?mkdirs=true`.
|
|
66
|
+
- **Create = implicit.** `PUT` to a non-existent path creates the file (write-with-no-prior). Omit `If-Match` for a create; send `If-Match: <sha256>` only when overwriting. A create where the file already exists (no `If-Match`) → 412, so creates can't silently clobber.
|
|
67
|
+
- `If-Match: <sha256>` → 412 on mismatch. **Required by default for overwrites** (see R4); explicit `?force=true` bypasses and is audited.
|
|
68
|
+
- Response: `{path, sha256, size, mtime, created: bool}`.
|
|
69
|
+
|
|
70
|
+
### 2.3 Listing (`GET .../ls`)
|
|
71
|
+
|
|
72
|
+
- Hard limits: `max entries`, `max depth`, cursor or hard cap. Excludes `.proof/`; ignores `.git/` (configurable).
|
|
73
|
+
- Each entry: `{name, type, size, mtime}`. Unauthorized paths omitted (scope-filtered).
|
|
74
|
+
|
|
75
|
+
### 2.4 Search (`POST .../search`)
|
|
76
|
+
|
|
77
|
+
- `kind: "grep" | "glob"`. No shell interpolation (spawn ripgrep with arg array, or in-process).
|
|
78
|
+
- Limits: timeout, max matches, max bytes scanned, skip binary. Each matched path re-checked against scope.
|
|
79
|
+
- This is the **round-trip-explosion mitigation** — one call replaces dozens of `ls`+`read`.
|
|
80
|
+
|
|
81
|
+
### 2.5 Move (`POST .../move`)
|
|
82
|
+
|
|
83
|
+
- Checks: source `read`+`mutate`, dest `mutate`.
|
|
84
|
+
- For `.md`: moves the `.proof/<path>.json` sidecar too. Lock ordering: single operation lock, or lock source+dest in **sorted key order** to avoid deadlock.
|
|
85
|
+
|
|
86
|
+
### 2.6 Delete (`DELETE .../file/<path>`)
|
|
87
|
+
|
|
88
|
+
- Requires the `delete` scope op (see §3.5) and `If-Match: <sha256>` — the agent must have read the current file first, so it cannot blind-delete. This `If-Match` requirement **is** the confirmation for v1.
|
|
89
|
+
- `.md`: also deletes the sidecar (R3).
|
|
90
|
+
- Directory delete is opt-in and explicit: `?recursive=true`; without it, deleting a non-empty dir is refused. (No human approval queue in v1 — deferred.)
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 3. Tier 2 — Collab (unchanged)
|
|
95
|
+
|
|
96
|
+
Markdown block-ops (`block.replace/insertAfter/insertBefore/delete/append/prepend`), `comment.*`, `suggestion.*`, `<proof-span>` marks, `baseRevision` optimistic concurrency, mandatory `Idempotency-Key`, sidecar JSON in `.proof/`. **No changes.** This remains the reviewable-prose path.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 3.5 Working mode vs Collaborating mode (THE central distinction)
|
|
101
|
+
|
|
102
|
+
The whole design hinges on the agent knowing **when a file is being actively co-edited by a human** (use the reviewable Tier-2 path) versus **when it's just a file to work on** (use fast raw Tier-1). Today nothing tells the agent this; a wrong guess clobbers a human's pending suggestions. We make the mode **discoverable, documented, and enforced** — three layers so the agent cannot miss it.
|
|
103
|
+
|
|
104
|
+
### The signal: `X-Collab-State` header (on every read, both tiers)
|
|
105
|
+
|
|
106
|
+
Every `GET fs/file/<path>` and every Tier-2 snapshot read returns:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
X-Collab-State: active | tracked | untracked | not-markdown
|
|
110
|
+
X-Collab-Snapshot: /api/agent/files/<path>.md # present when state != not-markdown
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
| State | Meaning | Agent should… |
|
|
114
|
+
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
115
|
+
| `active` | `.md` that is **live**: either has review artifacts (pending suggestions / unresolved comments / unreverted proof-spans) **OR has a current human edit lease** (someone has the doc open in the editor). A human is collaborating right now. | **Use Tier-2 block-ops.** Your edits become reviewable suggestions. A raw `PUT` is rejected unless `If-Collab-Match` matches (see R6). |
|
|
116
|
+
| `tracked` | `.md` with a sidecar, no artifacts, no lease. | **Prefer Tier-2 for any semantic/prose edit** so provenance accrues. Raw only for mechanical/whole-file ops (e.g. reformat, regenerate). Not a free pass. |
|
|
117
|
+
| `untracked` | `.md`, no sidecar, no lease — nobody has collaborated. | Raw ok. A reviewable (Tier-2) edit creates a sidecar so future edits are tracked. |
|
|
118
|
+
| `not-markdown` | Any non-`.md` file. | **Tier-1 raw only** (Tier-2 doesn't apply). |
|
|
119
|
+
|
|
120
|
+
**Definition of `active` (two sources, OR'd):**
|
|
121
|
+
|
|
122
|
+
1. **Artifacts:** `pendingSuggestions > 0` OR `unresolvedComments > 0` OR `proofSpanCount > 0` (from sidecar — cheap, already in memory on read).
|
|
123
|
+
2. **Human edit lease:** a short-TTL presence marker (default 90s, heartbeat ~30s) set when a human opens the doc in the editor and refreshed while it stays open. **This closes the false-negative GPT-5.5 caught:** a human who opens a doc but hasn't typed a suggestion yet still reads `active`, so the agent won't blind-write into a live session. Lease lives in-memory (or the existing SQLite), keyed by `(path)`, set by a lightweight `POST /api/wiki/presence` ping the editor already can issue alongside its chokidar SSE connection. No lease + no artifacts → not active.
|
|
124
|
+
|
|
125
|
+
### Layer 2 — documented (manifest + skill)
|
|
126
|
+
|
|
127
|
+
The `/api/agents/install` manifest and the skill state the rule in one sentence:
|
|
128
|
+
|
|
129
|
+
> _"Before editing a `.md`, read it and check `X-Collab-State`. If `active`, use block-ops (Tier 2) so a human can review. Otherwise use raw fs (Tier 1). For non-markdown, always use raw fs."_
|
|
130
|
+
|
|
131
|
+
### Layer 3 — enforced (R4 + R6 backstop)
|
|
132
|
+
|
|
133
|
+
Even a confused or racing agent cannot silently clobber a live session: a raw `PUT` to an `active` `.md` is **rejected with 409 `COLLAB_ACTIVE`** unless it carries a matching `If-Collab-Match` (R6) — and the state is re-checked atomically inside the write mutex, so a session that goes active mid-flight still wins. Any genuinely orphaned suggestion/comment is marked `stale` by reconciliation (R2), never dropped. The 409 returns the Tier-2 snapshot URL, re-teaching the mode.
|
|
134
|
+
|
|
135
|
+
### What the human sees
|
|
136
|
+
|
|
137
|
+
- Agent works in Tier-2 on an `active` doc → normal reviewable suggestions (existing UX).
|
|
138
|
+
- Agent raw-writes an `active` doc anyway → `file.rawWritten by ai:<id>` in the AI Panel feed + any unbindable anchors flagged `stale` (not vanished).
|
|
139
|
+
- Agent works on `untracked`/non-md → silent, fast, audited. No collab noise.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 4. Integration rules (load-bearing)
|
|
144
|
+
|
|
145
|
+
- **R1 — Shared lock.** A raw write/move/delete on a `.md` acquires the **same** `withFileMutex` key the ops-applier uses. No interleaving with block-ops mid-proof-span.
|
|
146
|
+
- **R2 — No fake provenance.** Raw writes never author proof-spans. After a raw write to a tracked `.md`, emit `file.rawWritten` (writer known) and **reconcile the sidecar synchronously, inside the same mutex** (rebuild `refMap`, set fingerprint to `newSha`, mark affected proof refs stale). Raw edits are unmarked by definition; human sees _who_ via the event, not as an accept/revert suggestion.
|
|
147
|
+
- **GOTCHA (do not regress):** do NOT just "bump fingerprint to newSha and let existing reconciliation fire later." The existing reconciliation triggers on `sidecar.fingerprint != file sha256`; if the raw write sets fingerprint current, the mismatch is gone and rebuild never runs. Because the writer is known, reconcile **eagerly within the write**, not lazily on next read.
|
|
148
|
+
- **R3 — Sidecar lifecycle.** Raw `mv`/`rm` of a `.md` moves/deletes its sidecar. **NOTE:** the human `wiki/move` route currently does a bare `rename` and does NOT move the sidecar — a latent bug that orphans `.proof/` JSON today. Build a shared `moveSidecar`/`deleteSidecar` helper in `sidecar.ts` and wire **both** the raw-fs path and `wiki/move` to it (fixes the existing bug for free).
|
|
149
|
+
- **R4 — Write guard (If-Match by default).** **All** mutating raw ops (`PUT`/`DELETE`/`move`) REQUIRE `If-Match: <sha256>` by default; 412 on mismatch. An explicit `?force=true` bypasses it and is recorded in the audit row. Rationale: agents edit code/config just as much as `.md`; optional concurrency = silent lost updates, and a format-based safety boundary is arbitrary. Cheap because the agent already has the sha from `GET`.
|
|
150
|
+
- **R5 — Scope unification + `delete` op.** Registration `scope.paths` glob already covers **directories natively** (e.g. `notes/**` grants the whole subtree — no file lists). Raw-fs honors the same glob, on every surface (read/ls/search/move/delete results all re-checked). **One addition:** split `delete` out of `mutate`, so `scope.ops` becomes `[read, mutate, delete]`. This lets a human grant "edit but never delete." `mutate` = create/overwrite/move; `delete` = remove. Back-compat: existing `[read, mutate]` agents simply can't delete until re-scoped. Glob dialect (v1): `**`, `*`, `?` only — no braces/negation; advertise this in the manifest.
|
|
151
|
+
- **R6 — Collab-state precondition on raw `.md` writes (closes the TOCTOU race).** `If-Match` protects _bytes_, not _collaboration intent_: a human could create a sidecar/lease after the agent's read but before its raw `PUT`, leaving bytes unchanged so `If-Match` still passes. To prevent silently writing into a session that went `active` mid-flight:
|
|
152
|
+
- Every `.md` read also returns `X-Collab-Revision: <n>` (bumped on any sidecar/lease state change).
|
|
153
|
+
- A raw `PUT` to a `.md` must, **inside the same mutex, re-read collab state immediately before writing**: if the doc is now `active` and the request did not supply a matching `If-Collab-Match: <n>`, **reject with 409 `COLLAB_ACTIVE`** + the Tier-2 snapshot URL. The agent then switches to block-ops. `?force=true` still bypasses (audited) for deliberate overrides.
|
|
154
|
+
- This makes the mode check atomic with the write, not advisory.
|
|
155
|
+
|
|
156
|
+
### Event taxonomy (don't overload)
|
|
157
|
+
|
|
158
|
+
- `file.rawWritten` — writer **known** (`by: ai:<id>`, `oldSha`, `newSha`, `path`, `at`). Emitted by raw-fs mutations.
|
|
159
|
+
- `file.externallyEdited` — writer **unknown** (chokidar watcher, vim, git). Existing behavior, untouched.
|
|
160
|
+
|
|
161
|
+
### Audit
|
|
162
|
+
|
|
163
|
+
- `.md`: append event to sidecar (already have the machinery).
|
|
164
|
+
- All other files: durable audit row (reuse SQLite). Non-negotiable — without it an agent could silently rewrite a PDF.
|
|
165
|
+
|
|
166
|
+
### Collab-anchor safety (raw `.md` overwrite)
|
|
167
|
+
|
|
168
|
+
A full-file `PUT` to a `.md` can strip `<proof-span>` marks and shift block boundaries, orphaning pending comments/suggestions. **Invariant:** after any raw write to a `.md`, the synchronous reconciliation (R2) MUST re-bind or mark-stale every affected proof ref **before** the human UI reads the snapshot. A suggestion/comment whose anchor no longer resolves is flagged `stale`, not silently dropped. This is the explicit guard that keeps Tier 1 from corrupting Tier 2.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 5. Discovery
|
|
173
|
+
|
|
174
|
+
Extend `/api/agents/install` manifest:
|
|
175
|
+
|
|
176
|
+
- Advertise the new `fs/*` routes.
|
|
177
|
+
- Add a `capabilities` block: `{ maxFileBytes, supportsRange, ifMatchRequired:true, forceBypass:true, search:["grep","glob"], globDialect:"**,*,?", scopeOps:["read","mutate","delete"], collabStates:["active","tracked","untracked","not-markdown"], collabPrecondition:"If-Collab-Match" }`.
|
|
178
|
+
- State the **working-vs-collaborating rule** (§3.5) in prose so the agent self-configures its tier choice.
|
|
179
|
+
- Advertise the optional MCP adapter package + version.
|
|
180
|
+
One manifest; agent self-configures. HTTP routes are the canonical contract.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 6. Client story
|
|
185
|
+
|
|
186
|
+
- **Canonical = the HTTP API.** Easy to curl, test, version. Independent of MCP transport churn.
|
|
187
|
+
- **`npx wiki-viewer-mcp`** = thin MCP adapter mapping standard MCP filesystem tools onto these endpoints:
|
|
188
|
+
- `read_file` → `GET fs/file`
|
|
189
|
+
- `write_file` → `PUT fs/file` (with `If-Match` from a prior read)
|
|
190
|
+
- `edit_file` (str-replace) → **client-side**: read → transform → `PUT If-Match`. (Server stays dumb; edit footguns live in the adapter.)
|
|
191
|
+
- `list_directory` → `GET fs/ls`
|
|
192
|
+
- `search` → `POST fs/search`
|
|
193
|
+
- `delete_file` → `DELETE fs/file` (requires `delete` scope + `If-Match`)
|
|
194
|
+
- The shim reads `X-Collab-State` and, when `active`, **warns or routes the edit through the Tier-2 block-op tools** instead of a blind raw write — so even off-the-shelf MCP clients respect collaborating mode.
|
|
195
|
+
- Do **not** make the core app MCP-native first. The app already has HTTP auth/discovery; the shim adapts to Claude Code / Cursor / Codex without poisoning the core.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 7. Path identity (must specify before build)
|
|
200
|
+
|
|
201
|
+
- Stable **relative** paths only; reject traversal and symlinks escaping root.
|
|
202
|
+
- Decide + document symlink listing behavior.
|
|
203
|
+
- Hard-deny: `.proof/`, `~/.wiki-viewer/.locks/`, the app SQLite db + config.
|
|
204
|
+
- Unicode normalization policy stated explicitly.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 8. Non-goals (explicit)
|
|
209
|
+
|
|
210
|
+
- **No remote compute / exec.** wiki-viewer is a remote **filesystem + review surface**, not a sandbox. Real exec needs quotas, secrets/env/cwd policy, process lifecycle, cancellation, logs — a different product. Server-side `search` covers the ~80% case (find code) without it. Future: a separate optional "workspace daemon" with its own scope + manifest capability.
|
|
211
|
+
- **No CRDT / Yjs / Hocuspocus.** File-on-disk stays truth.
|
|
212
|
+
- **No PATCH, no JSON-RPC, no batch endpoint** in v1.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## 9. Build order
|
|
217
|
+
|
|
218
|
+
1. Spine confirm: nothing new — reuse auth/scope/lock/safePath.
|
|
219
|
+
2. `GET/PUT/DELETE fs/file` + atomic write + `If-Match` + headers/ETag/Range.
|
|
220
|
+
3. Audit row + `file.rawWritten` event; wire R1/R2 for `.md`.
|
|
221
|
+
4. `fs/ls` (limits, scope filter) + `fs/move` (sidecar move, R3).
|
|
222
|
+
5. `fs/search` (grep/glob, limits).
|
|
223
|
+
6. Extend `/api/agents/install` manifest + capabilities.
|
|
224
|
+
7. `wiki-viewer-mcp` adapter (read/write/edit/list/search).
|
|
225
|
+
8. **Mode plumbing:** human edit-lease (`POST /api/wiki/presence` + TTL, editor heartbeat), `X-Collab-State` + `X-Collab-Revision` headers on both read paths, R6 atomic re-check + 409 `COLLAB_ACTIVE` on raw `.md` writes. Plus `delete` scope op + create/`?mkdirs`/`?recursive` semantics.
|
|
226
|
+
9. Tests: traversal, symlink escape, If-Match 412, create-collision 412, sidecar move (incl. `wiki/move` bug fix), scope filtering on ls/search, big-file Range, binary skip in grep, **`X-Collab-State` matrix incl. lease-only `active`**, **R6 TOCTOU: doc goes active between read and raw PUT → 409**, stale-anchor after raw `.md` overwrite, `delete` op gating, lease expiry flips active→tracked.
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```
|
|
@@ -1,231 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import SignInForm from "./signin-form";
|
|
2
|
+
import { passwordAuthEnabled } from "@/lib/auth/server";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
// Server component: resolve the available auth methods at render time so the
|
|
5
|
+
// initial HTML already reflects the configuration. This prevents the password
|
|
6
|
+
// form from flashing on load when it is disabled (AUTH_DISABLE_PASSWORD=1).
|
|
7
|
+
export const dynamic = "force-dynamic";
|
|
5
8
|
|
|
6
9
|
export default function SignInPage() {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const [name, setName] = useState("");
|
|
11
|
-
const [error, setError] = useState<string | null>(null);
|
|
12
|
-
const [loading, setLoading] = useState(false);
|
|
13
|
-
const [hasGoogle, setHasGoogle] = useState(false);
|
|
14
|
-
const [passwordAuth, setPasswordAuth] = useState(true);
|
|
15
|
-
const [callbackURL, setCallbackURL] = useState("/");
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
// Learn which auth methods to show. Public endpoint, no session needed.
|
|
19
|
-
fetch("/api/system/auth-config", { credentials: "include" })
|
|
20
|
-
.then((r) => r.json())
|
|
21
|
-
.then((data: unknown) => {
|
|
22
|
-
if (data && typeof data === "object") {
|
|
23
|
-
const cfg = data as { google?: boolean; passwordAuth?: boolean };
|
|
24
|
-
setHasGoogle(Boolean(cfg.google));
|
|
25
|
-
// Default to showing the password form unless told otherwise, so a
|
|
26
|
-
// failed fetch never leaves the user with no way to sign in.
|
|
27
|
-
setPasswordAuth(cfg.passwordAuth !== false);
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
.catch(() => {});
|
|
31
|
-
|
|
32
|
-
// Honour ?next= redirect param
|
|
33
|
-
const params = new URLSearchParams(window.location.search);
|
|
34
|
-
const next = params.get("next");
|
|
35
|
-
if (next && next.startsWith("/")) setCallbackURL(next);
|
|
36
|
-
}, []);
|
|
37
|
-
|
|
38
|
-
async function handleSubmit(e: FormEvent) {
|
|
39
|
-
e.preventDefault();
|
|
40
|
-
setError(null);
|
|
41
|
-
setLoading(true);
|
|
42
|
-
try {
|
|
43
|
-
if (mode === "signup") {
|
|
44
|
-
const res = await authClient.signUp.email({
|
|
45
|
-
email,
|
|
46
|
-
password,
|
|
47
|
-
name,
|
|
48
|
-
callbackURL,
|
|
49
|
-
});
|
|
50
|
-
if (res.error) {
|
|
51
|
-
setError(res.error.message ?? "Sign-up failed");
|
|
52
|
-
} else {
|
|
53
|
-
window.location.href = callbackURL;
|
|
54
|
-
}
|
|
55
|
-
} else {
|
|
56
|
-
const res = await authClient.signIn.email({
|
|
57
|
-
email,
|
|
58
|
-
password,
|
|
59
|
-
callbackURL,
|
|
60
|
-
});
|
|
61
|
-
if (res.error) {
|
|
62
|
-
const status = res.error.status;
|
|
63
|
-
if (status === 429) {
|
|
64
|
-
setError("Too many sign-in attempts. Wait a minute and try again.");
|
|
65
|
-
} else {
|
|
66
|
-
setError(res.error.message ?? "Sign-in failed");
|
|
67
|
-
}
|
|
68
|
-
} else {
|
|
69
|
-
window.location.href = callbackURL;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
} catch (err: unknown) {
|
|
73
|
-
setError(err instanceof Error ? err.message : "Unexpected error");
|
|
74
|
-
} finally {
|
|
75
|
-
setLoading(false);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function handleGoogle() {
|
|
80
|
-
setError(null);
|
|
81
|
-
setLoading(true);
|
|
82
|
-
try {
|
|
83
|
-
await authClient.signIn.social({ provider: "google", callbackURL });
|
|
84
|
-
} catch (err: unknown) {
|
|
85
|
-
setError(err instanceof Error ? err.message : "Google sign-in failed");
|
|
86
|
-
setLoading(false);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
10
|
+
const google = Boolean(
|
|
11
|
+
process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET,
|
|
12
|
+
);
|
|
90
13
|
return (
|
|
91
|
-
<
|
|
92
|
-
<div className="w-full max-w-sm rounded-lg border border-border bg-card p-8 shadow-sm">
|
|
93
|
-
<h1 className="mb-6 text-xl font-semibold text-foreground">Sign in to wiki-viewer</h1>
|
|
94
|
-
|
|
95
|
-
{hasGoogle && (
|
|
96
|
-
<>
|
|
97
|
-
<button
|
|
98
|
-
type="button"
|
|
99
|
-
onClick={handleGoogle}
|
|
100
|
-
disabled={loading}
|
|
101
|
-
className="flex w-full items-center justify-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium hover:bg-accent disabled:opacity-50"
|
|
102
|
-
>
|
|
103
|
-
<svg viewBox="0 0 24 24" className="h-4 w-4" aria-hidden="true">
|
|
104
|
-
<path
|
|
105
|
-
fill="#4285F4"
|
|
106
|
-
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
|
107
|
-
/>
|
|
108
|
-
<path
|
|
109
|
-
fill="#34A853"
|
|
110
|
-
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
|
111
|
-
/>
|
|
112
|
-
<path
|
|
113
|
-
fill="#FBBC05"
|
|
114
|
-
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z"
|
|
115
|
-
/>
|
|
116
|
-
<path
|
|
117
|
-
fill="#EA4335"
|
|
118
|
-
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
|
119
|
-
/>
|
|
120
|
-
</svg>
|
|
121
|
-
Continue with Google
|
|
122
|
-
</button>
|
|
123
|
-
{passwordAuth && (
|
|
124
|
-
<div className="my-4 flex items-center gap-3">
|
|
125
|
-
<hr className="flex-1 border-border" />
|
|
126
|
-
<span className="text-xs text-muted-foreground">or</span>
|
|
127
|
-
<hr className="flex-1 border-border" />
|
|
128
|
-
</div>
|
|
129
|
-
)}
|
|
130
|
-
</>
|
|
131
|
-
)}
|
|
132
|
-
|
|
133
|
-
{!passwordAuth && !hasGoogle && (
|
|
134
|
-
<p className="rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
|
135
|
-
No sign-in method is configured. Set GOOGLE_CLIENT_ID and
|
|
136
|
-
GOOGLE_CLIENT_SECRET, or enable email/password auth.
|
|
137
|
-
</p>
|
|
138
|
-
)}
|
|
139
|
-
|
|
140
|
-
{passwordAuth && (
|
|
141
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
142
|
-
{mode === "signup" && (
|
|
143
|
-
<div>
|
|
144
|
-
<label className="mb-1 block text-sm font-medium text-foreground" htmlFor="name">
|
|
145
|
-
Name
|
|
146
|
-
</label>
|
|
147
|
-
<input
|
|
148
|
-
id="name"
|
|
149
|
-
type="text"
|
|
150
|
-
autoComplete="name"
|
|
151
|
-
value={name}
|
|
152
|
-
onChange={(e) => setName(e.target.value)}
|
|
153
|
-
required
|
|
154
|
-
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
155
|
-
/>
|
|
156
|
-
</div>
|
|
157
|
-
)}
|
|
158
|
-
<div>
|
|
159
|
-
<label className="mb-1 block text-sm font-medium text-foreground" htmlFor="email">
|
|
160
|
-
Email
|
|
161
|
-
</label>
|
|
162
|
-
<input
|
|
163
|
-
id="email"
|
|
164
|
-
type="email"
|
|
165
|
-
autoComplete="email"
|
|
166
|
-
value={email}
|
|
167
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
168
|
-
required
|
|
169
|
-
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
170
|
-
/>
|
|
171
|
-
</div>
|
|
172
|
-
<div>
|
|
173
|
-
<label
|
|
174
|
-
className="mb-1 block text-sm font-medium text-foreground"
|
|
175
|
-
htmlFor="password"
|
|
176
|
-
>
|
|
177
|
-
Password
|
|
178
|
-
</label>
|
|
179
|
-
<input
|
|
180
|
-
id="password"
|
|
181
|
-
type="password"
|
|
182
|
-
autoComplete={mode === "signup" ? "new-password" : "current-password"}
|
|
183
|
-
value={password}
|
|
184
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
185
|
-
required
|
|
186
|
-
minLength={8}
|
|
187
|
-
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
188
|
-
/>
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
{error && (
|
|
192
|
-
<p className="rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
|
193
|
-
{error}
|
|
194
|
-
</p>
|
|
195
|
-
)}
|
|
196
|
-
|
|
197
|
-
<button
|
|
198
|
-
type="submit"
|
|
199
|
-
disabled={loading}
|
|
200
|
-
className="w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
|
201
|
-
>
|
|
202
|
-
{loading
|
|
203
|
-
? mode === "signup"
|
|
204
|
-
? "Creating account..."
|
|
205
|
-
: "Signing in..."
|
|
206
|
-
: mode === "signup"
|
|
207
|
-
? "Create account"
|
|
208
|
-
: "Sign in"}
|
|
209
|
-
</button>
|
|
210
|
-
</form>
|
|
211
|
-
)}
|
|
212
|
-
|
|
213
|
-
{passwordAuth && (
|
|
214
|
-
<p className="mt-4 text-center text-sm text-muted-foreground">
|
|
215
|
-
{mode === "signin" ? "No account?" : "Already have an account?"}{" "}
|
|
216
|
-
<button
|
|
217
|
-
type="button"
|
|
218
|
-
onClick={() => {
|
|
219
|
-
setMode(mode === "signin" ? "signup" : "signin");
|
|
220
|
-
setError(null);
|
|
221
|
-
}}
|
|
222
|
-
className="font-medium text-foreground underline-offset-4 hover:underline"
|
|
223
|
-
>
|
|
224
|
-
{mode === "signin" ? "Sign up" : "Sign in"}
|
|
225
|
-
</button>
|
|
226
|
-
</p>
|
|
227
|
-
)}
|
|
228
|
-
</div>
|
|
229
|
-
</div>
|
|
14
|
+
<SignInForm initialPasswordAuth={passwordAuthEnabled} initialGoogle={google} />
|
|
230
15
|
);
|
|
231
16
|
}
|