@seshat.sh/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -71,3 +71,5 @@ All commands show readable results and errors by default. Pass `--json` for stru
71
71
  `seshat mcp` starts the existing stdio MCP client and reuses the stored profile. Configure `SESHAT_UPLOAD_ROOTS` as a JSON array of allowed directories to enable local file uploads. Without it, inline uploads work and file access is disabled. A hosted MCP endpoint is not provided yet.
72
72
 
73
73
  This package contains only the service client. Server implementation and deployment sources are not distributed with it.
74
+
75
+ New terminal and agent connections report their device category, OS, and model when available. Seshat shows these details with the last service-observed IP in Agent access. MAC addresses and hardware serial numbers are not sent. Older connections retain a generic terminal icon until reconnected with this client.
@@ -0,0 +1,3 @@
1
+ import{z as r}from"zod";import{z as te}from"zod";var P=te.enum(["never","hour","day","week","month","year","custom"]);var b=10*1024*1024,z=["artifacts:read","artifacts:write","artifacts:delete","tokens:manage"],v=r.enum(z),l=r.string().regex(/^[a-f0-9]{48}$/),R=r.string().min(8).max(128).regex(/^[A-Za-z0-9_-]+$/),u=r.string().regex(/^seshat_[A-Za-z0-9_-]{43}$/),F=r.iso.datetime().transform(e=>new Date(e).toISOString()),h=r.object({title:r.string().trim().min(1).max(200).optional(),description:r.string().max(2e3).default(""),expiresAt:F.nullable().optional(),expirationPreset:P.nullable().optional(),password:r.string().min(12).max(128).nullable().optional(),generateMetadata:r.boolean().optional()}).strict().refine(e=>e.expirationPreset==null||(e.expirationPreset==="never"?e.expiresAt===null:typeof e.expiresAt=="string"),"Expiration preset must match an explicit expiration value"),N=r.object({filename:r.string().min(1).max(200).regex(/^[^/\\\x00-\x1f\x7f]+$/),contentType:r.string().max(150).regex(/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/i),size:r.number().int().min(1).max(b)}).strict(),_=r.object({title:h.shape.title.optional(),description:r.string().max(2e3).optional(),expiresAt:h.shape.expiresAt,expirationPreset:h.shape.expirationPreset,password:h.shape.password}).strict().refine(e=>e.expirationPreset==null||(e.expirationPreset==="never"?e.expiresAt===null:typeof e.expiresAt=="string"),"Expiration preset must match an explicit expiration value").refine(e=>Object.values(e).some(t=>t!==void 0),"Provide at least one metadata field"),$=r.object({slug:r.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).min(2).max(63),name:r.string().trim().min(1).max(100)}).strict(),f=r.object({name:r.string().trim().min(1).max(100),scopes:r.array(v).min(1).max(z.length),expiresAt:F.optional()}).strict(),g=r.object({id:l,title:r.string(),description:r.string(),filename:r.string(),contentType:r.string(),size:r.number(),createdAt:r.string(),expiresAt:r.string().nullable(),expirationPreset:P.nullable().default(null),url:r.url(),downloadUrl:r.url(),passwordProtected:r.boolean().default(!1),views:r.object({owner:r.number(),others:r.number()})}),q=r.object({artifacts:r.array(g),nextCursor:r.string().nullable()}),E=r.object({id:l,token:u,scopes:r.array(v),expiresAt:r.string().nullable()}),O=r.object({space:r.object({id:l,slug:r.string(),name:r.string()}),credential:E}),M=r.object({spaceId:l,tokenId:l,scopes:f.shape.scopes,space:O.shape.space.optional()}),H=r.object({error:r.object({code:r.string(),message:r.string()})});import{z as m}from"zod";import{z as o}from"zod";var B="https://app.seshat.sh",S=o.url().refine(e=>{let t=new URL(e);return!t.username&&!t.password&&!t.search&&!t.hash&&t.pathname==="/"&&(t.protocol==="https:"||t.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(t.hostname))},"Use an HTTPS origin, or HTTP localhost for development").transform(e=>new URL(e).origin),k=o.string().min(1).max(64).regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/),je=o.string().max(16).transform(e=>e.toUpperCase().replaceAll("-","").trim()).pipe(o.string().regex(/^[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{8}$/)),j=o.string().trim().min(1).max(80).regex(/^[^\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]+$/),re=o.strictObject({city:j.optional(),region:j.optional(),country:o.string().regex(/^[A-Z]{2}$/).optional()}).refine(e=>!!(e.city||e.region||e.country),"Location is unavailable"),C=o.strictObject({kind:o.enum(["laptop","desktop","phone","tablet","terminal"]),os:o.enum(["macos","windows","linux","android","ios","other"]),model:j.optional(),client:o.enum(["terminal","agent"])});var Ce=f.extend({tokenHash:o.string().regex(/^[a-f0-9]{64}$/),device:C.optional()}).strict().refine(e=>!e.expiresAt||Date.parse(e.expiresAt)>Date.now()&&Date.parse(e.expiresAt)<=Date.now()+366*864e5,"Choose a credential expiry within the next year"),Ie=o.strictObject({deviceCode:l}),Ue=o.strictObject({approve:o.boolean()}),T=o.object({id:l,slug:o.string(),name:o.string()}),De=o.object({deviceCode:l,userCode:o.string().regex(/^[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{4}-[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{4}$/),verificationUrl:o.url(),expiresAt:o.iso.datetime(),interval:o.number().int().min(1).max(60)}),Le=o.discriminatedUnion("status",[o.object({status:o.literal("pending"),interval:o.number().int().min(1).max(60)}),o.object({status:o.literal("denied")}),o.object({status:o.literal("approved"),id:l,space:T,scopes:f.shape.scopes,expiresAt:o.iso.datetime()})]),ze=o.object({device:C.nullable().default(null),userCode:o.string(),name:o.string(),scopes:f.shape.scopes,status:o.enum(["pending","approved","denied"]),expiresAt:o.iso.datetime(),tokenExpiresAt:o.iso.datetime(),space:T});var Re=o.object({devices:o.array(o.object({id:l,name:o.string(),device:C.nullable().default(null),scopes:f.shape.scopes,createdAt:o.iso.datetime(),completedAt:o.iso.datetime().nullable(),lastUsedAt:o.iso.datetime().nullable(),lastIp:o.union([o.ipv4(),o.ipv6()]).nullable().default(null),lastLocation:re.nullable().default(null),expiresAt:o.iso.datetime().nullable()}))});var ne=m.object({endpoint:S,token:u}).strict();async function oe({form:e,onProgress:t}){let n=new Response(e),i=n.headers.get("content-type");if(!i)throw new Error("Could not encode the upload");let a=new Uint8Array(await n.arrayBuffer()),s=0;return t({loadedBytes:0,totalBytes:a.byteLength}),{body:new ReadableStream({pull(p){let w=Math.min(s+65536,a.byteLength);p.enqueue(a.subarray(s,w)),s=w,t({loadedBytes:s,totalBytes:a.byteLength}),s===a.byteLength&&p.close()}},{highWaterMark:0}),headers:{"content-type":i,"content-length":String(a.byteLength)},duplex:"half"}}var A=class extends Error{constructor(n,i,a){super(i);this.code=n;this.status=a}code;status},x=class{settings;constructor(t){this.settings=ne.parse(t)}async request(t,n,i={}){let a=new Headers(i.headers);a.set("authorization",`Bearer ${this.settings.token}`),typeof i.body=="string"&&a.set("content-type","application/json");let s;try{s=await fetch(new URL(t,this.settings.endpoint),{...i,headers:a,redirect:"error",signal:i.signal?AbortSignal.any([i.signal,AbortSignal.timeout(6e4)]):AbortSignal.timeout(6e4)})}catch{throw new A("connection_failed","Request failed or timed out. Check SESHAT_ENDPOINT; retry uploads with the same --request-id")}let c=await s.json();if(!s.ok){let p=H.safeParse(c);throw new A(p.success?p.data.error.code:"request_failed",p.success?p.data.error.message:`HTTP ${s.status}`,s.status)}return n.parse(c)}async upload(t){let n=N.parse({filename:t.filename,contentType:t.contentType,size:t.bytes.byteLength}),i=h.parse(t.metadata),a=R.parse(t.requestId),s=new FormData;if(s.set("metadata",JSON.stringify(i)),s.set("file",new Blob([new Uint8Array(t.bytes)],{type:n.contentType}),n.filename),t.onProgress){let c=await oe({form:s,onProgress:t.onProgress}),p={...c,method:"POST",headers:{...c.headers,"idempotency-key":a}};return this.request("/v1/artifacts",g,p)}return this.request("/v1/artifacts",g,{method:"POST",body:s,headers:{"idempotency-key":a}})}list(t={}){let n=new URLSearchParams;return t.limit!==void 0&&n.set("limit",String(t.limit)),t.cursor!==void 0&&n.set("cursor",t.cursor),this.request(`/v1/artifacts?${n}`,q)}get(t){return this.request(`/v1/artifacts/${l.parse(t)}`,g)}update(t,n){return this.request(`/v1/artifacts/${l.parse(t)}`,g,{method:"PATCH",body:JSON.stringify(_.parse(n))})}delete(t){return this.request(`/v1/artifacts/${l.parse(t)}`,m.object({deleted:m.literal(!0),id:l}),{method:"DELETE"})}whoami(t={}){return this.request("/v1/me",M,{signal:t.signal})}completeConnection(){return this.request("/v1/auth/complete",m.object({completed:m.literal(!0)}),{method:"POST"})}revokeSelf(){return this.request("/v1/auth/logout",m.object({revoked:m.literal(!0),id:l}),{method:"POST"})}createSpace(t){return this.request("/v1/admin/spaces",O,{method:"POST",body:JSON.stringify($.parse(t))})}createToken(t){return this.request("/v1/tokens",E,{method:"POST",body:JSON.stringify(f.parse(t))})}revokeToken(t){return this.request(`/v1/tokens/${l.parse(t)}`,m.object({revoked:m.literal(!0),id:l}),{method:"DELETE"})}};import{constants as W}from"node:fs";import{lstat as ie,mkdir as ae,open as I,rename as se,unlink as J}from"node:fs/promises";import{homedir as ce}from"node:os";import{join as y,resolve as le}from"node:path";import{randomUUID as Y}from"node:crypto";import{setTimeout as pe}from"node:timers/promises";import{z as d}from"zod";var Qe=d.enum(["keychain","file"]),Z=d.strictObject({endpoint:S,tokenId:l,space:T,scopes:d.array(v),expiresAt:d.iso.datetime(),pendingConnection:d.literal(!0).optional(),credential:d.discriminatedUnion("kind",[d.strictObject({kind:d.literal("keychain"),account:d.string().min(1)}),d.strictObject({kind:d.literal("file"),token:u})])}),de=d.strictObject({version:d.literal(1),profiles:d.record(k,Z)});function X(){return le(process.env.SESHAT_CONFIG_DIR??y(process.env.XDG_CONFIG_HOME??y(ce(),".config"),"seshat"))}function G(e){return e instanceof Error&&"code"in e&&e.code==="ENOENT"}async function K(e,t=!1){let n=await ie(e);if(n.isSymbolicLink()||(t?!n.isDirectory():!n.isFile()))throw new Error("Seshat credential paths must be real files and directories, not symlinks");if(process.platform!=="win32"&&((n.mode&63)!==0||process.getuid&&n.uid!==process.getuid()))throw new Error(`Credential path must belong to you and have ${t?"0700":"0600"} permissions: ${e}`)}async function Q(){let e=X();try{await K(e,!0);let t=y(e,"profiles.json"),n=await I(t,W.O_RDONLY|(W.O_NOFOLLOW??0));try{let i=await n.stat();if(!i.isFile()||i.size>65536||process.platform!=="win32"&&((i.mode&63)!==0||process.getuid&&i.uid!==process.getuid()))throw new Error("Credential file must be a small, owner-only regular file");try{let a=de.safeParse(JSON.parse(await n.readFile("utf8")));if(a.success)return a.data}catch{}throw new Error("Invalid Seshat profile file. Run seshat login again")}finally{await n.close()}}catch(t){if(G(t))return{version:1,profiles:{}};throw t}}async function D(e,t){let n=X();await ae(n,{recursive:!0,mode:448}),await K(n,!0);let i=y(n,"profiles.lock"),a;for(let c=0;c<40;c++)try{a=await I(i,"wx",384);break}catch(p){if(!(p instanceof Error&&"code"in p&&p.code==="EEXIST"))throw p;await pe(50)}if(!a)throw new Error("Another Seshat process is updating profiles. Retry shortly; if it stopped unexpectedly, remove profiles.lock from your Seshat config directory");let s=y(n,`profiles-${Y()}.tmp`);try{t?.throwIfAborted();let c=e(await Q()),p=await I(s,"wx",384);try{let w=JSON.stringify(c)+`
2
+ `;if(Buffer.byteLength(w)>65536)throw new Error("Too many stored profiles; remove unused connections before adding another");await p.writeFile(w),await p.sync()}finally{await p.close()}t?.throwIfAborted(),await se(s,y(n,"profiles.json"))}finally{try{await J(s).catch(c=>{if(!G(c))throw c})}finally{await a.close(),await J(i)}}}async function L(e){let{AsyncEntry:t}=await import("@napi-rs/keyring");return new t("dev.djamil.seshat.cli",e,{linux:{store:"secret-service"}})}async function U(e){e.credential.kind==="keychain"&&await(await L(e.credential.account)).deletePassword(AbortSignal.timeout(15e3))}async function Ve(e){let t=k.parse(e.name),n=u.parse(e.token);if(e.storage==="file"&&process.platform==="win32")throw new Error("Use keychain storage with Windows Credential Manager");let i=Y();if(e.storage==="keychain")try{await(await L(i)).setPassword(n,e.signal?AbortSignal.any([e.signal,AbortSignal.timeout(15e3)]):AbortSignal.timeout(15e3))}catch{throw e.signal?.aborted?new Error("Connection cancelled"):new Error("The OS credential vault is unavailable. On a headless POSIX machine, rerun login with --storage file to use an owner-only credential file")}let a=Z.parse({pendingConnection:e.pendingConnection,endpoint:e.endpoint,tokenId:e.tokenId,space:e.space,scopes:e.scopes,expiresAt:e.expiresAt,credential:e.storage==="keychain"?{kind:"keychain",account:i}:{kind:"file",token:n}}),s;try{await D(c=>{if(s=Object.hasOwn(c.profiles,t)?c.profiles[t]:void 0,(s?.tokenId??null)!==e.expectedTokenId)throw new Error("This profile changed during login. Retry; the newer connection has been preserved");return{...c,profiles:{...c.profiles,[t]:a}}},e.signal)}catch(c){throw await U(a).catch(()=>{}),c}return s&&await U(s).catch(()=>{process.stderr.write(`The previous OS credential entry could not be removed. Revoke its token in Seshat Agent access.
3
+ `)}),a}function me(e){return k.parse(e??process.env.SESHAT_PROFILE??"default")}async function V(e){let t=await Q();return Object.hasOwn(t.profiles,e)?t.profiles[e]:void 0}async function fe(e){if(e.credential.kind==="file")return e.credential.token;try{return u.parse(await(await L(e.credential.account)).getPassword(AbortSignal.timeout(15e3)))}catch{throw new Error("Stored credential is unavailable. Run seshat login again for this profile")}}async function et(e,t){let n=await V(e);if(n){if(n.tokenId!==t)throw new Error("This profile changed during logout. The newer connection has been preserved");await U(n),await D(i=>{let a=Object.hasOwn(i.profiles,e)?i.profiles[e]:void 0;if(a&&a.tokenId!==t)throw new Error("This profile changed during logout. The newer connection has been preserved");let s={...i.profiles};return delete s[e],{...i,profiles:s}})}}async function ue(e,t,n){t.pendingConnection&&(await n.completeConnection(),await D(i=>{let a=Object.hasOwn(i.profiles,e)?i.profiles[e]:void 0;if(a?.tokenId!==t.tokenId)throw new Error("This profile changed during connection. The newer connection has been preserved");let{pendingConnection:s,...c}=a;return{...i,profiles:{...i.profiles,[e]:c}}}))}async function tt(e={}){let t=e.endpoint??process.env.SESHAT_ENDPOINT;if(process.env.SESHAT_TOKEN&&!e.profile&&!process.env.SESHAT_PROFILE)return new x({endpoint:t??B,token:process.env.SESHAT_TOKEN});let n=me(e.profile),i=await V(n);if(!i)throw new Error(`No credential for profile "${n}". Run seshat login --profile ${n}`);if(t&&S.parse(t)!==i.endpoint)throw new Error("This profile belongs to another endpoint. Log in with a different --profile; stored credentials are never sent to a different service");let a=new x({endpoint:i.endpoint,token:await fe(i)});return await ue(n,i,a),a}function rt(e,t){return{profile:e,endpoint:t.endpoint,tokenId:t.tokenId,space:t.space,scopes:t.scopes,expiresAt:t.expiresAt,storage:t.credential.kind}}import{realpath as ee,stat as he,readFile as ge}from"node:fs/promises";import{basename as ye,extname as we,relative as Se,isAbsolute as xe}from"node:path";var be={".html":"text/html",".htm":"text/html",".md":"text/markdown",".markdown":"text/markdown",".json":"application/json",".pdf":"application/pdf",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".txt":"text/plain"};async function st(e,t){let n=await ee(e);if(t&&!(await Promise.all(t.map(s=>ee(s)))).some(s=>{let c=Se(s,n);return c!==".."&&!c.startsWith("../")&&!xe(c)}))throw new Error("File is outside SESHAT_UPLOAD_ROOTS. Configure explicit directories for MCP file access");let i=await he(n);if(!i.isFile())throw new Error("Upload path must be a regular file");if(i.size<1||i.size>b)throw new Error(`File must contain 1 to ${b} bytes`);return{bytes:new Uint8Array(await ge(n)),filename:ye(n),contentType:be[we(n).toLowerCase()]??"application/octet-stream"}}export{b as a,v as b,l as c,R as d,h as e,_ as f,H as g,B as h,S as i,C as j,De as k,Le as l,A as m,x as n,Qe as o,Ve as p,me as q,V as r,fe as s,et as t,ue as u,tt as v,rt as w,st as x};
package/dist/cli.js CHANGED
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env node
2
- import{a as j,b as O,d as G,g as X,h as F,i as L,j as Z,k as H,l as E,m as A,n as V,o as Q,p as S,q as U,r as M,s as ee,t as K,u as R,v as D,w as te}from"./chunk-5LJAWVAV.js";import{parseArgs as Ee}from"node:util";import{pathToFileURL as Ie}from"node:url";import{realpathSync as Ce}from"node:fs";import{z as q}from"zod";import{stripVTControlCharacters as fe}from"node:util";function T(e){return fe(e).replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g," ").trim()}function N(e){return e?new Date(e).toLocaleDateString(void 0,{month:"short",day:"numeric",year:"numeric"}):"Never"}function h(e,o=[]){let r=Math.max(10,...o.map(([a])=>a.length+1));return`
3
- ${T(e)}
4
- ${o.length?`
5
- `+o.map(([a,d])=>` ${a.padEnd(r)}${T(d)}`).join(`
2
+ import{a as q,b as O,d as X,g as V,h as H,i as L,j as Z,k as Q,l as M,m as E,n as R,o as ee,p as te,q as T,r as N,s as K,t as ne,u as B,v as P,w as U,x as re}from"./chunk-FGC75MZR.js";import{parseArgs as _e}from"node:util";import{pathToFileURL as je}from"node:url";import{realpathSync as Fe}from"node:fs";import{z as F}from"zod";import{execFile as ye}from"node:child_process";import{readFile as se}from"node:fs/promises";import{promisify as ke}from"node:util";import{z as S}from"zod";var oe=ke(ye),ve=S.coerce.number().int().min(1).max(36),xe=S.object({SPHardwareDataType:S.array(S.object({machine_name:S.string().optional()}))}),Se=S.object({model:S.string().optional(),chassis:S.array(S.number()).nullable().optional()});function be(e){let s=e.platform==="darwin"?"macos":e.platform==="win32"?"windows":e.platform==="android"?"android":e.platform==="linux"?"linux":"other",r="terminal";return s==="android"||e.chassis===11?r="phone":e.chassis===30?r="tablet":/^MacBook/i.test(e.model??"")||[8,9,10,14,31,32].includes(e.chassis??0)?r="laptop":(/^(?:iMac|Mac (?:mini|Pro|Studio))/i.test(e.model??"")||[3,4,5,6,7,13,15,16,24].includes(e.chassis??0))&&(r="desktop"),{kind:r,os:s,...e.model?{model:e.model}:{},client:e.client}}async function ae(e){let s,r;try{if(process.platform==="darwin"){let{stdout:i}=await oe("/usr/sbin/system_profiler",["SPHardwareDataType","-detailLevel","mini","-json"],{timeout:2e3,maxBuffer:65536}),l=xe.safeParse(JSON.parse(i));l.success&&(s=l.data.SPHardwareDataType[0]?.machine_name)}else if(process.platform==="linux"){let i=await Promise.all([se("/sys/class/dmi/id/product_name","utf8").catch(()=>""),se("/sys/class/dmi/id/chassis_type","utf8").catch(()=>"")]);s=i[0]?.trim();let l=ve.safeParse(i[1]?.trim());l.success&&(r=l.data)}else if(process.platform==="win32"){let{stdout:i}=await oe("powershell.exe",["-NoProfile","-NonInteractive","-Command","[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new(); @{model=(Get-CimInstance Win32_ComputerSystem).Model;chassis=@((Get-CimInstance Win32_SystemEnclosure).ChassisTypes)} | ConvertTo-Json -Compress"],{timeout:2e3,maxBuffer:65536,windowsHide:!0}),l=Se.safeParse(JSON.parse(i));l.success&&(s=l.data.model,r=l.data.chassis?.[0])}}catch{}let a=Z.shape.model.safeParse(s);return be({platform:process.env.TERMUX_VERSION?"android":process.platform,model:a.success?a.data:void 0,chassis:r,client:e})}import{stripVTControlCharacters as Te}from"node:util";function I(e){return Te(e).replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g," ").trim()}function _(e){return e?new Date(e).toLocaleDateString(void 0,{month:"short",day:"numeric",year:"numeric"}):"Never"}function g(e,s=[]){let r=Math.max(10,...s.map(([a])=>a.length+1));return`
3
+ ${I(e)}
4
+ ${s.length?`
5
+ `+s.map(([a,i])=>` ${a.padEnd(r)}${I(i)}`).join(`
6
6
  `)+`
7
- `:""}`}function $(e){let o={"artifacts:read":"Read artifacts","artifacts:write":"Upload and edit","artifacts:delete":"Delete artifacts","tokens:manage":"Manage tokens"};return e.map(r=>o[r]).join(" \xB7 ")}function z(e,o,r){let a=e.profile==="default"?"":` --profile ${e.profile}`;return h(`${o?"\u2713 Already connected to":"\u2713 Connected to"} ${e.space.name}`,[["Profile",e.profile],["Service",e.endpoint],["Access",$(e.scopes)],["Expires",N(e.expiresAt)],["Stored in",e.storage==="keychain"?"OS credential vault":"Private credential file (0600)"],[r?"Agent uses":"Try next",`seshat upload ./report.html${a}${r?" --json":""}`]])}function P(e,o){return h(`${o} ${e.title}`,[["Link",e.url],["File",e.filename],["Size",`${new Intl.NumberFormat().format(e.size)} bytes`],["Expires",N(e.expiresAt)]])}function ne(e,o){return process.env.NO_COLOR!==void 0||process.env.TERM==="dumb"?e:e.replace(/^([✓✕].*)$/m,r=>`\x1B[1;${o}m${r}\x1B[0m`)}function _(e,o,r){process.stdout.write(r?(process.stdout.isTTY?ne(o,32):o)+`
7
+ `:""}`}function D(e){let s={"artifacts:read":"Read artifacts","artifacts:write":"Upload and edit","artifacts:delete":"Delete artifacts","tokens:manage":"Manage tokens"};return e.map(r=>s[r]).join(" \xB7 ")}function z(e,s,r){let a=e.profile==="default"?"":` --profile ${e.profile}`;return g(`${s?"\u2713 Already connected to":"\u2713 Connected to"} ${e.space.name}`,[["Profile",e.profile],["Service",e.endpoint],["Access",D(e.scopes)],["Expires",_(e.expiresAt)],["Stored in",e.storage==="keychain"?"OS credential vault":"Private credential file (0600)"],[r?"Agent uses":"Try next",`seshat upload ./report.html${a}${r?" --json":""}`]])}function $(e,s){return g(`${s} ${e.title}`,[["Link",e.url],["File",e.filename],["Size",`${new Intl.NumberFormat().format(e.size)} bytes`],["Expires",_(e.expiresAt)]])}function ie(e,s){return process.env.NO_COLOR!==void 0||process.env.TERM==="dumb"?e:e.replace(/^([✓✕].*)$/m,r=>`\x1B[1;${s}m${r}\x1B[0m`)}function j(e,s,r){process.stdout.write(r?(process.stdout.isTTY?ie(s,32):s)+`
8
8
  `:JSON.stringify(e)+`
9
- `)}function re(e,o){if(!o){process.stderr.write(JSON.stringify({error:{code:e.code,message:e.message},...e.requestId?{requestId:e.requestId}:{}})+`
10
- `);return}let r=h(`\u2715 ${e.message}`,[["Code",e.code],...e.hint?[["Next step",e.hint]]:[],...e.requestId?[["Retry ID",e.requestId]]:[]]);process.stderr.write((process.stderr.isTTY?ne(r,31):r)+`
11
- `)}import{randomBytes as me,createHash as he}from"node:crypto";import{execFile as ge}from"node:child_process";import{promisify as we}from"node:util";import{hostname as ye}from"node:os";import{setTimeout as ke}from"node:timers/promises";import{z as oe}from"zod";var B=we(ge);async function W(e,o,r,a,d){let g=await fetch(e+o,{method:"POST",redirect:"error",headers:{"content-type":"application/json"},body:JSON.stringify(r),signal:d?AbortSignal.any([d,AbortSignal.timeout(15e3)]):AbortSignal.timeout(15e3)}),n=await g.json();if(!g.ok){let m=X.safeParse(n);throw new E(m.success?m.data.error.code:"connection_failed",m.success?m.data.error.message:"Could not connect to Seshat",g.status)}let c=a.safeParse(n);if(!c.success)throw new Error("Unexpected connection response from the service");return c.data}async function ve(e){process.platform==="darwin"?await B("open",[e],{timeout:1e4}):process.platform==="win32"?await B("rundll32",["url.dll,FileProtocolHandler",e],{timeout:1e4}):await B("xdg-open",[e],{timeout:1e4})}async function se(e){let o=S(e.profile),r=L.parse(e.endpoint??process.env.SESHAT_ENDPOINT??F),a=V.parse(e.storage??"keychain");if(a==="file"&&process.platform==="win32")throw new Error("Use keychain storage with Windows Credential Manager");let d=oe.array(O).min(1).parse((e.scopes??"artifacts:read,artifacts:write").split(",")),g=oe.coerce.number().int().min(5).max(600).parse(e.timeout??600),n=await U(o),c;if(n){try{c=new A({endpoint:n.endpoint,token:await M(n)})}catch{}if(!e.force&&e.expiresAt===void 0&&e.name===void 0&&(e.storage===void 0||e.storage===n.credential.kind)&&c&&n.endpoint===r&&n.expiresAt>new Date().toISOString()&&[...new Set(d)].sort().join(",")===[...n.scopes].sort().join(","))try{return await K(o,n,c),await c.whoami(),{connected:!0,alreadyConnected:!0,...D(o,n)}}catch(y){if(!(y instanceof E&&y.status===401))throw y}}let m="seshat_"+me(32).toString("base64url"),w=he("sha256").update(m).digest("hex"),p=await W(r,"/v1/auth/device",{name:e.name??`${ye().slice(0,60)} \xB7 ${o.slice(0,32)}`,scopes:d,tokenHash:w,...e.expiresAt?{expiresAt:e.expiresAt}:{}},Z);if(!URL.canParse(p.verificationUrl))throw new Error("The service returned an invalid authorization URL");let s=new URL(p.verificationUrl);if(s.origin!==r||s.pathname!=="/connect"||s.username||s.password||s.hash||s.searchParams.get("code")!==p.userCode)throw new Error("The service returned an unexpected authorization URL");process.stderr.write(e.human?h("Connect this terminal to Seshat",[["Open",p.verificationUrl],["Code",p.userCode]])+`
9
+ `)}function ce(e,s){if(!s){process.stderr.write(JSON.stringify({error:{code:e.code,message:e.message},...e.requestId?{requestId:e.requestId}:{}})+`
10
+ `);return}let r=g(`\u2715 ${e.message}`,[["Code",e.code],...e.hint?[["Next step",e.hint]]:[],...e.requestId?[["Retry ID",e.requestId]]:[]]);process.stderr.write((process.stderr.isTTY?ie(r,31):r)+`
11
+ `)}import{randomBytes as Ie,createHash as Ee}from"node:crypto";import{execFile as Ce}from"node:child_process";import{promisify as Ae}from"node:util";import{hostname as Re}from"node:os";import{setTimeout as Pe}from"node:timers/promises";import{z as le}from"zod";var W=Ae(Ce);async function J(e,s,r,a,i){let l=await fetch(e+s,{method:"POST",redirect:"error",headers:{"content-type":"application/json"},body:JSON.stringify(r),signal:i?AbortSignal.any([i,AbortSignal.timeout(15e3)]):AbortSignal.timeout(15e3)}),n=await l.json();if(!l.ok){let h=V.safeParse(n);throw new E(h.success?h.data.error.code:"connection_failed",h.success?h.data.error.message:"Could not connect to Seshat",l.status)}let p=a.safeParse(n);if(!p.success)throw new Error("Unexpected connection response from the service");return p.data}async function De(e){process.platform==="darwin"?await W("open",[e],{timeout:1e4}):process.platform==="win32"?await W("rundll32",["url.dll,FileProtocolHandler",e],{timeout:1e4}):await W("xdg-open",[e],{timeout:1e4})}async function pe(e){let s=T(e.profile),r=L.parse(e.endpoint??process.env.SESHAT_ENDPOINT??H),a=ee.parse(e.storage??"keychain");if(a==="file"&&process.platform==="win32")throw new Error("Use keychain storage with Windows Credential Manager");let i=le.array(O).min(1).parse((e.scopes??"artifacts:read,artifacts:write").split(",")),l=le.coerce.number().int().min(5).max(600).parse(e.timeout??600),n=await N(s),p;if(n){try{p=new R({endpoint:n.endpoint,token:await K(n)})}catch{}if(!e.force&&e.expiresAt===void 0&&e.name===void 0&&(e.storage===void 0||e.storage===n.credential.kind)&&p&&n.endpoint===r&&n.expiresAt>new Date().toISOString()&&[...new Set(i)].sort().join(",")===[...n.scopes].sort().join(","))try{return await B(s,n,p),await p.whoami(),{connected:!0,alreadyConnected:!0,...U(s,n)}}catch(y){if(!(y instanceof E&&y.status===401))throw y}}let h="seshat_"+Ie(32).toString("base64url"),w=Ee("sha256").update(h).digest("hex"),d=await J(r,"/v1/auth/device",{name:e.name??`${Re().slice(0,60)} \xB7 ${s.slice(0,32)}`,scopes:i,tokenHash:w,device:await ae(e.agent?"agent":"terminal"),...e.expiresAt?{expiresAt:e.expiresAt}:{}},Q);if(!URL.canParse(d.verificationUrl))throw new Error("The service returned an invalid authorization URL");let o=new URL(d.verificationUrl);if(o.origin!==r||o.pathname!=="/connect"||o.username||o.password||o.hash||o.searchParams.get("code")!==d.userCode)throw new Error("The service returned an unexpected authorization URL");process.stderr.write(e.human?g("Connect this terminal to Seshat",[["Open",d.verificationUrl],["Code",d.userCode]])+`
12
12
  Check the code in your browser, then approve this device.
13
13
  Waiting for approval\u2026
14
14
 
15
15
  `:`Approve this terminal in Seshat:
16
- ${p.verificationUrl}
17
- Code: ${p.userCode}
18
- `),e.noBrowser||await ve(p.verificationUrl).catch(()=>process.stderr.write(`Open the URL above in your browser to continue.
19
- `));let k=new AbortController,b=()=>k.abort();process.once("SIGINT",b);let l=Math.min(Date.parse(p.expiresAt),Date.now()+g*1e3),v=!1;try{let y=p.interval;for(;Date.now()<l&&(await ke(Math.min(y*1e3,Math.max(0,l-Date.now())),void 0,{signal:k.signal}),!(Date.now()>=l));){let i;try{i=await W(r,"/v1/auth/device/poll",{deviceCode:p.deviceCode},H,k.signal)}catch(f){if(f instanceof E&&f.code==="slow_down"){y+=2;continue}throw f}if(i.status==="denied")throw new Error("Connection was denied. No credential was saved");if(i.status==="pending"){y=i.interval;continue}let u=await new A({endpoint:r,token:m}).whoami({signal:k.signal});if(u.spaceId!==i.space.id||u.tokenId!==i.id||[...new Set(d)].sort().join(",")!==[...u.scopes].sort().join(","))throw new Error("The approved credential does not match the requested space and permissions");k.signal.throwIfAborted();let t=await Q({name:o,endpoint:r,tokenId:i.id,token:m,space:i.space,scopes:i.scopes,expiresAt:i.expiresAt,storage:a,pendingConnection:!0,expectedTokenId:n?.tokenId??null,signal:k.signal});v=!0;try{await K(o,t,new A({endpoint:r,token:m}))}catch{throw new Error("Credential saved, but connection completion was interrupted. Retry seshat auth status with this --profile to finish, or login --force if its five-minute approval expired")}return c&&await c.revokeSelf().catch(()=>process.stderr.write(`The previous credential could not be revoked. Revoke it in Seshat Agent access.
20
- `)),{connected:!0,...D(o,t)}}throw new Error("Connection approval timed out. Run seshat login again")}finally{process.removeListener("SIGINT",b),v||await W(r,"/v1/auth/device/cancel",{deviceCode:p.deviceCode},H).catch(()=>{})}}async function ae(e){let o=S(e),r=await U(o);if(!r)return{disconnected:!0,alreadyDisconnected:!0,profile:o};try{await new A({endpoint:r.endpoint,token:await M(r)}).revokeSelf()}catch(a){if(!(a instanceof E&&a.status===401))throw new Error("Could not revoke this connection. Retry logout when the service is available; the local credential has been kept")}return await ee(o,r.tokenId),{disconnected:!0,profile:o}}async function ie(e={}){let o=S(e.profile);if(process.env.SESHAT_TOKEN&&!e.profile&&!process.env.SESHAT_PROFILE){let d=await(await R(e)).whoami();return{connected:!0,source:"environment",endpoint:L.parse(e.endpoint??process.env.SESHAT_ENDPOINT??F),...d}}let r=await U(o);if(!r)return{connected:!1,profile:o};let a=await(await R(e)).whoami();return{connected:!0,...D(o,r),...a}}import xe from"string-width";import{setInterval as be,clearInterval as Se}from"node:timers";var Te=new Intl.Segmenter(void 0,{granularity:"grapheme"}),I=e=>xe(e,{ambiguousIsNarrow:!1});function ce({value:e,columns:o}){if(I(e)<=o)return e;if(o<1)return"";let r=o>=I("\u2026")?"\u2026":".",a=o-I(r),d="";for(let{segment:g}of Te.segment(e)){let n=I(g);if(n>a)break;d+=g,a-=n}return d+r}var pe=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function le(e=process.stderr){let o=!!e.isTTY&&process.env.TERM!=="dumb",r={kind:"connecting"},a=0,d=!1,g="",n="artifact";function c(){if(d)return;let w=r.kind==="sending"?r.percent:r.kind==="processing"?100:null,p=`${r.kind}:${w===null?"":Math.floor(w/10)}`;if(!o&&g===p)return;g=p;let s=Math.max(0,(e.columns??100)-1),k=o?pe[a++%pe.length]??"\u280B":"\u2026",b=s-I(k+" "),l;switch(r.kind){case"connecting":l="Connecting to Seshat\u2026";break;case"reading":l=`Reading ${r.filename}\u2026`;break;case"processing":l=s<35?"Preparing link\u2026":"Preparing your link\u2026 100% sent";break;case"sending":{let y=`Uploading\u2026 ${r.percent}%`,i=Math.floor(r.percent/10),u=` ${"\u2588".repeat(i)}${"\u2591".repeat(10-i)} ${r.percent}%`,t=b-I("Uploading "+u);b<I(y)?l=`${r.percent}%`:t<4?l=y:l=`Uploading ${ce({value:r.filename,columns:t})}${u}`;break}}let v=`${k} ${l}`;e.write(o?`\r\x1B[2K${ce({value:v,columns:s})}`:v+`
21
- `)}c();let m=o?be(c,90):void 0;return m?.unref(),{reading(w){n=T(w),r={kind:"reading",filename:n},c()},transferred({loadedBytes:w,totalBytes:p}){let s=Math.min(100,Math.floor(100*w/p));r=s===100?{kind:"processing"}:{kind:"sending",filename:n,percent:s},c()},stop(){d||(d=!0,m&&Se(m),o&&e.write("\r\x1B[2K"))}}}var Y={root:`Seshat: publish and manage agent artifacts.
16
+ ${d.verificationUrl}
17
+ Code: ${d.userCode}
18
+ `),e.noBrowser||await De(d.verificationUrl).catch(()=>process.stderr.write(`Open the URL above in your browser to continue.
19
+ `));let k=new AbortController,b=()=>k.abort();process.once("SIGINT",b);let u=Math.min(Date.parse(d.expiresAt),Date.now()+l*1e3),v=!1;try{let y=d.interval;for(;Date.now()<u&&(await Pe(Math.min(y*1e3,Math.max(0,u-Date.now())),void 0,{signal:k.signal}),!(Date.now()>=u));){let c;try{c=await J(r,"/v1/auth/device/poll",{deviceCode:d.deviceCode},M,k.signal)}catch(m){if(m instanceof E&&m.code==="slow_down"){y+=2;continue}throw m}if(c.status==="denied")throw new Error("Connection was denied. No credential was saved");if(c.status==="pending"){y=c.interval;continue}let f=await new R({endpoint:r,token:h}).whoami({signal:k.signal});if(f.spaceId!==c.space.id||f.tokenId!==c.id||[...new Set(i)].sort().join(",")!==[...f.scopes].sort().join(","))throw new Error("The approved credential does not match the requested space and permissions");k.signal.throwIfAborted();let t=await te({name:s,endpoint:r,tokenId:c.id,token:h,space:c.space,scopes:c.scopes,expiresAt:c.expiresAt,storage:a,pendingConnection:!0,expectedTokenId:n?.tokenId??null,signal:k.signal});v=!0;try{await B(s,t,new R({endpoint:r,token:h}))}catch{throw new Error("Credential saved, but connection completion was interrupted. Retry seshat auth status with this --profile to finish, or login --force if its five-minute approval expired")}return p&&await p.revokeSelf().catch(()=>process.stderr.write(`The previous credential could not be revoked. Revoke it in Seshat Agent access.
20
+ `)),{connected:!0,...U(s,t)}}throw new Error("Connection approval timed out. Run seshat login again")}finally{process.removeListener("SIGINT",b),v||await J(r,"/v1/auth/device/cancel",{deviceCode:d.deviceCode},M).catch(()=>{})}}async function de(e){let s=T(e),r=await N(s);if(!r)return{disconnected:!0,alreadyDisconnected:!0,profile:s};try{await new R({endpoint:r.endpoint,token:await K(r)}).revokeSelf()}catch(a){if(!(a instanceof E&&a.status===401))throw new Error("Could not revoke this connection. Retry logout when the service is available; the local credential has been kept")}return await ne(s,r.tokenId),{disconnected:!0,profile:s}}async function ue(e={}){let s=T(e.profile);if(process.env.SESHAT_TOKEN&&!e.profile&&!process.env.SESHAT_PROFILE){let i=await(await P(e)).whoami();return{connected:!0,source:"environment",endpoint:L.parse(e.endpoint??process.env.SESHAT_ENDPOINT??H),...i}}let r=await N(s);if(!r)return{connected:!1,profile:s};let a=await(await P(e)).whoami();return{connected:!0,...U(s,r),...a}}import $e from"string-width";import{setInterval as Oe,clearInterval as Ne}from"node:timers";var Ue=new Intl.Segmenter(void 0,{granularity:"grapheme"}),C=e=>$e(e,{ambiguousIsNarrow:!1});function fe({value:e,columns:s}){if(C(e)<=s)return e;if(s<1)return"";let r=s>=C("\u2026")?"\u2026":".",a=s-C(r),i="";for(let{segment:l}of Ue.segment(e)){let n=C(l);if(n>a)break;i+=l,a-=n}return i+r}var me=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function he(e=process.stderr){let s=!!e.isTTY&&process.env.TERM!=="dumb",r={kind:"connecting"},a=0,i=!1,l="",n="artifact";function p(){if(i)return;let w=r.kind==="sending"?r.percent:r.kind==="processing"?100:null,d=`${r.kind}:${w===null?"":Math.floor(w/10)}`;if(!s&&l===d)return;l=d;let o=Math.max(0,(e.columns??100)-1),k=s?me[a++%me.length]??"\u280B":"\u2026",b=o-C(k+" "),u;switch(r.kind){case"connecting":u="Connecting to Seshat\u2026";break;case"reading":u=`Reading ${r.filename}\u2026`;break;case"processing":u=o<35?"Preparing link\u2026":"Preparing your link\u2026 100% sent";break;case"sending":{let y=`Uploading\u2026 ${r.percent}%`,c=Math.floor(r.percent/10),f=` ${"\u2588".repeat(c)}${"\u2591".repeat(10-c)} ${r.percent}%`,t=b-C("Uploading "+f);b<C(y)?u=`${r.percent}%`:t<4?u=y:u=`Uploading ${fe({value:r.filename,columns:t})}${f}`;break}}let v=`${k} ${u}`;e.write(s?`\r\x1B[2K${fe({value:v,columns:o})}`:v+`
21
+ `)}p();let h=s?Oe(p,90):void 0;return h?.unref(),{reading(w){n=I(w),r={kind:"reading",filename:n},p()},transferred({loadedBytes:w,totalBytes:d}){let o=Math.min(100,Math.floor(100*w/d));r=o===100?{kind:"processing"}:{kind:"sending",filename:n,percent:o},p()},stop(){i||(i=!0,h&&Ne(h),s&&e.write("\r\x1B[2K"))}}}var G={root:`Seshat: publish and manage agent artifacts.
22
22
 
23
23
  Commands default to human-readable output. Use --json for structured results and errors.
24
24
 
@@ -83,14 +83,14 @@ Examples:
83
83
  seshat agent setup --profile t3 --name "T3 agent"
84
84
  seshat agent setup --profile remote --storage file --no-browser`,"auth whoami":`Inspect the active token's space and scopes.
85
85
  Example: seshat auth whoami`,mcp:`Start the local stdio MCP server. SESHAT_UPLOAD_ROOTS is an optional JSON array of allowed directories.
86
- Example: seshat mcp`},Ae={help:{type:"boolean"},json:{type:"boolean"},title:{type:"string"},description:{type:"string"},"content-type":{type:"string"},filename:{type:"string"},"request-id":{type:"string"},"expires-at":{type:"string"},"no-expiry":{type:"boolean"},limit:{type:"string"},cursor:{type:"string"},yes:{type:"boolean"},"dry-run":{type:"boolean"},name:{type:"string"},scopes:{type:"string"},slug:{type:"string"},profile:{type:"string"},endpoint:{type:"string"},storage:{type:"string"},"no-browser":{type:"boolean"},timeout:{type:"string"},force:{type:"boolean"}},Re={"artifacts upload":["title","description","content-type","filename","request-id","expires-at","no-expiry"],"artifacts list":["limit","cursor"],"artifacts get":[],"artifacts update":["title","description","expires-at","no-expiry"],"artifacts delete":["yes","dry-run"],"tokens create":["name","scopes","expires-at"],"tokens revoke":["yes","dry-run"],"spaces create":["slug","name"],"auth whoami":[],"auth login":["name","scopes","expires-at","storage","no-browser","timeout","force"],"agent setup":["name","scopes","expires-at","storage","no-browser","timeout","force"],"auth logout":[],"auth status":[],mcp:[]};function x(e,o){let r=q.string().min(1).safeParse(e);if(!r.success)throw new Error(`Provide ${o}. Run this command with --help for an example`);return r.data}async function $e(){let e=[],o=0;for await(let r of process.stdin){let a=Buffer.from(r);if(o+=a.length,o>j)throw new Error(`stdin exceeds ${j} bytes`);e.push(a)}return new Uint8Array(Buffer.concat(e))}async function Pe(e){let o,r,a=!e.includes("--json"),d="default",g="seshat --help";try{let{values:n,positionals:c}=Ee({args:e,options:Ae,allowPositionals:!0}),m={login:"auth login",logout:"auth logout",whoami:"auth whoami",upload:"artifacts upload",list:"artifacts list"},w=c[0]??"",p=Object.hasOwn(m,w)?m[w]:void 0,s=p??(w==="mcp"?"mcp":c.slice(0,2).join(" "));if(n.help||!s){process.stdout.write((Y[s]??Y.root)+(s?`
86
+ Example: seshat mcp`},qe={help:{type:"boolean"},json:{type:"boolean"},title:{type:"string"},description:{type:"string"},"content-type":{type:"string"},filename:{type:"string"},"request-id":{type:"string"},"expires-at":{type:"string"},"no-expiry":{type:"boolean"},limit:{type:"string"},cursor:{type:"string"},yes:{type:"boolean"},"dry-run":{type:"boolean"},name:{type:"string"},scopes:{type:"string"},slug:{type:"string"},profile:{type:"string"},endpoint:{type:"string"},storage:{type:"string"},"no-browser":{type:"boolean"},timeout:{type:"string"},force:{type:"boolean"}},He={"artifacts upload":["title","description","content-type","filename","request-id","expires-at","no-expiry"],"artifacts list":["limit","cursor"],"artifacts get":[],"artifacts update":["title","description","expires-at","no-expiry"],"artifacts delete":["yes","dry-run"],"tokens create":["name","scopes","expires-at"],"tokens revoke":["yes","dry-run"],"spaces create":["slug","name"],"auth whoami":[],"auth login":["name","scopes","expires-at","storage","no-browser","timeout","force"],"agent setup":["name","scopes","expires-at","storage","no-browser","timeout","force"],"auth logout":[],"auth status":[],mcp:[]};function x(e,s){let r=F.string().min(1).safeParse(e);if(!r.success)throw new Error(`Provide ${s}. Run this command with --help for an example`);return r.data}async function Le(){let e=[],s=0;for await(let r of process.stdin){let a=Buffer.from(r);if(s+=a.length,s>q)throw new Error(`stdin exceeds ${q} bytes`);e.push(a)}return new Uint8Array(Buffer.concat(e))}async function Me(e){let s,r,a=!e.includes("--json"),i="default",l="seshat --help";try{let{values:n,positionals:p}=_e({args:e,options:qe,allowPositionals:!0}),h={login:"auth login",logout:"auth logout",whoami:"auth whoami",upload:"artifacts upload",list:"artifacts list"},w=p[0]??"",d=Object.hasOwn(h,w)?h[w]:void 0,o=d??(w==="mcp"?"mcp":p.slice(0,2).join(" "));if(n.help||!o){process.stdout.write((G[o]??G.root)+(o?`
87
87
 
88
88
  Output: human-readable by default. Use --json for structured results and errors.`:"")+`
89
- `);return}if(!Y[s])throw new Error("Unknown command. Run seshat --help");g=`seshat ${s} --help`,d=S(n.profile??(s==="agent setup"?"agent":void 0));let k=["artifacts upload","artifacts get","artifacts update","artifacts delete","tokens revoke"].includes(s);if(c.length>(s==="mcp"?1:(p?1:2)+(k?1:0)))throw new Error("Unexpected positional argument. Run this command with --help");let b=Object.keys(n).find(t=>!["help","json","profile","endpoint",...Re[s]??[]].includes(t));if(b)throw new Error(`--${b} is not supported by ${s}. Run this command with --help`);if(s==="mcp"){let{startMcp:t}=await import("./mcp.js");await t({profile:n.profile,endpoint:n.endpoint});return}if(n["no-expiry"]&&n["expires-at"])throw new Error("Choose --no-expiry or --expires-at");if(s==="auth login"||s==="agent setup"){let t=s==="agent setup"?S(n.profile??"agent"):S(n.profile),f=await se({profile:t,endpoint:n.endpoint,name:n.name,scopes:n.scopes??(s==="agent setup"?"artifacts:write":void 0),expiresAt:n["expires-at"],storage:n.storage,noBrowser:n["no-browser"],timeout:n.timeout,force:n.force,human:a}),C=s==="agent setup"?{...f,agent:{environment:{SESHAT_PROFILE:t},uploadCommand:`pnpx @seshat.sh/cli upload FILE --profile ${t} --json`,helpCommand:"pnpx @seshat.sh/cli upload --help"}}:f;_(C,z(f,"alreadyConnected"in f&&!!f.alreadyConnected,s==="agent setup"),a);return}if(s==="auth logout"||s==="auth status"){let t=s==="auth logout"?await ae(n.profile):await ie({profile:n.profile,endpoint:n.endpoint}),f="disconnected"in t?h(t.alreadyDisconnected?"\u2713 No saved connection":"\u2713 Device disconnected",[["Profile",t.profile]]):t.connected?"storage"in t?z(t,!0,!1):h("\u2713 Connected with SESHAT_TOKEN",[["Service",t.endpoint],["Space",t.space?.name??t.spaceId],["Access",$(t.scopes)]]):h("No connected device",[["Profile",t.profile],["Next step",`seshat login --profile ${t.profile}`]]);_(t,f,a);return}a&&s==="artifacts upload"&&(r=le());let l=await R({profile:n.profile,endpoint:n.endpoint}),v=c[p?1:2],y=n["no-expiry"]?null:n["expires-at"],i,u="";switch(s){case"auth whoami":{let t=await l.whoami();i={spaceId:t.spaceId,tokenId:t.tokenId,scopes:t.scopes},u=h("\u2713 Connected to Seshat",[["Space",t.space?.name??t.spaceId],["Access",$(t.scopes)]]);break}case"spaces create":{let t=await l.createSpace({slug:x(n.slug,"--slug"),name:x(n.name,"--name")});i=t,u=h(`\u2713 Created ${t.space.name}`,[["Space ID",t.space.id],["Token",t.credential.token],["Keep safe","Copy this token now. It will not be shown again."]]);break}case"tokens create":{let t=await l.createToken({name:x(n.name,"--name"),scopes:q.array(O).parse((n.scopes??"artifacts:write").split(",")),expiresAt:n["expires-at"]});i=t,u=h("\u2713 Token created",[["Token",t.token],["Access",$(t.scopes)],["Expires",N(t.expiresAt)],["Keep safe","Copy this token now. It will not be shown again."]]);break}case"tokens revoke":{let t=x(v,"TOKEN_ID");if(n["dry-run"]){i={dryRun:!0,action:"revoke_token",id:t},u=h("Preview: revoke token",[["Token ID",t],["To apply",`seshat tokens revoke ${t} --yes`]]);break}if(!n.yes)throw new Error("Revocation requires --yes. Inspect with seshat tokens revoke TOKEN_ID --dry-run");i=await l.revokeToken(t),u=h("\u2713 Token revoked",[["Token ID",t]]);break}case"artifacts list":{let t=await l.list({limit:n.limit===void 0?void 0:q.coerce.number().int().min(1).max(100).parse(n.limit),cursor:n.cursor});i=t,u=h(t.artifacts.length?`${t.artifacts.length} artifacts`:"No artifacts yet")+t.artifacts.map(f=>`
90
- ${T(f.title)}
91
- ${T(f.url)}
89
+ `);return}if(!G[o])throw new Error("Unknown command. Run seshat --help");l=`seshat ${o} --help`,i=T(n.profile??(o==="agent setup"?"agent":void 0));let k=["artifacts upload","artifacts get","artifacts update","artifacts delete","tokens revoke"].includes(o);if(p.length>(o==="mcp"?1:(d?1:2)+(k?1:0)))throw new Error("Unexpected positional argument. Run this command with --help");let b=Object.keys(n).find(t=>!["help","json","profile","endpoint",...He[o]??[]].includes(t));if(b)throw new Error(`--${b} is not supported by ${o}. Run this command with --help`);if(o==="mcp"){let{startMcp:t}=await import("./mcp.js");await t({profile:n.profile,endpoint:n.endpoint});return}if(n["no-expiry"]&&n["expires-at"])throw new Error("Choose --no-expiry or --expires-at");if(o==="auth login"||o==="agent setup"){let t=o==="agent setup"?T(n.profile??"agent"):T(n.profile),m=await pe({profile:t,endpoint:n.endpoint,name:n.name,scopes:n.scopes??(o==="agent setup"?"artifacts:write":void 0),expiresAt:n["expires-at"],storage:n.storage,noBrowser:n["no-browser"],timeout:n.timeout,force:n.force,human:a,agent:o==="agent setup"}),A=o==="agent setup"?{...m,agent:{environment:{SESHAT_PROFILE:t},uploadCommand:`pnpx @seshat.sh/cli upload FILE --profile ${t} --json`,helpCommand:"pnpx @seshat.sh/cli upload --help"}}:m;j(A,z(m,"alreadyConnected"in m&&!!m.alreadyConnected,o==="agent setup"),a);return}if(o==="auth logout"||o==="auth status"){let t=o==="auth logout"?await de(n.profile):await ue({profile:n.profile,endpoint:n.endpoint}),m="disconnected"in t?g(t.alreadyDisconnected?"\u2713 No saved connection":"\u2713 Device disconnected",[["Profile",t.profile]]):t.connected?"storage"in t?z(t,!0,!1):g("\u2713 Connected with SESHAT_TOKEN",[["Service",t.endpoint],["Space",t.space?.name??t.spaceId],["Access",D(t.scopes)]]):g("No connected device",[["Profile",t.profile],["Next step",`seshat login --profile ${t.profile}`]]);j(t,m,a);return}a&&o==="artifacts upload"&&(r=he());let u=await P({profile:n.profile,endpoint:n.endpoint}),v=p[d?1:2],y=n["no-expiry"]?null:n["expires-at"],c,f="";switch(o){case"auth whoami":{let t=await u.whoami();c={spaceId:t.spaceId,tokenId:t.tokenId,scopes:t.scopes},f=g("\u2713 Connected to Seshat",[["Space",t.space?.name??t.spaceId],["Access",D(t.scopes)]]);break}case"spaces create":{let t=await u.createSpace({slug:x(n.slug,"--slug"),name:x(n.name,"--name")});c=t,f=g(`\u2713 Created ${t.space.name}`,[["Space ID",t.space.id],["Token",t.credential.token],["Keep safe","Copy this token now. It will not be shown again."]]);break}case"tokens create":{let t=await u.createToken({name:x(n.name,"--name"),scopes:F.array(O).parse((n.scopes??"artifacts:write").split(",")),expiresAt:n["expires-at"]});c=t,f=g("\u2713 Token created",[["Token",t.token],["Access",D(t.scopes)],["Expires",_(t.expiresAt)],["Keep safe","Copy this token now. It will not be shown again."]]);break}case"tokens revoke":{let t=x(v,"TOKEN_ID");if(n["dry-run"]){c={dryRun:!0,action:"revoke_token",id:t},f=g("Preview: revoke token",[["Token ID",t],["To apply",`seshat tokens revoke ${t} --yes`]]);break}if(!n.yes)throw new Error("Revocation requires --yes. Inspect with seshat tokens revoke TOKEN_ID --dry-run");c=await u.revokeToken(t),f=g("\u2713 Token revoked",[["Token ID",t]]);break}case"artifacts list":{let t=await u.list({limit:n.limit===void 0?void 0:F.coerce.number().int().min(1).max(100).parse(n.limit),cursor:n.cursor});c=t,f=g(t.artifacts.length?`${t.artifacts.length} artifacts`:"No artifacts yet")+t.artifacts.map(m=>`
90
+ ${I(m.title)}
91
+ ${I(m.url)}
92
92
  `).join("")+(t.nextCursor?`
93
- Next page: seshat list --cursor ${T(t.nextCursor)}
94
- `:"");break}case"artifacts get":{let t=await l.get(x(v,"ARTIFACT_ID"));i=t,u=P(t,"Artifact:");break}case"artifacts update":{let t=await l.update(x(v,"ARTIFACT_ID"),{title:n.title,description:n.description,expiresAt:y});i=t,u=P(t,"\u2713 Updated");break}case"artifacts delete":{let t=x(v,"ARTIFACT_ID");if(n["dry-run"]){let f=await l.get(t);i={dryRun:!0,action:"delete_artifact",artifact:f},u=P(f,"Preview: delete")+`
93
+ Next page: seshat list --cursor ${I(t.nextCursor)}
94
+ `:"");break}case"artifacts get":{let t=await u.get(x(v,"ARTIFACT_ID"));c=t,f=$(t,"Artifact:");break}case"artifacts update":{let t=await u.update(x(v,"ARTIFACT_ID"),{title:n.title,description:n.description,expiresAt:y});c=t,f=$(t,"\u2713 Updated");break}case"artifacts delete":{let t=x(v,"ARTIFACT_ID");if(n["dry-run"]){let m=await u.get(t);c={dryRun:!0,action:"delete_artifact",artifact:m},f=$(m,"Preview: delete")+`
95
95
  To apply: seshat artifacts delete ${t} --yes
96
- `;break}if(!n.yes)throw new Error("Deletion requires --yes. Inspect with seshat artifacts delete ARTIFACT_ID --dry-run");i=await l.delete(t),u=h("\u2713 Artifact deleted",[["ID",t]]);break}case"artifacts upload":{let t=x(v,"FILE or - for stdin"),f=n.title;r?.reading(t==="-"?"stdin":t),o=G.parse(n["request-id"]??crypto.randomUUID());let C;if(t==="-"){let de=x(n.filename,"--filename for stdin"),ue=x(n["content-type"],"--content-type for stdin");C={bytes:await $e(),filename:de,contentType:ue}}else C=await te(t);let J=await l.upload({...C,contentType:n["content-type"]??C.contentType,metadata:{title:f,description:n.description??"",expiresAt:y},requestId:o,onProgress:r?.transferred});i={...J,requestId:o},u=P(J,"\u2713 Uploaded");break}}r?.stop(),_(i,u,a)}catch(n){r?.stop();let c=n instanceof E?n.code:"invalid_command",m=n instanceof q.ZodError?`Invalid options: ${n.issues.map(p=>p.message).join("; ")}. Run ${g}`:n instanceof Error?n.message:"Command failed",w=c==="unauthorized"?`Reconnect with seshat login --profile ${d}, or check SESHAT_TOKEN if you use it.`:c==="insufficient_scope"?`Reconnect with the required --scopes using seshat login --profile ${d}.`:c==="connection_failed"?"Check your connection and service endpoint. Retry uploads with the same --request-id.":void 0;re({code:c,message:m,requestId:o,hint:w},a),process.exitCode=1}}process.argv[1]&&import.meta.url===Ie(Ce(process.argv[1])).href&&await Pe(process.argv.slice(2));export{Pe as runCli};
96
+ `;break}if(!n.yes)throw new Error("Deletion requires --yes. Inspect with seshat artifacts delete ARTIFACT_ID --dry-run");c=await u.delete(t),f=g("\u2713 Artifact deleted",[["ID",t]]);break}case"artifacts upload":{let t=x(v,"FILE or - for stdin"),m=n.title;r?.reading(t==="-"?"stdin":t),s=X.parse(n["request-id"]??crypto.randomUUID());let A;if(t==="-"){let ge=x(n.filename,"--filename for stdin"),we=x(n["content-type"],"--content-type for stdin");A={bytes:await Le(),filename:ge,contentType:we}}else A=await re(t);let Y=await u.upload({...A,contentType:n["content-type"]??A.contentType,metadata:{title:m,description:n.description??"",expiresAt:y},requestId:s,onProgress:r?.transferred});c={...Y,requestId:s},f=$(Y,"\u2713 Uploaded");break}}r?.stop(),j(c,f,a)}catch(n){r?.stop();let p=n instanceof E?n.code:"invalid_command",h=n instanceof F.ZodError?`Invalid options: ${n.issues.map(d=>d.message).join("; ")}. Run ${l}`:n instanceof Error?n.message:"Command failed",w=p==="unauthorized"?`Reconnect with seshat login --profile ${i}, or check SESHAT_TOKEN if you use it.`:p==="insufficient_scope"?`Reconnect with the required --scopes using seshat login --profile ${i}.`:p==="connection_failed"?"Check your connection and service endpoint. Retry uploads with the same --request-id.":void 0;ce({code:p,message:h,requestId:s,hint:w},a),process.exitCode=1}}process.argv[1]&&import.meta.url===je(Fe(process.argv[1])).href&&await Me(process.argv.slice(2));export{Me as runCli};
package/dist/mcp.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{c as s,d as l,e as o,f as c,u,w as p}from"./chunk-5LJAWVAV.js";import{pathToFileURL as f}from"node:url";import{realpathSync as S}from"node:fs";import{McpServer as g}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as y}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as a}from"zod";var m=`Seshat publishes artifacts to public, unlisted links. Upload only content the user wants shared.
2
+ import{c as s,d as l,e as o,f as c,v as u,x as p}from"./chunk-FGC75MZR.js";import{pathToFileURL as f}from"node:url";import{realpathSync as S}from"node:fs";import{McpServer as g}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as y}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as a}from"zod";var m=`Seshat publishes artifacts to public, unlisted links. Upload only content the user wants shared.
3
3
  Run seshat login once to use a stored profile. SESHAT_PROFILE selects a named agent profile. For CI, set SESHAT_ENDPOINT and a tenant-scoped SESHAT_TOKEN, never in tool arguments.
4
4
  Supported readers: interactive HTML, Markdown, images, PDF, JSON, and plain text. Other formats download.
5
5
  Artifacts do not expire by default. Set expiresAt to an ISO date to expire one, or null for no expiration. Omit title or description to generate metadata automatically on Cloudflare; explicit values are preserved. Set metadata.password to a password of 12\u2013128 characters to protect an upload. Update password to null to remove protection; changing it invalidates previous unlocks.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seshat.sh/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Publish and share artifacts with Seshat from terminals and agents.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -1,3 +0,0 @@
1
- import{z as r}from"zod";import{z as V}from"zod";var P=V.enum(["never","hour","day","week","month","year","custom"]);var b=10*1024*1024,D=["artifacts:read","artifacts:write","artifacts:delete","tokens:manage"],v=r.enum(D),l=r.string().regex(/^[a-f0-9]{48}$/),z=r.string().min(8).max(128).regex(/^[A-Za-z0-9_-]+$/),h=r.string().regex(/^seshat_[A-Za-z0-9_-]{43}$/),L=r.iso.datetime().transform(e=>new Date(e).toISOString()),u=r.object({title:r.string().trim().min(1).max(200).optional(),description:r.string().max(2e3).default(""),expiresAt:L.nullable().optional(),expirationPreset:P.nullable().optional(),password:r.string().min(12).max(128).nullable().optional(),generateMetadata:r.boolean().optional()}).strict().refine(e=>e.expirationPreset==null||(e.expirationPreset==="never"?e.expiresAt===null:typeof e.expiresAt=="string"),"Expiration preset must match an explicit expiration value"),R=r.object({filename:r.string().min(1).max(200).regex(/^[^/\\\x00-\x1f\x7f]+$/),contentType:r.string().max(150).regex(/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/i),size:r.number().int().min(1).max(b)}).strict(),F=r.object({title:u.shape.title.optional(),description:r.string().max(2e3).optional(),expiresAt:u.shape.expiresAt,expirationPreset:u.shape.expirationPreset,password:u.shape.password}).strict().refine(e=>e.expirationPreset==null||(e.expirationPreset==="never"?e.expiresAt===null:typeof e.expiresAt=="string"),"Expiration preset must match an explicit expiration value").refine(e=>Object.values(e).some(t=>t!==void 0),"Provide at least one metadata field"),N=r.object({slug:r.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).min(2).max(63),name:r.string().trim().min(1).max(100)}).strict(),f=r.object({name:r.string().trim().min(1).max(100),scopes:r.array(v).min(1).max(D.length),expiresAt:L.optional()}).strict(),g=r.object({id:l,title:r.string(),description:r.string(),filename:r.string(),contentType:r.string(),size:r.number(),createdAt:r.string(),expiresAt:r.string().nullable(),expirationPreset:P.nullable().default(null),url:r.url(),downloadUrl:r.url(),passwordProtected:r.boolean().default(!1),views:r.object({owner:r.number(),others:r.number()})}),_=r.object({artifacts:r.array(g),nextCursor:r.string().nullable()}),E=r.object({id:l,token:h,scopes:r.array(v),expiresAt:r.string().nullable()}),O=r.object({space:r.object({id:l,slug:r.string(),name:r.string()}),credential:E}),$=r.object({spaceId:l,tokenId:l,scopes:f.shape.scopes,space:O.shape.space.optional()}),q=r.object({error:r.object({code:r.string(),message:r.string()})});import{z as m}from"zod";import{z as a}from"zod";var M="https://app.seshat.sh",S=a.url().refine(e=>{let t=new URL(e);return!t.username&&!t.password&&!t.search&&!t.hash&&t.pathname==="/"&&(t.protocol==="https:"||t.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(t.hostname))},"Use an HTTPS origin, or HTTP localhost for development").transform(e=>new URL(e).origin),k=a.string().min(1).max(64).regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/),Pe=a.string().max(16).transform(e=>e.toUpperCase().replaceAll("-","").trim()).pipe(a.string().regex(/^[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{8}$/)),Ee=f.extend({tokenHash:a.string().regex(/^[a-f0-9]{64}$/)}).strict().refine(e=>!e.expiresAt||Date.parse(e.expiresAt)>Date.now()&&Date.parse(e.expiresAt)<=Date.now()+366*864e5,"Choose a credential expiry within the next year"),Oe=a.strictObject({deviceCode:l}),je=a.strictObject({approve:a.boolean()}),T=a.object({id:l,slug:a.string(),name:a.string()}),Ce=a.object({deviceCode:l,userCode:a.string().regex(/^[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{4}-[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{4}$/),verificationUrl:a.url(),expiresAt:a.iso.datetime(),interval:a.number().int().min(1).max(60)}),Ue=a.discriminatedUnion("status",[a.object({status:a.literal("pending"),interval:a.number().int().min(1).max(60)}),a.object({status:a.literal("denied")}),a.object({status:a.literal("approved"),id:l,space:T,scopes:f.shape.scopes,expiresAt:a.iso.datetime()})]),Ie=a.object({userCode:a.string(),name:a.string(),scopes:f.shape.scopes,status:a.enum(["pending","approved","denied"]),expiresAt:a.iso.datetime(),tokenExpiresAt:a.iso.datetime(),space:T});var De=a.object({devices:a.array(a.object({id:l,name:a.string(),scopes:f.shape.scopes,createdAt:a.iso.datetime(),completedAt:a.iso.datetime().nullable(),lastUsedAt:a.iso.datetime().nullable(),expiresAt:a.iso.datetime().nullable()}))});var ee=m.object({endpoint:S,token:h}).strict();async function te({form:e,onProgress:t}){let o=new Response(e),n=o.headers.get("content-type");if(!n)throw new Error("Could not encode the upload");let i=new Uint8Array(await o.arrayBuffer()),s=0;return t({loadedBytes:0,totalBytes:i.byteLength}),{body:new ReadableStream({pull(p){let y=Math.min(s+65536,i.byteLength);p.enqueue(i.subarray(s,y)),s=y,t({loadedBytes:s,totalBytes:i.byteLength}),s===i.byteLength&&p.close()}},{highWaterMark:0}),headers:{"content-type":n,"content-length":String(i.byteLength)},duplex:"half"}}var A=class extends Error{constructor(o,n,i){super(n);this.code=o;this.status=i}code;status},x=class{settings;constructor(t){this.settings=ee.parse(t)}async request(t,o,n={}){let i=new Headers(n.headers);i.set("authorization",`Bearer ${this.settings.token}`),typeof n.body=="string"&&i.set("content-type","application/json");let s;try{s=await fetch(new URL(t,this.settings.endpoint),{...n,headers:i,redirect:"error",signal:n.signal?AbortSignal.any([n.signal,AbortSignal.timeout(6e4)]):AbortSignal.timeout(6e4)})}catch{throw new A("connection_failed","Request failed or timed out. Check SESHAT_ENDPOINT; retry uploads with the same --request-id")}let c=await s.json();if(!s.ok){let p=q.safeParse(c);throw new A(p.success?p.data.error.code:"request_failed",p.success?p.data.error.message:`HTTP ${s.status}`,s.status)}return o.parse(c)}async upload(t){let o=R.parse({filename:t.filename,contentType:t.contentType,size:t.bytes.byteLength}),n=u.parse(t.metadata),i=z.parse(t.requestId),s=new FormData;if(s.set("metadata",JSON.stringify(n)),s.set("file",new Blob([new Uint8Array(t.bytes)],{type:o.contentType}),o.filename),t.onProgress){let c=await te({form:s,onProgress:t.onProgress}),p={...c,method:"POST",headers:{...c.headers,"idempotency-key":i}};return this.request("/v1/artifacts",g,p)}return this.request("/v1/artifacts",g,{method:"POST",body:s,headers:{"idempotency-key":i}})}list(t={}){let o=new URLSearchParams;return t.limit!==void 0&&o.set("limit",String(t.limit)),t.cursor!==void 0&&o.set("cursor",t.cursor),this.request(`/v1/artifacts?${o}`,_)}get(t){return this.request(`/v1/artifacts/${l.parse(t)}`,g)}update(t,o){return this.request(`/v1/artifacts/${l.parse(t)}`,g,{method:"PATCH",body:JSON.stringify(F.parse(o))})}delete(t){return this.request(`/v1/artifacts/${l.parse(t)}`,m.object({deleted:m.literal(!0),id:l}),{method:"DELETE"})}whoami(t={}){return this.request("/v1/me",$,{signal:t.signal})}completeConnection(){return this.request("/v1/auth/complete",m.object({completed:m.literal(!0)}),{method:"POST"})}revokeSelf(){return this.request("/v1/auth/logout",m.object({revoked:m.literal(!0),id:l}),{method:"POST"})}createSpace(t){return this.request("/v1/admin/spaces",O,{method:"POST",body:JSON.stringify(N.parse(t))})}createToken(t){return this.request("/v1/tokens",E,{method:"POST",body:JSON.stringify(f.parse(t))})}revokeToken(t){return this.request(`/v1/tokens/${l.parse(t)}`,m.object({revoked:m.literal(!0),id:l}),{method:"DELETE"})}};import{constants as H}from"node:fs";import{lstat as re,mkdir as oe,open as j,rename as ne,unlink as B}from"node:fs/promises";import{homedir as ae}from"node:os";import{join as w,resolve as ie}from"node:path";import{randomUUID as J}from"node:crypto";import{setTimeout as se}from"node:timers/promises";import{z as d}from"zod";var Ze=d.enum(["keychain","file"]),W=d.strictObject({endpoint:S,tokenId:l,space:T,scopes:d.array(v),expiresAt:d.iso.datetime(),pendingConnection:d.literal(!0).optional(),credential:d.discriminatedUnion("kind",[d.strictObject({kind:d.literal("keychain"),account:d.string().min(1)}),d.strictObject({kind:d.literal("file"),token:h})])}),ce=d.strictObject({version:d.literal(1),profiles:d.record(k,W)});function Y(){return ie(process.env.SESHAT_CONFIG_DIR??w(process.env.XDG_CONFIG_HOME??w(ae(),".config"),"seshat"))}function X(e){return e instanceof Error&&"code"in e&&e.code==="ENOENT"}async function Z(e,t=!1){let o=await re(e);if(o.isSymbolicLink()||(t?!o.isDirectory():!o.isFile()))throw new Error("Seshat credential paths must be real files and directories, not symlinks");if(process.platform!=="win32"&&((o.mode&63)!==0||process.getuid&&o.uid!==process.getuid()))throw new Error(`Credential path must belong to you and have ${t?"0700":"0600"} permissions: ${e}`)}async function G(){let e=Y();try{await Z(e,!0);let t=w(e,"profiles.json"),o=await j(t,H.O_RDONLY|(H.O_NOFOLLOW??0));try{let n=await o.stat();if(!n.isFile()||n.size>65536||process.platform!=="win32"&&((n.mode&63)!==0||process.getuid&&n.uid!==process.getuid()))throw new Error("Credential file must be a small, owner-only regular file");try{let i=ce.safeParse(JSON.parse(await o.readFile("utf8")));if(i.success)return i.data}catch{}throw new Error("Invalid Seshat profile file. Run seshat login again")}finally{await o.close()}}catch(t){if(X(t))return{version:1,profiles:{}};throw t}}async function U(e,t){let o=Y();await oe(o,{recursive:!0,mode:448}),await Z(o,!0);let n=w(o,"profiles.lock"),i;for(let c=0;c<40;c++)try{i=await j(n,"wx",384);break}catch(p){if(!(p instanceof Error&&"code"in p&&p.code==="EEXIST"))throw p;await se(50)}if(!i)throw new Error("Another Seshat process is updating profiles. Retry shortly; if it stopped unexpectedly, remove profiles.lock from your Seshat config directory");let s=w(o,`profiles-${J()}.tmp`);try{t?.throwIfAborted();let c=e(await G()),p=await j(s,"wx",384);try{let y=JSON.stringify(c)+`
2
- `;if(Buffer.byteLength(y)>65536)throw new Error("Too many stored profiles; remove unused connections before adding another");await p.writeFile(y),await p.sync()}finally{await p.close()}t?.throwIfAborted(),await ne(s,w(o,"profiles.json"))}finally{try{await B(s).catch(c=>{if(!X(c))throw c})}finally{await i.close(),await B(n)}}}async function I(e){let{AsyncEntry:t}=await import("@napi-rs/keyring");return new t("dev.djamil.seshat.cli",e,{linux:{store:"secret-service"}})}async function C(e){e.credential.kind==="keychain"&&await(await I(e.credential.account)).deletePassword(AbortSignal.timeout(15e3))}async function Ge(e){let t=k.parse(e.name),o=h.parse(e.token);if(e.storage==="file"&&process.platform==="win32")throw new Error("Use keychain storage with Windows Credential Manager");let n=J();if(e.storage==="keychain")try{await(await I(n)).setPassword(o,e.signal?AbortSignal.any([e.signal,AbortSignal.timeout(15e3)]):AbortSignal.timeout(15e3))}catch{throw e.signal?.aborted?new Error("Connection cancelled"):new Error("The OS credential vault is unavailable. On a headless POSIX machine, rerun login with --storage file to use an owner-only credential file")}let i=W.parse({pendingConnection:e.pendingConnection,endpoint:e.endpoint,tokenId:e.tokenId,space:e.space,scopes:e.scopes,expiresAt:e.expiresAt,credential:e.storage==="keychain"?{kind:"keychain",account:n}:{kind:"file",token:o}}),s;try{await U(c=>{if(s=Object.hasOwn(c.profiles,t)?c.profiles[t]:void 0,(s?.tokenId??null)!==e.expectedTokenId)throw new Error("This profile changed during login. Retry; the newer connection has been preserved");return{...c,profiles:{...c.profiles,[t]:i}}},e.signal)}catch(c){throw await C(i).catch(()=>{}),c}return s&&await C(s).catch(()=>{process.stderr.write(`The previous OS credential entry could not be removed. Revoke its token in Seshat Agent access.
3
- `)}),i}function le(e){return k.parse(e??process.env.SESHAT_PROFILE??"default")}async function K(e){let t=await G();return Object.hasOwn(t.profiles,e)?t.profiles[e]:void 0}async function pe(e){if(e.credential.kind==="file")return e.credential.token;try{return h.parse(await(await I(e.credential.account)).getPassword(AbortSignal.timeout(15e3)))}catch{throw new Error("Stored credential is unavailable. Run seshat login again for this profile")}}async function Ke(e,t){let o=await K(e);if(o){if(o.tokenId!==t)throw new Error("This profile changed during logout. The newer connection has been preserved");await C(o),await U(n=>{let i=Object.hasOwn(n.profiles,e)?n.profiles[e]:void 0;if(i&&i.tokenId!==t)throw new Error("This profile changed during logout. The newer connection has been preserved");let s={...n.profiles};return delete s[e],{...n,profiles:s}})}}async function de(e,t,o){t.pendingConnection&&(await o.completeConnection(),await U(n=>{let i=Object.hasOwn(n.profiles,e)?n.profiles[e]:void 0;if(i?.tokenId!==t.tokenId)throw new Error("This profile changed during connection. The newer connection has been preserved");let{pendingConnection:s,...c}=i;return{...n,profiles:{...n.profiles,[e]:c}}}))}async function Qe(e={}){let t=e.endpoint??process.env.SESHAT_ENDPOINT;if(process.env.SESHAT_TOKEN&&!e.profile&&!process.env.SESHAT_PROFILE)return new x({endpoint:t??M,token:process.env.SESHAT_TOKEN});let o=le(e.profile),n=await K(o);if(!n)throw new Error(`No credential for profile "${o}". Run seshat login --profile ${o}`);if(t&&S.parse(t)!==n.endpoint)throw new Error("This profile belongs to another endpoint. Log in with a different --profile; stored credentials are never sent to a different service");let i=new x({endpoint:n.endpoint,token:await pe(n)});return await de(o,n,i),i}function Ve(e,t){return{profile:e,endpoint:t.endpoint,tokenId:t.tokenId,space:t.space,scopes:t.scopes,expiresAt:t.expiresAt,storage:t.credential.kind}}import{realpath as Q,stat as me,readFile as fe}from"node:fs/promises";import{basename as he,extname as ue,relative as ge,isAbsolute as we}from"node:path";var ye={".html":"text/html",".htm":"text/html",".md":"text/markdown",".markdown":"text/markdown",".json":"application/json",".pdf":"application/pdf",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".txt":"text/plain"};async function nt(e,t){let o=await Q(e);if(t&&!(await Promise.all(t.map(s=>Q(s)))).some(s=>{let c=ge(s,o);return c!==".."&&!c.startsWith("../")&&!we(c)}))throw new Error("File is outside SESHAT_UPLOAD_ROOTS. Configure explicit directories for MCP file access");let n=await me(o);if(!n.isFile())throw new Error("Upload path must be a regular file");if(n.size<1||n.size>b)throw new Error(`File must contain 1 to ${b} bytes`);return{bytes:new Uint8Array(await fe(o)),filename:he(o),contentType:ye[ue(o).toLowerCase()]??"application/octet-stream"}}export{b as a,v as b,l as c,z as d,u as e,F as f,q as g,M as h,S as i,Ce as j,Ue as k,A as l,x as m,Ze as n,Ge as o,le as p,K as q,pe as r,Ke as s,de as t,Qe as u,Ve as v,nt as w};