familiar-vtt 2.12.0 → 2.13.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.
@@ -131,6 +131,6 @@ data:
131
131
 
132
132
  `,t.enqueue(r.encode(o)),!0}catch(o){return this.onerror?.(o),!1}}handleUnsupportedRequest(){return this.onerror?.(new Error("Method not allowed.")),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32e3,message:"Method not allowed."},id:null}),{status:405,headers:{Allow:"GET, POST, DELETE","Content-Type":"application/json"}})}async handlePostRequest(t,r){try{let i=t.headers.get("accept");if(!i?.includes("application/json")||!i.includes("text/event-stream"))return this.onerror?.(new Error("Not Acceptable: Client must accept both application/json and text/event-stream")),this.createJsonErrorResponse(406,-32e3,"Not Acceptable: Client must accept both application/json and text/event-stream");let n=t.headers.get("content-type");if(!n||!n.includes("application/json"))return this.onerror?.(new Error("Unsupported Media Type: Content-Type must be application/json")),this.createJsonErrorResponse(415,-32e3,"Unsupported Media Type: Content-Type must be application/json");let o={headers:Object.fromEntries(t.headers.entries()),url:new URL(t.url)},a;if(r?.parsedBody!==void 0)a=r.parsedBody;else try{a=await t.json()}catch{return this.onerror?.(new Error("Parse error: Invalid JSON")),this.createJsonErrorResponse(400,-32700,"Parse error: Invalid JSON")}let s;try{Array.isArray(a)?s=a.map(_=>Es.parse(_)):s=[Es.parse(a)]}catch{return this.onerror?.(new Error("Parse error: Invalid JSON-RPC message")),this.createJsonErrorResponse(400,-32700,"Parse error: Invalid JSON-RPC message")}let l=s.some(Rs);if(l){if(this._initialized&&this.sessionId!==void 0)return this.onerror?.(new Error("Invalid Request: Server already initialized")),this.createJsonErrorResponse(400,-32600,"Invalid Request: Server already initialized");if(s.length>1)return this.onerror?.(new Error("Invalid Request: Only one initialization request is allowed")),this.createJsonErrorResponse(400,-32600,"Invalid Request: Only one initialization request is allowed");this.sessionId=this.sessionIdGenerator?.(),this._initialized=!0,this.sessionId&&this._onsessioninitialized&&await Promise.resolve(this._onsessioninitialized(this.sessionId))}if(!l){let _=this.validateSession(t);if(_)return _;let x=this.validateProtocolVersion(t);if(x)return x}if(!s.some(pn)){for(let _ of s)this.onmessage?.(_,{authInfo:r?.authInfo,requestInfo:o});return new Response(null,{status:202})}let d=crypto.randomUUID(),p=s.find(_=>Rs(_)),f=p?p.params.protocolVersion:t.headers.get("mcp-protocol-version")??UR;if(this._enableJsonResponse)return new Promise(_=>{this._streamMapping.set(d,{resolveJson:_,cleanup:()=>{this._streamMapping.delete(d)}});for(let x of s)pn(x)&&this._requestToStreamMapping.set(x.id,d);for(let x of s)this.onmessage?.(x,{authInfo:r?.authInfo,requestInfo:o})});let h=new TextEncoder,m,g=new ReadableStream({start:_=>{m=_},cancel:()=>{this._streamMapping.delete(d)}}),y={"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"};this.sessionId!==void 0&&(y["mcp-session-id"]=this.sessionId);for(let _ of s)pn(_)&&(this._streamMapping.set(d,{controller:m,encoder:h,cleanup:()=>{this._streamMapping.delete(d);try{m.close()}catch{}}}),this._requestToStreamMapping.set(_.id,d));await this.writePrimingEvent(m,h,d,f);for(let _ of s){let x,k;pn(_)&&this._eventStore&&f>="2025-11-25"&&(x=()=>{this.closeSSEStream(_.id)},k=()=>{this.closeStandaloneSSEStream()}),this.onmessage?.(_,{authInfo:r?.authInfo,requestInfo:o,closeSSEStream:x,closeStandaloneSSEStream:k})}return new Response(g,{status:200,headers:y})}catch(i){return this.onerror?.(i),this.createJsonErrorResponse(400,-32700,"Parse error",{data:String(i)})}}async handleDeleteRequest(t){let r=this.validateSession(t);if(r)return r;let i=this.validateProtocolVersion(t);return i||(await Promise.resolve(this._onsessionclosed?.(this.sessionId)),await this.close(),new Response(null,{status:200}))}validateSession(t){if(this.sessionIdGenerator===void 0)return;if(!this._initialized)return this.onerror?.(new Error("Bad Request: Server not initialized")),this.createJsonErrorResponse(400,-32e3,"Bad Request: Server not initialized");let r=t.headers.get("mcp-session-id");if(!r)return this.onerror?.(new Error("Bad Request: Mcp-Session-Id header is required")),this.createJsonErrorResponse(400,-32e3,"Bad Request: Mcp-Session-Id header is required");if(r!==this.sessionId)return this.onerror?.(new Error("Session not found")),this.createJsonErrorResponse(404,-32001,"Session not found")}validateProtocolVersion(t){let r=t.headers.get("mcp-protocol-version");if(r!==null&&!ko.includes(r))return this.onerror?.(new Error(`Bad Request: Unsupported protocol version: ${r} (supported versions: ${ko.join(", ")})`)),this.createJsonErrorResponse(400,-32e3,`Bad Request: Unsupported protocol version: ${r} (supported versions: ${ko.join(", ")})`)}async close(){this._streamMapping.forEach(({cleanup:t})=>{t()}),this._streamMapping.clear(),this._requestResponseMap.clear(),this.onclose?.()}closeSSEStream(t){let r=this._requestToStreamMapping.get(t);if(!r)return;let i=this._streamMapping.get(r);i&&i.cleanup()}closeStandaloneSSEStream(){let t=this._streamMapping.get(this._standaloneSseStreamId);t&&t.cleanup()}async send(t,r){let i=r?.relatedRequestId;if((Cr(t)||$o(t))&&(i=t.id),i===void 0){if(Cr(t)||$o(t))throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");let a;this._eventStore&&(a=await this._eventStore.storeEvent(this._standaloneSseStreamId,t));let s=this._streamMapping.get(this._standaloneSseStreamId);if(s===void 0)return;s.controller&&s.encoder&&this.writeSSEEvent(s.controller,s.encoder,t,a);return}let n=this._requestToStreamMapping.get(i);if(!n)throw new Error(`No connection established for request ID: ${String(i)}`);let o=this._streamMapping.get(n);if(!this._enableJsonResponse&&o?.controller&&o?.encoder){let a;this._eventStore&&(a=await this._eventStore.storeEvent(n,t)),this.writeSSEEvent(o.controller,o.encoder,t,a)}if(Cr(t)||$o(t)){this._requestResponseMap.set(i,t);let a=Array.from(this._requestToStreamMapping.entries()).filter(([l,u])=>u===n).map(([l])=>l);if(a.every(l=>this._requestResponseMap.has(l))){if(!o)throw new Error(`No connection established for request ID: ${String(i)}`);if(this._enableJsonResponse&&o.resolveJson){let l={"Content-Type":"application/json"};this.sessionId!==void 0&&(l["mcp-session-id"]=this.sessionId);let u=a.map(d=>this._requestResponseMap.get(d));u.length===1?o.resolveJson(new Response(JSON.stringify(u[0]),{status:200,headers:l})):o.resolveJson(new Response(JSON.stringify(u),{status:200,headers:l}))}else o.cleanup();for(let l of a)this._requestResponseMap.delete(l),this._requestToStreamMapping.delete(l)}}}};var fm=class{constructor(t={}){this._requestContext=new WeakMap,this._webStandardTransport=new mm(t),this._requestListener=nk(async r=>{let i=this._requestContext.get(r);return this._webStandardTransport.handleRequest(r,{authInfo:i?.authInfo,parsedBody:i?.parsedBody})},{overrideGlobalObjects:!1})}get sessionId(){return this._webStandardTransport.sessionId}set onclose(t){this._webStandardTransport.onclose=t}get onclose(){return this._webStandardTransport.onclose}set onerror(t){this._webStandardTransport.onerror=t}get onerror(){return this._webStandardTransport.onerror}set onmessage(t){this._webStandardTransport.onmessage=t}get onmessage(){return this._webStandardTransport.onmessage}async start(){return this._webStandardTransport.start()}async close(){return this._webStandardTransport.close()}async send(t,r){return this._webStandardTransport.send(t,r)}async handleRequest(t,r,i){let n=t.auth;await nk(async a=>this._webStandardTransport.handleRequest(a,{authInfo:n,parsedBody:i}),{overrideGlobalObjects:!1})(t,r)}closeSSEStream(t){this._webStandardTransport.closeSSEStream(t)}closeStandaloneSSEStream(){this._webStandardTransport.closeStandaloneSSEStream()}};var lG=100,uG=6e4,dG=50,pG=1048576;function mG(e){return new Promise(t=>{Bd(e,pG,{onBody:r=>t({ok:!0,body:r}),onOverflow:()=>t({ok:!1}),onError:()=>t({ok:!1}),onClose:()=>t({ok:!1})})})}async function ci(e,t){try{await t.close()}catch(r){$.warn({sessionId:e,err:r},"Error closing expired transport")}}function fG(e){let t,r=1/0;for(let[i,n]of e)n.lastActivity<r&&(r=n.lastActivity,t=i);if(t!==void 0){let i=e.get(t);$.warn({evictedSessionId:t,idleMs:Date.now()-r,activeSessions:e.size},"Max concurrent sessions reached \u2014 evicting least-recently-active session"),e.delete(t),i&&ci(t,i.transport)}}function Gj(e,t,r){let i=e.headers["mcp-session-id"],n=typeof i=="string"?i:void 0,o=n!==void 0&&n!==""?r.sessions.get(n):void 0;if(o){let a=o;if(!a.rateLimiter.consume()){let s=String(Math.ceil(a.rateLimiter.msUntilNextToken()/1e3));we(t,429,"Too many requests",{"Retry-After":s});return}a.lastActivity=Date.now(),$.debug({sessionId:n},"Routing to existing session"),(async()=>{try{await a.transport.handleRequest(e,t)}catch(s){$.error({sessionId:n,err:s},"handleRequest failed for existing session")}})();return}if(e.method!=="POST"){we(t,400,"Session required");return}(async()=>{let a=await mG(e);if(!a.ok){t.writableEnded||we(t,413,"Request body too large");return}let s;try{s=JSON.parse(a.body)}catch{we(t,400,"Invalid JSON body");return}if(!Rs(s)){we(t,400,"POST without session ID must be an initialize request");return}r.sessions.size>=dG&&fG(r.sessions),await hG(e,t,s,r)})()}async function hG(e,t,r,i){let{sessions:n,setup:o,dnsRebinding:a}=i,s,l=new fm({sessionIdGenerator:()=>cG(),onsessioninitialized:d=>{s=d,n.set(d,{transport:l,lastActivity:Date.now(),rateLimiter:zr.perWindow(lG,uG)}),$.info({sessionId:d,activeSessions:n.size},"Session registered")},enableDnsRebindingProtection:!0,allowedHosts:a.allowedHosts});$.info("New POST without session ID \u2014 creating new session");try{await Promise.race([o(l),new Promise((d,p)=>{setTimeout(()=>{p(new Error("Transport setup timed out"))},3e4).unref()})])}catch(d){$.error({err:d},"Transport setup failed"),we(t,503,"Session setup failed"),ci(s??"unknown",l);return}let u=l.onclose;l.onclose=()=>{u?.(),s!==void 0&&s!==""&&n.has(s)&&($.info({sessionId:s},"Transport closed \u2014 removing session from map"),n.delete(s))};try{await l.handleRequest(e,t,r)}catch(d){$.error({sessionId:s,err:d},"handleRequest failed for new session"),ci(s??"unknown",l),s!==void 0&&s!==""&&n.delete(s);return}(s===void 0||s==="")&&($.warn("New session POST completed but no session ID was generated \u2014 closing transport"),ci("no-id",l))}var Jj=1048576,$G=3600*1e3,TG=60*1e3;function IG(e){let t=setInterval(()=>{let r=Date.now();for(let[i,n]of e)r-n.lastActivity>=$G&&($.info({sessionId:i},"Session expired (idle timeout)"),e.delete(i),ci(i,n.transport))},TG);return t.unref(),t}function EG(e,t,r){return new Promise((i,n)=>{let o=a=>{a.code==="EADDRINUSE"?n(new Error(`Port ${String(t)} is already in use`)):n(a)};e.on("error",o),e.listen(t,r,()=>{e.removeListener("error",o),$.info({port:t,host:r},"Streamable HTTP transport listening"),i()})})}function PG(e,t,r,i){let n=e.socket.remoteAddress;if(n===void 0)return!0;let o=r.get(n);if(o)r.delete(n),r.set(n,o);else{if(r.size>=1e3){let a=r.keys().next().value;a!==void 0&&r.delete(a)}o=zr.perWindow(200,6e4),r.set(n,o)}if(!o.consume()){Vd(e,t,i);let a=String(Math.ceil(o.msUntilNextToken()/1e3));return we(t,429,"Rate limit exceeded for your IP \u2014 try again later",{"Retry-After":a}),!1}return!0}function RG(e){let{port:t,sessions:r,setup:i,allowedOrigins:n,globalLimiter:o,ipLimiters:a,secret:s,webhookHandler:l,dnsRebinding:u}=e;return(d,p)=>{if(!PG(d,p,a,n))return;if(!o.consume()){Vd(d,p,n);let h=String(Math.ceil(o.msUntilNextToken()/1e3));we(p,429,"Server rate limit exceeded \u2014 try again later",{"Retry-After":h});return}Vd(d,p,n);let f=new URL(d.url??"/",`http://localhost:${String(t)}`);if($.debug({method:d.method,path:f.pathname},"HTTP request received"),f.pathname==="/webhook/polar"&&l){l(d,p);return}if(hR(d,p,s)&&gR(d,p,n,Jj)){if(yR(d,p,Jj),vR(d,p),f.pathname!=="/mcp"&&f.pathname!=="/"){we(p,404,"Not found");return}f.pathname==="/"&&(d.url="/mcp"),Gj(d,p,{sessions:r,setup:i,dnsRebinding:u})}}}function zG(e,t){if(t)return;e.some(i=>{try{return!V_.has(new URL(i).hostname)}catch{return!1}})&&$.warn("CORS allows a remote origin but FAMILIAR_HTTP_ALLOWED_HOSTS is unset (localhost-only default) \u2014 remote requests will be rejected (403) until FAMILIAR_HTTP_ALLOWED_HOSTS includes the tunnel host")}async function Kj(e,t,r,i){let n=i?.host??"127.0.0.1",o=i?.secret,a=i?.webhookHandler,s=i?.allowedHosts??["127.0.0.1",`127.0.0.1:${String(e)}`,"localhost",`localhost:${String(e)}`],l={allowedHosts:s},u=new Map,d=new Set(r),p=new Map,f=zr.perWindow(500,6e4);$.info({corsOrigins:r,host:n,allowedHosts:s},"HTTP transport CORS + DNS-rebinding allowlists"),zG(r,i?.allowedHosts!==void 0),o!==void 0&&o!==""?$.info("HTTP transport bearer token authentication enabled"):$.warn("HTTP transport has no authentication \u2014 set FAMILIAR_HTTP_SECRET to secure it");let h=IG(u),m=kG(RG({port:e,sessions:u,setup:t,allowedOrigins:d,globalLimiter:f,ipLimiters:p,secret:o,webhookHandler:a,dnsRebinding:l}));m.headersTimeout=1e4,m.requestTimeout=3e4,m.maxConnections=100,m.on("clientError",(y,_)=>{$.debug({err:y},"HTTP client error (malformed request or connection reset)"),_.destroy()});let g=async()=>{clearInterval(h);let y=[...u.entries()];u.clear(),await Promise.all(y.map(([_,x])=>ci(_,x.transport))),$.info({closedSessions:y.length},"All HTTP sessions closed")};return m.on("error",y=>{$.error({err:y},"HTTP server runtime error")}),await EG(m,e,n),{server:m,closeAllSessions:g}}var q={readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},C={readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},ee={readOnlyHint:!1,destructiveHint:!0,idempotentHint:!1,openWorldHint:!1},H={readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},Ne={readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},wn={readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!1},hm={readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0},ik={readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0},Qj={readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!0};var gm=[{annotations:C,name:"toggle-condition",description:"Toggle a status condition on a token (e.g., prone, poisoned, blinded). Adds the condition if absent, removes it if present. Use get-available-conditions first to see valid condition names. Returns the token name and toggled condition.",inputSchema:c.object({identifier:c.string().max(256).describe("Token name or ID"),condition:c.string().max(256).describe('Condition name (e.g., "prone", "poisoned", "blinded")')})},{annotations:C,name:"apply-condition-to-group",description:"Toggle a status condition on multiple tokens at once. Adds the condition if absent, removes it if present (per token). Use for AoE effects like mass Prone, Frightened, or Poisoned. Use get-available-conditions first to see valid condition names. Returns per-token toggle results.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(50).describe("Token names or IDs to apply the condition to"),condition:c.string().max(256).describe('Condition name (e.g., "prone", "poisoned", "frightened")')})},{annotations:C,name:"start-combat",description:"Create a new combat encounter and add combatants from the current scene. If no identifiers are provided, all visible tokens on the active scene are added automatically (hidden tokens are skipped to preserve ambush tension \u2014 pass includeHidden:true to override, or use add-combatant mid-combat when hidden creatures reveal). Returns the combat tracker with all added combatants. After starting, use roll-initiative to set turn order.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).max(100).describe("Token names or Foundry IDs to add as combatants. Omit to add all tokens on the current scene. Explicit identifiers are always added regardless of hidden state.").optional(),includeHidden:c.boolean().optional().describe("When omitting identifiers, also add tokens that are currently hidden on the map (default: false). Only applies to the auto-add-all-tokens path.")})},{annotations:wn,name:"end-combat",description:"End the current combat encounter. Removes all combatants from the tracker and resets initiative order. Use when a fight ends or to clean up before starting a new encounter. Combat data (rounds, turns) is lost \u2014 check get-combat first if you need to record results."},{annotations:q,name:"get-combat",description:"Get the current combat tracker state. Returns round number, active turn, and all combatants with initiative order, HP, AC, and conditions. For a richer snapshot with positions and abilities for AI decision-making, use get-combat-snapshot (combat-ai bundle) instead."},{annotations:C,name:"roll-initiative",description:"Roll initiative for combatants in the active encounter. Returns rolled values and updated turn order. Omit combatantIds to roll for all combatants. For rolling only NPCs (letting players roll their own), use roll-npc-initiative instead.",inputSchema:c.object({combatantIds:c.array(c.string().max(256)).max(50).optional().describe("Specific combatant IDs to roll for (omit to roll for all)")})},{annotations:C,name:"roll-npc-initiative",description:"Roll initiative for all NPC combatants only, skipping player characters. Returns rolled values and updated turn order. Use when players prefer to roll their own initiative. For rolling everyone at once, use roll-initiative instead."},{annotations:C,name:"next-turn",description:"Advance combat to the next combatant's turn. Returns the new active combatant. If combat hasn't started yet, this begins the first turn. Automatically wraps to the next round after the last combatant."},{annotations:C,name:"next-round",description:"Advance combat to the next round, resetting to the top of initiative order. Returns the new round number and active combatant. Use to skip remaining turns in the current round."},{annotations:q,name:"get-available-conditions",description:"List all status conditions available in the game system (e.g., prone, poisoned, blinded, stunned). Returns every condition (no pagination) with ID and label. Named get-* rather than list-* because conditions are static system data, not user-created documents. Use to find valid condition IDs before applying them with toggle-condition or apply-condition-to-group."},{annotations:C,name:"apply-damage",description:"Apply damage to one or more targets in a single batch (temp HP absorption handled automatically). Returns per-target results with HP before/after. Use for AoE effects, lair actions, or environmental/trap damage that hits creatures directly. A creature ATTACKING another creature must NOT use this \u2014 use resolve-attack / resolve-spell (combat-ai bundle) instead, which enforce range, to-hit, and resistances. During active combat, single-target damage is rejected unless you pass `reason` to mark it as non-attack damage.",inputSchema:c.object({targets:c.array(c.object({identifier:c.string().max(256).describe("Token name or ID on the active scene"),amount:c.number().min(0).describe("Damage amount to apply"),type:c.string().max(256).optional().describe('Damage type (e.g., "fire", "slashing") \u2014 informational only')})).min(1).max(50).describe("Array of damage targets"),reason:c.string().max(280).optional().describe("Only for NON-attack single-target damage during combat (trap, environmental, lava, pre-resolved effect). A creature attacking another MUST use resolve-attack / resolve-spell instead \u2014 those enforce range + to-hit. Recorded in the [Familiar audit] chat.")})},{annotations:C,name:"heal-party",description:"Heal all party members or a specific character. Healing is capped at max HP. Omit identifier to heal the entire party. Use after combat, during a rest, or when applying healing spells/potions to multiple characters.",inputSchema:c.object({amount:c.number().min(1).describe("Amount of HP to restore"),identifier:c.string().max(256).optional().describe("Character name or actor ID (omit to heal all party members)")})},{annotations:C,name:"rest-party",description:"Apply short or long rest to all party members. Long rest: restores full HP, resets all resources, and restores spell slots. Short rest: allows hit dice recovery and resets short-rest resources. Triggers rest dialogs in chat. Returns per-character results with success/failure and HP recovery details.",inputSchema:c.object({type:c.enum(["short","long"]).describe('Rest type: "short" or "long"')})},{annotations:C,name:"add-combatant",description:"Add tokens to an active combat encounter as new combatants. Use for mid-combat reinforcements. Requires an active combat.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(50).describe("Token names or IDs to add to the active combat")})},{annotations:wn,name:"remove-combatant",description:"Remove combatants from the active combat encounter. Use for creatures that fled, were banished, or otherwise left combat. Use combatant IDs from get-combat.",inputSchema:c.object({combatantIds:c.array(c.string().max(256)).min(1).max(50).describe("Combatant IDs to remove (from get-combat results, not token IDs)")})},{annotations:C,name:"award-xp",description:"Award experience points to party members. Divides the total XP evenly among recipients. Use after combat or for milestone rewards.",inputSchema:c.object({amount:c.number().int().min(1).describe("Total XP to award (divided among recipients)"),identifiers:c.array(c.string().max(256)).max(20).optional().describe("Specific character names/IDs to award XP to (defaults to all party members)"),reason:c.string().max(512).optional().describe("Reason for the XP award (shown in chat)")})},{annotations:C,name:"quick-start-combat",description:"Create a combat encounter, add combatants, and roll initiative in one step. Omit identifiers to add all visible scene tokens (hidden tokens are skipped \u2014 pass includeHidden:true to include them, or use add-combatant mid-combat when they reveal). Set rollNpcOnly to let players roll their own initiative.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).max(100).optional().describe("Token names or IDs to add (defaults to all visible scene tokens). Explicit identifiers are always added regardless of hidden state."),rollNpcOnly:c.boolean().optional().describe("Only roll initiative for NPCs (default: false)"),includeHidden:c.boolean().optional().describe("When omitting identifiers, also add tokens that are currently hidden on the map (default: false). Only applies to the auto-add-all-tokens path.")})}];var ok=new Set(["dice","world","chat","combat","characters"]),ym=[{id:"dice",name:"Dice",description:"Roll dice with standard notation (1d20+5, 4d6kh3, 2d8+1d6, etc.). Always enabled.",toolCount:1},{id:"world",name:"World & System",description:"World metadata, game pause, in-game calendar, and user management. Get world name, system, module list, advance time, list/inspect/create users. Always enabled.",toolCount:7},{id:"chat",name:"Chat Messages",description:"Send messages to the Foundry chat log and retrieve recent message history. Supports whispers and in-character speech. Always enabled.",toolCount:2},{id:"combat",name:"Combat & Initiative",description:"Core combat flow: start/end encounters, roll initiative, advance turns/rounds, toggle/apply conditions, track HP, batch damage/healing, and party rests. Enable when: running any combat encounter. See also: combat-ai for automated NPC turns, effects for custom buffs/debuffs.",toolCount:17},{id:"characters",name:"Characters & Actors",description:"Full actor management: get/create/update/delete/duplicate actors, manage inventory, HP, abilities, and player ownership. Enable when: working with PCs, NPCs, or creatures. For character inventory use this bundle, not items. See also: compendium for importing entries, effects for buffs/debuffs.",toolCount:16},{id:"combat-ai",name:"Combat AI",description:"Automated NPC combat: get combat snapshots, resolve attacks/spells/saving throws/death saves/ability checks, grapples/shoves, multiattack, legendary actions, AoE targeting, move tokens, and undo the last resolved action. Enable when: running NPC turns automatically or resolving complex combat actions. Requires: combat bundle.",toolCount:15},{id:"scenes",name:"Scenes & Tokens",description:"Scene and token management: get/search/create/switch/update/delete scenes, place/move/update/delete tokens, batch token updates, set darkness and weather, reset fog, pan canvas, take screenshots. Enable when: setting up maps, moving tokens, or changing scene properties. For conditions (prone, stunned, etc.), use the combat bundle. See also: canvas for tiles/drawings, canvas-environment for walls/lights.",toolCount:19},{id:"compendium",name:"Compendium & Rules",description:"Search and look up SRD/compendium content (spells, monsters, items, classes), list packs, and import actors from compendiums into the world. Enable when: looking up rules, finding stat blocks, or importing compendium entries. See also: characters for adding compendium items to character inventory.",toolCount:4},{id:"journals",name:"Journals & Notes",description:"Journal entry and page management: list, search, create, read, update, and delete journal entries and their pages. Enable when: managing session notes, lore, handouts, or quest logs. See also: folders for organizing journal hierarchies.",toolCount:10},{id:"canvas",name:"Canvas Drawing",description:"Map tiles and freeform drawings on the game canvas. Enable when: adding map overlays, decorating scenes, or drawing on the battle map. See also: scenes for tokens, canvas-environment for walls/lights, regions for trigger zones.",toolCount:8},{id:"canvas-environment",name:"Canvas Environment",description:"Walls, doors, lights, ambient sounds, and map notes that define the scene environment. Controls vision blocking, movement barriers, lighting, and ambient audio. Enable when: building or modifying map layouts, placing lights, or adding environmental sounds. See also: scenes for scene-level properties, canvas for tiles/drawings.",toolCount:18},{id:"playlists",name:"Audio & Playlists",description:"Background music and sound effects: search/create/update/delete playlists and tracks, play/stop individual tracks or entire playlists, stop all audio. Enable when: managing session audio, playing background music, or triggering sound effects. See also: voice-generation for AI-generated audio.",toolCount:14},{id:"tables",name:"Rollable Tables",description:"Random rollable tables: list, search, get, roll, create, update, and delete tables for loot, encounters, random names, or custom generators. Enable when: rolling on random tables or managing table content. See also: macros for roll automation, cards for alternative randomization.",toolCount:7},{id:"cards",name:"Card Decks",description:"Card stack management: list, search, get, create, update, and delete card stacks, draw cards, shuffle and reset decks. Enable when: using card decks, drawing cards, or managing card-based game mechanics. See also: tables for alternative randomization, macros for automation.",toolCount:9},{id:"macros",name:"Macros",description:"Automation scripts: list, get, search, create, update, execute, and delete macros. Enable when: running or managing Foundry macros for automation. See also: tables for roll triggering, playlists for audio control.",toolCount:7},{id:"effects",name:"Active Effects",description:"Buffs, debuffs, and status effects on actors: list, get, create, update, and delete active effects. Enable when: applying or managing temporary stat modifications. See also: combat for conditions (prone, stunned, etc.).",toolCount:5},{id:"items",name:"Standalone Items",description:"World-level item management (not character inventory). List, search, get, create, update, and delete standalone items, treasures, and rewards. Enable when: creating loot, managing shop inventory, or building item catalogs. For character inventory, use the characters bundle instead.",toolCount:6},{id:"folders",name:"Folders",description:"Organize documents into folder hierarchies: list, get, create, update, and delete folders for any document type (actors, scenes, journals, items, etc.). Enable when: organizing the sidebar or managing folder structure. See also: any document bundle for organizing its content.",toolCount:5},{id:"regions",name:"Regions",description:"Scene trigger zones (Foundry v13+): list, get, create, update, and delete regions for scene automation behaviors. Enable when: setting up area triggers, teleporters, or scene automation. See also: scenes for scene-level properties.",toolCount:5},{id:"ember-events",name:"Ember Events",description:"Quest, event, and exploration tracking via the Ember module: list/get/begin/complete events, set outcomes, and read the party's region-map location (current hex, biome, fog/reveal, and adjacent events). Enable when: managing quests, story events, or hex-crawl exploration. Requires: Ember module installed in Foundry.",toolCount:6},{id:"image-generation",name:"Image Generation",description:"Generate images from text prompts using configured AI providers (OpenAI GPT Image, fal.ai Flux, Leonardo AI). Enable when: creating character portraits, scene art, or item illustrations.",toolCount:1},{id:"voice-generation",name:"Voice Generation",description:"Generate speech audio from text using configured TTS providers (ElevenLabs, Cartesia, OpenAI TTS). Assign unique voices to individual characters/NPCs. Enable when: creating NPC voice lines, narration audio, or managing per-character voice assignments.",toolCount:4},{id:"knowledge",name:"Knowledge & Memory",description:"Search campaign lore in the knowledge base and manage persistent campaign memories. Enable when: looking up campaign history, searching for specific lore, or saving important story developments.",toolCount:5},{id:"scene-generator",name:"Scene Generator",description:"Generate scenes with AI battle map backgrounds (image-only \u2014 no walls, tokens, or structural data). Provide rooms with dimensions to set scene grid size; room names are injected into the image prompt as atmosphere keywords. Set candidates>1 (max 4) to receive a candidate envelope and pick the favourite via apply-scene-candidate. Enable when: creating new battle maps, dungeons, taverns, or any scene from a description.",toolCount:2}],Nae=ym.reduce((e,t)=>e+t.toolCount,0);var bm="1.29.0",vm="_registeredTools",Yj=!1;function OG(e){return typeof e=="object"&&e!==null}async function jG(){try{let{createRequire:e}=await import("node:module"),r=e(import.meta.url)("@modelcontextprotocol/sdk/package.json");return typeof r=="object"&&r!==null&&"version"in r&&typeof r.version=="string"?r.version:void 0}catch{return}}function Xj(e){let t=e.split(".");return`${t[0]}.${t[1]}`}function Yo(e){let t=e;if(!(vm in t)||!OG(t[vm]))throw new Error(`McpServer is missing the internal '${vm}' property. This adapter was validated against @modelcontextprotocol/sdk ${bm}. The installed SDK version may have changed its internals. Update the SDK adapter for the new SDK version.`);return t[vm]}function eD(e){Yo(e)}async function tD(e){if(Yj)return;Yj=!0;let t=await jG();t!==void 0&&Xj(t)!==Xj(bm)&&e.warn({installedVersion:t,validatedVersion:bm},`MCP SDK version ${t} differs from validated version ${bm}. The tool bundle system accesses SDK internals \u2014 verify compatibility and update the validated SDK version constant in the SDK adapter.`)}var DG={content_policy:" Try rephrasing your prompt.",auth_error:" Check your API key in Foundry module settings.",quota_exceeded:" Check your billing at the provider dashboard.",rate_limit:" Wait a moment and try again.",FOUNDRY_DISCONNECTED:" Ensure Foundry VTT is running and the Familiar module is enabled.",QUERY_TIMEOUT:" The Foundry module took too long to respond. Try a simpler query.",QUERY_OVERLOADED:" Too many concurrent requests. Wait a moment and try again.",SEND_FAILED:" Connection to Foundry was lost. Check the connection.",RATE_LIMITED:" Too many requests. Wait a moment before trying again.",LICENSE_INVALID:" Check your Familiar subscription status."};function nD(e){return e===void 0?"":DG[e]??""}function rD(e,t){return{content:[{type:"text",text:`${e} failed: ${t}`}],isError:!0}}async function li(e,t,r){let i=await r();if(!i.success){let o=on(i.error),a=nD(ds(i.error));return{ok:!1,envelope:rD(e,`${o}${a}`)}}let n=t.safeParse(i.result);return n.success?{ok:!0,data:n.data}:{ok:!1,envelope:rD(e,`unexpected response format \u2014 ${n.error.message}`)}}async function NG(e,t,r,i){try{await e.sendNotification({method:"notifications/progress",params:{progressToken:t,progress:r,message:i}})}catch{}}function st(e,t,r){let i=e._meta?.progressToken;if(i===void 0)return()=>{};let n=0,o=!1,a=setInterval(()=>{o||(n+=1,NG(e,i,n,t))},r);return()=>{o=!0,clearInterval(a)}}function CG(e,t){let r=Math.round(512e3/1024),i=Math.round(t/1024);if(!Array.isArray(e))return`${JSON.stringify(e).slice(0,512e3)}
133
133
 
134
- [Response truncated \u2014 was ${String(i)}KB, exceeded ${String(r)}KB cap. Use more specific queries or filters.]`;let n=0,o=e.length;for(;n<o;){let a=Math.ceil((n+o)/2);JSON.stringify({truncated:!0,totalItems:e.length,items:e.slice(0,a)}).length<=512e3?n=a:o=a-1}return n===0?`[Response truncated \u2014 first item alone exceeded ${String(r)}KB cap (full result was ${String(i)}KB across ${String(e.length)} items). Use more specific queries or filters.]`:JSON.stringify({truncated:!0,totalItems:e.length,items:e.slice(0,n)})}function AG(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function MG(e){if(YP(e)!==!0)return"";let t=Md(e);return t!==void 0&&t>0?` (retryable after ${String(t)}ms)`:" (retryable)"}function v(e){if(e.success){let a=JSON.stringify(e.result),s=AG(e.result)?e.result:void 0,l=a.length<=512e3?a:CG(e.result,a.length);return s===void 0?{content:[{type:"text",text:l}]}:{content:[{type:"text",text:l}],structuredContent:s}}let t=on(e.error),r=ds(e.error),i=nD(r),n=MG(e.error);return{content:[{type:"text",text:`${r!==void 0&&r!==""?`Error [${r}]${n}`:"Error"}: ${t}${i}`}],isError:!0}}var Sm=class{bundles=new Map;enabledBundles=new Set;toolRegistry={};server;constructor(t){this.server=t}initialize(t,r=!1){this.toolRegistry=Yo(this.server);for(let n of ym){let o=t.get(n.id)??[],a={...n,toolNames:o};if(this.bundles.set(n.id,a),r||ok.has(n.id))this.enabledBundles.add(n.id);else for(let s of o)this.toolRegistry[s]?.disable()}let i=[...this.enabledBundles].reduce((n,o)=>n+(this.bundles.get(o)?.toolNames.length??0),0);$.info({coreBundles:[...this.enabledBundles],coreToolCount:i,totalBundles:this.bundles.size},r?"Tool bundles initialized (all enabled for HTTP)":"Tool bundles initialized (lazy loading enabled)")}enableBundle(t){let r=this.bundles.get(t);if(!r)return{enabled:!1,toolNames:[],error:`Unknown bundle: ${t}`};if(this.enabledBundles.has(t))return{enabled:!0,toolNames:r.toolNames,alreadyEnabled:!0};for(let i of r.toolNames)this.toolRegistry[i]?.enable();return this.enabledBundles.add(t),$.info({bundleId:t,toolCount:r.toolNames.length},"Tool bundle enabled"),{enabled:!0,toolNames:r.toolNames}}disableBundle(t){let r=this.bundles.get(t);if(!r)return{disabled:!1,toolNames:[],error:`Unknown bundle: ${t}`};if(ok.has(t))return{disabled:!1,toolNames:r.toolNames,error:`Core bundle "${t}" cannot be disabled`};if(!this.enabledBundles.has(t))return{disabled:!0,toolNames:r.toolNames,alreadyDisabled:!0};for(let i of r.toolNames)this.toolRegistry[i]?.disable();return this.enabledBundles.delete(t),$.info({bundleId:t,toolCount:r.toolNames.length},"Tool bundle disabled"),{disabled:!0,toolNames:r.toolNames}}listBundles(){return ym.map(t=>{let r=this.bundles.get(t.id);return{id:t.id,name:t.name,description:t.description,toolCount:r?r.toolNames.length:t.toolCount,enabled:this.enabledBundles.has(t.id)}})}isEnabled(t){return this.enabledBundles.has(t)}};function iD(e,t,r){UG(e,t),LG(e,t,r)}function UG(e,t){e.registerTool("list-tool-bundles",{description:"List available tool bundles. Tools are organized into domain bundles that can be enabled on demand. Core bundles (dice, world, chat, combat, characters) are always enabled. Enable additional bundles with enable-tool-bundle when you need their capabilities.",annotations:q},()=>({content:[{type:"text",text:JSON.stringify(t.listBundles())}]})),e.registerTool("enable-tool-bundle",{description:"Enable a tool bundle to access its tools. Call list-tool-bundles first to see available bundles. You can enable multiple bundles. Once enabled, the tools become available for use.",annotations:H,inputSchema:{bundleId:c.string().describe('Bundle ID to enable (e.g., "scenes", "compendium", "playlists")')}},({bundleId:r})=>{let i=t.enableBundle(r);return{content:[{type:"text",text:JSON.stringify(i)}],...i.enabled?{}:{isError:!0}}}),e.registerTool("disable-tool-bundle",{description:"Disable a previously enabled tool bundle to reduce active tool count. Core bundles (dice, world, chat, combat, characters) cannot be disabled. Call list-tool-bundles to see which bundles are currently enabled.",annotations:C,inputSchema:{bundleId:c.string().describe('Bundle ID to disable (e.g., "scenes", "compendium", "playlists")')}},({bundleId:r})=>{let i=t.disableBundle(r);return{content:[{type:"text",text:JSON.stringify(i)}],...i.disabled?{}:{isError:!0}}})}function LG(e,t,r){e.registerTool("get-server-diagnostics",{description:"Get Familiar server diagnostics: tool call metrics (counts, durations, errors) and bundle status. Use for troubleshooting performance or checking which bundles are active.",annotations:q},()=>({content:[{type:"text",text:JSON.stringify({bundles:t.listBundles(),metrics:r?.getSummary()??null})}]}))}function oD(e,t,r){return{query:async(i,n,o)=>(r.has(i)&&t.enableBundle("combat-ai"),e.query(i,n,o))}}var Ue=c.preprocess(Ud,c.record(c.string(),c.unknown()));var ui=[{annotations:q,name:"get-character",description:"Get a character's full stat block. Returns ability scores, HP, AC, skills, saves, items, spells, features, and biography. For a quick overview of all characters, use list-characters instead. Accepts character name (exact match) or Foundry actor ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID")})},{annotations:q,name:"list-characters",description:"List actors in the world with summary info (name, type, HP, AC, level). Returns a paginated list without full stat blocks. Filter by type: 'character' for PCs, 'npc' for NPCs, or 'all'. Response includes total count and hasMore flag. For full details on one character, use get-character instead.",inputSchema:c.object({type:c.enum(["all","character","npc"]).optional().describe("Filter by actor type (default: all)"),limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:q,name:"list-party-members",description:"Get all player-owned characters (the adventuring party). Returns a lightweight summary (name, HP, AC, level) by default. Set detailed:true for full character sheets including abilities, items, and effects. Use to quickly see party composition without listing all NPCs.",inputSchema:c.object({detailed:c.boolean().optional().describe("Return full character sheets instead of summaries (default: false)")})},{annotations:H,name:"update-character",description:'Update character data using Foundry dot-notation paths (e.g., {"system.attributes.hp.value": 25}). Use get-character first to see current values and valid paths. Accepts character name (exact match) or Foundry actor ID. Returns the updated actor name and ID.',inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),updates:Ue.describe('Foundry update data (e.g., {"system.attributes.hp.value": 25})')})},{annotations:q,name:"search-character-items",description:"Search a character's inventory, spells, and features. Returns a compact list of matching items (max 50) with IDs, names, and types. Use to find an item before calling get-character-item for full details. Filter by name substring, item type, or both.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),query:c.string().max(256).optional().describe('Filter by name substring (e.g., "sword", "fire")'),type:c.enum(["weapon","spell","feat","equipment","consumable","tool","loot","class","subclass","background","race"]).optional().describe("Filter by item type")})},{annotations:q,name:"get-character-item",description:"Get full details of a specific item, spell, or feature on a character. Returns description, properties, damage, range, and all game mechanics. Use the item ID from search-character-items results.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),itemId:c.string().max(256).describe("Item ID (from search-character-items results)")})},{annotations:C,name:"import-compendium-to-character",description:"Import an item or spell from a compendium pack onto a character's sheet. Copies the compendium entry as a new owned item. Use search-compendium (compendium bundle) first to find the pack key and entry ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),pack:c.string().max(256).describe('Compendium pack key (e.g., "dnd5e.spells")'),entryId:c.string().max(256).describe("Entry ID from search-compendium results")})},{annotations:ee,name:"delete-character-items",description:"Remove items, spells, or features from a character permanently. Accepts item names or Foundry document IDs. This is destructive and irreversible. Returns the actor name, count of deleted items, and their IDs.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),itemIds:c.array(c.string().max(256)).min(1).max(100).describe("Item names or Foundry document IDs to remove")})},{annotations:H,name:"rename-character",description:"Rename an actor and update its prototype token name to match. Returns the updated name. Accepts character name (exact match) or Foundry actor ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),name:c.string().max(256).describe("New name for the character")})},{annotations:C,name:"create-character",description:"Create a new blank character (NPC or PC) from scratch. Returns the new actor's ID. For creating variants of existing characters, use duplicate-character instead. To import a compendium creature, use the compendium bundle.",inputSchema:c.object({name:c.string().max(256).describe("Character name"),type:c.enum(["character","npc"]).optional().describe("Character type (default: npc)"),system:Ue.optional().describe("System data (e.g., ability scores, HP)"),img:c.string().max(256).optional().describe("Token/portrait image path"),folderId:c.string().max(256).optional().describe("Folder name or ID")})},{annotations:ee,name:"delete-character",description:"Delete a character from the world permanently. Removes the actor and all its owned items, spells, and features. This is irreversible. Tokens on scenes will become unlinked. Returns the deleted character name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID")})},{annotations:C,name:"duplicate-character",description:"Clone an existing character to create a copy with all items, spells, and features. Returns the new actor's ID. Use for creating NPC variants (e.g., 'Goblin Archer' from 'Goblin'). For creating characters from scratch, use create-character instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID to clone"),name:c.string().max(256).optional().describe('Name for the copy (default: "<original> (Copy)")')})},{annotations:wn,name:"assign-character-ownership",description:"Give a player ownership or limited access to a character. Permission levels: 0=NONE, 1=LIMITED (basic info), 2=OBSERVER (full view), 3=OWNER (full control). Use OWNER (3) to assign a character to a player. Use list-users to find valid user names/IDs. Returns the character name, user name, and applied permission level.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),user:c.string().max(256).describe("Player name or user ID"),level:c.number().int().min(0).max(3).describe("Permission level: 0=NONE, 1=LIMITED, 2=OBSERVER, 3=OWNER")})},{annotations:wn,name:"revoke-character-ownership",description:"Remove a player's access to a character by setting their permission to NONE. The character becomes invisible to that player unless default permissions allow it. Use list-users to find valid user names/IDs. Returns the character and user names.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),user:c.string().max(256).describe("Player name or user ID")})},{annotations:H,name:"update-character-item",description:'Update an item on a character using dot-notation paths (e.g., {"system.levels": 5, "system.equipped": true}). Use get-character-item first to see current values and valid paths. Accepts character name or actor ID.',inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),itemId:c.string().max(256).describe("Item ID on the character (from get-character or search-character-items)"),updates:Ue.describe("Foundry update data with dot-notation paths")})},{annotations:ee,name:"level-up-class",description:"Level up a class on a character. Increments the class level, adds average HP per level (half hit die + 1 + CON mod), and auto-updates proficiency and spell slots. Does not auto-grant class features \u2014 use import-compendium-to-character for those.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),className:c.string().max(256).describe('Class name on the character (e.g., "Rogue", "Paladin")'),levels:c.number().int().min(1).max(19).optional().describe("Number of levels to add (default: 1)")})}];var xm=ui[12],wm=ui[13];function aD(e,t){e.registerTool(xm.name,{description:xm.description,annotations:xm.annotations,inputSchema:xm.inputSchema.shape},async({identifier:r,user:i,level:n})=>v(await t.query("assign-character-ownership",{identifier:r,user:i,level:n}))),e.registerTool(wm.name,{description:wm.description,annotations:wm.annotations,inputSchema:wm.inputSchema.shape},async({identifier:r,user:i})=>v(await t.query("revoke-character-ownership",{identifier:r,user:i})))}var[km,$m,Tm,Im,Em,Pm,Rm,zm,Om,jm,Dm,Nm]=ui,Cm=ui[14],Am=ui[15];function qG(e,t){e.registerTool(km.name,{description:km.description,annotations:km.annotations,inputSchema:km.inputSchema.shape},async({identifier:r})=>v(await t.query("get-character",{identifier:r}))),e.registerTool($m.name,{description:$m.description,annotations:$m.annotations,inputSchema:$m.inputSchema.shape},async({type:r,limit:i,offset:n})=>v(await t.query("list-characters",{type:r,limit:i,offset:n}))),e.registerTool(Tm.name,{description:Tm.description,annotations:Tm.annotations,inputSchema:Tm.inputSchema.shape},async({detailed:r})=>v(await t.query("list-party-members",{detailed:r})))}function ZG(e,t){e.registerTool(Em.name,{description:Em.description,annotations:Em.annotations,inputSchema:Em.inputSchema.shape},async({identifier:r,query:i,type:n})=>v(await t.query("search-character-items",{identifier:r,query:i,type:n}))),e.registerTool(Pm.name,{description:Pm.description,annotations:Pm.annotations,inputSchema:Pm.inputSchema.shape},async({identifier:r,itemId:i})=>v(await t.query("get-character-item",{identifier:r,itemId:i})))}function FG(e,t){e.registerTool(Rm.name,{description:Rm.description,annotations:Rm.annotations,inputSchema:Rm.inputSchema.shape},async({identifier:r,pack:i,entryId:n})=>v(await t.query("import-compendium-to-character",{identifier:r,pack:i,entryId:n}))),e.registerTool(zm.name,{description:zm.description,annotations:zm.annotations,inputSchema:zm.inputSchema.shape},async({identifier:r,itemIds:i})=>v(await t.query("delete-character-items",{identifier:r,itemIds:i}))),e.registerTool(Cm.name,{description:Cm.description,annotations:Cm.annotations,inputSchema:Cm.inputSchema.shape},async({identifier:r,itemId:i,updates:n})=>v(await t.query("update-character-item",{identifier:r,itemId:i,updates:n}))),e.registerTool(Am.name,{description:Am.description,annotations:Am.annotations,inputSchema:Am.inputSchema.shape},async({identifier:r,className:i,levels:n})=>v(await t.query("level-up-class",{identifier:r,className:i,levels:n})))}function VG(e,t){ZG(e,t),FG(e,t)}function BG(e,t){e.registerTool(Im.name,{description:Im.description,annotations:Im.annotations,inputSchema:Im.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-character",{identifier:r,updates:i}))),e.registerTool(Om.name,{description:Om.description,annotations:Om.annotations,inputSchema:Om.inputSchema.shape},async({identifier:r,name:i})=>v(await t.query("rename-character",{identifier:r,name:i})))}function HG(e,t){e.registerTool(jm.name,{description:jm.description,annotations:jm.annotations,inputSchema:jm.inputSchema.shape},async({name:r,type:i,system:n,img:o,folderId:a})=>v(await t.query("create-character",{name:r,type:i,system:n,img:o,folderId:a}))),e.registerTool(Dm.name,{description:Dm.description,annotations:Dm.annotations,inputSchema:Dm.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-character",{identifier:r}))),e.registerTool(Nm.name,{description:Nm.description,annotations:Nm.annotations,inputSchema:Nm.inputSchema.shape},async({identifier:r,name:i})=>v(await t.query("duplicate-character",{identifier:r,name:i})))}function WG(e,t){BG(e,t),HG(e,t),aD(e,t)}function sD(e,t){qG(e,t),VG(e,t),WG(e,t)}var cD=3e4,lD=5e3,[Mm,Um,Lm,ak,sk,qm,ck,lk,uk,dk,Zm,Fm,Vm,Bm,Hm,Wm,Gm]=gm;function GG(e,t){e.registerTool(Mm.name,{description:Mm.description,annotations:Mm.annotations,inputSchema:Mm.inputSchema.shape},async({identifier:r,condition:i})=>v(await t.query("toggle-condition",{identifier:r,condition:i}))),e.registerTool(Um.name,{description:Um.description,annotations:Um.annotations,inputSchema:Um.inputSchema.shape},async({identifiers:r,condition:i})=>v(await t.query("apply-condition-to-group",{identifiers:r,condition:i})))}function JG(e,t){e.registerTool(Lm.name,{description:Lm.description,annotations:Lm.annotations,inputSchema:Lm.inputSchema.shape},async({identifiers:r,includeHidden:i})=>v(await t.query("start-combat",{identifiers:r,includeHidden:i}))),e.registerTool(ak.name,{description:ak.description,annotations:ak.annotations},async()=>v(await t.query("end-combat"))),e.registerTool(sk.name,{description:sk.description,annotations:sk.annotations},async()=>v(await t.query("get-combat"))),e.registerTool(qm.name,{description:qm.description,annotations:qm.annotations,inputSchema:qm.inputSchema.shape},async({combatantIds:r})=>v(await t.query("roll-initiative",{combatantIds:r}))),e.registerTool(ck.name,{description:ck.description,annotations:ck.annotations},async()=>v(await t.query("roll-npc-initiative"))),e.registerTool(Gm.name,{description:Gm.description,annotations:Gm.annotations,inputSchema:Gm.inputSchema.shape},async({identifiers:r,rollNpcOnly:i,includeHidden:n},o)=>{let a=st(o,"Starting combat\u2026",lD);try{return v(await t.query("quick-start-combat",{identifiers:r,rollNpcOnly:i,includeHidden:n},cD))}finally{a()}})}function KG(e,t){e.registerTool(lk.name,{description:lk.description,annotations:lk.annotations},async()=>v(await t.query("next-turn"))),e.registerTool(uk.name,{description:uk.description,annotations:uk.annotations},async()=>v(await t.query("next-round"))),e.registerTool(dk.name,{description:dk.description,annotations:dk.annotations},async()=>v(await t.query("get-available-conditions")))}function QG(e,t){JG(e,t),KG(e,t)}function YG(e,t){e.registerTool(Zm.name,{description:Zm.description,annotations:Zm.annotations,inputSchema:Zm.inputSchema.shape},async({targets:r,reason:i})=>v(await t.query("apply-damage",{targets:r,reason:i}))),e.registerTool(Fm.name,{description:Fm.description,annotations:Fm.annotations,inputSchema:Fm.inputSchema.shape},async({amount:r,identifier:i})=>v(await t.query("heal-party",{amount:r,identifier:i}))),e.registerTool(Vm.name,{description:Vm.description,annotations:Vm.annotations,inputSchema:Vm.inputSchema.shape},async({type:r},i)=>{let n=st(i,"Resting party\u2026",lD);try{return v(await t.query("rest-party",{type:r},cD))}finally{n()}})}function XG(e,t){e.registerTool(Bm.name,{description:Bm.description,annotations:Bm.annotations,inputSchema:Bm.inputSchema.shape},async({identifiers:r})=>v(await t.query("add-combatant",{identifiers:r}))),e.registerTool(Hm.name,{description:Hm.description,annotations:Hm.annotations,inputSchema:Hm.inputSchema.shape},async({combatantIds:r})=>v(await t.query("remove-combatant",{combatantIds:r}))),e.registerTool(Wm.name,{description:Wm.description,annotations:Wm.annotations,inputSchema:Wm.inputSchema.shape},async({amount:r,identifiers:i,reason:n})=>v(await t.query("award-xp",{amount:r,identifiers:i,reason:n})))}function uD(e,t){GG(e,t),QG(e,t),YG(e,t),XG(e,t)}var pk=[{annotations:q,name:"get-combat-snapshot",description:"Get a comprehensive combat snapshot for AI decision-making. Returns the current combatant's full stat block, all combatants with HP/AC/conditions, battlefield token positions with distances, and available weapons/spells/features. Designed to give an AI everything needed to decide an NPC's turn in a single call."},{annotations:C,name:"resolve-attack",description:"Resolve an NPC weapon attack against a target. Rolls the attack (with optional advantage/disadvantage), compares against target AC, rolls damage on hit (doubled dice on critical), applies damage to the target, and posts the result to chat. By default the attack is rejected if the target is outside the weapon's range \u2014 pass force:true for narrative cases (gliding monster, teleport-attack, mid-flight pounce); an audit message is posted to chat when this is used.",inputSchema:c.object({attacker:c.string().max(256).describe("Name or Foundry token ID of the attacking NPC"),target:c.string().max(256).describe("Name or Foundry token ID of the target"),weapon:c.string().max(256).describe("Weapon name or item ID on the attacker"),advantage:c.boolean().optional().describe("Roll with advantage (2d20 keep highest)"),disadvantage:c.boolean().optional().describe("Roll with disadvantage (2d20 keep lowest)"),smiteLevel:c.number().int().min(1).max(5).optional().describe("Divine Smite: expend a spell slot at this level for bonus radiant damage (2d8 + 1d8/level above 1st)"),force:c.boolean().optional().describe("Bypass the attacker-vs-target range check for narrative cases (e.g. a creature gliding into melee mid-air). Cover, walls, conditions, and everything else still apply. An audit message is posted to chat for transparency."),reason:c.string().max(280).optional().describe('Why the force bypass is justified (e.g. "gliding into melee", "teleport-attack chain", "Sentinel reaction"). Recorded in the [Familiar audit] chat.')})},{annotations:C,name:"resolve-movement",description:"Move a token to a new position on the battlefield during combat. Coordinates are in pixels (from scene top-left). Calculates distance moved in game units (feet), compares against the actor's walk speed, updates the token position, and reports movement remaining. Use this during combat turns instead of move-token \u2014 it enforces movement speed limits.",inputSchema:c.object({identifier:c.string().max(256).describe("Token name or Foundry ID to move"),x:c.number().int().describe("Target X position in pixels (from scene top-left)"),y:c.number().int().describe("Target Y position in pixels (from scene top-left)")})},{annotations:C,name:"resolve-spell",description:"Cast a spell from an NPC. Consumes a spell slot (if not a cantrip), resolves spell attacks or saving throws per target, rolls and applies damage, handles concentration, and posts results to chat. By default the cast is rejected if any target is outside the spell's range \u2014 pass force:true for narrative cases (teleport-bound spell, scrying-anchor target); an audit message is posted to chat when this is used.",inputSchema:c.object({caster:c.string().max(256).describe("Name or Foundry token ID of the caster"),spell:c.string().max(256).describe("Spell name or item ID on the caster"),targets:c.array(c.string().max(256)).max(50).describe("Token names or Foundry IDs of the targets"),upcastLevel:c.number().int().min(1).max(9).optional().describe("Cast at a higher spell level (upcasting)"),force:c.boolean().optional().describe("Bypass the caster-vs-target range check for narrative cases (e.g. scrying anchor, teleport-bound delivery). Spell mechanics and target validity still apply. An audit message is posted to chat for transparency."),reason:c.string().max(280).optional().describe('Why the force bypass is justified (e.g. "teleport-bound delivery", "scrying anchor target"). Recorded in the [Familiar audit] chat.')})},{annotations:C,name:"resolve-saving-throw",description:"Force a saving throw on a target. Rolls d20 + save modifier against a DC and reports success or failure.",inputSchema:c.object({target:c.string().max(256).describe("Name or Foundry token ID of the target"),ability:c.enum(["str","dex","con","int","wis","cha"]).describe("Ability for the save (str, dex, con, int, wis, cha)"),dc:c.number().int().min(1).max(99).describe("Difficulty class for the saving throw")})},{annotations:C,name:"resolve-death-save",description:"Resolve a death saving throw for a target at 0 HP. Rolls 1d20: natural 20 revives with 1 HP, natural 1 counts as two failures, >= 10 is a success, < 10 is a failure. 3 successes stabilize, 3 failures kill.",inputSchema:c.object({target:c.string().max(256).describe("Name or Foundry token ID of the target at 0 HP")})},{annotations:C,name:"resolve-ability-check",description:"Roll an ability check or skill check against a DC. Use for contested checks (grapple, shove), skill checks in combat (Athletics, Acrobatics), and Counterspell level contests. Unlike saving throws, ability checks use the raw ability modifier (or skill total if proficient).",inputSchema:c.object({target:c.string().max(256).describe("Token name or ID of the creature making the check"),ability:c.enum(["str","dex","con","int","wis","cha"]).describe("Ability to check (str, dex, con, int, wis, cha)"),dc:c.number().int().min(1).max(99).describe("Difficulty class to beat"),skill:c.enum(["acr","ani","arc","ath","dec","his","ins","itm","inv","med","nat","prc","prf","per","rel","sle","ste","sur"]).optional().describe("Skill to use (e.g. athletics, acrobatics) \u2014 adds proficiency bonus if proficient"),advantage:c.boolean().optional().describe("Force advantage on the roll"),disadvantage:c.boolean().optional().describe("Force disadvantage on the roll")})},{annotations:C,name:"resolve-multiattack",description:"Resolve a Multiattack action for an NPC. Parses the Multiattack feature description, identifies component attacks, and resolves each. Returns all attack results plus a damage summary. Falls back with an error message if parsing fails \u2014 use individual resolve-attack calls instead.",inputSchema:c.object({attacker:c.string().max(256).describe("Token name or ID of the attacker with Multiattack"),target:c.string().max(256).describe("Token name or ID of the target"),force:c.boolean().optional().describe("Narrative override: bypass the L1-A action-economy check (e.g. mid-fight Action Surge interaction the resolver does not yet model). Posts a [Familiar audit] chat trail. Does NOT bypass component-attack validation (range, cover, conditions still apply)."),reason:c.string().max(280).optional().describe('Why the force bypass is justified (e.g. "Action Surge", "legendary multiattack chain"). Recorded in the [Familiar audit] chat.')})},{annotations:C,name:"resolve-action",description:"Use the Dash, Dodge, or Disengage action. Dash doubles movement speed for the turn. Dodge gives disadvantage on incoming attacks until next turn. Disengage prevents opportunity attacks. Consumes the action for the turn \u2014 duplicate use throws (one Action per turn per PHB). Pass force:true for narrative cases where a feature legitimately bypasses the action-economy duplicate gate (Sentinel counter-OA, Reactive Strike, magical contingency); an audit chat message is posted to track the override.",inputSchema:c.object({identifier:c.string().max(256).describe("Token name or ID"),action:c.enum(["dash","dodge","disengage"]).describe("Action to take"),force:c.boolean().optional().describe("Bypass the action-economy duplicate-action gate (Sentinel/Reactive features, magical contingency, multi-action class features). Posts an audit chat message recording the override. Note: scope is action-economy ONLY \u2014 unlike resolve-attack/resolve-spell where force bypasses range, this force bypasses the duplicate-action gate. Default false."),reason:c.string().max(280).optional().describe('Why the force bypass is justified (e.g. "Sentinel reaction", "magical contingency"). Recorded in the [Familiar audit] chat.')})},{annotations:C,name:"resolve-legendary-action",description:"Use a legendary action between other creatures' turns. Validates charges, deducts the cost, and executes the action. Cannot be used on the creature's own turn. Legendary actions recharge at the start of the creature's turn.",inputSchema:c.object({actor:c.string().max(256).describe("Token name or ID of the creature with legendary actions"),feature:c.string().max(256).describe("Name of the legendary action feature to use")})},{annotations:q,name:"query-aoe-targets",description:"Find tokens within an area of effect. Supports five shapes: 'circle' (default \u2014 pass radius), 'cone' (PHB triangular 1:1 cone \u2014 pass direction + lengthFt; optional angleDeg overrides 53.13\xB0), 'line' (rectangle \u2014 pass direction + lengthFt; optional widthFt overrides 5ft), 'cube' (axis-aligned-in-local-frame square \u2014 pass direction + lengthFt for size), 'ray' (line variant \u2014 pass direction + lengthFt; optional widthFt overrides 5ft). Origin pixel coords come from battlefield token positions. Direction in degrees (0=East, 90=South, clockwise). Returns all tokens hit sorted by distance from origin. Use this to plan AoE spell targeting before casting.",inputSchema:c.object({x:c.number().int().describe("Origin X in pixels (cone apex / line start / cube corner / ray start / circle center)"),y:c.number().int().describe("Origin Y in pixels"),radius:c.number().min(1).max(1e3).optional().describe("Circle radius in feet \u2014 required when shape=circle or shape omitted"),shape:c.enum(["circle","cone","line","cube","ray"]).optional().describe("AoE shape. Defaults to 'circle' if omitted (backward-compatible with v2.6.0 clients)"),direction:c.number().min(0).max(360).optional().describe("Direction in degrees (0=East, 90=South, clockwise). Required for cone/line/cube/ray"),lengthFt:c.number().min(1).max(500).optional().describe("Primary dimension in feet (cone length / line length / cube size / ray length). Required for non-circle shapes"),widthFt:c.number().min(1).max(50).optional().describe("Width in feet \u2014 line/ray only. Defaults to 5 ft"),angleDeg:c.number().min(1).max(180).optional().describe("Cone aperture in degrees. Defaults to PHB 53.13\xB0 (1:1 cone) if omitted"),excludeTokenId:c.string().max(256).optional().describe("Token ID to exclude from results (e.g. the caster, so a dragon does not see itself in its own breath cone)")})},{annotations:q,name:"get-movement-options",description:"Get pre-scored tactical movement options for the current combatant. Returns up to 5 positions within walk speed, each scored and categorized (advance, retreat, flank, hold, approach-ally). Includes opportunity attack risk warnings and cover analysis. Scores factor in combat role (melee/ranged/caster), HP-based retreat urgency, and creature intelligence. Coordinates are in pixels for resolve-movement. Call this before deciding where to move.",inputSchema:c.object({identifier:c.string().max(256).describe("Token name or Foundry ID of the moving token")})},{annotations:q,name:"list-undoable-workflows",description:"List the combat actions that can be undone, newest first. Each entry is one resolved attack, multiattack, or spell from the current encounter (max 15 retained, FIFO). Returns each workflow's id, label, round, and turn. Only the most recent workflow can actually be undone (see undo-last-workflow). Call this to see what undo-last-workflow would revert."},{annotations:ee,name:"undo-last-workflow",description:"Revert the most recent combat workflow (attack, multiattack, or spell) by restoring every affected actor to the HP, spell slots, death saves, active effects, and defeated state captured just before it ran. Undo is last-only: pass no workflowId to undo the top of the stack, or pass a workflowId that MUST match the most recent entry (otherwise the call is rejected with the current top named, so you undo intervening workflows first). Foundry has no transactional rollback \u2014 this is a best-effort state restore, not a perfect replay. Posts an [UNDONE] chat message for the GM.",inputSchema:c.object({workflowId:c.string().max(256).optional().describe("Optional id of the workflow to undo (from list-undoable-workflows). Must be the most recent workflow. Omit to undo the top of the stack.")})},{annotations:C,name:"resolve-grapple-shove",description:"Resolve a 2024 grapple or shove (a special melee attack). The target makes a Strength OR Dexterity saving throw (its best) against DC 8 + the attacker's Strength modifier + proficiency bonus; on a failure the grapple applies the Grappled condition, and a shove applies Prone (mode:'prone', default) or pushes the target 5 ft (mode:'push'). On a success nothing happens. Requires the target be within reach (melee) \u2014 pass force:true for narrative cases (a creature gliding/teleporting into a grab); an audit message is posted to chat.",inputSchema:c.object({attacker:c.string().max(256).describe("Name or Foundry token ID of the grappling/shoving creature"),target:c.string().max(256).describe("Name or Foundry token ID of the target"),type:c.enum(["grapple","shove"]).describe("'grapple' applies Grappled on a failed save; 'shove' applies Prone or pushes 5 ft"),mode:c.enum(["prone","push"]).optional().describe("Shove only: 'prone' (default) knocks the target prone, 'push' moves it 5 ft away. Ignored for grapple."),force:c.boolean().optional().describe("Bypass the reach + turn/action-economy checks for narrative cases (gliding/teleporting grab). An audit message is posted to chat."),reason:c.string().max(280).optional().describe("Why the force bypass is justified. Recorded in the [Familiar audit] chat.")})}];var kn=c.object({x:c.number(),y:c.number()});var mk=c.object({action:c.string().max(256),message:c.string().max(1024)}),fk=c.object({success:c.boolean(),featureName:c.string().max(256),cost:c.number(),chargesRemaining:c.number(),message:c.string().max(1024)}),eJ=c.object({tokenId:c.string().max(256),name:c.string().max(256),distance:c.number()}),hk=c.object({shape:c.string().max(256),center:kn,radius:c.number(),targets:c.array(eJ)}),tJ=c.object({x:c.number(),y:c.number(),label:c.string().max(256),category:c.string().max(256),score:c.number(),distanceFt:c.number(),meleeTargets:c.array(c.string().max(256)),nearbyAllies:c.array(c.string().max(256)),opportunityAttackRisk:c.boolean().optional(),coverFromEnemy:c.string().max(256).optional()}),gk=c.object({tokenName:c.string().max(256),currentPosition:kn,speedFt:c.number(),options:c.array(tJ)});var rJ=c.object({value:c.number(),max:c.number(),temp:c.number()}),nJ=c.object({total:c.number(),natural:c.number(),modifier:c.number()}),iJ=c.object({total:c.number(),formula:c.string().max(256),type:c.string().max(256)}),oJ=c.object({property:c.enum(["topple","push","sap","slow"]),applied:c.boolean(),save:c.object({ability:c.string().max(8),dc:c.number(),total:c.number(),saved:c.boolean()}).optional(),condition:c.string().max(64).optional(),pushedFt:c.number().optional(),effect:c.string().max(64).optional(),note:c.string().max(256).optional()}),yk=c.object({hit:c.boolean(),critical:c.boolean(),attackRoll:nJ,damageRoll:iJ.nullable(),targetHpAfter:rJ,appliedDamage:c.number().optional(),damageModifiers:c.array(c.string().max(512)).optional(),conditionReasons:c.array(c.string().max(512)).optional(),reactionUsed:c.string().max(256).optional(),mastery:oJ.optional()}),aJ=c.object({name:c.string().max(256),hit:c.boolean(),saved:c.boolean(),damage:c.number(),appliedDamage:c.number().optional(),damageModifiers:c.array(c.string().max(512)).optional(),healed:c.number().optional(),conditionReasons:c.array(c.string().max(512)).optional()}),sJ=c.object({level:c.number(),remaining:c.number()}),cJ=c.object({missingEffect:c.boolean().optional(),inertEffect:c.boolean().optional()}),dD=c.object({success:c.boolean(),spellName:c.string().max(256),targets:c.array(aJ),slotExpended:sJ.optional(),effectsApplied:c.array(c.string().max(256)).optional(),audit:cJ.optional(),teleported:c.object({from:kn,to:kn,participants:c.array(c.string().max(256))}).optional()}),lJ=c.object({total:c.number(),natural:c.number(),modifier:c.number()}),pD=c.object({success:c.boolean(),roll:lJ,dc:c.number(),ability:c.string().max(256),autoFail:c.boolean().optional(),conditionReasons:c.array(c.string().max(512)).optional(),legendaryResistanceUsed:c.boolean().optional(),legendaryResistanceChargesRemaining:c.number().optional()}),mD=c.object({type:c.enum(["grapple","shove"]),saved:c.boolean(),applied:c.boolean(),dc:c.number(),ability:c.enum(["str","dex"]),roll:c.object({total:c.number(),natural:c.number(),modifier:c.number()}),condition:c.string().max(64).optional(),pushedFt:c.number().optional(),autoFail:c.boolean().optional(),conditionReasons:c.array(c.string().max(512)).optional(),legendaryResistanceUsed:c.boolean().optional(),legendaryResistanceChargesRemaining:c.number().optional()}),uJ=c.object({successes:c.number(),failures:c.number()}),fD=c.object({natural:c.number(),success:c.boolean(),deathSaves:uJ,stabilized:c.boolean(),revived:c.boolean(),dead:c.boolean()}),xse=c.object({targetName:c.string().max(256),dc:c.number(),roll:c.number(),saved:c.boolean(),concentrationLost:c.boolean()}),dJ=c.object({total:c.number(),natural:c.number(),modifier:c.number()}),hD=c.object({success:c.boolean(),roll:dJ,dc:c.number(),ability:c.string().max(256),skill:c.string().max(256).optional(),conditionReasons:c.array(c.string().max(512)).optional()}),gD=c.object({success:c.boolean(),from:kn,to:kn,distanceMoved:c.number(),movementRemaining:c.number(),overlap:c.boolean().optional()}),yD=c.object({attacks:c.array(yk),totalDamage:c.number(),summary:c.string().max(1024)});var vD=c.object({undone:c.literal(!0),workflowId:c.string(),label:c.string(),restoredActors:c.number()}),pJ=c.object({workflowId:c.string(),label:c.string(),round:c.number(),turn:c.number()}),bD=c.object({workflows:c.array(pJ)});var[vk,Jm,Km,Qm,Ym,Xm,ef,tf,rf,nf,of,af,bk,sf]=pk,cf=pk[14];async function It(e,t,r){let i=await li(e,t,r);return i.ok?{content:[{type:"text",text:JSON.stringify(i.data)}]}:i.envelope}function mJ(e,t){e.registerTool(vk.name,{description:vk.description,annotations:vk.annotations},async()=>v(await t.query("get-combat-snapshot"))),e.registerTool(Jm.name,{description:Jm.description,annotations:Jm.annotations,inputSchema:Jm.inputSchema.shape},async({attacker:r,target:i,weapon:n,advantage:o,disadvantage:a,smiteLevel:s,force:l,reason:u})=>It("resolve-attack",yk,()=>t.query("resolve-attack",{attacker:r,target:i,weapon:n,advantage:o,disadvantage:a,smiteLevel:s,force:l,reason:u}))),e.registerTool(Qm.name,{description:Qm.description,annotations:Qm.annotations,inputSchema:Qm.inputSchema.shape},async({caster:r,spell:i,targets:n,upcastLevel:o,force:a,reason:s})=>It("resolve-spell",dD,()=>t.query("resolve-spell",{caster:r,spell:i,targets:n,upcastLevel:o,force:a,reason:s}))),e.registerTool(Km.name,{description:Km.description,annotations:Km.annotations,inputSchema:Km.inputSchema.shape},async({identifier:r,x:i,y:n})=>It("resolve-movement",gD,()=>t.query("resolve-movement",{identifier:r,x:i,y:n})))}function fJ(e,t){e.registerTool(Ym.name,{description:Ym.description,annotations:Ym.annotations,inputSchema:Ym.inputSchema.shape},async({target:r,ability:i,dc:n})=>It("resolve-saving-throw",pD,()=>t.query("resolve-saving-throw",{target:r,ability:i,dc:n}))),e.registerTool(Xm.name,{description:Xm.description,annotations:Xm.annotations,inputSchema:Xm.inputSchema.shape},async({target:r})=>It("resolve-death-save",fD,()=>t.query("resolve-death-save",{target:r}))),e.registerTool(ef.name,{description:ef.description,annotations:ef.annotations,inputSchema:ef.inputSchema.shape},async({target:r,ability:i,dc:n,skill:o,advantage:a,disadvantage:s})=>It("resolve-ability-check",hD,()=>t.query("resolve-ability-check",{target:r,ability:i,dc:n,skill:o,advantage:a,disadvantage:s}))),e.registerTool(tf.name,{description:tf.description,annotations:tf.annotations,inputSchema:tf.inputSchema.shape},async({attacker:r,target:i,force:n,reason:o})=>It("resolve-multiattack",yD,()=>t.query("resolve-multiattack",{attacker:r,target:i,force:n,reason:o}))),hJ(e,t)}function hJ(e,t){e.registerTool(rf.name,{description:rf.description,annotations:rf.annotations,inputSchema:rf.inputSchema.shape},async({identifier:r,action:i,force:n,reason:o})=>It("resolve-action",mk,()=>t.query("resolve-action",{identifier:r,action:i,force:n,reason:o}))),e.registerTool(cf.name,{description:cf.description,annotations:cf.annotations,inputSchema:cf.inputSchema.shape},async({attacker:r,target:i,type:n,mode:o,force:a,reason:s})=>It("resolve-grapple-shove",mD,()=>t.query("resolve-grapple-shove",{attacker:r,target:i,type:n,mode:o,force:a,reason:s})))}function gJ(e,t){e.registerTool(nf.name,{description:nf.description,annotations:nf.annotations,inputSchema:nf.inputSchema.shape},async({actor:r,feature:i})=>It("resolve-legendary-action",fk,()=>t.query("resolve-legendary-action",{actor:r,feature:i}))),e.registerTool(of.name,{description:of.description,annotations:of.annotations,inputSchema:of.inputSchema.shape},async({x:r,y:i,radius:n,shape:o,direction:a,lengthFt:s,widthFt:l,angleDeg:u,excludeTokenId:d})=>It("query-aoe-targets",hk,()=>t.query("query-aoe-targets",{x:r,y:i,radius:n,shape:o,direction:a,lengthFt:s,widthFt:l,angleDeg:u,excludeTokenId:d}))),e.registerTool(af.name,{description:af.description,annotations:af.annotations,inputSchema:af.inputSchema.shape},async({identifier:r})=>It("get-movement-options",gk,()=>t.query("get-movement-options",{identifier:r}))),e.registerTool(bk.name,{description:bk.description,annotations:bk.annotations},async()=>It("list-undoable-workflows",bD,()=>t.query("list-undoable-workflows"))),e.registerTool(sf.name,{description:sf.description,annotations:sf.annotations,inputSchema:sf.inputSchema.shape},async({workflowId:r})=>It("undo-last-workflow",vD,()=>t.query("undo-last-workflow",{workflowId:r})))}function _D(e,t){mJ(e,t),fJ(e,t),gJ(e,t)}var SD=[{annotations:q,name:"search-compendium",description:"Search compendium packs for items, creatures, spells, or journal entries by name. Returns matching entries with names, pack keys, and IDs. For Actor packs, supports filtering by CR, creature type (beast/undead/fiend/etc.), and size. Use get-compendium-entry to read full stat blocks or descriptions of a result.",inputSchema:c.object({query:c.string().max(256).describe('Search term (e.g., "fireball", "goblin", "longsword")'),type:c.string().max(256).optional().describe('Filter by pack type: "Actor", "Item", "JournalEntry", etc.'),cr:c.number().min(0).optional().describe("Filter creatures by Challenge Rating (e.g., 0.25, 1, 5)"),creatureType:c.string().max(256).optional().describe('Filter creatures by type (e.g., "beast", "undead", "fiend")'),size:c.enum(["tiny","sm","med","lg","huge","grg"]).optional().describe("Filter creatures by size")})},{annotations:q,name:"get-compendium-entry",description:"Get full details of a compendium entry. Returns complete stat blocks for creatures, spell descriptions with mechanics, or item properties (HTML stripped to plain text). For JournalEntry packs, returns the entry's pages as plain text (paginated via pageOffset/pageLimit). For Adventure packs, returns a manifest of the names and IDs of every contained journal, scene, actor, item, table, playlist, macro, and card. Use the pack key and ID from search-compendium results.",inputSchema:c.object({pack:c.string().max(256).describe('Pack collection key (e.g., "dnd5e.monsters")'),entryId:c.string().max(256).describe("Entry ID from search results"),pageOffset:c.number().int().min(0).optional().describe("JournalEntry packs only: index of the first page to return (default 0)"),pageLimit:c.number().int().min(1).max(50).optional().describe("JournalEntry packs only: max pages to return (default 10, max 50)")})},{annotations:q,name:"list-compendium-packs",description:"List all available compendium packs. Returns each pack's ID (collection key, e.g., 'dnd5e.spells'), document type (Actor, Item, JournalEntry, Scene, etc.), human-readable label, and entry count. Use the returned pack IDs with search-compendium and get-compendium-entry to find and retrieve specific entries."},{annotations:C,name:"create-character-from-compendium",description:"Import a creature or NPC from a compendium pack into the world as a new actor with its full stat block, items, spells, and features. Returns the new actor ID. Use search-compendium first to find the pack key and entry ID. For creating a blank character from scratch, use create-character (characters bundle) instead.",inputSchema:c.object({pack:c.string().max(256).describe('Pack collection key (e.g., "dnd5e.monsters")'),entryId:c.string().max(256).describe("Entry ID from search results"),name:c.string().max(256).optional().describe('Override actor name (e.g., "Goblin Archer")')})}];var yJ=3e4,vJ=5e3,[lf,uf,_k,df]=SD;function xD(e,t){e.registerTool(lf.name,{description:lf.description,annotations:lf.annotations,inputSchema:lf.inputSchema.shape},async({query:r,type:i,cr:n,creatureType:o,size:a})=>v(await t.query("search-compendium",{query:r,type:i,cr:n,creatureType:o,size:a}))),e.registerTool(uf.name,{description:uf.description,annotations:uf.annotations,inputSchema:uf.inputSchema.shape},async({pack:r,entryId:i,pageOffset:n,pageLimit:o},a)=>{let s=st(a,"Reading compendium entry\u2026",vJ);try{return v(await t.query("get-compendium-entry",{pack:r,entryId:i,pageOffset:n,pageLimit:o},yJ))}finally{s()}}),e.registerTool(_k.name,{description:_k.description,annotations:_k.annotations},async()=>v(await t.query("list-compendium-packs"))),e.registerTool(df.name,{description:df.description,annotations:df.annotations,inputSchema:df.inputSchema.shape},async({pack:r,entryId:i,name:n})=>v(await t.query("create-character-from-compendium",{pack:r,entryId:i,name:n})))}var wD=[{annotations:C,name:"roll-dice",description:"Roll a dice formula and return the numeric result. Supports standard notation (1d20+5, 2d6+3) and advanced syntax (4d6kh3 keep highest, 2d20kl1 keep lowest). Result is posted to Foundry chat by default. For attack rolls or spell damage during combat, prefer resolve-attack or resolve-spell (combat-ai bundle) instead.",inputSchema:c.object({formula:c.string().max(256).describe('Dice formula (e.g., "1d20+5", "2d6+3", "4d6kh3")'),showInChat:c.boolean().optional().describe("Show the roll result in Foundry chat (default: true)")})}];function kD(e,t){let[r]=wD;e.registerTool(r.name,{description:r.description,annotations:r.annotations,inputSchema:r.inputSchema.shape},async({formula:i,showInChat:n})=>v(await t.query("roll-dice",{formula:i,showInChat:n})))}var $D=[{annotations:q,name:"list-journals",description:"List journal entries in the world with pagination. Returns each entry's name, ID, page count with page names/IDs, and folder path. Response includes total count and hasMore flag. Filter by folder name for organized worlds. For searching journal content, use search-journals instead.",inputSchema:c.object({folder:c.string().max(256).optional().describe("Filter by folder name (substring match)"),limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:q,name:"search-journals",description:"Search journal entries by title and page content text. Returns matching entries with names, IDs, and text snippets showing where the search term appears. Use to find specific lore, notes, or session records. For browsing all journals, use list-journals instead.",inputSchema:c.object({query:c.string().max(256).describe("Search term to find in journal titles and page content")})},{annotations:q,name:"get-journal",description:"Get a full journal entry with all page content rendered as plain text (HTML stripped). Returns the entry name and all pages with their names, IDs, types, and full content. For reading a single page, use get-journal-page instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID")})},{annotations:q,name:"get-journal-page",description:"Get a single page from a journal entry with full content. Use the page ID from list-journals or get-journal results. For reading all pages at once, use get-journal instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID"),pageId:c.string().max(256).describe("Page ID (from list-journals or get-journal results)")})},{annotations:C,name:"create-journal",description:"Create a new journal entry, optionally with initial pages of content. Returns the new journal ID and page IDs. Pages support HTML formatting. Use for session notes, lore, NPC details, handouts, or quest tracking.",inputSchema:c.object({name:c.string().max(256).describe("Journal entry name"),folderId:c.string().max(256).optional().describe("Folder name or ID to place the journal in"),pages:c.array(c.object({name:c.string().max(256),content:c.string().max(5e4)})).max(100).optional().describe("Initial pages to create (each with name and HTML/text content)")})},{annotations:C,name:"create-journal-page",description:"Add a new page to an existing journal entry. Returns the new page ID. Supports text (HTML or plain text), image, video, and PDF page types. Default type is text.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID"),name:c.string().max(256).describe("Page name"),content:c.string().max(5e4).describe("Page content (HTML or plain text)"),type:c.enum(["text","image","video","pdf"]).optional().describe("Page type (default: text)")})},{annotations:H,name:"update-journal",description:"Rename a journal entry. For updating page content, use update-journal-page instead. Moving a journal between folders and changing ownership are not supported through this tool.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID"),name:c.string().max(256).describe("New journal entry name")})},{annotations:H,name:"update-journal-page",description:"Replace the content of an existing journal page. Accepts HTML or plain text. Use get-journal first to read the current content before overwriting.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID"),pageId:c.string().max(256).describe("Page ID (from get-journal results)"),content:c.string().max(5e4).describe("New page content (HTML or plain text)")})},{annotations:ee,name:"delete-journal",description:"Delete a journal entry and all its pages permanently. This is irreversible. Map pins linked to this journal will lose their link. Returns the deleted journal name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID")})},{annotations:ee,name:"delete-journal-page",description:"Delete a single page from a journal entry. The journal entry itself and other pages are preserved. This is irreversible. Returns the journal name and deleted page ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID"),pageId:c.string().max(256).describe("Page ID to delete")})}];var TD=3e4,ID=5e3,[pf,mf,ff,hf,gf,yf,vf,bf,_f,Sf]=$D;function bJ(e,t){e.registerTool(pf.name,{description:pf.description,annotations:pf.annotations,inputSchema:pf.inputSchema.shape},async({folder:r,limit:i,offset:n})=>v(await t.query("list-journals",{folder:r,limit:i,offset:n}))),e.registerTool(mf.name,{description:mf.description,annotations:mf.annotations,inputSchema:mf.inputSchema.shape},async({query:r})=>v(await t.query("search-journals",{query:r}))),e.registerTool(ff.name,{description:ff.description,annotations:ff.annotations,inputSchema:ff.inputSchema.shape},async({identifier:r},i)=>{let n=st(i,"Reading journal\u2026",ID);try{return v(await t.query("get-journal",{identifier:r},TD))}finally{n()}}),e.registerTool(hf.name,{description:hf.description,annotations:hf.annotations,inputSchema:hf.inputSchema.shape},async({identifier:r,pageId:i},n)=>{let o=st(n,"Reading journal page\u2026",ID);try{return v(await t.query("get-journal-page",{identifier:r,pageId:i},TD))}finally{o()}})}function _J(e,t){e.registerTool(gf.name,{description:gf.description,annotations:gf.annotations,inputSchema:gf.inputSchema.shape},async({name:r,folderId:i,pages:n})=>v(await t.query("create-journal",{name:r,folderId:i,pages:n}))),e.registerTool(yf.name,{description:yf.description,annotations:yf.annotations,inputSchema:yf.inputSchema.shape},async({identifier:r,name:i,content:n,type:o})=>v(await t.query("create-journal-page",{identifier:r,name:i,content:n,type:o}))),e.registerTool(vf.name,{description:vf.description,annotations:vf.annotations,inputSchema:vf.inputSchema.shape},async({identifier:r,name:i})=>v(await t.query("update-journal",{identifier:r,updates:{name:i}}))),e.registerTool(bf.name,{description:bf.description,annotations:bf.annotations,inputSchema:bf.inputSchema.shape},async({identifier:r,pageId:i,content:n})=>v(await t.query("update-journal-page",{identifier:r,pageId:i,content:n}))),e.registerTool(_f.name,{description:_f.description,annotations:_f.annotations,inputSchema:_f.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-journal",{identifier:r}))),e.registerTool(Sf.name,{description:Sf.description,annotations:Sf.annotations,inputSchema:Sf.inputSchema.shape},async({identifier:r,pageId:i})=>v(await t.query("delete-journal-page",{identifier:r,pageId:i})))}function ED(e,t){bJ(e,t),_J(e,t)}var PD=[{annotations:H,name:"move-token",description:"Move a token to x,y pixel coordinates on the current scene. Use for general repositioning outside of combat. During combat turns, use resolve-movement (combat-ai bundle) instead \u2014 it tracks movement speed and remaining distance. Returns `moved: false` and a `warning` string if walls or regions clipped the destination \u2014 re-read the token position via get-current-scene before retrying.",inputSchema:c.object({identifier:c.string().max(256).describe("Token name or ID"),x:c.number().min(-5e4).max(5e4).describe("Target X position in pixels (from scene top-left)"),y:c.number().min(-5e4).max(5e4).describe("Target Y position in pixels (from scene top-left)")})},{annotations:H,name:"update-token",description:"Update token visual properties on the current scene. Supports hidden, lock, elevation, size, rotation, and disposition. For moving a token to new coordinates, use move-token instead. For applying status conditions, use toggle-condition.",inputSchema:c.object({identifier:c.string().max(256).describe("Token name or ID"),hidden:c.boolean().optional().describe("Hide/show the token"),elevation:c.number().optional().describe("Token elevation in feet"),width:c.number().min(.5).optional().describe("Token width in grid units"),height:c.number().min(.5).optional().describe("Token height in grid units"),rotation:c.number().min(0).max(360).optional().describe("Token rotation in degrees (0-360)"),disposition:c.number().int().min(-2).max(1).optional().describe("Token disposition: -2=secret, -1=hostile, 0=neutral, 1=friendly"),locked:c.boolean().optional().describe("Lock the token: blocks player drag, keyboard movement, and mousewheel rotation on the canvas. API repositioning (move-token) still works while locked.")})},{annotations:C,name:"update-tokens",description:"Batch update properties on multiple tokens at once. Supports hidden, lock, elevation, size, rotation, and disposition. Returns per-token results. Use to hide/reveal a group of tokens, change dispositions for multiple NPCs, or adjust elevation for a flying group.",inputSchema:c.object({updates:c.array(c.object({identifier:c.string().max(256).describe("Token name or ID"),hidden:c.boolean().optional().describe("Hide/show the token"),elevation:c.number().optional().describe("Token elevation in feet"),width:c.number().min(.5).optional().describe("Token width in grid units"),height:c.number().min(.5).optional().describe("Token height in grid units"),rotation:c.number().min(0).max(360).optional().describe("Token rotation in degrees (0-360)"),disposition:c.number().int().min(-2).max(1).optional().describe("Token disposition: -2=secret, -1=hostile, 0=neutral, 1=friendly"),locked:c.boolean().optional().describe("Lock the token: blocks player drag, keyboard movement, and mousewheel rotation. API repositioning still works while locked.")})).min(1).max(100).describe("Array of token updates")})},{annotations:C,name:"create-tokens",description:"Place one or more tokens on the active scene from world actors. Supports batch placement for encounter setup. Returns the created token IDs. Use start-combat afterward to begin a combat encounter with the placed tokens.",inputSchema:c.object({tokens:c.array(c.object({actorId:c.string().max(256).describe("World actor ID"),x:c.number().min(-5e4).max(5e4).describe("X position in pixels (from scene top-left)"),y:c.number().min(-5e4).max(5e4).describe("Y position in pixels (from scene top-left)"),hidden:c.boolean().optional().describe("Hidden from players (default: false)"),disposition:c.number().int().min(-2).max(1).optional().describe("-2=secret, -1=hostile, 0=neutral, 1=friendly"),name:c.string().max(256).optional().describe("Override token name")})).max(100).describe("Array of tokens to place")})},{annotations:ee,name:"delete-tokens",description:"Remove tokens from the active scene permanently. Accepts token names or IDs. This does not delete the underlying actor \u2014 only the token placement on this scene. Returns the count of deleted tokens, their IDs, and any errors for tokens not found.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Token names or IDs to delete")})}];var Ve=[{annotations:q,name:"get-current-scene",description:"Get the active scene with paginated token data. Returns scene name, dimensions (width/height in pixels), grid settings (size, distance), darkness level, and tokens with name, position (x,y in pixels), HP, AC, conditions, and vision range. Token list includes total count and hasMore flag. Use at the start of combat or when you need to understand the current battlefield layout.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max tokens to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of tokens to skip (default: 0)")})},{annotations:Ne,name:"get-scene",description:"Get full details of any scene by name or ID, including dimensions, grid settings, darkness level, weather, background image, folder, and paginated token list. Unlike get-current-scene (which only returns the active scene), this works for any scene in the world.",inputSchema:c.object({identifier:c.string().max(256).describe("Scene name or Foundry ID"),limit:c.number().int().min(1).max(500).optional().describe("Max tokens to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of tokens to skip (default: 0)")})},{annotations:q,name:"list-scenes",description:"List scenes in the world with pagination. Returns each scene's name, ID, active/viewed status, and thumbnail. Response includes total count and hasMore flag. Use to find a scene before switching to it with switch-scene.",inputSchema:c.object({limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:Ne,name:"search-scenes",description:"Search scenes by name. Returns matching scenes with names, IDs, and active status. Use to find a scene when you only know part of the name.",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against scene name")})},{annotations:H,name:"switch-scene",description:"Switch the active scene by name or ID. All players are pulled to the new scene. Returns the new active scene data. Use pan-canvas afterward to focus on a specific area.",inputSchema:c.object({identifier:c.string().max(256).describe("Scene name or ID")})},...PD,{annotations:C,name:"create-scene",description:"Create a blank scene with NO background image. Only use this for empty grids or technical scenes \u2014 if the user wants a battle map, dungeon, tavern, or any scene with a background image, prefer generate-scene (enable the scene-generator bundle) for AI-generated scenes, which creates the scene AND generates + applies the background in one step. Returns the new scene ID. The scene is not activated automatically \u2014 use switch-scene to make it active. Default size is 4000\xD73000 pixels with 100px grid squares (5ft per square).",inputSchema:c.object({name:c.string().max(256).describe("Scene name"),width:c.number().min(1).optional().describe("Scene width in pixels (default: 4000)"),height:c.number().min(1).optional().describe("Scene height in pixels (default: 3000)"),gridSize:c.number().min(1).optional().describe("Grid square size in pixels (default: 100)"),gridDistance:c.number().min(1).optional().describe("Grid distance per square in feet (default: 5)"),background:c.string().max(256).optional().describe("Background image path")})},{annotations:H,name:"update-scene",description:"Update scene properties using Foundry dot-notation paths. Supports name, grid, dimensions, background image, navigation visibility, and more. Use get-current-scene first to see current values.",inputSchema:c.object({identifier:c.string().max(256).describe("Scene name or ID"),updates:Ue.describe('Foundry update data (e.g., {"name": "New Name", "navigation": true, "background.src": "path"})')})},{annotations:ee,name:"delete-scene",description:"Delete a scene from the world permanently. Removes all tokens, walls, lights, and sounds on the scene. This is irreversible. Returns the deleted scene name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Scene name or ID")})},{annotations:H,name:"pan-canvas",description:"Pan and zoom the GM canvas camera to a position. Animates smoothly over the specified duration. Use after switch-scene to focus on a specific area, or to draw attention to a location during gameplay.",inputSchema:c.object({x:c.number().optional().describe("Target X position in pixels (from scene top-left)"),y:c.number().optional().describe("Target Y position in pixels (from scene top-left)"),scale:c.number().min(.1).max(3).optional().describe("Zoom level (0.3=far, 1.0=normal, 2.0=close)"),duration:c.number().min(0).optional().describe("Animation duration in ms (default: 1000)")})},{annotations:H,name:"set-darkness",description:"Set the scene darkness level (0=bright daylight, 0.5=twilight, 1=pitch black). Returns the new darkness value. Use animate:true for smooth day/night transitions. Affects token vision and light source visibility.",inputSchema:c.object({darkness:c.number().min(0).max(1).describe("Darkness level: 0=daylight, 0.5=twilight, 1=full darkness"),animate:c.boolean().optional().describe("Smoothly animate the transition (default: false)")})},{annotations:wn,name:"reset-fog",description:"Reset fog of war exploration for the active scene, hiding all previously revealed areas. This is irreversible \u2014 players will need to re-explore. Use when resetting a dungeon or starting a new exploration phase. Returns the scene name."},{annotations:q,name:"get-weather-effects",description:"List all available weather effect IDs that can be applied with set-weather. Returns effect names and IDs. Use to discover valid weather options before applying one."},{annotations:H,name:"set-weather",description:"Set a weather particle effect on the active scene (rain, fog, snow, etc.). Use get-weather-effects to see valid IDs. Pass an empty string to clear the current weather. Only one weather effect can be active at a time.",inputSchema:c.object({effect:c.string().max(256).describe('Weather effect ID (e.g., "rain", "fog", "snow") or empty string to clear')})},{annotations:q,name:"get-screenshot",description:"Capture an annotated JPEG screenshot of the GM's current Foundry VTT canvas view. Tokens are overlaid with numbered colored badges (green=friendly, red=hostile, yellow=neutral). Returns the image and a text legend mapping badge numbers to token name, disposition, HP, and AC. Use to verify token positions, inspect the battlefield, or reference tokens by number in conversation.",inputSchema:c.object({maxWidth:c.number().min(1).max(3840).optional().describe("Max image width in pixels (default: 1280)"),maxHeight:c.number().min(1).max(2160).optional().describe("Max image height in pixels (default: 720)"),quality:c.number().min(.1).max(1).optional().describe("JPEG quality 0.1-1.0 (default: 0.80)"),annotate:c.boolean().optional().describe("Overlay numbered token badges with legend (default: true)")})}];var SJ=c.object({imageData:c.string(),mimeType:c.string(),width:c.number(),height:c.number(),legend:c.string()}),xf=Ve[8],wf=Ve[9],kf=Ve[13],$f=Ve[14],Sk=Ve[15],xk=Ve[16],Tf=Ve[17],If=Ve[18];function RD(e,t){e.registerTool(xf.name,{description:xf.description,annotations:xf.annotations,inputSchema:xf.inputSchema.shape},async({tokens:r})=>v(await t.query("create-tokens",{tokens:r}))),e.registerTool(wf.name,{description:wf.description,annotations:wf.annotations,inputSchema:wf.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-tokens",{identifiers:r})))}function zD(e,t){e.registerTool(kf.name,{description:kf.description,annotations:kf.annotations,inputSchema:kf.inputSchema.shape},async({x:r,y:i,scale:n,duration:o})=>v(await t.query("pan-canvas",{x:r,y:i,scale:n,duration:o}))),e.registerTool($f.name,{description:$f.description,annotations:$f.annotations,inputSchema:$f.inputSchema.shape},async({darkness:r,animate:i})=>v(await t.query("set-darkness",{darkness:r,animate:i}))),e.registerTool(Sk.name,{description:Sk.description,annotations:Sk.annotations},async()=>v(await t.query("reset-fog")))}function OD(e,t){e.registerTool(xk.name,{description:xk.description,annotations:xk.annotations},async()=>v(await t.query("get-weather-effects"))),e.registerTool(Tf.name,{description:Tf.description,annotations:Tf.annotations,inputSchema:Tf.inputSchema.shape},async({effect:r})=>v(await t.query("set-weather",{effect:r}))),e.registerTool(If.name,{description:If.description,annotations:If.annotations,inputSchema:If.inputSchema.shape},async({maxWidth:r,maxHeight:i,quality:n,annotate:o})=>{let a=await t.query("get-screenshot",{maxWidth:r,maxHeight:i,quality:n,annotate:o});if(!a.success)return{content:[{type:"text",text:`Error: ${on(a.error)}`}],isError:!0};let s=SJ.safeParse(a.result);return s.success?{content:[{type:"image",data:s.data.imageData,mimeType:s.data.mimeType},{type:"text",text:s.data.legend!==""?s.data.legend:`Screenshot: ${s.data.width}x${s.data.height}`}]}:{content:[{type:"text",text:`Screenshot failed: unexpected response format \u2014 ${s.error.message}`}],isError:!0}})}var Ef=Ve[0],Pf=Ve[1],Rf=Ve[2],zf=Ve[3],Of=Ve[4],jf=Ve[5],Df=Ve[6],Nf=Ve[7],Cf=Ve[10],Af=Ve[11],Mf=Ve[12];function xJ(e,t){e.registerTool(Ef.name,{description:Ef.description,annotations:Ef.annotations,inputSchema:Ef.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("get-current-scene",{limit:r,offset:i}))),e.registerTool(Pf.name,{description:Pf.description,annotations:Pf.annotations,inputSchema:Pf.inputSchema.shape},async({identifier:r,limit:i,offset:n})=>v(await t.query("get-scene",{identifier:r,limit:i,offset:n}))),e.registerTool(Rf.name,{description:Rf.description,annotations:Rf.annotations,inputSchema:Rf.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-scenes",{limit:r,offset:i}))),e.registerTool(zf.name,{description:zf.description,annotations:zf.annotations,inputSchema:zf.inputSchema.shape},async({query:r})=>v(await t.query("search-scenes",{query:r}))),e.registerTool(Of.name,{description:Of.description,annotations:Of.annotations,inputSchema:Of.inputSchema.shape},async({identifier:r})=>v(await t.query("switch-scene",{identifier:r})))}function wJ(e,t){e.registerTool(jf.name,{description:jf.description,annotations:jf.annotations,inputSchema:jf.inputSchema.shape},async({identifier:r,x:i,y:n})=>v(await t.query("move-token",{identifier:r,x:i,y:n})))}function kJ(e,t){e.registerTool(Df.name,{description:Df.description,annotations:Df.annotations,inputSchema:Df.inputSchema.shape},async({identifier:r,...i})=>v(await t.query("update-token",{identifier:r,updates:i}))),e.registerTool(Nf.name,{description:Nf.description,annotations:Nf.annotations,inputSchema:Nf.inputSchema.shape},async({updates:r})=>{let i=r.map(({identifier:n,...o})=>({identifier:n,changes:o}));return v(await t.query("update-tokens",{updates:i}))})}function $J(e,t){wJ(e,t),kJ(e,t)}function TJ(e,t){e.registerTool(Cf.name,{description:Cf.description,annotations:Cf.annotations,inputSchema:Cf.inputSchema.shape},async({name:r,width:i,height:n,gridSize:o,gridDistance:a,background:s})=>v(await t.query("create-scene",{name:r,width:i,height:n,gridSize:o,gridDistance:a,background:s}))),e.registerTool(Af.name,{description:Af.description,annotations:Af.annotations,inputSchema:Af.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-scene",{identifier:r,updates:i}))),e.registerTool(Mf.name,{description:Mf.description,annotations:Mf.annotations,inputSchema:Mf.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-scene",{identifier:r})))}function IJ(e,t){zD(e,t),OD(e,t)}function jD(e,t){xJ(e,t),$J(e,t),RD(e,t),TJ(e,t),IJ(e,t)}var DD=[{annotations:q,name:"get-world-info",description:"Get Foundry VTT world metadata. Returns game system, world name, Foundry version, and all connected players with their roles. Use at the start of a session to orient yourself."},{annotations:C,name:"toggle-pause",description:"Pause or unpause the game. Returns the new pause state. Players cannot take actions while the game is paused. Omit the paused parameter to toggle.",inputSchema:c.object({paused:c.boolean().optional().describe("true to pause, false to unpause, omit to toggle")})},{annotations:q,name:"get-time",description:"Get the current in-game date and time. Returns year, month, day, hour, minute, and second in the game world's calendar. Use before advance-time to check the current time and plan time skips."},{annotations:C,name:"advance-time",description:"Advance the in-game clock by a specified duration. Returns the new in-game time. Use for long rests (8 hours), short rests (1 hour), travel montages, or narrative time skips. Specify at least one time unit.",inputSchema:c.object({seconds:c.number().optional().describe("Seconds to advance"),minutes:c.number().optional().describe("Minutes to advance"),hours:c.number().optional().describe("Hours to advance (e.g., 8 for long rest)"),days:c.number().optional().describe("Days to advance")})},{annotations:q,name:"list-users",description:"List all users in the game world. Returns each user's name, ID, role (Gamemaster/Player/Trusted/Assistant/None), online status, assigned character, and color. Use to find valid user names before assign-ownership, revoke-ownership, or send-chat-message with whisperTo."},{annotations:q,name:"get-user",description:"Get detailed information about a specific user. Returns name, ID, role, online status, assigned character, color, and a list of all actors they own. Accepts user name (exact match) or Foundry user ID.",inputSchema:c.object({identifier:c.string().max(256).describe("User name or Foundry user ID")})},{annotations:C,name:"create-user",description:"Create a new Foundry user account. Role defaults to PLAYER (1); valid roles: 0=NONE, 1=PLAYER, 2=TRUSTED, 3=ASSISTANT, 4=GAMEMASTER. Optionally auto-link a character (assigns OWNER permission). Use for onboarding new players or setting up test users. Password is NOT settable via this tool \u2014 Foundry creates accounts passwordless by default; the player sets their own password on first login.",inputSchema:c.object({name:c.string().min(1).max(80).describe("Display name for the new user (required)"),role:c.number().int().min(0).max(4).optional().describe("User role: 0=NONE, 1=PLAYER (default), 2=TRUSTED, 3=ASSISTANT, 4=GAMEMASTER"),character:c.string().max(256).optional().describe("Optional character name or actor ID to auto-link (sets OWNER permission)"),color:c.string().max(16).optional().describe('Optional hex color for the user (e.g. "#ff0000"); Foundry auto-generates if omitted'),pronouns:c.string().max(64).optional().describe("Optional pronouns string")})}];function ND(e,t){let[r,i,n,o,a,s,l]=DD;e.registerTool(r.name,{description:r.description,annotations:r.annotations},async()=>v(await t.query("get-world-info"))),e.registerTool(i.name,{description:i.description,annotations:i.annotations,inputSchema:i.inputSchema.shape},async({paused:u})=>v(await t.query("toggle-pause",{paused:u}))),e.registerTool(n.name,{description:n.description,annotations:n.annotations},async()=>v(await t.query("get-time"))),e.registerTool(o.name,{description:o.description,annotations:o.annotations,inputSchema:o.inputSchema.shape},async({seconds:u,minutes:d,hours:p,days:f})=>v(await t.query("advance-time",{seconds:u,minutes:d,hours:p,days:f}))),e.registerTool(a.name,{description:a.description,annotations:a.annotations},async()=>v(await t.query("list-users"))),e.registerTool(s.name,{description:s.description,annotations:s.annotations,inputSchema:s.inputSchema.shape},async({identifier:u})=>v(await t.query("get-user",{identifier:u}))),e.registerTool(l.name,{description:l.description,annotations:l.annotations,inputSchema:l.inputSchema.shape},async({name:u,role:d,character:p,color:f,pronouns:h})=>v(await t.query("create-user",{name:u,role:d,character:p,color:f,pronouns:h})))}var di=[{annotations:q,name:"list-tiles",description:"List tiles on the active scene (paginated). Returns each tile's ID, position (x,y in pixels), dimensions (width/height in pixels), image path, opacity, rotation, overhead status, and visibility. Use to find tile IDs before updating or deleting them.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"create-tile",description:"Place a tile image on the active scene for decoration, overlays, or roof tiles. Set overhead:true to render above tokens (useful for building roofs that hide interiors). The image file must exist in Foundry data.",inputSchema:c.object({src:c.string().max(256).describe('Image file path (relative to Foundry data, e.g., "tiles/floor.webp")'),x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),width:c.number().min(1).describe("Tile width in pixels"),height:c.number().min(1).describe("Tile height in pixels"),overhead:c.boolean().optional().describe("Render above tokens (default: false)"),hidden:c.boolean().optional().describe("Hidden from players (default: false)"),alpha:c.number().min(0).max(1).optional().describe("Opacity 0-1 (default: 1)"),rotation:c.number().min(0).max(360).optional().describe("Rotation in degrees")})},{annotations:H,name:"update-tile",description:"Update a tile on the scene. Move, resize, change opacity, toggle overhead, or swap the image. Use list-tiles to find the tile ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the tile to update"),updates:Ue.describe("Properties to update (x, y, width, height, alpha, rotation, hidden, overhead, etc.)")})},{annotations:ee,name:"delete-tiles",description:"Remove tiles from the active scene permanently. The image files on disk are not deleted. Returns the count of deleted tiles and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Tile IDs to delete")})},{annotations:q,name:"list-drawings",description:"List drawings on the active scene (paginated). Returns each drawing's ID, shape type (r=rectangle, e=ellipse, p=polygon, f=freehand), position (x,y in pixels), dimensions, colors, and text content. Use to find drawing IDs before updating or deleting them.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"create-drawing",description:"Draw a shape on the canvas for marking areas, annotations, or visual effects. All coordinates and dimensions are in pixels. Supports rectangles (r), ellipses (e), polygons (p), and freehand (f). Add text labels with the text parameter. For spell area markers, prefer create-region instead \u2014 it uses game units (feet) and integrates with the combat system.",inputSchema:c.object({type:c.enum(["r","e","p","f"]).describe("Shape type: r=rectangle, e=ellipse, p=polygon, f=freehand"),x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),width:c.number().min(1).describe("Width in pixels"),height:c.number().min(1).describe("Height in pixels"),strokeColor:c.string().max(256).optional().describe('Stroke hex color (e.g., "#FF0000")'),fillColor:c.string().max(256).optional().describe("Fill hex color"),fillAlpha:c.number().min(0).max(1).optional().describe("Fill opacity 0-1"),strokeWidth:c.number().min(0).optional().describe("Stroke width in pixels"),text:c.string().max(5e4).optional().describe("Text content (for text drawings)"),fontSize:c.number().min(1).optional().describe("Font size in pixels"),textColor:c.string().max(256).optional().describe("Text hex color")})},{annotations:H,name:"update-drawing",description:"Update a drawing on the scene. Move, resize, recolor, or change text content. Use list-drawings to find the drawing ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the drawing to update"),updates:Ue.describe("Properties to update")})},{annotations:ee,name:"delete-drawings",description:"Remove drawings from the active scene permanently. Returns the count of deleted drawings and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Drawing IDs to delete")})}];var Uf=di[4],Lf=di[5],qf=di[6],Zf=di[7];function CD(e,t){e.registerTool(Uf.name,{description:Uf.description,annotations:Uf.annotations,inputSchema:Uf.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-drawings",{limit:r,offset:i}))),e.registerTool(Lf.name,{description:Lf.description,annotations:Lf.annotations,inputSchema:Lf.inputSchema.shape},async({type:r,x:i,y:n,width:o,height:a,strokeColor:s,fillColor:l,fillAlpha:u,strokeWidth:d,text:p,fontSize:f,textColor:h})=>v(await t.query("create-drawing",{type:r,x:i,y:n,width:o,height:a,strokeColor:s,fillColor:l,fillAlpha:u,strokeWidth:d,text:p,fontSize:f,textColor:h}))),e.registerTool(qf.name,{description:qf.description,annotations:qf.annotations,inputSchema:qf.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-drawing",{identifier:r,updates:i}))),e.registerTool(Zf.name,{description:Zf.description,annotations:Zf.annotations,inputSchema:Zf.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-drawings",{identifiers:r})))}var[Ff,Vf,Bf,Hf]=di;function EJ(e,t){e.registerTool(Ff.name,{description:Ff.description,annotations:Ff.annotations,inputSchema:Ff.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-tiles",{limit:r,offset:i}))),e.registerTool(Vf.name,{description:Vf.description,annotations:Vf.annotations,inputSchema:Vf.inputSchema.shape},async({src:r,x:i,y:n,width:o,height:a,overhead:s,hidden:l,alpha:u,rotation:d})=>v(await t.query("create-tile",{src:r,x:i,y:n,width:o,height:a,overhead:s,hidden:l,alpha:u,rotation:d}))),e.registerTool(Bf.name,{description:Bf.description,annotations:Bf.annotations,inputSchema:Bf.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-tile",{identifier:r,updates:i}))),e.registerTool(Hf.name,{description:Hf.description,annotations:Hf.annotations,inputSchema:Hf.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-tiles",{identifiers:r})))}function AD(e,t){EJ(e,t),CD(e,t)}var MD=[{annotations:q,name:"list-sounds",description:"List ambient sounds on the active scene (paginated). Returns each sound's ID, position (in pixels), audio path, hearing radius (in feet), volume, and easing. For playlist/music control, use the playlists bundle.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"create-sound",description:"Place a positional ambient sound on the scene (e.g., waterfall, tavern noise). Tokens hear it louder when nearby. For background music, use the playlists bundle.",inputSchema:c.object({x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),path:c.string().max(256).describe("Audio file path (relative to Foundry data)"),radius:c.number().min(0).describe("Hearing radius in feet (how far tokens can hear the sound)"),volume:c.number().min(0).max(1).optional().describe("Volume 0-1 (default: 1)"),easing:c.boolean().optional().describe("Volume eases with distance (default: true)"),hidden:c.boolean().optional().describe("Hidden from players")})},{annotations:H,name:"update-sound",description:"Update an ambient sound source on the scene. Move, change volume, radius, audio path, or easing. Use list-sounds to find the sound ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the sound to update"),updates:Ue.describe("Properties to update (x, y, path, radius, volume, easing)")})},{annotations:ee,name:"delete-sounds",description:"Remove ambient sound sources from the active scene permanently. Accepts sound IDs from list-sounds. Returns the count of deleted sounds and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Sound IDs to delete")})},{annotations:q,name:"list-notes",description:"List map pins/notes on the active scene (paginated). Returns each note's ID, position (in pixels), label, linked journal entry/page IDs, and visibility.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"create-note",description:"Place a map pin on the scene, optionally linked to a journal entry. Use for points of interest, quest locations, or NPC markers.",inputSchema:c.object({x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),label:c.string().max(256).optional().describe("Pin label text"),entryId:c.string().max(256).optional().describe("Journal entry ID to link"),pageId:c.string().max(256).optional().describe("Journal page ID to link"),iconSize:c.number().min(1).optional().describe("Icon size in pixels (default: 40)"),textColor:c.string().max(256).optional().describe("Label text hex color"),hidden:c.boolean().optional().describe("Hidden from players")})},{annotations:H,name:"update-note",description:"Update a map pin on the scene. Move, change label, or link to a different journal entry. Use list-notes to find the note ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the note to update"),updates:Ue.describe("Properties to update (x, y, label, entryId, pageId)")})},{annotations:ee,name:"delete-notes",description:"Remove map pins from the active scene permanently. Does not delete the linked journal entries \u2014 only the pins themselves. Returns the count of deleted notes and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Note IDs to delete")})}];var lr=[{annotations:q,name:"list-walls",description:"List wall segments on the active scene (paginated). Returns each wall's ID, endpoints (in pixels), blocking levels for light/movement/sight/sound, and door type/state. Use before creating or modifying walls.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"toggle-door",description:"Open, close, or lock a door on the active scene. Omit state to toggle open/closed. Returns the new door state. Use list-walls to find door IDs.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the wall/door"),state:c.number().int().min(0).max(2).optional().describe("Door state: 0=closed, 1=open, 2=locked. Omit to toggle open/closed.")})},{annotations:H,name:"close-all-doors",description:"Force close every door on the active scene at once. Returns the count of doors closed. Use for resetting a dungeon between runs. For individual door control, use toggle-door instead."},{annotations:C,name:"create-walls",description:"Create wall segments on the active scene (batch supported). Walls block vision, movement, light, and sound \u2014 blocking levels: 0=none, 10=limited (partial), 20=normal (full). Use presets: basic (blocks all), terrain (limited sight), door, window (see-through), secret, invisible (movement only), ethereal (vision only). Returns wall IDs.",inputSchema:c.object({walls:c.array(c.object({c:c.array(c.number()).length(4).describe("Wall endpoints [x1, y1, x2, y2] in pixels (from scene top-left)"),preset:c.enum(["basic","terrain","invisible","ethereal","window","door","secret"]).optional().describe("Wall type preset (overridden by explicit values below)"),light:c.number().min(0).max(20).optional().describe("Light blocking: 0=none, 10=limited, 20=normal"),move:c.number().min(0).max(20).optional().describe("Movement blocking: 0=none, 10=limited, 20=normal"),sight:c.number().min(0).max(20).optional().describe("Sight blocking: 0=none, 10=limited, 20=normal"),sound:c.number().min(0).max(20).optional().describe("Sound blocking: 0=none, 10=limited, 20=normal"),door:c.number().int().min(0).max(2).optional().describe("Door type: 0=none, 1=door, 2=secret"),ds:c.number().int().min(0).max(2).optional().describe("Door state: 0=closed, 1=open, 2=locked"),dir:c.number().int().min(0).max(2).optional().describe("Wall direction (one-way restriction): 0=both (default, blocks both sides), 1=left (one-way blocking from the left side of the c[0]\u2192c[1] segment), 2=right (one-way blocking from the right side). Verify which side blocks first by snapshotting cover/LoS from each side after creation; swap c[0]\u2194c[1] (mirror the segment endpoints) to flip the blocking direction.")})).max(100).describe("Array of walls to create")})},{annotations:H,name:"update-wall",description:"Update a wall segment's properties. Change blocking levels, convert to a door, move endpoints, or adjust light/sound penetration. Use list-walls to find the wall ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the wall to update"),updates:Ue.describe("Properties to update (light, move, sight, sound, door, ds, dir, c)")})},{annotations:ee,name:"delete-walls",description:"Remove wall segments from the active scene permanently. Accepts wall IDs from list-walls. This affects vision, movement, and sound blocking immediately. Returns the count of deleted walls and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Wall IDs to delete")})},{annotations:q,name:"list-lights",description:"List ambient lights on the active scene (paginated). Returns each light's ID, position (in pixels), bright/dim radius (in feet), color, animation, and visibility.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"create-light",description:"Place an ambient light on the scene. Configure bright/dim radius, color, animations (torch, pulse, ghostly), and emission angle. Set negative:true for a darkness source.",inputSchema:c.object({x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),dim:c.number().min(0).describe("Dim light radius in feet (e.g., 40 for a torch)"),bright:c.number().min(0).describe("Bright light radius in feet (e.g., 20 for a torch)"),color:c.string().max(256).optional().describe('Light hex color (e.g., "#ff9329" for torchlight)'),alpha:c.number().min(0).max(1).optional().describe("Color intensity 0-1 (default: 0.5)"),angle:c.number().min(0).max(360).optional().describe("Emission angle in degrees (360=omnidirectional)"),negative:c.boolean().optional().describe("Creates darkness instead of light"),animationType:c.string().max(256).optional().describe("Animation: torch, pulse, chroma, wave, fog, sunburst, dome, emanation, ghostly, energy, roiling, hole, vortex"),animationSpeed:c.number().int().min(1).max(10).optional().describe("Animation speed 1-10 (default: 5)"),animationIntensity:c.number().int().min(1).max(10).optional().describe("Animation intensity 1-10 (default: 5)"),hidden:c.boolean().optional().describe("Hidden from players")})},{annotations:H,name:"update-light",description:"Update an ambient light source on the active scene. Supports x/y position (in pixels from top-left), config.bright and config.dim (radius in feet), config.color (hex), config.angle (0-360), config.animation.type and config.animation.speed, and hidden (visibility toggle). Use list-lights to find the light identifier first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the light to update"),updates:Ue.describe("Properties to update (x, y, config.dim, config.bright, config.color)")})},{annotations:ee,name:"delete-lights",description:"Remove ambient light sources from the active scene permanently. Accepts light IDs from list-lights. Returns the count of deleted lights and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Light IDs to delete")})},...MD];var Wf=lr[10],Gf=lr[11],Jf=lr[12],Kf=lr[13],Qf=lr[14],Yf=lr[15],Xf=lr[16],eh=lr[17];function UD(e,t){e.registerTool(Wf.name,{description:Wf.description,annotations:Wf.annotations,inputSchema:Wf.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-sounds",{limit:r,offset:i}))),e.registerTool(Gf.name,{description:Gf.description,annotations:Gf.annotations,inputSchema:Gf.inputSchema.shape},async({x:r,y:i,path:n,radius:o,volume:a,easing:s,hidden:l})=>v(await t.query("create-sound",{x:r,y:i,path:n,radius:o,volume:a,easing:s,hidden:l}))),e.registerTool(Jf.name,{description:Jf.description,annotations:Jf.annotations,inputSchema:Jf.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-sound",{identifier:r,updates:i}))),e.registerTool(Kf.name,{description:Kf.description,annotations:Kf.annotations,inputSchema:Kf.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-sounds",{identifiers:r})))}function LD(e,t){e.registerTool(Qf.name,{description:Qf.description,annotations:Qf.annotations,inputSchema:Qf.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-notes",{limit:r,offset:i}))),e.registerTool(Yf.name,{description:Yf.description,annotations:Yf.annotations,inputSchema:Yf.inputSchema.shape},async({x:r,y:i,label:n,entryId:o,pageId:a,iconSize:s,textColor:l,hidden:u})=>v(await t.query("create-note",{x:r,y:i,label:n,entryId:o,pageId:a,iconSize:s,textColor:l,hidden:u}))),e.registerTool(Xf.name,{description:Xf.description,annotations:Xf.annotations,inputSchema:Xf.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-note",{identifier:r,updates:i}))),e.registerTool(eh.name,{description:eh.description,annotations:eh.annotations,inputSchema:eh.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-notes",{identifiers:r})))}var[th,rh,wk,nh,ih,oh,ah,sh,ch,lh]=lr;function PJ(e,t){e.registerTool(th.name,{description:th.description,annotations:th.annotations,inputSchema:th.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-walls",{limit:r,offset:i}))),e.registerTool(rh.name,{description:rh.description,annotations:rh.annotations,inputSchema:rh.inputSchema.shape},async({identifier:r,state:i})=>v(await t.query("toggle-door",{identifier:r,state:i}))),e.registerTool(wk.name,{description:wk.description,annotations:wk.annotations},async()=>v(await t.query("close-all-doors"))),e.registerTool(nh.name,{description:nh.description,annotations:nh.annotations,inputSchema:nh.inputSchema.shape},async({walls:r})=>v(await t.query("create-walls",{walls:r}))),e.registerTool(ih.name,{description:ih.description,annotations:ih.annotations,inputSchema:ih.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-wall",{identifier:r,updates:i}))),e.registerTool(oh.name,{description:oh.description,annotations:oh.annotations,inputSchema:oh.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-walls",{identifiers:r})))}function RJ(e,t){e.registerTool(ah.name,{description:ah.description,annotations:ah.annotations,inputSchema:ah.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-lights",{limit:r,offset:i}))),e.registerTool(sh.name,{description:sh.description,annotations:sh.annotations,inputSchema:sh.inputSchema.shape},async r=>v(await t.query("create-light",r))),e.registerTool(ch.name,{description:ch.description,annotations:ch.annotations,inputSchema:ch.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-light",{identifier:r,updates:i}))),e.registerTool(lh.name,{description:lh.description,annotations:lh.annotations,inputSchema:lh.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-lights",{identifiers:r})))}function qD(e,t){PJ(e,t),RJ(e,t),UD(e,t),LD(e,t)}var ZD=[{annotations:C,name:"send-chat-message",description:"Send a message to the Foundry chat log. Supports HTML formatting, speaker attribution, whispers to specific players, and emote actions. Use for narrative text, NPC dialogue, GM announcements, or private messages. For dice results, use roll-dice instead. Returns the sent content and speaker name.",inputSchema:c.object({content:c.string().max(5e4).describe("Message content (supports HTML)"),speaker:c.string().max(256).optional().describe("Speaker name or character name (default: GM)"),whisperTo:c.array(c.string().max(256)).max(50).optional().describe("Player names to whisper to (private message). Omit for public message."),isEmote:c.boolean().optional().describe("Send as an emote/action message (default: false)")})},{annotations:q,name:"get-recent-messages",description:"Retrieve recent messages from the Foundry chat log. Returns the most recent N messages (default 20, max 100) with content, speaker, timestamp, type, and whisper recipients. Also returns the total message count in the log. Named get-* rather than list-* because it fetches a tail slice of the log, not a paginated collection.",inputSchema:c.object({count:c.number().int().min(1).max(100).optional().describe("Number of recent messages to retrieve (default: 20, max: 100)")})}];function FD(e,t){let[r,i]=ZD;e.registerTool(r.name,{description:r.description,annotations:r.annotations,inputSchema:r.inputSchema.shape},async({content:n,speaker:o,whisperTo:a,isEmote:s})=>v(await t.query("send-chat-message",{content:n,speaker:o,whisperTo:a,isEmote:s}))),e.registerTool(i.name,{description:i.description,annotations:i.annotations,inputSchema:i.inputSchema.shape},async({count:n})=>v(await t.query("get-recent-messages",{count:n})))}var Xo=[{annotations:q,name:"list-playlists",description:"List playlists in the world with pagination. Returns each playlist's name, ID, playback mode (sequential/shuffle/simultaneous), playing status, and track count with names. Response includes total count and hasMore flag. Use to find a playlist before playing it or to check what's currently playing.",inputSchema:c.object({limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:Ne,name:"search-playlists",description:"Search playlists by name or track name. Returns matching playlists with names, IDs, playing status, and track count. Use to find a playlist when you only know part of the name or a track title.",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against playlist or track name")})},{annotations:q,name:"get-playlist",description:"Get full details of a playlist including all tracks with names, paths, volume, and playing status. Returns playback mode and fade settings. Use to find track names before playing them with play-track.",inputSchema:c.object({identifier:c.string().max(256).describe("Playlist name or Foundry ID")})},{annotations:H,name:"play-playlist",description:"Start playing an entire playlist by name or ID. Plays according to the playlist mode (sequential, shuffle, or simultaneous). For playing a single track, use play-track instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Playlist name or Foundry ID")})},{annotations:H,name:"play-track",description:"Play a specific track within a playlist by name or ID. For playing an entire playlist, use play-playlist instead. Use get-playlist to see available tracks.",inputSchema:c.object({playlist:c.string().max(256).describe("Playlist name or ID"),trackId:c.string().max(256).describe("Track name or ID")})},{annotations:H,name:"stop-playlist",description:"Stop a specific playing playlist by name or ID. For stopping everything at once, use stop-all-playlists instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Playlist name or Foundry ID")})},{annotations:H,name:"stop-track",description:"Stop a specific track within a playlist. Other tracks in the playlist continue playing. Returns the playlist and track names.",inputSchema:c.object({playlist:c.string().max(256).describe("Playlist name or ID"),trackId:c.string().max(256).describe("Track name or ID")})},{annotations:H,name:"stop-all-playlists",description:"Stop all currently playing playlists and tracks at once. Use for scene transitions or when silence is needed. For stopping a single playlist, use stop-playlist instead."},{annotations:C,name:"create-playlist",description:"Create a new empty playlist with a playback mode. Returns the new playlist ID. Add tracks afterward with create-track. Modes: 0=Sequential (plays tracks in order), 1=Shuffle (random order), 2=Simultaneous (all tracks play at once, ideal for ambient soundscapes).",inputSchema:c.object({name:c.string().max(256).describe("Playlist name"),description:c.string().max(5e4).optional().describe("Playlist description"),mode:c.number().int().min(0).max(2).optional().describe("Playback mode: 0 = Sequential, 1 = Shuffle, 2 = Simultaneous"),fade:c.number().min(0).optional().describe("Default fade duration in milliseconds"),folderId:c.string().max(256).optional().describe("Folder name or ID to place the playlist in")})},{annotations:H,name:"update-playlist",description:"Update a playlist's properties. Change name, description, playback mode (0=Sequential, 1=Shuffle, 2=Simultaneous), or fade duration. For modifying individual tracks, use update-track instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Playlist name or Foundry ID"),name:c.string().max(256).optional().describe("New playlist name"),description:c.string().max(5e4).optional().describe("New description"),mode:c.number().int().min(0).max(2).optional().describe("Playback mode: 0 = Sequential, 1 = Shuffle, 2 = Simultaneous"),fade:c.number().min(0).optional().describe("Default fade duration in milliseconds")})},{annotations:ee,name:"delete-playlist",description:"Delete a playlist and all its tracks permanently. This is irreversible. Stops playback if the playlist is currently playing. Returns the deleted playlist name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Playlist name or Foundry ID")})},{annotations:C,name:"create-track",description:'Add a new audio track to an existing playlist. The audio file must already exist in the Foundry data directory (e.g., "music/tavern.mp3"). Returns the new track ID. Use list-playlists or get-playlist to find the playlist first.',inputSchema:c.object({playlist:c.string().max(256).describe("Playlist name or ID"),name:c.string().max(256).describe("Track name"),path:c.string().max(256).describe('Audio file path (relative to Foundry data, e.g. "music/tavern.mp3")'),volume:c.number().min(0).max(1).optional().describe("Volume level (0 to 1, default: 0.5)"),repeat:c.boolean().optional().describe("Loop the track (default: false)"),fade:c.number().min(0).optional().describe("Fade duration in milliseconds")})},{annotations:H,name:"update-track",description:"Update a track's properties within a playlist. Change name, volume, loop setting, or fade duration. For updating the playlist itself, use update-playlist instead.",inputSchema:c.object({playlist:c.string().max(256).describe("Playlist name or ID"),trackId:c.string().max(256).describe("Track name or ID"),name:c.string().max(256).optional().describe("New track name"),volume:c.number().min(0).max(1).optional().describe("Volume level (0 to 1)"),repeat:c.boolean().optional().describe("Loop the track"),fade:c.number().min(0).optional().describe("Fade duration in milliseconds")})},{annotations:ee,name:"delete-track",description:"Remove a track from a playlist permanently. The audio file on disk is not deleted. For deleting the entire playlist, use delete-playlist instead. Returns the playlist and deleted track names.",inputSchema:c.object({playlist:c.string().max(256).describe("Playlist name or ID"),trackId:c.string().max(256).describe("Track name or ID")})}];var uh=Xo[11],dh=Xo[12],ph=Xo[13];function VD(e,t){e.registerTool(uh.name,{description:uh.description,annotations:uh.annotations,inputSchema:uh.inputSchema.shape},async({playlist:r,name:i,path:n,volume:o,repeat:a,fade:s})=>v(await t.query("create-track",{playlist:r,name:i,path:n,volume:o,repeat:a,fade:s}))),e.registerTool(dh.name,{description:dh.description,annotations:dh.annotations,inputSchema:dh.inputSchema.shape},async({playlist:r,trackId:i,name:n,volume:o,repeat:a,fade:s})=>{let l={};return n!==void 0&&(l.name=n),o!==void 0&&(l.volume=o),a!==void 0&&(l.repeat=a),s!==void 0&&(l.fade=s),v(await t.query("update-track",{playlist:r,trackId:i,updates:l}))})}function BD(e,t){e.registerTool(ph.name,{description:ph.description,annotations:ph.annotations,inputSchema:ph.inputSchema.shape},async({playlist:r,trackId:i})=>v(await t.query("delete-track",{playlist:r,trackId:i})))}var[mh,fh,hh,gh,yh,vh,bh,kk,_h,Sh,xh]=Xo;function zJ(e,t){e.registerTool(mh.name,{description:mh.description,annotations:mh.annotations,inputSchema:mh.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-playlists",{limit:r,offset:i}))),e.registerTool(fh.name,{description:fh.description,annotations:fh.annotations,inputSchema:fh.inputSchema.shape},async({query:r})=>v(await t.query("search-playlists",{query:r}))),e.registerTool(hh.name,{description:hh.description,annotations:hh.annotations,inputSchema:hh.inputSchema.shape},async({identifier:r})=>v(await t.query("get-playlist",{identifier:r})))}function OJ(e,t){e.registerTool(gh.name,{description:gh.description,annotations:gh.annotations,inputSchema:gh.inputSchema.shape},async({identifier:r})=>v(await t.query("play-playlist",{identifier:r}))),e.registerTool(yh.name,{description:yh.description,annotations:yh.annotations,inputSchema:yh.inputSchema.shape},async({playlist:r,trackId:i})=>v(await t.query("play-track",{playlist:r,trackId:i})))}function jJ(e,t){e.registerTool(vh.name,{description:vh.description,annotations:vh.annotations,inputSchema:vh.inputSchema.shape},async({identifier:r})=>v(await t.query("stop-playlist",{identifier:r}))),e.registerTool(bh.name,{description:bh.description,annotations:bh.annotations,inputSchema:bh.inputSchema.shape},async({playlist:r,trackId:i})=>v(await t.query("stop-track",{playlist:r,trackId:i}))),e.registerTool(kk.name,{description:kk.description,annotations:kk.annotations},async()=>v(await t.query("stop-all-playlists")))}function DJ(e,t){e.registerTool(_h.name,{description:_h.description,annotations:_h.annotations,inputSchema:_h.inputSchema.shape},async({name:r,description:i,mode:n,fade:o,folderId:a})=>v(await t.query("create-playlist",{name:r,description:i,mode:n,fade:o,folderId:a}))),e.registerTool(Sh.name,{description:Sh.description,annotations:Sh.annotations,inputSchema:Sh.inputSchema.shape},async({identifier:r,name:i,description:n,mode:o,fade:a})=>{let s={};return i!==void 0&&(s.name=i),n!==void 0&&(s.description=n),o!==void 0&&(s.mode=o),a!==void 0&&(s.fade=a),v(await t.query("update-playlist",{identifier:r,updates:s}))})}function NJ(e,t){e.registerTool(xh.name,{description:xh.description,annotations:xh.annotations,inputSchema:xh.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-playlist",{identifier:r})))}function HD(e,t){zJ(e,t),OJ(e,t),jJ(e,t),DJ(e,t),NJ(e,t),VD(e,t),BD(e,t)}var WD=[{annotations:q,name:"list-tables",description:`List rollable tables in the world with pagination. Returns each table's name, ID, dice formula (e.g., "1d6"), result count, and description. Response includes total count and hasMore flag. Use to find a table before rolling on it with roll-table.`,inputSchema:c.object({limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:Ne,name:"search-tables",description:"Search rollable tables by name or description text. Returns matching tables with names, IDs, dice formulas, result counts, and description previews. Use to find a specific table when you don't know its exact name.",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against table name or description")})},{annotations:q,name:"get-table",description:"Get a rollable table with its dice formula and all result entries. Returns the full table content. Use to review table contents before rolling with roll-table.",inputSchema:c.object({identifier:c.string().max(256).describe("Table name or Foundry ID")})},{annotations:C,name:"roll-table",description:"Roll on a rollable table using its dice formula and return the randomly selected result. Posts the roll and result to Foundry chat by default. For simple dice rolls without a table, use roll-dice (dice bundle) instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Table name or Foundry ID"),showInChat:c.boolean().optional().describe("Show the roll result in Foundry chat (default: true)")})},{annotations:C,name:"create-table",description:"Create a new rollable table with a dice formula and result entries. Returns the new table ID. Each result has text and an optional weight or explicit dice range. Use for random encounters, loot tables, weather, NPC names, or any randomized content. If no formula is specified, it auto-generates based on result count.",inputSchema:c.object({name:c.string().max(256).describe("Table name"),formula:c.string().max(256).optional().describe('Dice formula (e.g. "1d6", "1d20", "2d10")'),description:c.string().max(5e4).optional().describe("Table description"),replacement:c.boolean().optional().describe("Draw with replacement (default: true)"),folderId:c.string().max(256).optional().describe("Folder name or ID to place the table in"),results:c.array(c.object({text:c.string().max(5e4).describe("Result text"),weight:c.number().optional().describe("Result weight (default: 1)"),rangeStart:c.number().optional().describe("Range start (auto-assigned if omitted)"),rangeEnd:c.number().optional().describe("Range end (auto-assigned if omitted)")})).max(100).optional().describe("Table results/entries")})},{annotations:H,name:"update-table",description:"Update a rollable table's properties. Change name, dice formula, description, or draw-with-replacement mode. Does not modify individual results \u2014 use Foundry UI for that.",inputSchema:c.object({identifier:c.string().max(256).describe("Table name or Foundry ID"),name:c.string().max(256).optional().describe("New table name"),formula:c.string().max(256).optional().describe("New dice formula"),description:c.string().max(5e4).optional().describe("New description"),replacement:c.boolean().optional().describe("Draw with replacement")})},{annotations:ee,name:"delete-table",description:"Delete a rollable table and all its result entries permanently. This is irreversible. Returns the deleted table name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Table name or Foundry ID")})}];var[wh,kh,$h,Th,Ih,Eh,Ph]=WD;function CJ(e,t){e.registerTool(wh.name,{description:wh.description,annotations:wh.annotations,inputSchema:wh.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-tables",{limit:r,offset:i}))),e.registerTool(kh.name,{description:kh.description,annotations:kh.annotations,inputSchema:kh.inputSchema.shape},async({query:r})=>v(await t.query("search-tables",{query:r}))),e.registerTool($h.name,{description:$h.description,annotations:$h.annotations,inputSchema:$h.inputSchema.shape},async({identifier:r})=>v(await t.query("get-table",{identifier:r}))),e.registerTool(Th.name,{description:Th.description,annotations:Th.annotations,inputSchema:Th.inputSchema.shape},async({identifier:r,showInChat:i})=>v(await t.query("roll-table",{identifier:r,showInChat:i})))}function AJ(e,t){e.registerTool(Ih.name,{description:Ih.description,annotations:Ih.annotations,inputSchema:Ih.inputSchema.shape},async({name:r,formula:i,description:n,replacement:o,folderId:a,results:s})=>v(await t.query("create-table",{name:r,formula:i,description:n,replacement:o,folderId:a,results:s}))),e.registerTool(Eh.name,{description:Eh.description,annotations:Eh.annotations,inputSchema:Eh.inputSchema.shape},async({identifier:r,name:i,formula:n,description:o,replacement:a})=>{let s={};return i!==void 0&&(s.name=i),n!==void 0&&(s.formula=n),o!==void 0&&(s.description=o),a!==void 0&&(s.replacement=a),v(await t.query("update-table",{identifier:r,updates:s}))}),e.registerTool(Ph.name,{description:Ph.description,annotations:Ph.annotations,inputSchema:Ph.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-table",{identifier:r})))}function GD(e,t){CJ(e,t),AJ(e,t)}var JD=[{annotations:q,name:"list-card-stacks",description:"List all card stacks in the world. Returns every stack (no pagination) with name, ID, type (deck/hand/pile), card count, and description. Use to find a stack before drawing cards or checking a player's hand."},{annotations:Ne,name:"search-card-stacks",description:"Search card stacks by name or card name. Returns matching stacks with names, IDs, types, and card counts. Use to find a deck or hand when you only know part of the name.",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against stack or card name")})},{annotations:q,name:"get-card-stack",description:"Get a card stack with all its cards, including card names, faces, and drawn status. Use to see the full deck contents or a player's hand.",inputSchema:c.object({identifier:c.string().max(256).describe("Card stack name or Foundry ID")})},{annotations:C,name:"create-card-stack",description:`Create a new card stack. Use type "deck" for a draw pile, "hand" for a player's hand, or "pile" for a discard/community pile. Returns the new stack ID.`,inputSchema:c.object({name:c.string().max(256).describe("Card stack name"),type:c.enum(["deck","hand","pile"]).describe("Stack type"),description:c.string().max(5e4).optional().describe("Stack description")})},{annotations:H,name:"update-card-stack",description:"Update a card stack's properties. Change the name, description, or type. For drawing or shuffling cards, use draw-cards or shuffle-deck instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Card stack name or Foundry ID"),name:c.string().max(256).optional().describe("New stack name"),description:c.string().max(5e4).optional().describe("New stack description"),type:c.enum(["deck","hand","pile"]).optional().describe("New stack type")})},{annotations:ee,name:"delete-card-stack",description:"Delete a card stack and all its cards permanently. This is irreversible. Returns the deleted stack name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Card stack name or Foundry ID")})},{annotations:C,name:"draw-cards",description:"Draw or deal cards from one stack to another (e.g., deck \u2192 hand, deck \u2192 discard pile). Cards are moved, not copied \u2014 they leave the source stack. Returns the drawn card names and IDs. Use get-card-stack to see available cards first.",inputSchema:c.object({source:c.string().max(256).describe("Source stack name or ID (e.g., the deck)"),destination:c.string().max(256).describe("Destination stack name or ID (e.g., a hand or pile)"),count:c.number().int().min(1).describe("Number of cards to draw")})},{annotations:C,name:"shuffle-deck",description:"Shuffle a card stack to randomize the card order. Use before dealing or drawing from a deck. Does not recall cards from other stacks \u2014 use reset-deck for that.",inputSchema:c.object({identifier:c.string().max(256).describe("Card stack name or Foundry ID")})},{annotations:H,name:"reset-deck",description:"Reset a deck by recalling all dealt cards from hands and piles back into it, restoring it to its original state. Use to start a new round or game. Does not shuffle \u2014 use shuffle-deck afterward to randomize the order.",inputSchema:c.object({identifier:c.string().max(256).describe("Card stack name or Foundry ID")})}];var[$k,Rh,zh,Oh,jh,Dh,Nh,Ch,Ah]=JD;function MJ(e,t){e.registerTool($k.name,{description:$k.description,annotations:$k.annotations},async()=>v(await t.query("list-card-stacks"))),e.registerTool(Rh.name,{description:Rh.description,annotations:Rh.annotations,inputSchema:Rh.inputSchema.shape},async({query:r})=>v(await t.query("search-card-stacks",{query:r}))),e.registerTool(zh.name,{description:zh.description,annotations:zh.annotations,inputSchema:zh.inputSchema.shape},async({identifier:r})=>v(await t.query("get-card-stack",{identifier:r}))),e.registerTool(Oh.name,{description:Oh.description,annotations:Oh.annotations,inputSchema:Oh.inputSchema.shape},async({name:r,type:i,description:n})=>v(await t.query("create-card-stack",{name:r,type:i,description:n}))),e.registerTool(jh.name,{description:jh.description,annotations:jh.annotations,inputSchema:jh.inputSchema.shape},async({identifier:r,name:i,description:n,type:o})=>{let a={};return i!==void 0&&(a.name=i),n!==void 0&&(a.description=n),o!==void 0&&(a.type=o),v(await t.query("update-card-stack",{identifier:r,updates:a}))}),e.registerTool(Dh.name,{description:Dh.description,annotations:Dh.annotations,inputSchema:Dh.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-card-stack",{identifier:r})))}function UJ(e,t){e.registerTool(Nh.name,{description:Nh.description,annotations:Nh.annotations,inputSchema:Nh.inputSchema.shape},async({source:r,destination:i,count:n})=>v(await t.query("draw-cards",{source:r,destination:i,count:n}))),e.registerTool(Ch.name,{description:Ch.description,annotations:Ch.annotations,inputSchema:Ch.inputSchema.shape},async({identifier:r})=>v(await t.query("shuffle-deck",{identifier:r}))),e.registerTool(Ah.name,{description:Ah.description,annotations:Ah.annotations,inputSchema:Ah.inputSchema.shape},async({identifier:r})=>v(await t.query("reset-deck",{identifier:r})))}function KD(e,t){MJ(e,t),UJ(e,t)}var QD=[{annotations:q,name:"list-macros",description:"List all macros in the world. Returns every macro (no pagination) with name, ID, type (script or chat), and command preview. Use to find a macro before executing or deleting it."},{annotations:Ne,name:"get-macro",description:"Get the full details of a macro including its complete command/code, type, author, and folder. Use when you need to read or review a macro's full source code.",inputSchema:c.object({identifier:c.string().max(256).describe("Macro name or Foundry ID")})},{annotations:Ne,name:"search-macros",description:"Search macros by name or command text. Returns matching macros with names, IDs, types, and match location. Use to find macros related to a topic or containing specific code.",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against macro name or command")})},{annotations:ee,name:"execute-macro",description:'Execute a macro by name or ID. Script macros run arbitrary JavaScript with full Foundry API access \u2014 use with caution as they can modify any game data. Chat macros send chat commands (e.g., "/roll 1d20"). Returns the macro name and type.',inputSchema:c.object({identifier:c.string().max(256).describe("Macro name or Foundry ID")})},{annotations:ee,name:"create-macro",description:'Create a new chat macro (runs chat commands like /roll 1d20 or /whisper GM Hello). Only "chat" macros can be created through the AI \u2014 "script" macros run arbitrary JavaScript with full Foundry API access and are blocked for safety; passing type "script" is rejected, and such macros must be authored by the GM directly in Foundry.',inputSchema:c.object({name:c.string().max(256).describe("Macro name"),command:c.string().max(1e4).describe("Macro command/code to execute"),type:c.enum(["script","chat"]).optional().describe('Macro type. Only "chat" (the default) can be created via the AI; "script" is rejected \u2014 script macros are GM-authored only.')})},{annotations:H,name:"update-macro",description:'Update a macro: change its name, command code, or icon. The type cannot be promoted to "script" through the AI (existing GM-authored script macros keep their type and remain executable). For executing a macro, use execute-macro instead.',inputSchema:c.object({identifier:c.string().max(256).describe("Macro name or Foundry ID"),name:c.string().max(256).optional().describe("New macro name"),type:c.enum(["script","chat"]).optional().describe('New macro type. Can only be set to "chat"; promoting a macro to "script" through the AI is rejected.'),command:c.string().max(1e4).optional().describe("New macro command/code"),img:c.string().max(256).optional().describe("New macro icon image path")})},{annotations:ee,name:"delete-macro",description:"Delete a macro from the world permanently. This is irreversible. Returns the deleted macro name.",inputSchema:c.object({identifier:c.string().max(256).describe("Macro name or Foundry ID")})}];var[Tk,Mh,Uh,Lh,qh,Zh,Fh]=QD;function YD(e,t){e.registerTool(Tk.name,{description:Tk.description,annotations:Tk.annotations},async()=>v(await t.query("list-macros"))),e.registerTool(Mh.name,{description:Mh.description,annotations:Mh.annotations,inputSchema:Mh.inputSchema.shape},async({identifier:r})=>v(await t.query("get-macro",{identifier:r}))),e.registerTool(Uh.name,{description:Uh.description,annotations:Uh.annotations,inputSchema:Uh.inputSchema.shape},async({query:r})=>v(await t.query("search-macros",{query:r}))),e.registerTool(Lh.name,{description:Lh.description,annotations:Lh.annotations,inputSchema:Lh.inputSchema.shape},async({identifier:r})=>v(await t.query("execute-macro",{identifier:r}))),e.registerTool(qh.name,{description:qh.description,annotations:qh.annotations,inputSchema:qh.inputSchema.shape},async({name:r,command:i,type:n})=>v(await t.query("create-macro",{name:r,command:i,type:n}))),e.registerTool(Zh.name,{description:Zh.description,annotations:Zh.annotations,inputSchema:Zh.inputSchema.shape},async({identifier:r,name:i,type:n,command:o,img:a})=>{let s={};return i!==void 0&&(s.name=i),n!==void 0&&(s.type=n),o!==void 0&&(s.command=o),a!==void 0&&(s.img=a),v(await t.query("update-macro",{identifier:r,updates:s}))}),e.registerTool(Fh.name,{description:Fh.description,annotations:Fh.annotations,inputSchema:Fh.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-macro",{identifier:r})))}var XD=[{annotations:q,name:"list-events",description:"List all Ember campaign quest events with their current status (none/available/active/complete). Returns every event (no pagination) with name, ID, type, step, and active/complete flags, plus the currently active event. Use get-event for full details. Requires the Ember module."},{annotations:q,name:"get-event",description:"Get full details of a specific Ember quest event including scene, NPCs, music, journal page, and outcomes. Supports partial name matching. Requires the Ember module.",inputSchema:c.object({identifier:c.string().max(256).describe("Event ID or name (partial match supported)")})},{annotations:C,name:"begin-event",description:"Begin an Ember quest event, triggering its full lifecycle: switches to the event scene, spawns NPC tokens, starts music/soundscape, and opens the journal page. Use list-events to find available events. Requires the Ember module.",inputSchema:c.object({identifier:c.string().max(256).describe("Event ID or name (partial match supported)")})},{annotations:C,name:"complete-event",description:"Complete an Ember quest event, triggering its onEnd lifecycle hook. Omit identifier to complete the currently active event. Use after the party finishes the encounter. Requires the Ember module.",inputSchema:c.object({identifier:c.string().max(256).optional().describe("Event ID or name. Omit to complete the currently active event.")})},{annotations:H,name:"set-event-outcome",description:"Set outcomes on an Ember quest event to record party choices and decisions. Outcomes are key-value pairs tracked by the narrative system for branching storylines. Requires the Ember module.",inputSchema:c.object({identifier:c.string().max(256).describe("Event ID or name"),outcomes:Ue.describe("Outcome key-value pairs to set on the event")})},{annotations:q,name:"get-party-location",description:"Read the party's current position on the Ember region (hex-crawl) map: the current hex \u2014 id (s.iiii.jjjj), biome, location, terrain, slice, and whether it is revealed/explored \u2014 the narrative events that can occur there, and the same compact readout for every adjacent hex. Returns onRegionMap=false (hex fields null) when the party is not on an Ember region scene. Read-only: does not trigger events, advance time, or alter the map. Requires the Ember module."}];var[Ik,Vh,Bh,Hh,Wh,Ek]=XD;function LJ(e,t){e.registerTool(Ik.name,{description:Ik.description,annotations:Ik.annotations},async()=>v(await t.query("list-events"))),e.registerTool(Vh.name,{description:Vh.description,annotations:Vh.annotations,inputSchema:Vh.inputSchema.shape},async({identifier:r})=>v(await t.query("get-event",{identifier:r}))),e.registerTool(Ek.name,{description:Ek.description,annotations:Ek.annotations},async()=>v(await t.query("get-party-location")))}function qJ(e,t){e.registerTool(Bh.name,{description:Bh.description,annotations:Bh.annotations,inputSchema:Bh.inputSchema.shape},async({identifier:r})=>v(await t.query("begin-event",{identifier:r}))),e.registerTool(Hh.name,{description:Hh.description,annotations:Hh.annotations,inputSchema:Hh.inputSchema.shape},async({identifier:r})=>v(await t.query("complete-event",{identifier:r}))),e.registerTool(Wh.name,{description:Wh.description,annotations:Wh.annotations,inputSchema:Wh.inputSchema.shape},async({identifier:r,outcomes:i})=>v(await t.query("set-event-outcome",{identifier:r,outcomes:i})))}function eN(e,t){LJ(e,t),qJ(e,t)}var tN=[{annotations:q,name:"list-effects",description:"List all active effects (buffs, debuffs, spell effects) on an actor. Returns each effect's name, ID, enabled/disabled status, icon, remaining duration, and attribute changes (e.g., +2 AC, 1d4 to attacks). For token-level status icons (prone, poisoned), use toggle-condition (combat bundle) instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or Foundry actor ID")})},{annotations:Ne,name:"get-effect",description:"Get full details of a single active effect on an actor, including all attribute changes, duration, origin, and status. Use when you need to inspect one specific effect's details rather than listing all effects.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or Foundry actor ID"),effectId:c.string().max(256).describe("Effect ID (from list-effects)")})},{annotations:C,name:"create-effect",description:"Add a custom active effect to an actor that modifies its attributes. Effects use Foundry change modes: 0=CUSTOM (no automatic effect), 1=MULTIPLY (multiply attribute by value), 2=ADD (add value to attribute, e.g., +2 AC), 3=DOWNGRADE (apply only if lower than current), 4=UPGRADE (apply only if higher than current), 5=OVERRIDE (replace attribute value entirely). Supports timed duration in combat rounds, turns, or real-time seconds. For simple status icons (prone, poisoned), use toggle-condition (combat bundle) instead. Returns the actor name and created effect ID and name.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or Foundry actor ID"),name:c.string().max(256).describe('Effect name (e.g., "Bless", "Rage", "Shield of Faith")'),changes:c.array(c.object({key:c.string().max(256).describe('System data path (e.g., "system.attributes.ac.bonus")'),mode:c.number().int().min(0).max(5).describe("Change mode: 0=CUSTOM, 1=MULTIPLY, 2=ADD, 3=DOWNGRADE, 4=UPGRADE, 5=OVERRIDE"),value:c.string().max(256).describe('Value to apply (e.g., "2", "1d4")')})).max(100).optional().describe("Attribute changes this effect applies"),icon:c.string().max(256).optional().describe("Effect icon path"),disabled:c.boolean().optional().describe("Create in disabled state (default: false)"),duration:c.object({rounds:c.number().int().min(0).optional(),turns:c.number().int().min(0).optional(),seconds:c.number().min(0).optional()}).optional().describe("Effect duration")})},{annotations:H,name:"update-effect",description:"Update an active effect on an actor. Change name, enable/disable, modify attribute changes, or adjust duration. Use list-effects to find the effect ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or Foundry actor ID"),effectId:c.string().max(256).describe("Effect ID (from list-effects)"),updates:Ue.describe('Update data (e.g., {disabled: true, name: "New Name"})')})},{annotations:ee,name:"delete-effect",description:"Remove an active effect from an actor permanently. Use when a spell or ability effect ends. Use list-effects to find the effect ID first. Returns the actor name and deleted effect ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or Foundry actor ID"),effectId:c.string().max(256).describe("Effect ID (from list-effects)")})}];var[Gh,Jh,Kh,Qh,Yh]=tN;function ZJ(e,t){e.registerTool(Gh.name,{description:Gh.description,annotations:Gh.annotations,inputSchema:Gh.inputSchema.shape},async({identifier:r})=>v(await t.query("list-effects",{identifier:r}))),e.registerTool(Jh.name,{description:Jh.description,annotations:Jh.annotations,inputSchema:Jh.inputSchema.shape},async({identifier:r,effectId:i})=>v(await t.query("get-effect",{identifier:r,effectId:i})))}function FJ(e,t){e.registerTool(Kh.name,{description:Kh.description,annotations:Kh.annotations,inputSchema:Kh.inputSchema.shape},async({identifier:r,name:i,changes:n,icon:o,disabled:a,duration:s})=>v(await t.query("create-effect",{identifier:r,effectData:{name:i,changes:n,icon:o,disabled:a,duration:s}}))),e.registerTool(Qh.name,{description:Qh.description,annotations:Qh.annotations,inputSchema:Qh.inputSchema.shape},async({identifier:r,effectId:i,updates:n})=>v(await t.query("update-effect",{identifier:r,effectId:i,updates:n}))),e.registerTool(Yh.name,{description:Yh.description,annotations:Yh.annotations,inputSchema:Yh.inputSchema.shape},async({identifier:r,effectId:i})=>v(await t.query("delete-effect",{identifier:r,effectId:i})))}function rN(e,t){ZJ(e,t),FJ(e,t)}var nN=[{annotations:Ne,name:"list-items",description:"List standalone world items (not owned by any actor) with pagination. Returns each item's name, ID, type (weapon/spell/equipment/etc.), and folder. Response includes total count and hasMore flag. Filter by item type or folder name. For items on a character's sheet, use search-character-items (characters bundle) instead.",inputSchema:c.object({type:c.string().max(256).optional().describe("Filter by item type (weapon, spell, equipment, consumable, tool, loot, feat, class, subclass, background)"),folder:c.string().max(256).optional().describe("Filter by folder name (substring match)"),limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:Ne,name:"search-items",description:"Search standalone world items by name or description text. Returns matching items with names, IDs, types, and relevance. For searching items on a character, use search-character-items (characters bundle). For compendium items, use search-compendium (compendium bundle).",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against item name or description"),type:c.string().max(256).optional().describe("Filter by item type (weapon, spell, equipment, consumable, tool, loot, feat, class, subclass, background)")})},{annotations:Ne,name:"get-item",description:"Get full details of a standalone world item including name, type, description (HTML), system-specific properties (damage, weight, rarity, etc.), and image path. For items owned by a character, use get-character-item (characters bundle) instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Item name or Foundry ID")})},{annotations:H,name:"update-item",description:"Update a standalone world item using Foundry dot-notation paths. Change name, system data, image, or other properties. Use get-item first to see current values.",inputSchema:c.object({identifier:c.string().max(256).describe("Item name or Foundry ID"),updates:Ue.describe("Foundry update data (e.g. { name, system, img })")})},{annotations:C,name:"create-item",description:"Create a standalone world item (not owned by any actor). Returns the new item ID. Specify the item type (weapon, spell, equipment, consumable, tool, loot, feat, class, subclass, background) and optional system data. For adding items directly to a character, use import-compendium-to-character (characters bundle) instead.",inputSchema:c.object({name:c.string().max(256).describe("Item name"),type:c.string().max(256).describe("Item type (weapon, spell, equipment, consumable, tool, loot, feat, class, subclass, background)"),system:Ue.optional().describe("System-specific data"),img:c.string().max(256).optional().describe("Item image path"),folderId:c.string().max(256).optional().describe("Folder name or ID")})},{annotations:ee,name:"delete-item",description:"Delete a standalone world item permanently. This is irreversible. Returns the deleted item name and ID. For removing items from a character, use delete-character-items (characters bundle) instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Item name or Foundry ID")})}];var[Xh,eg,tg,rg,ng,ig]=nN;function VJ(e,t){e.registerTool(Xh.name,{description:Xh.description,annotations:Xh.annotations,inputSchema:Xh.inputSchema.shape},async({type:r,folder:i,limit:n,offset:o})=>v(await t.query("list-items",{type:r,folder:i,limit:n,offset:o}))),e.registerTool(eg.name,{description:eg.description,annotations:eg.annotations,inputSchema:eg.inputSchema.shape},async({query:r,type:i})=>v(await t.query("search-items",{query:r,type:i}))),e.registerTool(tg.name,{description:tg.description,annotations:tg.annotations,inputSchema:tg.inputSchema.shape},async({identifier:r})=>v(await t.query("get-item",{identifier:r})))}function BJ(e,t){e.registerTool(rg.name,{description:rg.description,annotations:rg.annotations,inputSchema:rg.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-item",{identifier:r,updates:i}))),e.registerTool(ng.name,{description:ng.description,annotations:ng.annotations,inputSchema:ng.inputSchema.shape},async({name:r,type:i,system:n,img:o,folderId:a})=>v(await t.query("create-item",{name:r,type:i,system:n,img:o,folderId:a}))),e.registerTool(ig.name,{description:ig.description,annotations:ig.annotations,inputSchema:ig.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-item",{identifier:r})))}function iN(e,t){VJ(e,t),BJ(e,t)}var oN=c.enum(["Actor","Item","Scene","JournalEntry","RollableTable","Macro","Cards","Playlist"]),aN=[{annotations:q,name:"list-folders",description:"List all folders in the world organized by nesting hierarchy. Returns each folder's name, ID, type, parent folder, color, and document count. Filter by document type (Actor, Item, Scene, JournalEntry, etc.) to see only relevant folders.",inputSchema:c.object({type:oN.optional().describe("Filter by document type")})},{annotations:Ne,name:"get-folder",description:"Get full details of a folder including its name, type, parent, color, depth, and a list of all documents it contains. Use to inspect a folder's contents or metadata.",inputSchema:c.object({identifier:c.string().max(256).describe("Folder name or Foundry ID")})},{annotations:C,name:"create-folder",description:"Create a new folder for organizing documents in the sidebar. Specify the document type this folder holds (Actor, Item, Scene, JournalEntry, RollableTable, Macro, Cards, Playlist) and an optional parent folder for nesting. Returns the new folder ID.",inputSchema:c.object({name:c.string().max(256).describe("Folder name"),type:oN.describe("Document type this folder holds"),parentId:c.string().max(256).optional().describe("Parent folder name or ID for nesting")})},{annotations:H,name:"update-folder",description:"Update a folder \u2014 rename, change parent (for re-nesting), or set a display color. Use list-folders to find the folder ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Folder name or Foundry ID"),name:c.string().max(256).optional().describe("New folder name"),parentId:c.string().max(256).optional().describe("New parent folder name or ID to re-nest under (omit to leave the parent unchanged)"),color:c.string().max(256).optional().describe('Folder color (hex, e.g., "#ff0000")')})},{annotations:ee,name:"delete-folder",description:"Delete a folder from the world permanently. Documents inside the folder are moved to root level, not deleted. Only the folder organization is removed. Returns the deleted folder name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Folder name or Foundry ID")})}];var[og,ag,sg,cg,lg]=aN;function HJ(e,t){e.registerTool(og.name,{description:og.description,annotations:og.annotations,inputSchema:og.inputSchema.shape},async({type:r})=>v(await t.query("list-folders",{type:r}))),e.registerTool(ag.name,{description:ag.description,annotations:ag.annotations,inputSchema:ag.inputSchema.shape},async({identifier:r})=>v(await t.query("get-folder",{identifier:r}))),e.registerTool(sg.name,{description:sg.description,annotations:sg.annotations,inputSchema:sg.inputSchema.shape},async({name:r,type:i,parentId:n})=>v(await t.query("create-folder",{name:r,type:i,parentId:n})))}function WJ(e,t){e.registerTool(cg.name,{description:cg.description,annotations:cg.annotations,inputSchema:cg.inputSchema.shape},async({identifier:r,name:i,parentId:n,color:o})=>{let a={};return i!==void 0&&(a.name=i),n!==void 0&&(a.parentId=n),o!==void 0&&(a.color=o),v(await t.query("update-folder",{identifier:r,updates:a}))}),e.registerTool(lg.name,{description:lg.description,annotations:lg.annotations,inputSchema:lg.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-folder",{identifier:r})))}function sN(e,t){HJ(e,t),WJ(e,t)}var cN=c.discriminatedUnion("type",[c.object({type:c.literal("rectangle"),x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),width:c.number().min(1).describe("Width in pixels"),height:c.number().min(1).describe("Height in pixels"),rotation:c.number().min(0).max(360).optional().describe("Rotation in degrees"),hole:c.boolean().optional().describe("Cut out this shape (default: false)")}),c.object({type:c.literal("circle"),x:c.number().describe("Center X in pixels (from scene top-left)"),y:c.number().describe("Center Y in pixels (from scene top-left)"),radius:c.number().min(1).describe("Radius in pixels"),hole:c.boolean().optional().describe("Cut out this shape (default: false)")}),c.object({type:c.literal("ellipse"),x:c.number().describe("Center X in pixels (from scene top-left)"),y:c.number().describe("Center Y in pixels (from scene top-left)"),radiusX:c.number().min(1).describe("Horizontal radius in pixels"),radiusY:c.number().min(1).describe("Vertical radius in pixels"),rotation:c.number().min(0).max(360).optional().describe("Rotation in degrees"),hole:c.boolean().optional().describe("Cut out this shape (default: false)")}),c.object({type:c.literal("polygon"),points:c.array(c.number()).min(6).max(100).describe("Flat array of x,y pairs: [x1,y1,x2,y2,...] (min 3 points)"),hole:c.boolean().optional().describe("Cut out this shape (default: false)")})]),lN=c.object({name:c.string().max(256).optional(),type:c.string().max(256).describe("Behavior type: adjustDarknessLevel, displayScrollingText, executeMacro, executeScript, pauseGame, suppressWeather, teleportToken, toggleBehavior"),disabled:c.boolean().optional(),system:Ue.optional().describe("Behavior-specific configuration")}),uN=[{annotations:q,name:"list-regions",description:"List all regions (Foundry v13 trigger zones) on the active scene. Returns every region (no pagination) with name, ID, shape summary (type, position in pixels, dimensions), behavior types, and color. Use to find region IDs before inspecting or modifying them."},{annotations:q,name:"get-region",description:"Get a region with full details including all shapes (rectangle, circle, ellipse, polygon) with positions in pixels, attached behaviors with their configuration, elevation range, and display color. Use for inspecting region setup before modifying it.",inputSchema:c.object({identifier:c.string().max(256).describe("Region name or Foundry ID")})},{annotations:H,name:"update-region",description:"Update a region on the active scene. Modify name, display color, shapes, or behaviors. Shapes and behaviors are replaced entirely when provided \u2014 include all desired entries, not just changes.",inputSchema:c.object({identifier:c.string().max(256).describe("Region name or Foundry ID"),name:c.string().max(256).optional().describe("New region name"),color:c.string().max(256).optional().describe('New display color (hex, e.g., "#ff0000")'),shapes:c.array(cN).max(100).optional().describe("Replace region shapes"),behaviors:c.array(lN).max(100).optional().describe("Replace region behaviors")})},{annotations:C,name:"create-region",description:"Create a trigger region on the active scene (Foundry v13+). Regions trigger behaviors when tokens enter, exit, or move within the defined area. All shape coordinates are in pixels (from scene top-left). Attach behaviors like executeMacro (run code on enter/exit), teleportToken (portal), displayScrollingText (area labels), pauseGame, or suppressWeather.",inputSchema:c.object({name:c.string().max(256).describe("Region name"),shapes:c.array(cN).max(100).describe("Region shapes (rectangle, circle, ellipse, or polygon)"),color:c.string().max(256).optional().describe('Display color (hex, e.g., "#ff0000")'),behaviors:c.array(lN).max(100).optional().describe("Behaviors to attach to the region")})},{annotations:ee,name:"delete-regions",description:"Delete regions from the active scene permanently. Removes the trigger zones, all shapes, and all attached behaviors. Does not affect tokens currently inside the region. Returns the count of deleted regions, their IDs, and the scene name.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Region IDs to delete")})}];var[Pk,ug,dg,pg,mg]=uN;function GJ(e,t){e.registerTool(Pk.name,{description:Pk.description,annotations:Pk.annotations},async()=>v(await t.query("list-regions"))),e.registerTool(ug.name,{description:ug.description,annotations:ug.annotations,inputSchema:ug.inputSchema.shape},async({identifier:r})=>v(await t.query("get-region",{identifier:r})))}function JJ(e,t){e.registerTool(dg.name,{description:dg.description,annotations:dg.annotations,inputSchema:dg.inputSchema.shape},async({identifier:r,name:i,color:n,shapes:o,behaviors:a})=>{let s={};return i!==void 0&&(s.name=i),n!==void 0&&(s.color=n),o!==void 0&&(s.shapes=o),a!==void 0&&(s.behaviors=a),v(await t.query("update-region",{identifier:r,updates:s}))})}function KJ(e,t){e.registerTool(pg.name,{description:pg.description,annotations:pg.annotations,inputSchema:pg.inputSchema.shape},async({name:r,shapes:i,color:n,behaviors:o})=>v(await t.query("create-region",{name:r,shapes:i,color:n,behaviors:o}))),e.registerTool(mg.name,{description:mg.description,annotations:mg.annotations,inputSchema:mg.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-regions",{identifiers:r})))}function dN(e,t){GJ(e,t),JJ(e,t),KJ(e,t)}import{readFile as QJ}from"node:fs/promises";import YJ from"node:path";var pN=[{annotations:hm,name:"generate-image",description:"Generate an image and display it in the chat. Use for character portraits, item illustrations, concept art, or any visual the user wants to SEE in conversation. Does NOT create or modify scenes \u2014 for battle maps and scene backgrounds, use generate-scene instead. Returns the image inline as base64. Be specific in prompts about subject, style, composition, lighting, and mood. Requires image generation to be enabled with a valid API key.",inputSchema:c.object({prompt:c.string().max(1e4).describe("Detailed image generation prompt. Be specific about subject, style, composition, lighting, and mood. For fantasy RPG content, include relevant details like setting, character features, and atmosphere."),size:c.enum(["square","landscape","portrait"]).optional().describe("Image aspect ratio (default: square). Use landscape for wide scenes, portrait for characters."),style:c.string().max(256).optional().describe('Optional style modifier (e.g., "oil painting", "watercolor", "pixel art", "photorealistic"). Appended to the prompt to influence the visual style.')})}];var XJ=5e3,e8=9e4,t8=c.object({imagePath:c.string(),mimeType:c.string(),provider:c.string(),model:c.string()}),[fg]=pN;function r8(e){if(e.includes("\0")||e.includes("\\"))return!1;let t=e.split("/");return t.includes("..")?!1:t[0]==="familiar"}async function n8(e){let{imagePath:t,mimeType:r,provider:i,model:n,foundryDataPath:o}=e;if(!r8(t))return{content:[{type:"text",text:"Image generation failed: invalid image path"}],isError:!0};try{let a=YJ.join(o,t);return{content:[{type:"image",data:(await QJ(a)).toString("base64"),mimeType:r},{type:"text",text:`Image generated by ${i} (${n})`}]}}catch(a){return{content:[{type:"text",text:`Image saved to Foundry (${t}) but could not be read: ${yr(a)}`}],isError:!0}}}function mN(e,t,{foundryDataPath:r}){e.registerTool(fg.name,{description:fg.description,annotations:fg.annotations,inputSchema:fg.inputSchema.shape},async({prompt:i,size:n,style:o},a)=>{let s=st(a,"Generating image\u2026",XJ);try{let l=await li("Image generation",t8,()=>t.query("generate-image",{prompt:i,size:n,style:o},e8));return l.ok?r==null?{content:[{type:"text",text:`Image saved to Foundry (${l.data.imagePath}) but cannot be returned \u2014 set FAMILIAR_FOUNDRY_DATA env var`}],isError:!0}:await n8({imagePath:l.data.imagePath,mimeType:l.data.mimeType,provider:l.data.provider,model:l.data.model,foundryDataPath:r}):l.envelope}finally{s()}})}var fN=[{annotations:ik,name:"generate-voice",description:"Generate speech audio (text-to-speech) from text using the configured TTS provider (ElevenLabs, Cartesia, or OpenAI TTS). Returns audio that plays in Foundry for the GM. Use for NPC dialogue, dramatic narration, or reading text aloud. Keep text under 500 characters for best results. Requires voice generation to be enabled and a voice provider configured in Foundry module settings.",inputSchema:c.object({text:c.string().max(1e4).describe("The text to convert to speech. Keep it concise for best results \u2014 under 500 characters."),voice:c.string().max(256).optional().describe("Optional voice ID to use instead of the configured default. Each provider has different voice IDs \u2014 check provider documentation."),characterId:c.string().max(256).optional().describe("Optional actor name or ID. Resolves the voice from the character\u2019s stored voice (set via set-character-voice). Ignored if voice is explicitly provided."),language:c.string().max(16).optional().describe('Optional ISO 639-1 language code (e.g. "en", "nl", "de", "fr"). Currently consumed by Cartesia (42 languages); ignored by providers that auto-detect language. Defaults to "en".'),emotion:c.string().max(64).optional().describe('Optional emotion/tone hint. Use sparingly \u2014 one word per call, only when the line needs distinct emotional colouring. Reliable values: "angry", "sad", "happy", "excited", "calm", "scared", "whispering", "shouting", "nervous", "tired". Applied on Cartesia (generation_config.emotion) and OpenAI (instructions prefix). Has NO effect on ElevenLabs under the default model: ElevenLabs emotion needs audio tags, which only render on the eleven_v3 model (not the default eleven_multilingual_v2). Unknown emotions are silently ignored on all providers \u2014 pick a value from the reliable list above.')})},{annotations:ik,name:"generate-dialogue",description:"Generate multi-voice dialogue with different voices for DM narration and character speech. Each segment specifies text and a role (dm or character) to select the corresponding voice provider from settings. All segments generate in parallel for speed, then play sequentially as an audio queue with no gaps between lines. Use this instead of multiple generate-voice calls when you have a conversation between the DM and characters. Max 20 segments per call.",inputSchema:c.object({segments:c.array(c.object({text:c.string().max(1e4).describe("The text to speak."),role:c.enum(["dm","character"]).describe('Voice slot: "dm" uses DM/Narrator voice settings, "character" uses Character voice settings.'),voice:c.string().max(256).optional().describe("Optional voice ID override for this segment."),characterId:c.string().max(256).optional().describe("Optional actor name or ID. Resolves the voice from the character\u2019s stored voice (set via set-character-voice). Ignored if voice is explicitly provided."),instructions:c.string().max(1e3).optional().describe('Tone/style instructions for this segment (OpenAI TTS only). E.g. "Speak in a menacing whisper".'),speed:c.number().min(.25).max(4).optional().describe("Speech speed for this segment (OpenAI TTS only). 0.25\u20134.0, default 1.0."),stability:c.number().min(0).max(1).optional().describe("Voice stability for this segment (ElevenLabs only). Lower = more expressive."),similarityBoost:c.number().min(0).max(1).optional().describe("Similarity boost for this segment (ElevenLabs only). Higher = closer to original voice."),language:c.string().max(16).optional().describe('Optional ISO 639-1 language code (e.g. "en", "nl", "de"). Consumed by Cartesia; ignored by auto-detecting providers.'),emotion:c.string().max(64).optional().describe('Optional emotion/tone hint for this segment (e.g. "angry", "sad", "whispering"). Applied on Cartesia/OpenAI; no effect on ElevenLabs under the default model \u2014 see generate-voice.emotion for the full guidance.')})).min(1).max(20).describe("Dialogue segments in playback order. Each uses the DM or character voice slot.")})},{annotations:H,name:"set-character-voice",description:"Assign a TTS voice to a specific character or NPC. The voice ID is stored on the actor and automatically used when that character speaks via generate-voice or generate-dialogue. Voice IDs are provider-specific (e.g. ElevenLabs voice_id, OpenAI voice name). Pass an empty voiceId to clear the assignment.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or ID to assign the voice to."),voiceId:c.string().max(256).describe("Provider-specific voice ID to assign. Pass empty string to clear the voice assignment.")})},{annotations:Ne,name:"get-character-voice",description:"Get the TTS voice ID assigned to a specific character or NPC. Returns the voice ID if one is configured, or null if the character uses the default voice slot.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or ID to look up.")})}];var hN=5e3,bg=6e4,i8=c.object({provider:c.string(),model:c.string(),text:c.string()}),o8=c.object({successCount:c.number(),failureCount:c.number(),segments:c.array(c.object({text:c.string(),role:c.string(),index:c.number(),provider:c.string(),model:c.string(),error:c.string().optional()}))}),a8=c.object({voiceId:c.string().optional()}),[hg,gg,yg,vg]=fN;function s8(e,t){e.registerTool(hg.name,{description:hg.description,annotations:hg.annotations,inputSchema:hg.inputSchema.shape},async({text:r,voice:i,characterId:n,language:o,emotion:a},s)=>{let l=st(s,"Generating audio\u2026",hN);try{let u=await li("Voice generation",i8,()=>t.query("generate-voice",{text:r,voice:i,characterId:n,language:o,emotion:a},bg));return u.ok?{content:[{type:"text",text:`Audio generated by ${u.data.provider} (${u.data.model}) and playing in Foundry VTT. Spoken text: "${u.data.text}"`}]}:u.envelope}finally{l()}})}function c8(e,t){e.registerTool(gg.name,{description:gg.description,annotations:gg.annotations,inputSchema:gg.inputSchema.shape},async({segments:r},i)=>{let n=st(i,"Generating dialogue\u2026",hN);try{let o=await li("Dialogue generation",o8,()=>t.query("generate-dialogue",{segments:r},bg*2));if(!o.ok)return o.envelope;let a=o.data.segments.map(s=>s.error!=null&&s.error!==""?` [${s.role}] FAILED: ${s.error}`:` [${s.role}] "${s.text}" \u2014 ${s.provider}`);return{content:[{type:"text",text:`Dialogue (${String(o.data.successCount)}/${String(o.data.segments.length)} segments) playing sequentially in Foundry:
134
+ [Response truncated \u2014 was ${String(i)}KB, exceeded ${String(r)}KB cap. Use more specific queries or filters.]`;let n=0,o=e.length;for(;n<o;){let a=Math.ceil((n+o)/2);JSON.stringify({truncated:!0,totalItems:e.length,items:e.slice(0,a)}).length<=512e3?n=a:o=a-1}return n===0?`[Response truncated \u2014 first item alone exceeded ${String(r)}KB cap (full result was ${String(i)}KB across ${String(e.length)} items). Use more specific queries or filters.]`:JSON.stringify({truncated:!0,totalItems:e.length,items:e.slice(0,n)})}function AG(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function MG(e){if(YP(e)!==!0)return"";let t=Md(e);return t!==void 0&&t>0?` (retryable after ${String(t)}ms)`:" (retryable)"}function v(e){if(e.success){let a=JSON.stringify(e.result),s=AG(e.result)?e.result:void 0,l=a.length<=512e3?a:CG(e.result,a.length);return s===void 0?{content:[{type:"text",text:l}]}:{content:[{type:"text",text:l}],structuredContent:s}}let t=on(e.error),r=ds(e.error),i=nD(r),n=MG(e.error);return{content:[{type:"text",text:`${r!==void 0&&r!==""?`Error [${r}]${n}`:"Error"}: ${t}${i}`}],isError:!0}}var Sm=class{bundles=new Map;enabledBundles=new Set;toolRegistry={};server;constructor(t){this.server=t}initialize(t,r=!1){this.toolRegistry=Yo(this.server);for(let n of ym){let o=t.get(n.id)??[],a={...n,toolNames:o};if(this.bundles.set(n.id,a),r||ok.has(n.id))this.enabledBundles.add(n.id);else for(let s of o)this.toolRegistry[s]?.disable()}let i=[...this.enabledBundles].reduce((n,o)=>n+(this.bundles.get(o)?.toolNames.length??0),0);$.info({coreBundles:[...this.enabledBundles],coreToolCount:i,totalBundles:this.bundles.size},r?"Tool bundles initialized (all enabled for HTTP)":"Tool bundles initialized (lazy loading enabled)")}enableBundle(t){let r=this.bundles.get(t);if(!r)return{enabled:!1,toolNames:[],error:`Unknown bundle: ${t}`};if(this.enabledBundles.has(t))return{enabled:!0,toolNames:r.toolNames,alreadyEnabled:!0};for(let i of r.toolNames)this.toolRegistry[i]?.enable();return this.enabledBundles.add(t),$.info({bundleId:t,toolCount:r.toolNames.length},"Tool bundle enabled"),{enabled:!0,toolNames:r.toolNames}}disableBundle(t){let r=this.bundles.get(t);if(!r)return{disabled:!1,toolNames:[],error:`Unknown bundle: ${t}`};if(ok.has(t))return{disabled:!1,toolNames:r.toolNames,error:`Core bundle "${t}" cannot be disabled`};if(!this.enabledBundles.has(t))return{disabled:!0,toolNames:r.toolNames,alreadyDisabled:!0};for(let i of r.toolNames)this.toolRegistry[i]?.disable();return this.enabledBundles.delete(t),$.info({bundleId:t,toolCount:r.toolNames.length},"Tool bundle disabled"),{disabled:!0,toolNames:r.toolNames}}listBundles(){return ym.map(t=>{let r=this.bundles.get(t.id);return{id:t.id,name:t.name,description:t.description,toolCount:r?r.toolNames.length:t.toolCount,enabled:this.enabledBundles.has(t.id)}})}isEnabled(t){return this.enabledBundles.has(t)}};function iD(e,t,r){UG(e,t),LG(e,t,r)}function UG(e,t){e.registerTool("list-tool-bundles",{description:"List available tool bundles. Tools are organized into domain bundles that can be enabled on demand. Core bundles (dice, world, chat, combat, characters) are always enabled. Enable additional bundles with enable-tool-bundle when you need their capabilities.",annotations:q},()=>({content:[{type:"text",text:JSON.stringify(t.listBundles())}]})),e.registerTool("enable-tool-bundle",{description:"Enable a tool bundle to access its tools. Call list-tool-bundles first to see available bundles. You can enable multiple bundles. Once enabled, the tools become available for use.",annotations:H,inputSchema:{bundleId:c.string().describe('Bundle ID to enable (e.g., "scenes", "compendium", "playlists")')}},({bundleId:r})=>{let i=t.enableBundle(r);return{content:[{type:"text",text:JSON.stringify(i)}],...i.enabled?{}:{isError:!0}}}),e.registerTool("disable-tool-bundle",{description:"Disable a previously enabled tool bundle to reduce active tool count. Core bundles (dice, world, chat, combat, characters) cannot be disabled. Call list-tool-bundles to see which bundles are currently enabled.",annotations:C,inputSchema:{bundleId:c.string().describe('Bundle ID to disable (e.g., "scenes", "compendium", "playlists")')}},({bundleId:r})=>{let i=t.disableBundle(r);return{content:[{type:"text",text:JSON.stringify(i)}],...i.disabled?{}:{isError:!0}}})}function LG(e,t,r){e.registerTool("get-server-diagnostics",{description:"Get Familiar server diagnostics: tool call metrics (counts, durations, errors) and bundle status. Use for troubleshooting performance or checking which bundles are active.",annotations:q},()=>({content:[{type:"text",text:JSON.stringify({bundles:t.listBundles(),metrics:r?.getSummary()??null})}]}))}function oD(e,t,r){return{query:async(i,n,o)=>(r.has(i)&&t.enableBundle("combat-ai"),e.query(i,n,o))}}var Ue=c.preprocess(Ud,c.record(c.string(),c.unknown()));var ui=[{annotations:q,name:"get-character",description:"Get a character's full stat block. Returns ability scores, HP, AC, skills, saves, items, spells, features, and biography. For a quick overview of all characters, use list-characters instead. Accepts character name (exact match) or Foundry actor ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID")})},{annotations:q,name:"list-characters",description:"List actors in the world with summary info (name, type, HP, AC, level). Returns a paginated list without full stat blocks. Filter by type: 'character' for PCs, 'npc' for NPCs, or 'all'. Response includes total count and hasMore flag. For full details on one character, use get-character instead.",inputSchema:c.object({type:c.enum(["all","character","npc"]).optional().describe("Filter by actor type (default: all)"),limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:q,name:"list-party-members",description:"Get all player-owned characters (the adventuring party). Returns a lightweight summary (name, HP, AC, level) by default. Set detailed:true for full character sheets including abilities, items, and effects. Use to quickly see party composition without listing all NPCs.",inputSchema:c.object({detailed:c.boolean().optional().describe("Return full character sheets instead of summaries (default: false)")})},{annotations:H,name:"update-character",description:'Update character data using Foundry dot-notation paths (e.g., {"system.attributes.hp.value": 25}). Use get-character first to see current values and valid paths. Accepts character name (exact match) or Foundry actor ID. Returns the updated actor name and ID.',inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),updates:Ue.describe('Foundry update data (e.g., {"system.attributes.hp.value": 25})')})},{annotations:q,name:"search-character-items",description:"Search a character's inventory, spells, and features. Returns a compact list of matching items (max 50) with IDs, names, and types. Use to find an item before calling get-character-item for full details. Filter by name substring, item type, or both.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),query:c.string().max(256).optional().describe('Filter by name substring (e.g., "sword", "fire")'),type:c.enum(["weapon","spell","feat","equipment","consumable","tool","loot","class","subclass","background","race"]).optional().describe("Filter by item type")})},{annotations:q,name:"get-character-item",description:"Get full details of a specific item, spell, or feature on a character. Returns description, properties, damage, range, and all game mechanics. Use the item ID from search-character-items results.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),itemId:c.string().max(256).describe("Item ID (from search-character-items results)")})},{annotations:C,name:"import-compendium-to-character",description:"Import an item or spell from a compendium pack onto a character's sheet. Copies the compendium entry as a new owned item. Use search-compendium (compendium bundle) first to find the pack key and entry ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),pack:c.string().max(256).describe('Compendium pack key (e.g., "dnd5e.spells")'),entryId:c.string().max(256).describe("Entry ID from search-compendium results")})},{annotations:ee,name:"delete-character-items",description:"Remove items, spells, or features from a character permanently. Accepts item names or Foundry document IDs. This is destructive and irreversible. Returns the actor name, count of deleted items, and their IDs.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),itemIds:c.array(c.string().max(256)).min(1).max(100).describe("Item names or Foundry document IDs to remove")})},{annotations:H,name:"rename-character",description:"Rename an actor and update its prototype token name to match. Returns the updated name. Accepts character name (exact match) or Foundry actor ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),name:c.string().max(256).describe("New name for the character")})},{annotations:C,name:"create-character",description:"Create a new blank character (NPC or PC) from scratch. Returns the new actor's ID. For creating variants of existing characters, use duplicate-character instead. To import a compendium creature, use the compendium bundle.",inputSchema:c.object({name:c.string().max(256).describe("Character name"),type:c.enum(["character","npc"]).optional().describe("Character type (default: npc)"),system:Ue.optional().describe("System data (e.g., ability scores, HP)"),img:c.string().max(256).optional().describe("Token/portrait image path"),folderId:c.string().max(256).optional().describe("Folder name or ID")})},{annotations:ee,name:"delete-character",description:"Delete a character from the world permanently. Removes the actor and all its owned items, spells, and features. This is irreversible. Tokens on scenes will become unlinked. Returns the deleted character name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID")})},{annotations:C,name:"duplicate-character",description:"Clone an existing character to create a copy with all items, spells, and features. Returns the new actor's ID. Use for creating NPC variants (e.g., 'Goblin Archer' from 'Goblin'). For creating characters from scratch, use create-character instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID to clone"),name:c.string().max(256).optional().describe('Name for the copy (default: "<original> (Copy)")')})},{annotations:wn,name:"assign-character-ownership",description:"Give a player ownership or limited access to a character. Permission levels: 0=NONE, 1=LIMITED (basic info), 2=OBSERVER (full view), 3=OWNER (full control). Use OWNER (3) to assign a character to a player. Use list-users to find valid user names/IDs. Returns the character name, user name, and applied permission level.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),user:c.string().max(256).describe("Player name or user ID"),level:c.number().int().min(0).max(3).describe("Permission level: 0=NONE, 1=LIMITED, 2=OBSERVER, 3=OWNER")})},{annotations:wn,name:"revoke-character-ownership",description:"Remove a player's access to a character by setting their permission to NONE. The character becomes invisible to that player unless default permissions allow it. Use list-users to find valid user names/IDs. Returns the character and user names.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),user:c.string().max(256).describe("Player name or user ID")})},{annotations:H,name:"update-character-item",description:'Update an item on a character using dot-notation paths (e.g., {"system.levels": 5, "system.equipped": true}). Use get-character-item first to see current values and valid paths. Accepts character name or actor ID.',inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),itemId:c.string().max(256).describe("Item ID on the character (from get-character or search-character-items)"),updates:Ue.describe("Foundry update data with dot-notation paths")})},{annotations:ee,name:"level-up-class",description:"Level up a class on a character. Increments the class level, adds average HP per level (half hit die + 1 + CON mod), and auto-updates proficiency and spell slots. Does not auto-grant class features \u2014 use import-compendium-to-character for those.",inputSchema:c.object({identifier:c.string().max(256).describe("Character name or Foundry actor ID"),className:c.string().max(256).describe('Class name on the character (e.g., "Rogue", "Paladin")'),levels:c.number().int().min(1).max(19).optional().describe("Number of levels to add (default: 1)")})}];var xm=ui[12],wm=ui[13];function aD(e,t){e.registerTool(xm.name,{description:xm.description,annotations:xm.annotations,inputSchema:xm.inputSchema.shape},async({identifier:r,user:i,level:n})=>v(await t.query("assign-character-ownership",{identifier:r,user:i,level:n}))),e.registerTool(wm.name,{description:wm.description,annotations:wm.annotations,inputSchema:wm.inputSchema.shape},async({identifier:r,user:i})=>v(await t.query("revoke-character-ownership",{identifier:r,user:i})))}var[km,$m,Tm,Im,Em,Pm,Rm,zm,Om,jm,Dm,Nm]=ui,Cm=ui[14],Am=ui[15];function qG(e,t){e.registerTool(km.name,{description:km.description,annotations:km.annotations,inputSchema:km.inputSchema.shape},async({identifier:r})=>v(await t.query("get-character",{identifier:r}))),e.registerTool($m.name,{description:$m.description,annotations:$m.annotations,inputSchema:$m.inputSchema.shape},async({type:r,limit:i,offset:n})=>v(await t.query("list-characters",{type:r,limit:i,offset:n}))),e.registerTool(Tm.name,{description:Tm.description,annotations:Tm.annotations,inputSchema:Tm.inputSchema.shape},async({detailed:r})=>v(await t.query("list-party-members",{detailed:r})))}function ZG(e,t){e.registerTool(Em.name,{description:Em.description,annotations:Em.annotations,inputSchema:Em.inputSchema.shape},async({identifier:r,query:i,type:n})=>v(await t.query("search-character-items",{identifier:r,query:i,type:n}))),e.registerTool(Pm.name,{description:Pm.description,annotations:Pm.annotations,inputSchema:Pm.inputSchema.shape},async({identifier:r,itemId:i})=>v(await t.query("get-character-item",{identifier:r,itemId:i})))}function FG(e,t){e.registerTool(Rm.name,{description:Rm.description,annotations:Rm.annotations,inputSchema:Rm.inputSchema.shape},async({identifier:r,pack:i,entryId:n})=>v(await t.query("import-compendium-to-character",{identifier:r,pack:i,entryId:n}))),e.registerTool(zm.name,{description:zm.description,annotations:zm.annotations,inputSchema:zm.inputSchema.shape},async({identifier:r,itemIds:i})=>v(await t.query("delete-character-items",{identifier:r,itemIds:i}))),e.registerTool(Cm.name,{description:Cm.description,annotations:Cm.annotations,inputSchema:Cm.inputSchema.shape},async({identifier:r,itemId:i,updates:n})=>v(await t.query("update-character-item",{identifier:r,itemId:i,updates:n}))),e.registerTool(Am.name,{description:Am.description,annotations:Am.annotations,inputSchema:Am.inputSchema.shape},async({identifier:r,className:i,levels:n})=>v(await t.query("level-up-class",{identifier:r,className:i,levels:n})))}function VG(e,t){ZG(e,t),FG(e,t)}function BG(e,t){e.registerTool(Im.name,{description:Im.description,annotations:Im.annotations,inputSchema:Im.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-character",{identifier:r,updates:i}))),e.registerTool(Om.name,{description:Om.description,annotations:Om.annotations,inputSchema:Om.inputSchema.shape},async({identifier:r,name:i})=>v(await t.query("rename-character",{identifier:r,name:i})))}function HG(e,t){e.registerTool(jm.name,{description:jm.description,annotations:jm.annotations,inputSchema:jm.inputSchema.shape},async({name:r,type:i,system:n,img:o,folderId:a})=>v(await t.query("create-character",{name:r,type:i,system:n,img:o,folderId:a}))),e.registerTool(Dm.name,{description:Dm.description,annotations:Dm.annotations,inputSchema:Dm.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-character",{identifier:r}))),e.registerTool(Nm.name,{description:Nm.description,annotations:Nm.annotations,inputSchema:Nm.inputSchema.shape},async({identifier:r,name:i})=>v(await t.query("duplicate-character",{identifier:r,name:i})))}function WG(e,t){BG(e,t),HG(e,t),aD(e,t)}function sD(e,t){qG(e,t),VG(e,t),WG(e,t)}var cD=3e4,lD=5e3,[Mm,Um,Lm,ak,sk,qm,ck,lk,uk,dk,Zm,Fm,Vm,Bm,Hm,Wm,Gm]=gm;function GG(e,t){e.registerTool(Mm.name,{description:Mm.description,annotations:Mm.annotations,inputSchema:Mm.inputSchema.shape},async({identifier:r,condition:i})=>v(await t.query("toggle-condition",{identifier:r,condition:i}))),e.registerTool(Um.name,{description:Um.description,annotations:Um.annotations,inputSchema:Um.inputSchema.shape},async({identifiers:r,condition:i})=>v(await t.query("apply-condition-to-group",{identifiers:r,condition:i})))}function JG(e,t){e.registerTool(Lm.name,{description:Lm.description,annotations:Lm.annotations,inputSchema:Lm.inputSchema.shape},async({identifiers:r,includeHidden:i})=>v(await t.query("start-combat",{identifiers:r,includeHidden:i}))),e.registerTool(ak.name,{description:ak.description,annotations:ak.annotations},async()=>v(await t.query("end-combat"))),e.registerTool(sk.name,{description:sk.description,annotations:sk.annotations},async()=>v(await t.query("get-combat"))),e.registerTool(qm.name,{description:qm.description,annotations:qm.annotations,inputSchema:qm.inputSchema.shape},async({combatantIds:r})=>v(await t.query("roll-initiative",{combatantIds:r}))),e.registerTool(ck.name,{description:ck.description,annotations:ck.annotations},async()=>v(await t.query("roll-npc-initiative"))),e.registerTool(Gm.name,{description:Gm.description,annotations:Gm.annotations,inputSchema:Gm.inputSchema.shape},async({identifiers:r,rollNpcOnly:i,includeHidden:n},o)=>{let a=st(o,"Starting combat\u2026",lD);try{return v(await t.query("quick-start-combat",{identifiers:r,rollNpcOnly:i,includeHidden:n},cD))}finally{a()}})}function KG(e,t){e.registerTool(lk.name,{description:lk.description,annotations:lk.annotations},async()=>v(await t.query("next-turn"))),e.registerTool(uk.name,{description:uk.description,annotations:uk.annotations},async()=>v(await t.query("next-round"))),e.registerTool(dk.name,{description:dk.description,annotations:dk.annotations},async()=>v(await t.query("get-available-conditions")))}function QG(e,t){JG(e,t),KG(e,t)}function YG(e,t){e.registerTool(Zm.name,{description:Zm.description,annotations:Zm.annotations,inputSchema:Zm.inputSchema.shape},async({targets:r,reason:i})=>v(await t.query("apply-damage",{targets:r,reason:i}))),e.registerTool(Fm.name,{description:Fm.description,annotations:Fm.annotations,inputSchema:Fm.inputSchema.shape},async({amount:r,identifier:i})=>v(await t.query("heal-party",{amount:r,identifier:i}))),e.registerTool(Vm.name,{description:Vm.description,annotations:Vm.annotations,inputSchema:Vm.inputSchema.shape},async({type:r},i)=>{let n=st(i,"Resting party\u2026",lD);try{return v(await t.query("rest-party",{type:r},cD))}finally{n()}})}function XG(e,t){e.registerTool(Bm.name,{description:Bm.description,annotations:Bm.annotations,inputSchema:Bm.inputSchema.shape},async({identifiers:r})=>v(await t.query("add-combatant",{identifiers:r}))),e.registerTool(Hm.name,{description:Hm.description,annotations:Hm.annotations,inputSchema:Hm.inputSchema.shape},async({combatantIds:r})=>v(await t.query("remove-combatant",{combatantIds:r}))),e.registerTool(Wm.name,{description:Wm.description,annotations:Wm.annotations,inputSchema:Wm.inputSchema.shape},async({amount:r,identifiers:i,reason:n})=>v(await t.query("award-xp",{amount:r,identifiers:i,reason:n})))}function uD(e,t){GG(e,t),QG(e,t),YG(e,t),XG(e,t)}var pk=[{annotations:q,name:"get-combat-snapshot",description:"Get a comprehensive combat snapshot for AI decision-making. Returns the current combatant's full stat block, all combatants with HP/AC/conditions, battlefield token positions with distances, and available weapons/spells/features. Designed to give an AI everything needed to decide an NPC's turn in a single call."},{annotations:C,name:"resolve-attack",description:"Resolve an NPC weapon attack against a target. Rolls the attack (with optional advantage/disadvantage), compares against target AC, rolls damage on hit (doubled dice on critical), applies damage to the target, and posts the result to chat. By default the attack is rejected if the target is outside the weapon's range \u2014 pass force:true for narrative cases (gliding monster, teleport-attack, mid-flight pounce); an audit message is posted to chat when this is used.",inputSchema:c.object({attacker:c.string().max(256).describe("Name or Foundry token ID of the attacking NPC"),target:c.string().max(256).describe("Name or Foundry token ID of the target"),weapon:c.string().max(256).describe("Weapon name or item ID on the attacker"),advantage:c.boolean().optional().describe("Roll with advantage (2d20 keep highest)"),disadvantage:c.boolean().optional().describe("Roll with disadvantage (2d20 keep lowest)"),smiteLevel:c.number().int().min(1).max(5).optional().describe("Divine Smite: expend a spell slot at this level for bonus radiant damage (2d8 + 1d8/level above 1st)"),force:c.boolean().optional().describe("Bypass the attacker-vs-target range check for narrative cases (e.g. a creature gliding into melee mid-air). Cover, walls, conditions, and everything else still apply. An audit message is posted to chat for transparency."),reason:c.string().max(280).optional().describe('Why the force bypass is justified (e.g. "gliding into melee", "teleport-attack chain", "Sentinel reaction"). Recorded in the [Familiar audit] chat.')})},{annotations:C,name:"resolve-movement",description:"Move a token to a new position on the battlefield during combat. Coordinates are in pixels (from scene top-left). Calculates distance moved in game units (feet), compares against the actor's walk speed, updates the token position, and reports movement remaining. Use this during combat turns instead of move-token \u2014 it enforces movement speed limits.",inputSchema:c.object({identifier:c.string().max(256).describe("Token name or Foundry ID to move"),x:c.number().int().describe("Target X position in pixels (from scene top-left)"),y:c.number().int().describe("Target Y position in pixels (from scene top-left)")})},{annotations:C,name:"resolve-spell",description:"Cast a spell from an NPC. Consumes a spell slot (if not a cantrip), resolves spell attacks or saving throws per target, rolls and applies damage, handles concentration, and posts results to chat. By default the cast is rejected if any target is outside the spell's range \u2014 pass force:true for narrative cases (teleport-bound spell, scrying-anchor target); an audit message is posted to chat when this is used.",inputSchema:c.object({caster:c.string().max(256).describe("Name or Foundry token ID of the caster"),spell:c.string().max(256).describe("Spell name or item ID on the caster"),targets:c.array(c.string().max(256)).max(50).describe("Token names or Foundry IDs of the targets"),upcastLevel:c.number().int().min(1).max(9).optional().describe("Cast at a higher spell level (upcasting)"),force:c.boolean().optional().describe("Bypass the caster-vs-target range check for narrative cases (e.g. scrying anchor, teleport-bound delivery). Spell mechanics and target validity still apply. An audit message is posted to chat for transparency."),reason:c.string().max(280).optional().describe('Why the force bypass is justified (e.g. "teleport-bound delivery", "scrying anchor target"). Recorded in the [Familiar audit] chat.')})},{annotations:C,name:"resolve-saving-throw",description:"Force a saving throw on a target. Rolls d20 + save modifier against a DC and reports success or failure.",inputSchema:c.object({target:c.string().max(256).describe("Name or Foundry token ID of the target"),ability:c.enum(["str","dex","con","int","wis","cha"]).describe("Ability for the save (str, dex, con, int, wis, cha)"),dc:c.number().int().min(1).max(99).describe("Difficulty class for the saving throw")})},{annotations:C,name:"resolve-death-save",description:"Resolve a death saving throw for a target at 0 HP. Rolls 1d20: natural 20 revives with 1 HP, natural 1 counts as two failures, >= 10 is a success, < 10 is a failure. 3 successes stabilize, 3 failures kill.",inputSchema:c.object({target:c.string().max(256).describe("Name or Foundry token ID of the target at 0 HP")})},{annotations:C,name:"resolve-ability-check",description:"Roll an ability check or skill check against a DC. Use for contested checks (grapple, shove), skill checks in combat (Athletics, Acrobatics), and Counterspell level contests. Unlike saving throws, ability checks use the raw ability modifier (or skill total if proficient).",inputSchema:c.object({target:c.string().max(256).describe("Token name or ID of the creature making the check"),ability:c.enum(["str","dex","con","int","wis","cha"]).describe("Ability to check (str, dex, con, int, wis, cha)"),dc:c.number().int().min(1).max(99).describe("Difficulty class to beat"),skill:c.enum(["acr","ani","arc","ath","dec","his","ins","itm","inv","med","nat","prc","prf","per","rel","sle","ste","sur"]).optional().describe("Skill to use (e.g. athletics, acrobatics) \u2014 adds proficiency bonus if proficient"),advantage:c.boolean().optional().describe("Force advantage on the roll"),disadvantage:c.boolean().optional().describe("Force disadvantage on the roll")})},{annotations:C,name:"resolve-multiattack",description:"Resolve a Multiattack action for an NPC. Parses the Multiattack feature description, identifies component attacks, and resolves each. Returns all attack results plus a damage summary. Falls back with an error message if parsing fails \u2014 use individual resolve-attack calls instead.",inputSchema:c.object({attacker:c.string().max(256).describe("Token name or ID of the attacker with Multiattack"),target:c.string().max(256).describe("Token name or ID of the target"),force:c.boolean().optional().describe("Narrative override: bypass the L1-A action-economy check (e.g. mid-fight Action Surge interaction the resolver does not yet model). Posts a [Familiar audit] chat trail. Does NOT bypass component-attack validation (range, cover, conditions still apply)."),reason:c.string().max(280).optional().describe('Why the force bypass is justified (e.g. "Action Surge", "legendary multiattack chain"). Recorded in the [Familiar audit] chat.')})},{annotations:C,name:"resolve-action",description:"Use the Dash, Dodge, or Disengage action. Dash doubles movement speed for the turn. Dodge gives disadvantage on incoming attacks until next turn. Disengage prevents opportunity attacks. Consumes the action for the turn \u2014 duplicate use throws (one Action per turn per PHB). Pass force:true for narrative cases where a feature legitimately bypasses the action-economy duplicate gate (Sentinel counter-OA, Reactive Strike, magical contingency); an audit chat message is posted to track the override.",inputSchema:c.object({identifier:c.string().max(256).describe("Token name or ID"),action:c.enum(["dash","dodge","disengage"]).describe("Action to take"),force:c.boolean().optional().describe("Bypass the action-economy duplicate-action gate (Sentinel/Reactive features, magical contingency, multi-action class features). Posts an audit chat message recording the override. Note: scope is action-economy ONLY \u2014 unlike resolve-attack/resolve-spell where force bypasses range, this force bypasses the duplicate-action gate. Default false."),reason:c.string().max(280).optional().describe('Why the force bypass is justified (e.g. "Sentinel reaction", "magical contingency"). Recorded in the [Familiar audit] chat.')})},{annotations:C,name:"resolve-legendary-action",description:"Use a legendary action between other creatures' turns. Validates charges, deducts the cost, and executes the action. Cannot be used on the creature's own turn. Legendary actions recharge at the start of the creature's turn.",inputSchema:c.object({actor:c.string().max(256).describe("Token name or ID of the creature with legendary actions"),feature:c.string().max(256).describe("Name of the legendary action feature to use")})},{annotations:q,name:"query-aoe-targets",description:"Find tokens within an area of effect. Supports five shapes: 'circle' (default \u2014 pass radius), 'cone' (PHB triangular 1:1 cone \u2014 pass direction + lengthFt; optional angleDeg overrides 53.13\xB0), 'line' (rectangle \u2014 pass direction + lengthFt; optional widthFt overrides 5ft), 'cube' (axis-aligned-in-local-frame square \u2014 pass direction + lengthFt for size), 'ray' (line variant \u2014 pass direction + lengthFt; optional widthFt overrides 5ft). Origin pixel coords come from battlefield token positions. Direction in degrees (0=East, 90=South, clockwise). Returns all tokens hit sorted by distance from origin. Use this to plan AoE spell targeting before casting.",inputSchema:c.object({x:c.number().int().describe("Origin X in pixels (cone apex / line start / cube corner / ray start / circle center)"),y:c.number().int().describe("Origin Y in pixels"),radius:c.number().min(1).max(1e3).optional().describe("Circle radius in feet \u2014 required when shape=circle or shape omitted"),shape:c.enum(["circle","cone","line","cube","ray"]).optional().describe("AoE shape. Defaults to 'circle' if omitted (backward-compatible with v2.6.0 clients)"),direction:c.number().min(0).max(360).optional().describe("Direction in degrees (0=East, 90=South, clockwise). Required for cone/line/cube/ray"),lengthFt:c.number().min(1).max(500).optional().describe("Primary dimension in feet (cone length / line length / cube size / ray length). Required for non-circle shapes"),widthFt:c.number().min(1).max(50).optional().describe("Width in feet \u2014 line/ray only. Defaults to 5 ft"),angleDeg:c.number().min(1).max(180).optional().describe("Cone aperture in degrees. Defaults to PHB 53.13\xB0 (1:1 cone) if omitted"),excludeTokenId:c.string().max(256).optional().describe("Token ID to exclude from results (e.g. the caster, so a dragon does not see itself in its own breath cone)")})},{annotations:q,name:"get-movement-options",description:"Get pre-scored tactical movement options for the current combatant. Returns up to 5 positions within walk speed, each scored and categorized (advance, retreat, flank, hold, approach-ally). Includes opportunity attack risk warnings and cover analysis. Scores factor in combat role (melee/ranged/caster), HP-based retreat urgency, and creature intelligence. Coordinates are in pixels for resolve-movement. Call this before deciding where to move.",inputSchema:c.object({identifier:c.string().max(256).describe("Token name or Foundry ID of the moving token")})},{annotations:q,name:"list-undoable-workflows",description:"List the combat actions that can be undone, newest first. Each entry is one resolved attack, multiattack, or spell from the current encounter (max 15 retained, FIFO). Returns each workflow's id, label, round, and turn. Only the most recent workflow can actually be undone (see undo-last-workflow). Call this to see what undo-last-workflow would revert."},{annotations:ee,name:"undo-last-workflow",description:"Revert the most recent combat workflow (attack, multiattack, or spell) by restoring every affected actor to the HP, spell slots, death saves, active effects, and defeated state captured just before it ran. Undo is last-only: pass no workflowId to undo the top of the stack, or pass a workflowId that MUST match the most recent entry (otherwise the call is rejected with the current top named, so you undo intervening workflows first). Foundry has no transactional rollback \u2014 this is a best-effort state restore, not a perfect replay. Posts an [UNDONE] chat message for the GM.",inputSchema:c.object({workflowId:c.string().max(256).optional().describe("Optional id of the workflow to undo (from list-undoable-workflows). Must be the most recent workflow. Omit to undo the top of the stack.")})},{annotations:C,name:"resolve-grapple-shove",description:"Resolve a 2024 grapple or shove (a special melee attack). The target makes a Strength OR Dexterity saving throw (its best) against DC 8 + the attacker's Strength modifier + proficiency bonus; on a failure the grapple applies the Grappled condition, and a shove applies Prone (mode:'prone', default) or pushes the target 5 ft (mode:'push'). On a success nothing happens. Requires the target be within reach (melee) \u2014 pass force:true for narrative cases (a creature gliding/teleporting into a grab); an audit message is posted to chat.",inputSchema:c.object({attacker:c.string().max(256).describe("Name or Foundry token ID of the grappling/shoving creature"),target:c.string().max(256).describe("Name or Foundry token ID of the target"),type:c.enum(["grapple","shove"]).describe("'grapple' applies Grappled on a failed save; 'shove' applies Prone or pushes 5 ft"),mode:c.enum(["prone","push"]).optional().describe("Shove only: 'prone' (default) knocks the target prone, 'push' moves it 5 ft away. Ignored for grapple."),force:c.boolean().optional().describe("Bypass the reach + turn/action-economy checks for narrative cases (gliding/teleporting grab). An audit message is posted to chat."),reason:c.string().max(280).optional().describe("Why the force bypass is justified. Recorded in the [Familiar audit] chat.")})}];var kn=c.object({x:c.number(),y:c.number()});var mk=c.object({action:c.string().max(256),message:c.string().max(1024)}),fk=c.object({success:c.boolean(),featureName:c.string().max(256),cost:c.number(),chargesRemaining:c.number(),message:c.string().max(1024)}),eJ=c.object({tokenId:c.string().max(256),name:c.string().max(256),distance:c.number()}),hk=c.object({shape:c.string().max(256),center:kn,radius:c.number(),targets:c.array(eJ)}),tJ=c.object({x:c.number(),y:c.number(),label:c.string().max(256),category:c.string().max(256),score:c.number(),distanceFt:c.number(),meleeTargets:c.array(c.string().max(256)),nearbyAllies:c.array(c.string().max(256)),opportunityAttackRisk:c.boolean().optional(),coverFromEnemy:c.string().max(256).optional()}),gk=c.object({tokenName:c.string().max(256),currentPosition:kn,speedFt:c.number(),options:c.array(tJ)});var rJ=c.object({value:c.number(),max:c.number(),temp:c.number()}),nJ=c.object({total:c.number(),natural:c.number(),modifier:c.number()}),iJ=c.object({total:c.number(),formula:c.string().max(256),type:c.string().max(256)}),oJ=c.object({property:c.enum(["topple","push","sap","slow"]),applied:c.boolean(),save:c.object({ability:c.string().max(8),dc:c.number(),total:c.number(),saved:c.boolean()}).optional(),condition:c.string().max(64).optional(),pushedFt:c.number().optional(),effect:c.string().max(64).optional(),note:c.string().max(256).optional()}),yk=c.object({hit:c.boolean(),critical:c.boolean(),attackRoll:nJ,damageRoll:iJ.nullable(),targetHpAfter:rJ,appliedDamage:c.number().optional(),damageModifiers:c.array(c.string().max(512)).optional(),conditionReasons:c.array(c.string().max(512)).optional(),reactionUsed:c.string().max(256).optional(),mastery:oJ.optional()}),aJ=c.object({name:c.string().max(256),hit:c.boolean(),saved:c.boolean(),damage:c.number(),appliedDamage:c.number().optional(),damageModifiers:c.array(c.string().max(512)).optional(),healed:c.number().optional(),conditionReasons:c.array(c.string().max(512)).optional()}),sJ=c.object({level:c.number(),remaining:c.number()}),cJ=c.object({missingEffect:c.boolean().optional(),inertEffect:c.boolean().optional()}),dD=c.object({success:c.boolean(),spellName:c.string().max(256),targets:c.array(aJ),slotExpended:sJ.optional(),effectsApplied:c.array(c.string().max(256)).optional(),audit:cJ.optional(),teleported:c.object({from:kn,to:kn,participants:c.array(c.string().max(256))}).optional()}),lJ=c.object({total:c.number(),natural:c.number(),modifier:c.number()}),pD=c.object({success:c.boolean(),roll:lJ,dc:c.number(),ability:c.string().max(256),autoFail:c.boolean().optional(),conditionReasons:c.array(c.string().max(512)).optional(),legendaryResistanceUsed:c.boolean().optional(),legendaryResistanceChargesRemaining:c.number().optional()}),mD=c.object({type:c.enum(["grapple","shove"]),saved:c.boolean(),applied:c.boolean(),dc:c.number(),ability:c.enum(["str","dex"]),roll:c.object({total:c.number(),natural:c.number(),modifier:c.number()}),condition:c.string().max(64).optional(),pushedFt:c.number().optional(),autoFail:c.boolean().optional(),conditionReasons:c.array(c.string().max(512)).optional(),legendaryResistanceUsed:c.boolean().optional(),legendaryResistanceChargesRemaining:c.number().optional()}),uJ=c.object({successes:c.number(),failures:c.number()}),fD=c.object({natural:c.number(),success:c.boolean(),deathSaves:uJ,stabilized:c.boolean(),revived:c.boolean(),dead:c.boolean()}),xse=c.object({targetName:c.string().max(256),dc:c.number(),roll:c.number(),saved:c.boolean(),concentrationLost:c.boolean()}),dJ=c.object({total:c.number(),natural:c.number(),modifier:c.number()}),hD=c.object({success:c.boolean(),roll:dJ,dc:c.number(),ability:c.string().max(256),skill:c.string().max(256).optional(),conditionReasons:c.array(c.string().max(512)).optional()}),gD=c.object({success:c.boolean(),from:kn,to:kn,distanceMoved:c.number(),movementRemaining:c.number(),overlap:c.boolean().optional()}),yD=c.object({attacks:c.array(yk),totalDamage:c.number(),summary:c.string().max(1024)});var vD=c.object({undone:c.literal(!0),workflowId:c.string(),label:c.string(),restoredActors:c.number()}),pJ=c.object({workflowId:c.string(),label:c.string(),round:c.number(),turn:c.number()}),bD=c.object({workflows:c.array(pJ)});var[vk,Jm,Km,Qm,Ym,Xm,ef,tf,rf,nf,of,af,bk,sf]=pk,cf=pk[14];async function It(e,t,r){let i=await li(e,t,r);return i.ok?{content:[{type:"text",text:JSON.stringify(i.data)}]}:i.envelope}function mJ(e,t){e.registerTool(vk.name,{description:vk.description,annotations:vk.annotations},async()=>v(await t.query("get-combat-snapshot"))),e.registerTool(Jm.name,{description:Jm.description,annotations:Jm.annotations,inputSchema:Jm.inputSchema.shape},async({attacker:r,target:i,weapon:n,advantage:o,disadvantage:a,smiteLevel:s,force:l,reason:u})=>It("resolve-attack",yk,()=>t.query("resolve-attack",{attacker:r,target:i,weapon:n,advantage:o,disadvantage:a,smiteLevel:s,force:l,reason:u}))),e.registerTool(Qm.name,{description:Qm.description,annotations:Qm.annotations,inputSchema:Qm.inputSchema.shape},async({caster:r,spell:i,targets:n,upcastLevel:o,force:a,reason:s})=>It("resolve-spell",dD,()=>t.query("resolve-spell",{caster:r,spell:i,targets:n,upcastLevel:o,force:a,reason:s}))),e.registerTool(Km.name,{description:Km.description,annotations:Km.annotations,inputSchema:Km.inputSchema.shape},async({identifier:r,x:i,y:n})=>It("resolve-movement",gD,()=>t.query("resolve-movement",{identifier:r,x:i,y:n})))}function fJ(e,t){e.registerTool(Ym.name,{description:Ym.description,annotations:Ym.annotations,inputSchema:Ym.inputSchema.shape},async({target:r,ability:i,dc:n})=>It("resolve-saving-throw",pD,()=>t.query("resolve-saving-throw",{target:r,ability:i,dc:n}))),e.registerTool(Xm.name,{description:Xm.description,annotations:Xm.annotations,inputSchema:Xm.inputSchema.shape},async({target:r})=>It("resolve-death-save",fD,()=>t.query("resolve-death-save",{target:r}))),e.registerTool(ef.name,{description:ef.description,annotations:ef.annotations,inputSchema:ef.inputSchema.shape},async({target:r,ability:i,dc:n,skill:o,advantage:a,disadvantage:s})=>It("resolve-ability-check",hD,()=>t.query("resolve-ability-check",{target:r,ability:i,dc:n,skill:o,advantage:a,disadvantage:s}))),e.registerTool(tf.name,{description:tf.description,annotations:tf.annotations,inputSchema:tf.inputSchema.shape},async({attacker:r,target:i,force:n,reason:o})=>It("resolve-multiattack",yD,()=>t.query("resolve-multiattack",{attacker:r,target:i,force:n,reason:o}))),hJ(e,t)}function hJ(e,t){e.registerTool(rf.name,{description:rf.description,annotations:rf.annotations,inputSchema:rf.inputSchema.shape},async({identifier:r,action:i,force:n,reason:o})=>It("resolve-action",mk,()=>t.query("resolve-action",{identifier:r,action:i,force:n,reason:o}))),e.registerTool(cf.name,{description:cf.description,annotations:cf.annotations,inputSchema:cf.inputSchema.shape},async({attacker:r,target:i,type:n,mode:o,force:a,reason:s})=>It("resolve-grapple-shove",mD,()=>t.query("resolve-grapple-shove",{attacker:r,target:i,type:n,mode:o,force:a,reason:s})))}function gJ(e,t){e.registerTool(nf.name,{description:nf.description,annotations:nf.annotations,inputSchema:nf.inputSchema.shape},async({actor:r,feature:i})=>It("resolve-legendary-action",fk,()=>t.query("resolve-legendary-action",{actor:r,feature:i}))),e.registerTool(of.name,{description:of.description,annotations:of.annotations,inputSchema:of.inputSchema.shape},async({x:r,y:i,radius:n,shape:o,direction:a,lengthFt:s,widthFt:l,angleDeg:u,excludeTokenId:d})=>It("query-aoe-targets",hk,()=>t.query("query-aoe-targets",{x:r,y:i,radius:n,shape:o,direction:a,lengthFt:s,widthFt:l,angleDeg:u,excludeTokenId:d}))),e.registerTool(af.name,{description:af.description,annotations:af.annotations,inputSchema:af.inputSchema.shape},async({identifier:r})=>It("get-movement-options",gk,()=>t.query("get-movement-options",{identifier:r}))),e.registerTool(bk.name,{description:bk.description,annotations:bk.annotations},async()=>It("list-undoable-workflows",bD,()=>t.query("list-undoable-workflows"))),e.registerTool(sf.name,{description:sf.description,annotations:sf.annotations,inputSchema:sf.inputSchema.shape},async({workflowId:r})=>It("undo-last-workflow",vD,()=>t.query("undo-last-workflow",{workflowId:r})))}function _D(e,t){mJ(e,t),fJ(e,t),gJ(e,t)}var SD=[{annotations:q,name:"search-compendium",description:"Search compendium packs for items, creatures, spells, or journal entries by name. Returns matching entries with names, pack keys, and IDs. For Actor packs, supports filtering by CR, creature type (beast/undead/fiend/etc.), and size. Use get-compendium-entry to read full stat blocks or descriptions of a result.",inputSchema:c.object({query:c.string().max(256).describe('Search term (e.g., "fireball", "goblin", "longsword")'),type:c.string().max(256).optional().describe('Filter by pack type: "Actor", "Item", "JournalEntry", etc.'),cr:c.number().min(0).optional().describe("Filter creatures by Challenge Rating (e.g., 0.25, 1, 5)"),creatureType:c.string().max(256).optional().describe('Filter creatures by type (e.g., "beast", "undead", "fiend")'),size:c.enum(["tiny","sm","med","lg","huge","grg"]).optional().describe("Filter creatures by size")})},{annotations:q,name:"get-compendium-entry",description:"Get full details of a compendium entry. Returns complete stat blocks for creatures, spell descriptions with mechanics, or item properties (HTML stripped to plain text). For JournalEntry packs, returns the entry's pages as plain text (paginated via pageOffset/pageLimit). For Adventure packs, returns a manifest of the names and IDs of every contained journal, scene, actor, item, table, playlist, macro, and card. Use the pack key and ID from search-compendium results.",inputSchema:c.object({pack:c.string().max(256).describe('Pack collection key (e.g., "dnd5e.monsters")'),entryId:c.string().max(256).describe("Entry ID from search results"),pageOffset:c.number().int().min(0).optional().describe("JournalEntry packs only: index of the first page to return (default 0)"),pageLimit:c.number().int().min(1).max(50).optional().describe("JournalEntry packs only: max pages to return (default 10, max 50)")})},{annotations:q,name:"list-compendium-packs",description:"List all available compendium packs. Returns each pack's ID (collection key, e.g., 'dnd5e.spells'), document type (Actor, Item, JournalEntry, Scene, etc.), human-readable label, and entry count. Use the returned pack IDs with search-compendium and get-compendium-entry to find and retrieve specific entries."},{annotations:C,name:"create-character-from-compendium",description:"Import a creature or NPC from a compendium pack into the world as a new actor with its full stat block, items, spells, and features. Returns the new actor ID. Use search-compendium first to find the pack key and entry ID. For creating a blank character from scratch, use create-character (characters bundle) instead.",inputSchema:c.object({pack:c.string().max(256).describe('Pack collection key (e.g., "dnd5e.monsters")'),entryId:c.string().max(256).describe("Entry ID from search results"),name:c.string().max(256).optional().describe('Override actor name (e.g., "Goblin Archer")')})}];var yJ=3e4,vJ=5e3,[lf,uf,_k,df]=SD;function xD(e,t){e.registerTool(lf.name,{description:lf.description,annotations:lf.annotations,inputSchema:lf.inputSchema.shape},async({query:r,type:i,cr:n,creatureType:o,size:a})=>v(await t.query("search-compendium",{query:r,type:i,cr:n,creatureType:o,size:a}))),e.registerTool(uf.name,{description:uf.description,annotations:uf.annotations,inputSchema:uf.inputSchema.shape},async({pack:r,entryId:i,pageOffset:n,pageLimit:o},a)=>{let s=st(a,"Reading compendium entry\u2026",vJ);try{return v(await t.query("get-compendium-entry",{pack:r,entryId:i,pageOffset:n,pageLimit:o},yJ))}finally{s()}}),e.registerTool(_k.name,{description:_k.description,annotations:_k.annotations},async()=>v(await t.query("list-compendium-packs"))),e.registerTool(df.name,{description:df.description,annotations:df.annotations,inputSchema:df.inputSchema.shape},async({pack:r,entryId:i,name:n})=>v(await t.query("create-character-from-compendium",{pack:r,entryId:i,name:n})))}var wD=[{annotations:C,name:"roll-dice",description:"Roll a dice formula and return the numeric result. Supports standard notation (1d20+5, 2d6+3) and advanced syntax (4d6kh3 keep highest, 2d20kl1 keep lowest). Result is posted to Foundry chat by default. For attack rolls or spell damage during combat, prefer resolve-attack or resolve-spell (combat-ai bundle) instead.",inputSchema:c.object({formula:c.string().max(256).describe('Dice formula (e.g., "1d20+5", "2d6+3", "4d6kh3")'),showInChat:c.boolean().optional().describe("Show the roll result in Foundry chat (default: true)")})}];function kD(e,t){let[r]=wD;e.registerTool(r.name,{description:r.description,annotations:r.annotations,inputSchema:r.inputSchema.shape},async({formula:i,showInChat:n})=>v(await t.query("roll-dice",{formula:i,showInChat:n})))}var $D=[{annotations:q,name:"list-journals",description:"List journal entries in the world with pagination. Returns each entry's name, ID, page count with page names/IDs, and folder path. Response includes total count and hasMore flag. Filter by folder name for organized worlds. For searching journal content, use search-journals instead.",inputSchema:c.object({folder:c.string().max(256).optional().describe("Filter by folder name (substring match)"),limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:q,name:"search-journals",description:"Search journal entries by title and page content text. Returns matching entries with names, IDs, and text snippets showing where the search term appears. Use to find specific lore, notes, or session records. For browsing all journals, use list-journals instead.",inputSchema:c.object({query:c.string().max(256).describe("Search term to find in journal titles and page content")})},{annotations:q,name:"get-journal",description:"Get a full journal entry with all page content rendered as plain text (HTML stripped). Returns the entry name and all pages with their names, IDs, types, and full content. For reading a single page, use get-journal-page instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID")})},{annotations:q,name:"get-journal-page",description:"Get a single page from a journal entry with full content. Use the page ID from list-journals or get-journal results. For reading all pages at once, use get-journal instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID"),pageId:c.string().max(256).describe("Page ID (from list-journals or get-journal results)")})},{annotations:C,name:"create-journal",description:"Create a new journal entry, optionally with initial pages of content. Returns the new journal ID and page IDs. Pages support HTML formatting. Use for session notes, lore, NPC details, handouts, or quest tracking.",inputSchema:c.object({name:c.string().max(256).describe("Journal entry name"),folderId:c.string().max(256).optional().describe("Folder name or ID to place the journal in"),pages:c.array(c.object({name:c.string().max(256),content:c.string().max(5e4)})).max(100).optional().describe("Initial pages to create (each with name and HTML/text content)")})},{annotations:C,name:"create-journal-page",description:"Add a new page to an existing journal entry. Returns the new page ID. Supports text (HTML or plain text), image, video, and PDF page types. Default type is text.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID"),name:c.string().max(256).describe("Page name"),content:c.string().max(5e4).describe("Page content (HTML or plain text)"),type:c.enum(["text","image","video","pdf"]).optional().describe("Page type (default: text)")})},{annotations:H,name:"update-journal",description:"Rename a journal entry. For updating page content, use update-journal-page instead. Moving a journal between folders and changing ownership are not supported through this tool.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID"),name:c.string().max(256).describe("New journal entry name")})},{annotations:H,name:"update-journal-page",description:"Replace the content of an existing journal page. Accepts HTML or plain text. Use get-journal first to read the current content before overwriting.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID"),pageId:c.string().max(256).describe("Page ID (from get-journal results)"),content:c.string().max(5e4).describe("New page content (HTML or plain text)")})},{annotations:ee,name:"delete-journal",description:"Delete a journal entry and all its pages permanently. This is irreversible. Map pins linked to this journal will lose their link. Returns the deleted journal name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID")})},{annotations:ee,name:"delete-journal-page",description:"Delete a single page from a journal entry. The journal entry itself and other pages are preserved. This is irreversible. Returns the journal name and deleted page ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Journal entry name or Foundry ID"),pageId:c.string().max(256).describe("Page ID to delete")})}];var TD=3e4,ID=5e3,[pf,mf,ff,hf,gf,yf,vf,bf,_f,Sf]=$D;function bJ(e,t){e.registerTool(pf.name,{description:pf.description,annotations:pf.annotations,inputSchema:pf.inputSchema.shape},async({folder:r,limit:i,offset:n})=>v(await t.query("list-journals",{folder:r,limit:i,offset:n}))),e.registerTool(mf.name,{description:mf.description,annotations:mf.annotations,inputSchema:mf.inputSchema.shape},async({query:r})=>v(await t.query("search-journals",{query:r}))),e.registerTool(ff.name,{description:ff.description,annotations:ff.annotations,inputSchema:ff.inputSchema.shape},async({identifier:r},i)=>{let n=st(i,"Reading journal\u2026",ID);try{return v(await t.query("get-journal",{identifier:r},TD))}finally{n()}}),e.registerTool(hf.name,{description:hf.description,annotations:hf.annotations,inputSchema:hf.inputSchema.shape},async({identifier:r,pageId:i},n)=>{let o=st(n,"Reading journal page\u2026",ID);try{return v(await t.query("get-journal-page",{identifier:r,pageId:i},TD))}finally{o()}})}function _J(e,t){e.registerTool(gf.name,{description:gf.description,annotations:gf.annotations,inputSchema:gf.inputSchema.shape},async({name:r,folderId:i,pages:n})=>v(await t.query("create-journal",{name:r,folderId:i,pages:n}))),e.registerTool(yf.name,{description:yf.description,annotations:yf.annotations,inputSchema:yf.inputSchema.shape},async({identifier:r,name:i,content:n,type:o})=>v(await t.query("create-journal-page",{identifier:r,name:i,content:n,type:o}))),e.registerTool(vf.name,{description:vf.description,annotations:vf.annotations,inputSchema:vf.inputSchema.shape},async({identifier:r,name:i})=>v(await t.query("update-journal",{identifier:r,updates:{name:i}}))),e.registerTool(bf.name,{description:bf.description,annotations:bf.annotations,inputSchema:bf.inputSchema.shape},async({identifier:r,pageId:i,content:n})=>v(await t.query("update-journal-page",{identifier:r,pageId:i,content:n}))),e.registerTool(_f.name,{description:_f.description,annotations:_f.annotations,inputSchema:_f.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-journal",{identifier:r}))),e.registerTool(Sf.name,{description:Sf.description,annotations:Sf.annotations,inputSchema:Sf.inputSchema.shape},async({identifier:r,pageId:i})=>v(await t.query("delete-journal-page",{identifier:r,pageId:i})))}function ED(e,t){bJ(e,t),_J(e,t)}var PD=[{annotations:H,name:"move-token",description:"Move a token to x,y pixel coordinates on the current scene. Use for general repositioning outside of combat. During combat turns, use resolve-movement (combat-ai bundle) instead \u2014 it tracks movement speed and remaining distance. Returns `moved: false` and a `warning` string if walls or regions clipped the destination \u2014 re-read the token position via get-current-scene before retrying.",inputSchema:c.object({identifier:c.string().max(256).describe("Token name or ID"),x:c.number().min(-5e4).max(5e4).describe("Target X position in pixels (from scene top-left)"),y:c.number().min(-5e4).max(5e4).describe("Target Y position in pixels (from scene top-left)")})},{annotations:H,name:"update-token",description:"Update token visual properties on the current scene. Supports hidden, lock, elevation, size, rotation, and disposition. For moving a token to new coordinates, use move-token instead. For applying status conditions, use toggle-condition.",inputSchema:c.object({identifier:c.string().max(256).describe("Token name or ID"),hidden:c.boolean().optional().describe("Hide/show the token"),elevation:c.number().optional().describe("Token elevation in feet"),width:c.number().min(.5).optional().describe("Token width in grid units"),height:c.number().min(.5).optional().describe("Token height in grid units"),rotation:c.number().min(0).max(360).optional().describe("Token rotation in degrees (0-360)"),disposition:c.number().int().min(-2).max(1).optional().describe("Token disposition: -2=secret, -1=hostile, 0=neutral, 1=friendly"),locked:c.boolean().optional().describe("Lock the token: blocks player drag, keyboard movement, and mousewheel rotation on the canvas. API repositioning (move-token) still works while locked.")})},{annotations:C,name:"update-tokens",description:"Batch update properties on multiple tokens at once. Supports hidden, lock, elevation, size, rotation, and disposition. Returns per-token results. Use to hide/reveal a group of tokens, change dispositions for multiple NPCs, or adjust elevation for a flying group.",inputSchema:c.object({updates:c.array(c.object({identifier:c.string().max(256).describe("Token name or ID"),hidden:c.boolean().optional().describe("Hide/show the token"),elevation:c.number().optional().describe("Token elevation in feet"),width:c.number().min(.5).optional().describe("Token width in grid units"),height:c.number().min(.5).optional().describe("Token height in grid units"),rotation:c.number().min(0).max(360).optional().describe("Token rotation in degrees (0-360)"),disposition:c.number().int().min(-2).max(1).optional().describe("Token disposition: -2=secret, -1=hostile, 0=neutral, 1=friendly"),locked:c.boolean().optional().describe("Lock the token: blocks player drag, keyboard movement, and mousewheel rotation. API repositioning still works while locked.")})).min(1).max(100).describe("Array of token updates")})},{annotations:C,name:"create-tokens",description:"Place one or more tokens on the active scene from world actors. Supports batch placement for encounter setup. Returns the created token IDs. Use start-combat afterward to begin a combat encounter with the placed tokens.",inputSchema:c.object({tokens:c.array(c.object({actorId:c.string().max(256).describe("World actor ID"),x:c.number().min(-5e4).max(5e4).describe("X position in pixels (from scene top-left)"),y:c.number().min(-5e4).max(5e4).describe("Y position in pixels (from scene top-left)"),hidden:c.boolean().optional().describe("Hidden from players (default: false)"),disposition:c.number().int().min(-2).max(1).optional().describe("-2=secret, -1=hostile, 0=neutral, 1=friendly"),name:c.string().max(256).optional().describe("Override token name")})).max(100).describe("Array of tokens to place")})},{annotations:ee,name:"delete-tokens",description:"Remove tokens from the active scene permanently. Accepts token names or IDs. This does not delete the underlying actor \u2014 only the token placement on this scene. Returns the count of deleted tokens, their IDs, and any errors for tokens not found.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Token names or IDs to delete")})}];var Ve=[{annotations:q,name:"get-current-scene",description:"Get the active scene with paginated token data. Returns scene name, dimensions (width/height in pixels), grid settings (size, distance), darkness level, and tokens with name, position (x,y in pixels), HP, AC, conditions, and vision range. Token list includes total count and hasMore flag. Use at the start of combat or when you need to understand the current battlefield layout.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max tokens to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of tokens to skip (default: 0)")})},{annotations:Ne,name:"get-scene",description:"Get full details of any scene by name or ID, including dimensions, grid settings, darkness level, weather, background image, folder, and paginated token list. Unlike get-current-scene (which only returns the active scene), this works for any scene in the world.",inputSchema:c.object({identifier:c.string().max(256).describe("Scene name or Foundry ID"),limit:c.number().int().min(1).max(500).optional().describe("Max tokens to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of tokens to skip (default: 0)")})},{annotations:q,name:"list-scenes",description:"List scenes in the world with pagination. Returns each scene's name, ID, active/viewed status, and thumbnail. Response includes total count and hasMore flag. Use to find a scene before switching to it with switch-scene.",inputSchema:c.object({limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:Ne,name:"search-scenes",description:"Search scenes by name. Returns matching scenes with names, IDs, and active status. Use to find a scene when you only know part of the name.",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against scene name")})},{annotations:H,name:"switch-scene",description:"Switch the active scene by name or ID. All players are pulled to the new scene. Returns the new active scene data. Use pan-canvas afterward to focus on a specific area.",inputSchema:c.object({identifier:c.string().max(256).describe("Scene name or ID")})},...PD,{annotations:C,name:"create-scene",description:"Create a blank scene with NO background image. Only use this for empty grids or technical scenes \u2014 if the user wants a battle map, dungeon, tavern, or any scene with a background image, prefer generate-scene (enable the scene-generator bundle) for AI-generated scenes, which creates the scene AND generates + applies the background in one step. Returns the new scene ID. The scene is not activated automatically \u2014 use switch-scene to make it active. Default size is 4000\xD73000 pixels with 100px grid squares (5ft per square).",inputSchema:c.object({name:c.string().max(256).describe("Scene name"),width:c.number().min(1).optional().describe("Scene width in pixels (default: 4000)"),height:c.number().min(1).optional().describe("Scene height in pixels (default: 3000)"),gridSize:c.number().min(1).optional().describe("Grid square size in pixels (default: 100)"),gridDistance:c.number().min(1).optional().describe("Grid distance per square in feet (default: 5)"),background:c.string().max(256).optional().describe("Background image path")})},{annotations:H,name:"update-scene",description:"Update scene properties using Foundry dot-notation paths. Supports name, grid, dimensions, background image, navigation visibility, and more. Use get-current-scene first to see current values.",inputSchema:c.object({identifier:c.string().max(256).describe("Scene name or ID"),updates:Ue.describe('Foundry update data (e.g., {"name": "New Name", "navigation": true, "background.src": "path"})')})},{annotations:ee,name:"delete-scene",description:"Delete a scene from the world permanently. Removes all tokens, walls, lights, and sounds on the scene. This is irreversible. Returns the deleted scene name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Scene name or ID")})},{annotations:H,name:"pan-canvas",description:"Pan and zoom the GM canvas camera to a position. Animates smoothly over the specified duration. Use after switch-scene to focus on a specific area, or to draw attention to a location during gameplay.",inputSchema:c.object({x:c.number().optional().describe("Target X position in pixels (from scene top-left)"),y:c.number().optional().describe("Target Y position in pixels (from scene top-left)"),scale:c.number().min(.1).max(3).optional().describe("Zoom level (0.3=far, 1.0=normal, 2.0=close)"),duration:c.number().min(0).optional().describe("Animation duration in ms (default: 1000)")})},{annotations:H,name:"set-darkness",description:"Set the scene darkness level (0=bright daylight, 0.5=twilight, 1=pitch black). Returns the new darkness value. Use animate:true for smooth day/night transitions. Affects token vision and light source visibility.",inputSchema:c.object({darkness:c.number().min(0).max(1).describe("Darkness level: 0=daylight, 0.5=twilight, 1=full darkness"),animate:c.boolean().optional().describe("Smoothly animate the transition (default: false)")})},{annotations:wn,name:"reset-fog",description:"Reset fog of war exploration for the active scene, hiding all previously revealed areas. This is irreversible \u2014 players will need to re-explore. Use when resetting a dungeon or starting a new exploration phase. Returns the scene name."},{annotations:q,name:"get-weather-effects",description:"List all available weather effect IDs that can be applied with set-weather. Returns effect names and IDs. Use to discover valid weather options before applying one."},{annotations:H,name:"set-weather",description:"Set a weather particle effect on the active scene (rain, fog, snow, etc.). Use get-weather-effects to see valid IDs. Pass an empty string to clear the current weather. Only one weather effect can be active at a time.",inputSchema:c.object({effect:c.string().max(256).describe('Weather effect ID (e.g., "rain", "fog", "snow") or empty string to clear')})},{annotations:q,name:"get-screenshot",description:"Capture an annotated JPEG screenshot of the GM's current Foundry VTT canvas view. Tokens are overlaid with numbered colored badges (green=friendly, red=hostile, yellow=neutral). Returns the image and a text legend mapping badge numbers to token name, disposition, HP, and AC. Use to verify token positions, inspect the battlefield, or reference tokens by number in conversation.",inputSchema:c.object({maxWidth:c.number().min(1).max(3840).optional().describe("Max image width in pixels (default: 1280)"),maxHeight:c.number().min(1).max(2160).optional().describe("Max image height in pixels (default: 720)"),quality:c.number().min(.1).max(1).optional().describe("JPEG quality 0.1-1.0 (default: 0.80)"),annotate:c.boolean().optional().describe("Overlay numbered token badges with legend (default: true)")})}];var SJ=c.object({imageData:c.string(),mimeType:c.string(),width:c.number(),height:c.number(),legend:c.string()}),xf=Ve[8],wf=Ve[9],kf=Ve[13],$f=Ve[14],Sk=Ve[15],xk=Ve[16],Tf=Ve[17],If=Ve[18];function RD(e,t){e.registerTool(xf.name,{description:xf.description,annotations:xf.annotations,inputSchema:xf.inputSchema.shape},async({tokens:r})=>v(await t.query("create-tokens",{tokens:r}))),e.registerTool(wf.name,{description:wf.description,annotations:wf.annotations,inputSchema:wf.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-tokens",{identifiers:r})))}function zD(e,t){e.registerTool(kf.name,{description:kf.description,annotations:kf.annotations,inputSchema:kf.inputSchema.shape},async({x:r,y:i,scale:n,duration:o})=>v(await t.query("pan-canvas",{x:r,y:i,scale:n,duration:o}))),e.registerTool($f.name,{description:$f.description,annotations:$f.annotations,inputSchema:$f.inputSchema.shape},async({darkness:r,animate:i})=>v(await t.query("set-darkness",{darkness:r,animate:i}))),e.registerTool(Sk.name,{description:Sk.description,annotations:Sk.annotations},async()=>v(await t.query("reset-fog")))}function OD(e,t){e.registerTool(xk.name,{description:xk.description,annotations:xk.annotations},async()=>v(await t.query("get-weather-effects"))),e.registerTool(Tf.name,{description:Tf.description,annotations:Tf.annotations,inputSchema:Tf.inputSchema.shape},async({effect:r})=>v(await t.query("set-weather",{effect:r}))),e.registerTool(If.name,{description:If.description,annotations:If.annotations,inputSchema:If.inputSchema.shape},async({maxWidth:r,maxHeight:i,quality:n,annotate:o})=>{let a=await t.query("get-screenshot",{maxWidth:r,maxHeight:i,quality:n,annotate:o});if(!a.success)return{content:[{type:"text",text:`Error: ${on(a.error)}`}],isError:!0};let s=SJ.safeParse(a.result);return s.success?{content:[{type:"image",data:s.data.imageData,mimeType:s.data.mimeType},{type:"text",text:s.data.legend!==""?s.data.legend:`Screenshot: ${s.data.width}x${s.data.height}`}]}:{content:[{type:"text",text:`Screenshot failed: unexpected response format \u2014 ${s.error.message}`}],isError:!0}})}var Ef=Ve[0],Pf=Ve[1],Rf=Ve[2],zf=Ve[3],Of=Ve[4],jf=Ve[5],Df=Ve[6],Nf=Ve[7],Cf=Ve[10],Af=Ve[11],Mf=Ve[12];function xJ(e,t){e.registerTool(Ef.name,{description:Ef.description,annotations:Ef.annotations,inputSchema:Ef.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("get-current-scene",{limit:r,offset:i}))),e.registerTool(Pf.name,{description:Pf.description,annotations:Pf.annotations,inputSchema:Pf.inputSchema.shape},async({identifier:r,limit:i,offset:n})=>v(await t.query("get-scene",{identifier:r,limit:i,offset:n}))),e.registerTool(Rf.name,{description:Rf.description,annotations:Rf.annotations,inputSchema:Rf.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-scenes",{limit:r,offset:i}))),e.registerTool(zf.name,{description:zf.description,annotations:zf.annotations,inputSchema:zf.inputSchema.shape},async({query:r})=>v(await t.query("search-scenes",{query:r}))),e.registerTool(Of.name,{description:Of.description,annotations:Of.annotations,inputSchema:Of.inputSchema.shape},async({identifier:r})=>v(await t.query("switch-scene",{identifier:r})))}function wJ(e,t){e.registerTool(jf.name,{description:jf.description,annotations:jf.annotations,inputSchema:jf.inputSchema.shape},async({identifier:r,x:i,y:n})=>v(await t.query("move-token",{identifier:r,x:i,y:n})))}function kJ(e,t){e.registerTool(Df.name,{description:Df.description,annotations:Df.annotations,inputSchema:Df.inputSchema.shape},async({identifier:r,...i})=>v(await t.query("update-token",{identifier:r,updates:i}))),e.registerTool(Nf.name,{description:Nf.description,annotations:Nf.annotations,inputSchema:Nf.inputSchema.shape},async({updates:r})=>{let i=r.map(({identifier:n,...o})=>({identifier:n,changes:o}));return v(await t.query("update-tokens",{updates:i}))})}function $J(e,t){wJ(e,t),kJ(e,t)}function TJ(e,t){e.registerTool(Cf.name,{description:Cf.description,annotations:Cf.annotations,inputSchema:Cf.inputSchema.shape},async({name:r,width:i,height:n,gridSize:o,gridDistance:a,background:s})=>v(await t.query("create-scene",{name:r,width:i,height:n,gridSize:o,gridDistance:a,background:s}))),e.registerTool(Af.name,{description:Af.description,annotations:Af.annotations,inputSchema:Af.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-scene",{identifier:r,updates:i}))),e.registerTool(Mf.name,{description:Mf.description,annotations:Mf.annotations,inputSchema:Mf.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-scene",{identifier:r})))}function IJ(e,t){zD(e,t),OD(e,t)}function jD(e,t){xJ(e,t),$J(e,t),RD(e,t),TJ(e,t),IJ(e,t)}var DD=[{annotations:q,name:"get-world-info",description:"Get Foundry VTT world metadata. Returns game system, world name, Foundry version, and all connected players with their roles. Use at the start of a session to orient yourself."},{annotations:C,name:"toggle-pause",description:"Pause or unpause the game. Returns the new pause state. Players cannot take actions while the game is paused. Omit the paused parameter to toggle.",inputSchema:c.object({paused:c.boolean().optional().describe("true to pause, false to unpause, omit to toggle")})},{annotations:q,name:"get-time",description:"Get the current in-game date and time. Returns year, month, day, hour, minute, and second in the game world's calendar. Use before advance-time to check the current time and plan time skips."},{annotations:C,name:"advance-time",description:"Advance the in-game clock by a specified duration. Returns the new in-game time. Use for long rests (8 hours), short rests (1 hour), travel montages, or narrative time skips. Specify at least one time unit.",inputSchema:c.object({seconds:c.number().optional().describe("Seconds to advance"),minutes:c.number().optional().describe("Minutes to advance"),hours:c.number().optional().describe("Hours to advance (e.g., 8 for long rest)"),days:c.number().optional().describe("Days to advance")})},{annotations:q,name:"list-users",description:"List all users in the game world. Returns each user's name, ID, role (Gamemaster/Player/Trusted/Assistant/None), online status, assigned character, and color. Use to find valid user names before assign-ownership, revoke-ownership, or send-chat-message with whisperTo."},{annotations:q,name:"get-user",description:"Get detailed information about a specific user. Returns name, ID, role, online status, assigned character, color, and a list of all actors they own. Accepts user name (exact match) or Foundry user ID.",inputSchema:c.object({identifier:c.string().max(256).describe("User name or Foundry user ID")})},{annotations:C,name:"create-user",description:"Create a new Foundry user account. Role defaults to PLAYER (1); valid roles: 0=NONE, 1=PLAYER, 2=TRUSTED, 3=ASSISTANT, 4=GAMEMASTER. Optionally auto-link a character (assigns OWNER permission). Use for onboarding new players or setting up test users. Password is NOT settable via this tool \u2014 Foundry creates accounts passwordless by default; the player sets their own password on first login.",inputSchema:c.object({name:c.string().min(1).max(80).describe("Display name for the new user (required)"),role:c.number().int().min(0).max(4).optional().describe("User role: 0=NONE, 1=PLAYER (default), 2=TRUSTED, 3=ASSISTANT, 4=GAMEMASTER"),character:c.string().max(256).optional().describe("Optional character name or actor ID to auto-link (sets OWNER permission)"),color:c.string().max(16).optional().describe('Optional hex color for the user (e.g. "#ff0000"); Foundry auto-generates if omitted'),pronouns:c.string().max(64).optional().describe("Optional pronouns string")})}];function ND(e,t){let[r,i,n,o,a,s,l]=DD;e.registerTool(r.name,{description:r.description,annotations:r.annotations},async()=>v(await t.query("get-world-info"))),e.registerTool(i.name,{description:i.description,annotations:i.annotations,inputSchema:i.inputSchema.shape},async({paused:u})=>v(await t.query("toggle-pause",{paused:u}))),e.registerTool(n.name,{description:n.description,annotations:n.annotations},async()=>v(await t.query("get-time"))),e.registerTool(o.name,{description:o.description,annotations:o.annotations,inputSchema:o.inputSchema.shape},async({seconds:u,minutes:d,hours:p,days:f})=>v(await t.query("advance-time",{seconds:u,minutes:d,hours:p,days:f}))),e.registerTool(a.name,{description:a.description,annotations:a.annotations},async()=>v(await t.query("list-users"))),e.registerTool(s.name,{description:s.description,annotations:s.annotations,inputSchema:s.inputSchema.shape},async({identifier:u})=>v(await t.query("get-user",{identifier:u}))),e.registerTool(l.name,{description:l.description,annotations:l.annotations,inputSchema:l.inputSchema.shape},async({name:u,role:d,character:p,color:f,pronouns:h})=>v(await t.query("create-user",{name:u,role:d,character:p,color:f,pronouns:h})))}var di=[{annotations:q,name:"list-tiles",description:"List tiles on the active scene (paginated). Returns each tile's ID, position (x,y in pixels), dimensions (width/height in pixels), image path, opacity, rotation, overhead status, and visibility. Use to find tile IDs before updating or deleting them.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"create-tile",description:"Place a tile image on the active scene for decoration, overlays, or roof tiles. Set overhead:true to render above tokens (useful for building roofs that hide interiors). The image file must exist in Foundry data.",inputSchema:c.object({src:c.string().max(256).describe('Image file path (relative to Foundry data, e.g., "tiles/floor.webp")'),x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),width:c.number().min(1).describe("Tile width in pixels"),height:c.number().min(1).describe("Tile height in pixels"),overhead:c.boolean().optional().describe("Render above tokens (default: false)"),hidden:c.boolean().optional().describe("Hidden from players (default: false)"),alpha:c.number().min(0).max(1).optional().describe("Opacity 0-1 (default: 1)"),rotation:c.number().min(0).max(360).optional().describe("Rotation in degrees")})},{annotations:H,name:"update-tile",description:"Update a tile on the scene. Move, resize, change opacity, toggle overhead, or swap the image. Use list-tiles to find the tile ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the tile to update"),updates:Ue.describe("Properties to update (x, y, width, height, alpha, rotation, hidden, overhead, etc.)")})},{annotations:ee,name:"delete-tiles",description:"Remove tiles from the active scene permanently. The image files on disk are not deleted. Returns the count of deleted tiles and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Tile IDs to delete")})},{annotations:q,name:"list-drawings",description:"List drawings on the active scene (paginated). Returns each drawing's ID, shape type (r=rectangle, e=ellipse, p=polygon, f=freehand), position (x,y in pixels), dimensions, colors, and text content. Use to find drawing IDs before updating or deleting them.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"create-drawing",description:"Draw a shape on the canvas for marking areas, annotations, or visual effects. All coordinates and dimensions are in pixels. Supports rectangles (r), ellipses (e), polygons (p), and freehand (f). Add text labels with the text parameter. For spell area markers, prefer create-region instead \u2014 it uses game units (feet) and integrates with the combat system.",inputSchema:c.object({type:c.enum(["r","e","p","f"]).describe("Shape type: r=rectangle, e=ellipse, p=polygon, f=freehand"),x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),width:c.number().min(1).describe("Width in pixels"),height:c.number().min(1).describe("Height in pixels"),strokeColor:c.string().max(256).optional().describe('Stroke hex color (e.g., "#FF0000")'),fillColor:c.string().max(256).optional().describe("Fill hex color"),fillAlpha:c.number().min(0).max(1).optional().describe("Fill opacity 0-1"),strokeWidth:c.number().min(0).optional().describe("Stroke width in pixels"),text:c.string().max(5e4).optional().describe("Text content (for text drawings)"),fontSize:c.number().min(1).optional().describe("Font size in pixels"),textColor:c.string().max(256).optional().describe("Text hex color")})},{annotations:H,name:"update-drawing",description:"Update a drawing on the scene. Move, resize, recolor, or change text content. Use list-drawings to find the drawing ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the drawing to update"),updates:Ue.describe("Properties to update")})},{annotations:ee,name:"delete-drawings",description:"Remove drawings from the active scene permanently. Returns the count of deleted drawings and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Drawing IDs to delete")})}];var Uf=di[4],Lf=di[5],qf=di[6],Zf=di[7];function CD(e,t){e.registerTool(Uf.name,{description:Uf.description,annotations:Uf.annotations,inputSchema:Uf.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-drawings",{limit:r,offset:i}))),e.registerTool(Lf.name,{description:Lf.description,annotations:Lf.annotations,inputSchema:Lf.inputSchema.shape},async({type:r,x:i,y:n,width:o,height:a,strokeColor:s,fillColor:l,fillAlpha:u,strokeWidth:d,text:p,fontSize:f,textColor:h})=>v(await t.query("create-drawing",{type:r,x:i,y:n,width:o,height:a,strokeColor:s,fillColor:l,fillAlpha:u,strokeWidth:d,text:p,fontSize:f,textColor:h}))),e.registerTool(qf.name,{description:qf.description,annotations:qf.annotations,inputSchema:qf.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-drawing",{identifier:r,updates:i}))),e.registerTool(Zf.name,{description:Zf.description,annotations:Zf.annotations,inputSchema:Zf.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-drawings",{identifiers:r})))}var[Ff,Vf,Bf,Hf]=di;function EJ(e,t){e.registerTool(Ff.name,{description:Ff.description,annotations:Ff.annotations,inputSchema:Ff.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-tiles",{limit:r,offset:i}))),e.registerTool(Vf.name,{description:Vf.description,annotations:Vf.annotations,inputSchema:Vf.inputSchema.shape},async({src:r,x:i,y:n,width:o,height:a,overhead:s,hidden:l,alpha:u,rotation:d})=>v(await t.query("create-tile",{src:r,x:i,y:n,width:o,height:a,overhead:s,hidden:l,alpha:u,rotation:d}))),e.registerTool(Bf.name,{description:Bf.description,annotations:Bf.annotations,inputSchema:Bf.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-tile",{identifier:r,updates:i}))),e.registerTool(Hf.name,{description:Hf.description,annotations:Hf.annotations,inputSchema:Hf.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-tiles",{identifiers:r})))}function AD(e,t){EJ(e,t),CD(e,t)}var MD=[{annotations:q,name:"list-sounds",description:"List ambient sounds on the active scene (paginated). Returns each sound's ID, position (in pixels), audio path, hearing radius (in feet), volume, and easing. For playlist/music control, use the playlists bundle.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"create-sound",description:"Place a positional ambient sound on the scene (e.g., waterfall, tavern noise). Tokens hear it louder when nearby. For background music, use the playlists bundle.",inputSchema:c.object({x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),path:c.string().max(256).describe("Audio file path (relative to Foundry data)"),radius:c.number().min(0).describe("Hearing radius in feet (how far tokens can hear the sound)"),volume:c.number().min(0).max(1).optional().describe("Volume 0-1 (default: 1)"),easing:c.boolean().optional().describe("Volume eases with distance (default: true)"),hidden:c.boolean().optional().describe("Hidden from players")})},{annotations:H,name:"update-sound",description:"Update an ambient sound source on the scene. Move, change volume, radius, audio path, or easing. Use list-sounds to find the sound ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the sound to update"),updates:Ue.describe("Properties to update (x, y, path, radius, volume, easing)")})},{annotations:ee,name:"delete-sounds",description:"Remove ambient sound sources from the active scene permanently. Accepts sound IDs from list-sounds. Returns the count of deleted sounds and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Sound IDs to delete")})},{annotations:q,name:"list-notes",description:"List map pins/notes on the active scene (paginated). Returns each note's ID, position (in pixels), label, linked journal entry/page IDs, and visibility.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"create-note",description:"Place a map pin on the scene, optionally linked to a journal entry. Use for points of interest, quest locations, or NPC markers.",inputSchema:c.object({x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),label:c.string().max(256).optional().describe("Pin label text"),entryId:c.string().max(256).optional().describe("Journal entry ID to link"),pageId:c.string().max(256).optional().describe("Journal page ID to link"),iconSize:c.number().min(1).optional().describe("Icon size in pixels (default: 40)"),textColor:c.string().max(256).optional().describe("Label text hex color"),hidden:c.boolean().optional().describe("Hidden from players")})},{annotations:H,name:"update-note",description:"Update a map pin on the scene. Move, change label, or link to a different journal entry. Use list-notes to find the note ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the note to update"),updates:Ue.describe("Properties to update (x, y, label, entryId, pageId)")})},{annotations:ee,name:"delete-notes",description:"Remove map pins from the active scene permanently. Does not delete the linked journal entries \u2014 only the pins themselves. Returns the count of deleted notes and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Note IDs to delete")})}];var lr=[{annotations:q,name:"list-walls",description:"List wall segments on the active scene (paginated). Returns each wall's ID, endpoints (in pixels), blocking levels for light/movement/sight/sound, and door type/state. Use before creating or modifying walls.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"toggle-door",description:"Open, close, or lock a door on the active scene. Omit state to toggle open/closed. Returns the new door state. Use list-walls to find door IDs.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the wall/door"),state:c.number().int().min(0).max(2).optional().describe("Door state: 0=closed, 1=open, 2=locked. Omit to toggle open/closed.")})},{annotations:H,name:"close-all-doors",description:"Force close every door on the active scene at once. Returns the count of doors closed. Use for resetting a dungeon between runs. For individual door control, use toggle-door instead."},{annotations:C,name:"create-walls",description:"Create wall segments on the active scene (batch supported). Walls block vision, movement, light, and sound \u2014 blocking levels: 0=none, 10=limited (partial), 20=normal (full). Use presets: basic (blocks all), terrain (limited sight), door, window (see-through), secret, invisible (movement only), ethereal (vision only). Returns wall IDs.",inputSchema:c.object({walls:c.array(c.object({c:c.array(c.number()).length(4).describe("Wall endpoints [x1, y1, x2, y2] in pixels (from scene top-left)"),preset:c.enum(["basic","terrain","invisible","ethereal","window","door","secret"]).optional().describe("Wall type preset (overridden by explicit values below)"),light:c.number().min(0).max(20).optional().describe("Light blocking: 0=none, 10=limited, 20=normal"),move:c.number().min(0).max(20).optional().describe("Movement blocking: 0=none, 10=limited, 20=normal"),sight:c.number().min(0).max(20).optional().describe("Sight blocking: 0=none, 10=limited, 20=normal"),sound:c.number().min(0).max(20).optional().describe("Sound blocking: 0=none, 10=limited, 20=normal"),door:c.number().int().min(0).max(2).optional().describe("Door type: 0=none, 1=door, 2=secret"),ds:c.number().int().min(0).max(2).optional().describe("Door state: 0=closed, 1=open, 2=locked"),dir:c.number().int().min(0).max(2).optional().describe("Wall direction (one-way restriction): 0=both (default, blocks both sides), 1=left (one-way blocking from the left side of the c[0]\u2192c[1] segment), 2=right (one-way blocking from the right side). Verify which side blocks first by snapshotting cover/LoS from each side after creation; swap c[0]\u2194c[1] (mirror the segment endpoints) to flip the blocking direction.")})).max(100).describe("Array of walls to create")})},{annotations:H,name:"update-wall",description:"Update a wall segment's properties. Change blocking levels, convert to a door, move endpoints, or adjust light/sound penetration. Use list-walls to find the wall ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the wall to update"),updates:Ue.describe("Properties to update (light, move, sight, sound, door, ds, dir, c)")})},{annotations:ee,name:"delete-walls",description:"Remove wall segments from the active scene permanently. Accepts wall IDs from list-walls. This affects vision, movement, and sound blocking immediately. Returns the count of deleted walls and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Wall IDs to delete")})},{annotations:q,name:"list-lights",description:"List ambient lights on the active scene (paginated). Returns each light's ID, position (in pixels), bright/dim radius (in feet), color, animation, and visibility.",inputSchema:c.object({limit:c.number().int().min(1).max(500).optional().describe("Max results to return (default: 100)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:C,name:"create-light",description:"Place an ambient light on the scene. Configure bright/dim radius, color, animations (torch, pulse, ghostly), and emission angle. Set negative:true for a darkness source.",inputSchema:c.object({x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),dim:c.number().min(0).describe("Dim light radius in feet (e.g., 40 for a torch)"),bright:c.number().min(0).describe("Bright light radius in feet (e.g., 20 for a torch)"),color:c.string().max(256).optional().describe('Light hex color (e.g., "#ff9329" for torchlight)'),alpha:c.number().min(0).max(1).optional().describe("Color intensity 0-1 (default: 0.5)"),angle:c.number().min(0).max(360).optional().describe("Emission angle in degrees (360=omnidirectional)"),negative:c.boolean().optional().describe("Creates darkness instead of light"),animationType:c.string().max(256).optional().describe("Animation: torch, pulse, chroma, wave, fog, sunburst, dome, emanation, ghostly, energy, roiling, hole, vortex"),animationSpeed:c.number().int().min(1).max(10).optional().describe("Animation speed 1-10 (default: 5)"),animationIntensity:c.number().int().min(1).max(10).optional().describe("Animation intensity 1-10 (default: 5)"),hidden:c.boolean().optional().describe("Hidden from players")})},{annotations:H,name:"update-light",description:"Update an ambient light source on the active scene. Supports x/y position (in pixels from top-left), config.bright and config.dim (radius in feet), config.color (hex), config.angle (0-360), config.animation.type and config.animation.speed, and hidden (visibility toggle). Use list-lights to find the light identifier first.",inputSchema:c.object({identifier:c.string().max(256).describe("Foundry document ID of the light to update"),updates:Ue.describe("Properties to update (x, y, config.dim, config.bright, config.color)")})},{annotations:ee,name:"delete-lights",description:"Remove ambient light sources from the active scene permanently. Accepts light IDs from list-lights. Returns the count of deleted lights and their IDs.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Light IDs to delete")})},...MD];var Wf=lr[10],Gf=lr[11],Jf=lr[12],Kf=lr[13],Qf=lr[14],Yf=lr[15],Xf=lr[16],eh=lr[17];function UD(e,t){e.registerTool(Wf.name,{description:Wf.description,annotations:Wf.annotations,inputSchema:Wf.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-sounds",{limit:r,offset:i}))),e.registerTool(Gf.name,{description:Gf.description,annotations:Gf.annotations,inputSchema:Gf.inputSchema.shape},async({x:r,y:i,path:n,radius:o,volume:a,easing:s,hidden:l})=>v(await t.query("create-sound",{x:r,y:i,path:n,radius:o,volume:a,easing:s,hidden:l}))),e.registerTool(Jf.name,{description:Jf.description,annotations:Jf.annotations,inputSchema:Jf.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-sound",{identifier:r,updates:i}))),e.registerTool(Kf.name,{description:Kf.description,annotations:Kf.annotations,inputSchema:Kf.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-sounds",{identifiers:r})))}function LD(e,t){e.registerTool(Qf.name,{description:Qf.description,annotations:Qf.annotations,inputSchema:Qf.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-notes",{limit:r,offset:i}))),e.registerTool(Yf.name,{description:Yf.description,annotations:Yf.annotations,inputSchema:Yf.inputSchema.shape},async({x:r,y:i,label:n,entryId:o,pageId:a,iconSize:s,textColor:l,hidden:u})=>v(await t.query("create-note",{x:r,y:i,label:n,entryId:o,pageId:a,iconSize:s,textColor:l,hidden:u}))),e.registerTool(Xf.name,{description:Xf.description,annotations:Xf.annotations,inputSchema:Xf.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-note",{identifier:r,updates:i}))),e.registerTool(eh.name,{description:eh.description,annotations:eh.annotations,inputSchema:eh.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-notes",{identifiers:r})))}var[th,rh,wk,nh,ih,oh,ah,sh,ch,lh]=lr;function PJ(e,t){e.registerTool(th.name,{description:th.description,annotations:th.annotations,inputSchema:th.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-walls",{limit:r,offset:i}))),e.registerTool(rh.name,{description:rh.description,annotations:rh.annotations,inputSchema:rh.inputSchema.shape},async({identifier:r,state:i})=>v(await t.query("toggle-door",{identifier:r,state:i}))),e.registerTool(wk.name,{description:wk.description,annotations:wk.annotations},async()=>v(await t.query("close-all-doors"))),e.registerTool(nh.name,{description:nh.description,annotations:nh.annotations,inputSchema:nh.inputSchema.shape},async({walls:r})=>v(await t.query("create-walls",{walls:r}))),e.registerTool(ih.name,{description:ih.description,annotations:ih.annotations,inputSchema:ih.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-wall",{identifier:r,updates:i}))),e.registerTool(oh.name,{description:oh.description,annotations:oh.annotations,inputSchema:oh.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-walls",{identifiers:r})))}function RJ(e,t){e.registerTool(ah.name,{description:ah.description,annotations:ah.annotations,inputSchema:ah.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-lights",{limit:r,offset:i}))),e.registerTool(sh.name,{description:sh.description,annotations:sh.annotations,inputSchema:sh.inputSchema.shape},async r=>v(await t.query("create-light",r))),e.registerTool(ch.name,{description:ch.description,annotations:ch.annotations,inputSchema:ch.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-light",{identifier:r,updates:i}))),e.registerTool(lh.name,{description:lh.description,annotations:lh.annotations,inputSchema:lh.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-lights",{identifiers:r})))}function qD(e,t){PJ(e,t),RJ(e,t),UD(e,t),LD(e,t)}var ZD=[{annotations:C,name:"send-chat-message",description:"Send a message to the Foundry chat log. Supports HTML formatting, speaker attribution, whispers to specific players, and emote actions. Use for narrative text, NPC dialogue, GM announcements, or private messages. For dice results, use roll-dice instead. Returns the sent content and speaker name.",inputSchema:c.object({content:c.string().max(5e4).describe("Message content (supports HTML)"),speaker:c.string().max(256).optional().describe("Speaker name or character name (default: GM)"),whisperTo:c.array(c.string().max(256)).max(50).optional().describe("Player names to whisper to (private message). Omit for public message."),isEmote:c.boolean().optional().describe("Send as an emote/action message (default: false)")})},{annotations:q,name:"get-recent-messages",description:"Retrieve recent messages from the Foundry chat log. Returns the most recent N messages (default 20, max 100) with content, speaker, timestamp, type, and whisper recipients. Also returns the total message count in the log. Named get-* rather than list-* because it fetches a tail slice of the log, not a paginated collection.",inputSchema:c.object({count:c.number().int().min(1).max(100).optional().describe("Number of recent messages to retrieve (default: 20, max: 100)")})}];function FD(e,t){let[r,i]=ZD;e.registerTool(r.name,{description:r.description,annotations:r.annotations,inputSchema:r.inputSchema.shape},async({content:n,speaker:o,whisperTo:a,isEmote:s})=>v(await t.query("send-chat-message",{content:n,speaker:o,whisperTo:a,isEmote:s}))),e.registerTool(i.name,{description:i.description,annotations:i.annotations,inputSchema:i.inputSchema.shape},async({count:n})=>v(await t.query("get-recent-messages",{count:n})))}var Xo=[{annotations:q,name:"list-playlists",description:"List playlists in the world with pagination. Returns each playlist's name, ID, playback mode (sequential/shuffle/simultaneous), playing status, and track count with names. Response includes total count and hasMore flag. Use to find a playlist before playing it or to check what's currently playing.",inputSchema:c.object({limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:Ne,name:"search-playlists",description:"Search playlists by name or track name. Returns matching playlists with names, IDs, playing status, and track count. Use to find a playlist when you only know part of the name or a track title.",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against playlist or track name")})},{annotations:q,name:"get-playlist",description:"Get full details of a playlist including all tracks with names, paths, volume, and playing status. Returns playback mode and fade settings. Use to find track names before playing them with play-track.",inputSchema:c.object({identifier:c.string().max(256).describe("Playlist name or Foundry ID")})},{annotations:H,name:"play-playlist",description:"Start playing an entire playlist by name or ID. Plays according to the playlist mode (sequential, shuffle, or simultaneous). For playing a single track, use play-track instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Playlist name or Foundry ID")})},{annotations:H,name:"play-track",description:"Play a specific track within a playlist by name or ID. For playing an entire playlist, use play-playlist instead. Use get-playlist to see available tracks.",inputSchema:c.object({playlist:c.string().max(256).describe("Playlist name or ID"),trackId:c.string().max(256).describe("Track name or ID")})},{annotations:H,name:"stop-playlist",description:"Stop a specific playing playlist by name or ID. For stopping everything at once, use stop-all-playlists instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Playlist name or Foundry ID")})},{annotations:H,name:"stop-track",description:"Stop a specific track within a playlist. Other tracks in the playlist continue playing. Returns the playlist and track names.",inputSchema:c.object({playlist:c.string().max(256).describe("Playlist name or ID"),trackId:c.string().max(256).describe("Track name or ID")})},{annotations:H,name:"stop-all-playlists",description:"Stop all currently playing playlists and tracks at once. Use for scene transitions or when silence is needed. For stopping a single playlist, use stop-playlist instead."},{annotations:C,name:"create-playlist",description:"Create a new empty playlist with a playback mode. Returns the new playlist ID. Add tracks afterward with create-track. Modes: 0=Sequential (plays tracks in order), 1=Shuffle (random order), 2=Simultaneous (all tracks play at once, ideal for ambient soundscapes).",inputSchema:c.object({name:c.string().max(256).describe("Playlist name"),description:c.string().max(5e4).optional().describe("Playlist description"),mode:c.number().int().min(0).max(2).optional().describe("Playback mode: 0 = Sequential, 1 = Shuffle, 2 = Simultaneous"),fade:c.number().min(0).optional().describe("Default fade duration in milliseconds"),folderId:c.string().max(256).optional().describe("Folder name or ID to place the playlist in")})},{annotations:H,name:"update-playlist",description:"Update a playlist's properties. Change name, description, playback mode (0=Sequential, 1=Shuffle, 2=Simultaneous), or fade duration. For modifying individual tracks, use update-track instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Playlist name or Foundry ID"),name:c.string().max(256).optional().describe("New playlist name"),description:c.string().max(5e4).optional().describe("New description"),mode:c.number().int().min(0).max(2).optional().describe("Playback mode: 0 = Sequential, 1 = Shuffle, 2 = Simultaneous"),fade:c.number().min(0).optional().describe("Default fade duration in milliseconds")})},{annotations:ee,name:"delete-playlist",description:"Delete a playlist and all its tracks permanently. This is irreversible. Stops playback if the playlist is currently playing. Returns the deleted playlist name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Playlist name or Foundry ID")})},{annotations:C,name:"create-track",description:'Add a new audio track to an existing playlist. The audio file must already exist in the Foundry data directory (e.g., "music/tavern.mp3"). Returns the new track ID. Use list-playlists or get-playlist to find the playlist first.',inputSchema:c.object({playlist:c.string().max(256).describe("Playlist name or ID"),name:c.string().max(256).describe("Track name"),path:c.string().max(256).describe('Audio file path (relative to Foundry data, e.g. "music/tavern.mp3")'),volume:c.number().min(0).max(1).optional().describe("Volume level (0 to 1, default: 0.5)"),repeat:c.boolean().optional().describe("Loop the track (default: false)"),fade:c.number().min(0).optional().describe("Fade duration in milliseconds")})},{annotations:H,name:"update-track",description:"Update a track's properties within a playlist. Change name, volume, loop setting, or fade duration. For updating the playlist itself, use update-playlist instead.",inputSchema:c.object({playlist:c.string().max(256).describe("Playlist name or ID"),trackId:c.string().max(256).describe("Track name or ID"),name:c.string().max(256).optional().describe("New track name"),volume:c.number().min(0).max(1).optional().describe("Volume level (0 to 1)"),repeat:c.boolean().optional().describe("Loop the track"),fade:c.number().min(0).optional().describe("Fade duration in milliseconds")})},{annotations:ee,name:"delete-track",description:"Remove a track from a playlist permanently. The audio file on disk is not deleted. For deleting the entire playlist, use delete-playlist instead. Returns the playlist and deleted track names.",inputSchema:c.object({playlist:c.string().max(256).describe("Playlist name or ID"),trackId:c.string().max(256).describe("Track name or ID")})}];var uh=Xo[11],dh=Xo[12],ph=Xo[13];function VD(e,t){e.registerTool(uh.name,{description:uh.description,annotations:uh.annotations,inputSchema:uh.inputSchema.shape},async({playlist:r,name:i,path:n,volume:o,repeat:a,fade:s})=>v(await t.query("create-track",{playlist:r,name:i,path:n,volume:o,repeat:a,fade:s}))),e.registerTool(dh.name,{description:dh.description,annotations:dh.annotations,inputSchema:dh.inputSchema.shape},async({playlist:r,trackId:i,name:n,volume:o,repeat:a,fade:s})=>{let l={};return n!==void 0&&(l.name=n),o!==void 0&&(l.volume=o),a!==void 0&&(l.repeat=a),s!==void 0&&(l.fade=s),v(await t.query("update-track",{playlist:r,trackId:i,updates:l}))})}function BD(e,t){e.registerTool(ph.name,{description:ph.description,annotations:ph.annotations,inputSchema:ph.inputSchema.shape},async({playlist:r,trackId:i})=>v(await t.query("delete-track",{playlist:r,trackId:i})))}var[mh,fh,hh,gh,yh,vh,bh,kk,_h,Sh,xh]=Xo;function zJ(e,t){e.registerTool(mh.name,{description:mh.description,annotations:mh.annotations,inputSchema:mh.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-playlists",{limit:r,offset:i}))),e.registerTool(fh.name,{description:fh.description,annotations:fh.annotations,inputSchema:fh.inputSchema.shape},async({query:r})=>v(await t.query("search-playlists",{query:r}))),e.registerTool(hh.name,{description:hh.description,annotations:hh.annotations,inputSchema:hh.inputSchema.shape},async({identifier:r})=>v(await t.query("get-playlist",{identifier:r})))}function OJ(e,t){e.registerTool(gh.name,{description:gh.description,annotations:gh.annotations,inputSchema:gh.inputSchema.shape},async({identifier:r})=>v(await t.query("play-playlist",{identifier:r}))),e.registerTool(yh.name,{description:yh.description,annotations:yh.annotations,inputSchema:yh.inputSchema.shape},async({playlist:r,trackId:i})=>v(await t.query("play-track",{playlist:r,trackId:i})))}function jJ(e,t){e.registerTool(vh.name,{description:vh.description,annotations:vh.annotations,inputSchema:vh.inputSchema.shape},async({identifier:r})=>v(await t.query("stop-playlist",{identifier:r}))),e.registerTool(bh.name,{description:bh.description,annotations:bh.annotations,inputSchema:bh.inputSchema.shape},async({playlist:r,trackId:i})=>v(await t.query("stop-track",{playlist:r,trackId:i}))),e.registerTool(kk.name,{description:kk.description,annotations:kk.annotations},async()=>v(await t.query("stop-all-playlists")))}function DJ(e,t){e.registerTool(_h.name,{description:_h.description,annotations:_h.annotations,inputSchema:_h.inputSchema.shape},async({name:r,description:i,mode:n,fade:o,folderId:a})=>v(await t.query("create-playlist",{name:r,description:i,mode:n,fade:o,folderId:a}))),e.registerTool(Sh.name,{description:Sh.description,annotations:Sh.annotations,inputSchema:Sh.inputSchema.shape},async({identifier:r,name:i,description:n,mode:o,fade:a})=>{let s={};return i!==void 0&&(s.name=i),n!==void 0&&(s.description=n),o!==void 0&&(s.mode=o),a!==void 0&&(s.fade=a),v(await t.query("update-playlist",{identifier:r,updates:s}))})}function NJ(e,t){e.registerTool(xh.name,{description:xh.description,annotations:xh.annotations,inputSchema:xh.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-playlist",{identifier:r})))}function HD(e,t){zJ(e,t),OJ(e,t),jJ(e,t),DJ(e,t),NJ(e,t),VD(e,t),BD(e,t)}var WD=[{annotations:q,name:"list-tables",description:`List rollable tables in the world with pagination. Returns each table's name, ID, dice formula (e.g., "1d6"), result count, and description. Response includes total count and hasMore flag. Use to find a table before rolling on it with roll-table.`,inputSchema:c.object({limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:Ne,name:"search-tables",description:"Search rollable tables by name or description text. Returns matching tables with names, IDs, dice formulas, result counts, and description previews. Use to find a specific table when you don't know its exact name.",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against table name or description")})},{annotations:q,name:"get-table",description:"Get a rollable table with its dice formula and all result entries. Returns the full table content. Use to review table contents before rolling with roll-table.",inputSchema:c.object({identifier:c.string().max(256).describe("Table name or Foundry ID")})},{annotations:C,name:"roll-table",description:"Roll on a rollable table using its dice formula and return the randomly selected result. Posts the roll and result to Foundry chat by default. For simple dice rolls without a table, use roll-dice (dice bundle) instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Table name or Foundry ID"),showInChat:c.boolean().optional().describe("Show the roll result in Foundry chat (default: true)")})},{annotations:C,name:"create-table",description:"Create a new rollable table with a dice formula and result entries. Returns the new table ID. Each result has text and an optional weight or explicit dice range. Use for random encounters, loot tables, weather, NPC names, or any randomized content. If no formula is specified, it auto-generates based on result count.",inputSchema:c.object({name:c.string().max(256).describe("Table name"),formula:c.string().max(256).optional().describe('Dice formula (e.g. "1d6", "1d20", "2d10")'),description:c.string().max(5e4).optional().describe("Table description"),replacement:c.boolean().optional().describe("Draw with replacement (default: true)"),folderId:c.string().max(256).optional().describe("Folder name or ID to place the table in"),results:c.array(c.object({text:c.string().max(5e4).describe("Result text"),weight:c.number().optional().describe("Result weight (default: 1)"),rangeStart:c.number().optional().describe("Range start (auto-assigned if omitted)"),rangeEnd:c.number().optional().describe("Range end (auto-assigned if omitted)")})).max(100).optional().describe("Table results/entries")})},{annotations:H,name:"update-table",description:"Update a rollable table's properties. Change name, dice formula, description, or draw-with-replacement mode. Does not modify individual results \u2014 use Foundry UI for that.",inputSchema:c.object({identifier:c.string().max(256).describe("Table name or Foundry ID"),name:c.string().max(256).optional().describe("New table name"),formula:c.string().max(256).optional().describe("New dice formula"),description:c.string().max(5e4).optional().describe("New description"),replacement:c.boolean().optional().describe("Draw with replacement")})},{annotations:ee,name:"delete-table",description:"Delete a rollable table and all its result entries permanently. This is irreversible. Returns the deleted table name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Table name or Foundry ID")})}];var[wh,kh,$h,Th,Ih,Eh,Ph]=WD;function CJ(e,t){e.registerTool(wh.name,{description:wh.description,annotations:wh.annotations,inputSchema:wh.inputSchema.shape},async({limit:r,offset:i})=>v(await t.query("list-tables",{limit:r,offset:i}))),e.registerTool(kh.name,{description:kh.description,annotations:kh.annotations,inputSchema:kh.inputSchema.shape},async({query:r})=>v(await t.query("search-tables",{query:r}))),e.registerTool($h.name,{description:$h.description,annotations:$h.annotations,inputSchema:$h.inputSchema.shape},async({identifier:r})=>v(await t.query("get-table",{identifier:r}))),e.registerTool(Th.name,{description:Th.description,annotations:Th.annotations,inputSchema:Th.inputSchema.shape},async({identifier:r,showInChat:i})=>v(await t.query("roll-table",{identifier:r,showInChat:i})))}function AJ(e,t){e.registerTool(Ih.name,{description:Ih.description,annotations:Ih.annotations,inputSchema:Ih.inputSchema.shape},async({name:r,formula:i,description:n,replacement:o,folderId:a,results:s})=>v(await t.query("create-table",{name:r,formula:i,description:n,replacement:o,folderId:a,results:s}))),e.registerTool(Eh.name,{description:Eh.description,annotations:Eh.annotations,inputSchema:Eh.inputSchema.shape},async({identifier:r,name:i,formula:n,description:o,replacement:a})=>{let s={};return i!==void 0&&(s.name=i),n!==void 0&&(s.formula=n),o!==void 0&&(s.description=o),a!==void 0&&(s.replacement=a),v(await t.query("update-table",{identifier:r,updates:s}))}),e.registerTool(Ph.name,{description:Ph.description,annotations:Ph.annotations,inputSchema:Ph.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-table",{identifier:r})))}function GD(e,t){CJ(e,t),AJ(e,t)}var JD=[{annotations:q,name:"list-card-stacks",description:"List all card stacks in the world. Returns every stack (no pagination) with name, ID, type (deck/hand/pile), card count, and description. Use to find a stack before drawing cards or checking a player's hand."},{annotations:Ne,name:"search-card-stacks",description:"Search card stacks by name or card name. Returns matching stacks with names, IDs, types, and card counts. Use to find a deck or hand when you only know part of the name.",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against stack or card name")})},{annotations:q,name:"get-card-stack",description:"Get a card stack with all its cards, including card names, faces, and drawn status. Use to see the full deck contents or a player's hand.",inputSchema:c.object({identifier:c.string().max(256).describe("Card stack name or Foundry ID")})},{annotations:C,name:"create-card-stack",description:`Create a new card stack. Use type "deck" for a draw pile, "hand" for a player's hand, or "pile" for a discard/community pile. Returns the new stack ID.`,inputSchema:c.object({name:c.string().max(256).describe("Card stack name"),type:c.enum(["deck","hand","pile"]).describe("Stack type"),description:c.string().max(5e4).optional().describe("Stack description")})},{annotations:H,name:"update-card-stack",description:"Update a card stack's properties. Change the name, description, or type. For drawing or shuffling cards, use draw-cards or shuffle-deck instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Card stack name or Foundry ID"),name:c.string().max(256).optional().describe("New stack name"),description:c.string().max(5e4).optional().describe("New stack description"),type:c.enum(["deck","hand","pile"]).optional().describe("New stack type")})},{annotations:ee,name:"delete-card-stack",description:"Delete a card stack and all its cards permanently. This is irreversible. Returns the deleted stack name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Card stack name or Foundry ID")})},{annotations:C,name:"draw-cards",description:"Draw or deal cards from one stack to another (e.g., deck \u2192 hand, deck \u2192 discard pile). Cards are moved, not copied \u2014 they leave the source stack. Returns the drawn card names and IDs. Use get-card-stack to see available cards first.",inputSchema:c.object({source:c.string().max(256).describe("Source stack name or ID (e.g., the deck)"),destination:c.string().max(256).describe("Destination stack name or ID (e.g., a hand or pile)"),count:c.number().int().min(1).describe("Number of cards to draw")})},{annotations:C,name:"shuffle-deck",description:"Shuffle a card stack to randomize the card order. Use before dealing or drawing from a deck. Does not recall cards from other stacks \u2014 use reset-deck for that.",inputSchema:c.object({identifier:c.string().max(256).describe("Card stack name or Foundry ID")})},{annotations:H,name:"reset-deck",description:"Reset a deck by recalling all dealt cards from hands and piles back into it, restoring it to its original state. Use to start a new round or game. Does not shuffle \u2014 use shuffle-deck afterward to randomize the order.",inputSchema:c.object({identifier:c.string().max(256).describe("Card stack name or Foundry ID")})}];var[$k,Rh,zh,Oh,jh,Dh,Nh,Ch,Ah]=JD;function MJ(e,t){e.registerTool($k.name,{description:$k.description,annotations:$k.annotations},async()=>v(await t.query("list-card-stacks"))),e.registerTool(Rh.name,{description:Rh.description,annotations:Rh.annotations,inputSchema:Rh.inputSchema.shape},async({query:r})=>v(await t.query("search-card-stacks",{query:r}))),e.registerTool(zh.name,{description:zh.description,annotations:zh.annotations,inputSchema:zh.inputSchema.shape},async({identifier:r})=>v(await t.query("get-card-stack",{identifier:r}))),e.registerTool(Oh.name,{description:Oh.description,annotations:Oh.annotations,inputSchema:Oh.inputSchema.shape},async({name:r,type:i,description:n})=>v(await t.query("create-card-stack",{name:r,type:i,description:n}))),e.registerTool(jh.name,{description:jh.description,annotations:jh.annotations,inputSchema:jh.inputSchema.shape},async({identifier:r,name:i,description:n,type:o})=>{let a={};return i!==void 0&&(a.name=i),n!==void 0&&(a.description=n),o!==void 0&&(a.type=o),v(await t.query("update-card-stack",{identifier:r,updates:a}))}),e.registerTool(Dh.name,{description:Dh.description,annotations:Dh.annotations,inputSchema:Dh.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-card-stack",{identifier:r})))}function UJ(e,t){e.registerTool(Nh.name,{description:Nh.description,annotations:Nh.annotations,inputSchema:Nh.inputSchema.shape},async({source:r,destination:i,count:n})=>v(await t.query("draw-cards",{source:r,destination:i,count:n}))),e.registerTool(Ch.name,{description:Ch.description,annotations:Ch.annotations,inputSchema:Ch.inputSchema.shape},async({identifier:r})=>v(await t.query("shuffle-deck",{identifier:r}))),e.registerTool(Ah.name,{description:Ah.description,annotations:Ah.annotations,inputSchema:Ah.inputSchema.shape},async({identifier:r})=>v(await t.query("reset-deck",{identifier:r})))}function KD(e,t){MJ(e,t),UJ(e,t)}var QD=[{annotations:q,name:"list-macros",description:"List all macros in the world. Returns every macro (no pagination) with name, ID, type (script or chat), and command preview. Use to find a macro before executing or deleting it."},{annotations:Ne,name:"get-macro",description:"Get the full details of a macro including its complete command/code, type, author, and folder. Use when you need to read or review a macro's full source code.",inputSchema:c.object({identifier:c.string().max(256).describe("Macro name or Foundry ID")})},{annotations:Ne,name:"search-macros",description:"Search macros by name or command text. Returns matching macros with names, IDs, types, and match location. Use to find macros related to a topic or containing specific code.",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against macro name or command")})},{annotations:ee,name:"execute-macro",description:'Execute a macro by name or ID. Script macros run arbitrary JavaScript with full Foundry API access \u2014 use with caution as they can modify any game data. Chat macros send chat commands (e.g., "/roll 1d20"). Returns the macro name and type.',inputSchema:c.object({identifier:c.string().max(256).describe("Macro name or Foundry ID")})},{annotations:ee,name:"create-macro",description:'Create a new chat macro (runs chat commands like /roll 1d20 or /whisper GM Hello). Only "chat" macros can be created through the AI \u2014 "script" macros run arbitrary JavaScript with full Foundry API access and are blocked for safety; passing type "script" is rejected, and such macros must be authored by the GM directly in Foundry.',inputSchema:c.object({name:c.string().max(256).describe("Macro name"),command:c.string().max(1e4).describe("Macro command/code to execute"),type:c.enum(["script","chat"]).optional().describe('Macro type. Only "chat" (the default) can be created via the AI; "script" is rejected \u2014 script macros are GM-authored only.')})},{annotations:H,name:"update-macro",description:'Update a macro: change its name, command code, or icon. The type cannot be promoted to "script" through the AI (existing GM-authored script macros keep their type and remain executable). For executing a macro, use execute-macro instead.',inputSchema:c.object({identifier:c.string().max(256).describe("Macro name or Foundry ID"),name:c.string().max(256).optional().describe("New macro name"),type:c.enum(["script","chat"]).optional().describe('New macro type. Can only be set to "chat"; promoting a macro to "script" through the AI is rejected.'),command:c.string().max(1e4).optional().describe("New macro command/code"),img:c.string().max(256).optional().describe("New macro icon image path")})},{annotations:ee,name:"delete-macro",description:"Delete a macro from the world permanently. This is irreversible. Returns the deleted macro name.",inputSchema:c.object({identifier:c.string().max(256).describe("Macro name or Foundry ID")})}];var[Tk,Mh,Uh,Lh,qh,Zh,Fh]=QD;function YD(e,t){e.registerTool(Tk.name,{description:Tk.description,annotations:Tk.annotations},async()=>v(await t.query("list-macros"))),e.registerTool(Mh.name,{description:Mh.description,annotations:Mh.annotations,inputSchema:Mh.inputSchema.shape},async({identifier:r})=>v(await t.query("get-macro",{identifier:r}))),e.registerTool(Uh.name,{description:Uh.description,annotations:Uh.annotations,inputSchema:Uh.inputSchema.shape},async({query:r})=>v(await t.query("search-macros",{query:r}))),e.registerTool(Lh.name,{description:Lh.description,annotations:Lh.annotations,inputSchema:Lh.inputSchema.shape},async({identifier:r})=>v(await t.query("execute-macro",{identifier:r}))),e.registerTool(qh.name,{description:qh.description,annotations:qh.annotations,inputSchema:qh.inputSchema.shape},async({name:r,command:i,type:n})=>v(await t.query("create-macro",{name:r,command:i,type:n}))),e.registerTool(Zh.name,{description:Zh.description,annotations:Zh.annotations,inputSchema:Zh.inputSchema.shape},async({identifier:r,name:i,type:n,command:o,img:a})=>{let s={};return i!==void 0&&(s.name=i),n!==void 0&&(s.type=n),o!==void 0&&(s.command=o),a!==void 0&&(s.img=a),v(await t.query("update-macro",{identifier:r,updates:s}))}),e.registerTool(Fh.name,{description:Fh.description,annotations:Fh.annotations,inputSchema:Fh.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-macro",{identifier:r})))}var XD=[{annotations:q,name:"list-events",description:"List all Ember campaign quest events with their current status (none/available/active/complete). Returns every event (no pagination) with name, ID, type, step, and active/complete flags, plus the currently active event. Use get-event for full details. Requires the Ember module."},{annotations:q,name:"get-event",description:"Get full details of a specific Ember quest event including scene, NPCs, music, journal page, and outcomes. Supports partial name matching. Requires the Ember module.",inputSchema:c.object({identifier:c.string().max(256).describe("Event ID or name (partial match supported)")})},{annotations:C,name:"begin-event",description:"Begin an Ember quest event, triggering its full lifecycle: switches to the event scene, spawns NPC tokens, starts music/soundscape, and opens the journal page. Use list-events to find available events. Requires the Ember module.",inputSchema:c.object({identifier:c.string().max(256).describe("Event ID or name (partial match supported)")})},{annotations:C,name:"complete-event",description:"Complete an Ember quest event, triggering its onEnd lifecycle hook. Omit identifier to complete the currently active event. Use after the party finishes the encounter. Requires the Ember module.",inputSchema:c.object({identifier:c.string().max(256).optional().describe("Event ID or name. Omit to complete the currently active event.")})},{annotations:H,name:"set-event-outcome",description:"Set outcomes on an Ember quest event to record party choices and decisions. Outcomes are key-value pairs tracked by the narrative system for branching storylines. Requires the Ember module.",inputSchema:c.object({identifier:c.string().max(256).describe("Event ID or name"),outcomes:Ue.describe("Outcome key-value pairs to set on the event")})},{annotations:q,name:"get-party-location",description:"Read the party's current position on the Ember region (hex-crawl) map: the current hex \u2014 id (s.iiii.jjjj), biome, location, terrain, slice, and whether it is revealed/explored \u2014 the narrative events that can occur there, and the same compact readout for every adjacent hex. Returns onRegionMap=false (hex fields null) when the party is not on an Ember region scene. Read-only: does not trigger events, advance time, or alter the map. Requires the Ember module."}];var[Ik,Vh,Bh,Hh,Wh,Ek]=XD;function LJ(e,t){e.registerTool(Ik.name,{description:Ik.description,annotations:Ik.annotations},async()=>v(await t.query("list-events"))),e.registerTool(Vh.name,{description:Vh.description,annotations:Vh.annotations,inputSchema:Vh.inputSchema.shape},async({identifier:r})=>v(await t.query("get-event",{identifier:r}))),e.registerTool(Ek.name,{description:Ek.description,annotations:Ek.annotations},async()=>v(await t.query("get-party-location")))}function qJ(e,t){e.registerTool(Bh.name,{description:Bh.description,annotations:Bh.annotations,inputSchema:Bh.inputSchema.shape},async({identifier:r})=>v(await t.query("begin-event",{identifier:r}))),e.registerTool(Hh.name,{description:Hh.description,annotations:Hh.annotations,inputSchema:Hh.inputSchema.shape},async({identifier:r})=>v(await t.query("complete-event",{identifier:r}))),e.registerTool(Wh.name,{description:Wh.description,annotations:Wh.annotations,inputSchema:Wh.inputSchema.shape},async({identifier:r,outcomes:i})=>v(await t.query("set-event-outcome",{identifier:r,outcomes:i})))}function eN(e,t){LJ(e,t),qJ(e,t)}var tN=[{annotations:q,name:"list-effects",description:"List all active effects (buffs, debuffs, spell effects) on an actor. Returns each effect's name, ID, enabled/disabled status, icon, remaining duration, and attribute changes (e.g., +2 AC, 1d4 to attacks). For token-level status icons (prone, poisoned), use toggle-condition (combat bundle) instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or Foundry actor ID")})},{annotations:Ne,name:"get-effect",description:"Get full details of a single active effect on an actor, including all attribute changes, duration, origin, and status. Use when you need to inspect one specific effect's details rather than listing all effects.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or Foundry actor ID"),effectId:c.string().max(256).describe("Effect ID (from list-effects)")})},{annotations:C,name:"create-effect",description:"Add a custom active effect to an actor that modifies its attributes. Effects use Foundry change modes: 0=CUSTOM (no automatic effect), 1=MULTIPLY (multiply attribute by value), 2=ADD (add value to attribute, e.g., +2 AC), 3=DOWNGRADE (apply only if lower than current), 4=UPGRADE (apply only if higher than current), 5=OVERRIDE (replace attribute value entirely). Supports timed duration in combat rounds, turns, or real-time seconds. For simple status icons (prone, poisoned), use toggle-condition (combat bundle) instead. Returns the actor name and created effect ID and name.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or Foundry actor ID"),name:c.string().max(256).describe('Effect name (e.g., "Bless", "Rage", "Shield of Faith")'),changes:c.array(c.object({key:c.string().max(256).describe('System data path (e.g., "system.attributes.ac.bonus")'),mode:c.number().int().min(0).max(5).describe("Change mode: 0=CUSTOM, 1=MULTIPLY, 2=ADD, 3=DOWNGRADE, 4=UPGRADE, 5=OVERRIDE"),value:c.string().max(256).describe('Value to apply (e.g., "2", "1d4")')})).max(100).optional().describe("Attribute changes this effect applies"),icon:c.string().max(256).optional().describe("Effect icon path"),disabled:c.boolean().optional().describe("Create in disabled state (default: false)"),duration:c.object({rounds:c.number().int().min(0).optional(),turns:c.number().int().min(0).optional(),seconds:c.number().min(0).optional()}).optional().describe("Effect duration")})},{annotations:H,name:"update-effect",description:"Update an active effect on an actor. Change name, enable/disable, modify attribute changes, or adjust duration. Use list-effects to find the effect ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or Foundry actor ID"),effectId:c.string().max(256).describe("Effect ID (from list-effects)"),updates:Ue.describe('Update data (e.g., {disabled: true, name: "New Name"})')})},{annotations:ee,name:"delete-effect",description:"Remove an active effect from an actor permanently. Use when a spell or ability effect ends. Use list-effects to find the effect ID first. Returns the actor name and deleted effect ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or Foundry actor ID"),effectId:c.string().max(256).describe("Effect ID (from list-effects)")})}];var[Gh,Jh,Kh,Qh,Yh]=tN;function ZJ(e,t){e.registerTool(Gh.name,{description:Gh.description,annotations:Gh.annotations,inputSchema:Gh.inputSchema.shape},async({identifier:r})=>v(await t.query("list-effects",{identifier:r}))),e.registerTool(Jh.name,{description:Jh.description,annotations:Jh.annotations,inputSchema:Jh.inputSchema.shape},async({identifier:r,effectId:i})=>v(await t.query("get-effect",{identifier:r,effectId:i})))}function FJ(e,t){e.registerTool(Kh.name,{description:Kh.description,annotations:Kh.annotations,inputSchema:Kh.inputSchema.shape},async({identifier:r,name:i,changes:n,icon:o,disabled:a,duration:s})=>v(await t.query("create-effect",{identifier:r,effectData:{name:i,changes:n,icon:o,disabled:a,duration:s}}))),e.registerTool(Qh.name,{description:Qh.description,annotations:Qh.annotations,inputSchema:Qh.inputSchema.shape},async({identifier:r,effectId:i,updates:n})=>v(await t.query("update-effect",{identifier:r,effectId:i,updates:n}))),e.registerTool(Yh.name,{description:Yh.description,annotations:Yh.annotations,inputSchema:Yh.inputSchema.shape},async({identifier:r,effectId:i})=>v(await t.query("delete-effect",{identifier:r,effectId:i})))}function rN(e,t){ZJ(e,t),FJ(e,t)}var nN=[{annotations:Ne,name:"list-items",description:"List standalone world items (not owned by any actor) with pagination. Returns each item's name, ID, type (weapon/spell/equipment/etc.), and folder. Response includes total count and hasMore flag. Filter by item type or folder name. For items on a character's sheet, use search-character-items (characters bundle) instead.",inputSchema:c.object({type:c.string().max(256).optional().describe("Filter by item type (weapon, spell, equipment, consumable, tool, loot, feat, class, subclass, background)"),folder:c.string().max(256).optional().describe("Filter by folder name (substring match)"),limit:c.number().int().min(1).max(200).optional().describe("Max results to return (default: 50)"),offset:c.number().int().min(0).optional().describe("Number of results to skip (default: 0)")})},{annotations:Ne,name:"search-items",description:"Search standalone world items by name or description text. Returns matching items with names, IDs, types, and relevance. For searching items on a character, use search-character-items (characters bundle). For compendium items, use search-compendium (compendium bundle).",inputSchema:c.object({query:c.string().max(256).describe("Search text to match against item name or description"),type:c.string().max(256).optional().describe("Filter by item type (weapon, spell, equipment, consumable, tool, loot, feat, class, subclass, background)")})},{annotations:Ne,name:"get-item",description:"Get full details of a standalone world item including name, type, description (HTML), system-specific properties (damage, weight, rarity, etc.), and image path. For items owned by a character, use get-character-item (characters bundle) instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Item name or Foundry ID")})},{annotations:H,name:"update-item",description:"Update a standalone world item using Foundry dot-notation paths. Change name, system data, image, or other properties. Use get-item first to see current values.",inputSchema:c.object({identifier:c.string().max(256).describe("Item name or Foundry ID"),updates:Ue.describe("Foundry update data (e.g. { name, system, img })")})},{annotations:C,name:"create-item",description:"Create a standalone world item (not owned by any actor). Returns the new item ID. Specify the item type (weapon, spell, equipment, consumable, tool, loot, feat, class, subclass, background) and optional system data. For adding items directly to a character, use import-compendium-to-character (characters bundle) instead.",inputSchema:c.object({name:c.string().max(256).describe("Item name"),type:c.string().max(256).describe("Item type (weapon, spell, equipment, consumable, tool, loot, feat, class, subclass, background)"),system:Ue.optional().describe("System-specific data"),img:c.string().max(256).optional().describe("Item image path"),folderId:c.string().max(256).optional().describe("Folder name or ID")})},{annotations:ee,name:"delete-item",description:"Delete a standalone world item permanently. This is irreversible. Returns the deleted item name and ID. For removing items from a character, use delete-character-items (characters bundle) instead.",inputSchema:c.object({identifier:c.string().max(256).describe("Item name or Foundry ID")})}];var[Xh,eg,tg,rg,ng,ig]=nN;function VJ(e,t){e.registerTool(Xh.name,{description:Xh.description,annotations:Xh.annotations,inputSchema:Xh.inputSchema.shape},async({type:r,folder:i,limit:n,offset:o})=>v(await t.query("list-items",{type:r,folder:i,limit:n,offset:o}))),e.registerTool(eg.name,{description:eg.description,annotations:eg.annotations,inputSchema:eg.inputSchema.shape},async({query:r,type:i})=>v(await t.query("search-items",{query:r,type:i}))),e.registerTool(tg.name,{description:tg.description,annotations:tg.annotations,inputSchema:tg.inputSchema.shape},async({identifier:r})=>v(await t.query("get-item",{identifier:r})))}function BJ(e,t){e.registerTool(rg.name,{description:rg.description,annotations:rg.annotations,inputSchema:rg.inputSchema.shape},async({identifier:r,updates:i})=>v(await t.query("update-item",{identifier:r,updates:i}))),e.registerTool(ng.name,{description:ng.description,annotations:ng.annotations,inputSchema:ng.inputSchema.shape},async({name:r,type:i,system:n,img:o,folderId:a})=>v(await t.query("create-item",{name:r,type:i,system:n,img:o,folderId:a}))),e.registerTool(ig.name,{description:ig.description,annotations:ig.annotations,inputSchema:ig.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-item",{identifier:r})))}function iN(e,t){VJ(e,t),BJ(e,t)}var oN=c.enum(["Actor","Item","Scene","JournalEntry","RollableTable","Macro","Cards","Playlist"]),aN=[{annotations:q,name:"list-folders",description:"List all folders in the world organized by nesting hierarchy. Returns each folder's name, ID, type, parent folder, color, and document count. Filter by document type (Actor, Item, Scene, JournalEntry, etc.) to see only relevant folders.",inputSchema:c.object({type:oN.optional().describe("Filter by document type")})},{annotations:Ne,name:"get-folder",description:"Get full details of a folder including its name, type, parent, color, depth, and a list of all documents it contains. Use to inspect a folder's contents or metadata.",inputSchema:c.object({identifier:c.string().max(256).describe("Folder name or Foundry ID")})},{annotations:C,name:"create-folder",description:"Create a new folder for organizing documents in the sidebar. Specify the document type this folder holds (Actor, Item, Scene, JournalEntry, RollableTable, Macro, Cards, Playlist) and an optional parent folder for nesting. Returns the new folder ID.",inputSchema:c.object({name:c.string().max(256).describe("Folder name"),type:oN.describe("Document type this folder holds"),parentId:c.string().max(256).optional().describe("Parent folder name or ID for nesting")})},{annotations:H,name:"update-folder",description:"Update a folder \u2014 rename, change parent (for re-nesting), or set a display color. Use list-folders to find the folder ID first.",inputSchema:c.object({identifier:c.string().max(256).describe("Folder name or Foundry ID"),name:c.string().max(256).optional().describe("New folder name"),parentId:c.string().max(256).optional().describe("New parent folder name or ID to re-nest under (omit to leave the parent unchanged)"),color:c.string().max(256).optional().describe('Folder color (hex, e.g., "#ff0000")')})},{annotations:ee,name:"delete-folder",description:"Delete a folder from the world permanently. Documents inside the folder are moved to root level, not deleted. Only the folder organization is removed. Returns the deleted folder name and ID.",inputSchema:c.object({identifier:c.string().max(256).describe("Folder name or Foundry ID")})}];var[og,ag,sg,cg,lg]=aN;function HJ(e,t){e.registerTool(og.name,{description:og.description,annotations:og.annotations,inputSchema:og.inputSchema.shape},async({type:r})=>v(await t.query("list-folders",{type:r}))),e.registerTool(ag.name,{description:ag.description,annotations:ag.annotations,inputSchema:ag.inputSchema.shape},async({identifier:r})=>v(await t.query("get-folder",{identifier:r}))),e.registerTool(sg.name,{description:sg.description,annotations:sg.annotations,inputSchema:sg.inputSchema.shape},async({name:r,type:i,parentId:n})=>v(await t.query("create-folder",{name:r,type:i,parentId:n})))}function WJ(e,t){e.registerTool(cg.name,{description:cg.description,annotations:cg.annotations,inputSchema:cg.inputSchema.shape},async({identifier:r,name:i,parentId:n,color:o})=>{let a={};return i!==void 0&&(a.name=i),n!==void 0&&(a.parentId=n),o!==void 0&&(a.color=o),v(await t.query("update-folder",{identifier:r,updates:a}))}),e.registerTool(lg.name,{description:lg.description,annotations:lg.annotations,inputSchema:lg.inputSchema.shape},async({identifier:r})=>v(await t.query("delete-folder",{identifier:r})))}function sN(e,t){HJ(e,t),WJ(e,t)}var cN=c.discriminatedUnion("type",[c.object({type:c.literal("rectangle"),x:c.number().describe("X position in pixels (from scene top-left)"),y:c.number().describe("Y position in pixels (from scene top-left)"),width:c.number().min(1).describe("Width in pixels"),height:c.number().min(1).describe("Height in pixels"),rotation:c.number().min(0).max(360).optional().describe("Rotation in degrees"),hole:c.boolean().optional().describe("Cut out this shape (default: false)")}),c.object({type:c.literal("circle"),x:c.number().describe("Center X in pixels (from scene top-left)"),y:c.number().describe("Center Y in pixels (from scene top-left)"),radius:c.number().min(1).describe("Radius in pixels"),hole:c.boolean().optional().describe("Cut out this shape (default: false)")}),c.object({type:c.literal("ellipse"),x:c.number().describe("Center X in pixels (from scene top-left)"),y:c.number().describe("Center Y in pixels (from scene top-left)"),radiusX:c.number().min(1).describe("Horizontal radius in pixels"),radiusY:c.number().min(1).describe("Vertical radius in pixels"),rotation:c.number().min(0).max(360).optional().describe("Rotation in degrees"),hole:c.boolean().optional().describe("Cut out this shape (default: false)")}),c.object({type:c.literal("polygon"),points:c.array(c.number()).min(6).max(100).describe("Flat array of x,y pairs: [x1,y1,x2,y2,...] (min 3 points)"),hole:c.boolean().optional().describe("Cut out this shape (default: false)")})]),lN=c.object({name:c.string().max(256).optional(),type:c.string().max(256).describe("Behavior type: adjustDarknessLevel, displayScrollingText, executeMacro, executeScript, pauseGame, suppressWeather, teleportToken, toggleBehavior"),disabled:c.boolean().optional(),system:Ue.optional().describe("Behavior-specific configuration")}),uN=[{annotations:q,name:"list-regions",description:"List all regions (Foundry v13 trigger zones) on the active scene. Returns every region (no pagination) with name, ID, shape summary (type, position in pixels, dimensions), behavior types, and color. Use to find region IDs before inspecting or modifying them."},{annotations:q,name:"get-region",description:"Get a region with full details including all shapes (rectangle, circle, ellipse, polygon) with positions in pixels, attached behaviors with their configuration, elevation range, and display color. Use for inspecting region setup before modifying it.",inputSchema:c.object({identifier:c.string().max(256).describe("Region name or Foundry ID")})},{annotations:H,name:"update-region",description:"Update a region on the active scene. Modify name, display color, shapes, or behaviors. Shapes and behaviors are replaced entirely when provided \u2014 include all desired entries, not just changes.",inputSchema:c.object({identifier:c.string().max(256).describe("Region name or Foundry ID"),name:c.string().max(256).optional().describe("New region name"),color:c.string().max(256).optional().describe('New display color (hex, e.g., "#ff0000")'),shapes:c.array(cN).max(100).optional().describe("Replace region shapes"),behaviors:c.array(lN).max(100).optional().describe("Replace region behaviors")})},{annotations:C,name:"create-region",description:"Create a trigger region on the active scene (Foundry v13+). Regions trigger behaviors when tokens enter, exit, or move within the defined area. All shape coordinates are in pixels (from scene top-left). Attach behaviors like executeMacro (run code on enter/exit), teleportToken (portal), displayScrollingText (area labels), pauseGame, or suppressWeather.",inputSchema:c.object({name:c.string().max(256).describe("Region name"),shapes:c.array(cN).max(100).describe("Region shapes (rectangle, circle, ellipse, or polygon)"),color:c.string().max(256).optional().describe('Display color (hex, e.g., "#ff0000")'),behaviors:c.array(lN).max(100).optional().describe("Behaviors to attach to the region")})},{annotations:ee,name:"delete-regions",description:"Delete regions from the active scene permanently. Removes the trigger zones, all shapes, and all attached behaviors. Does not affect tokens currently inside the region. Returns the count of deleted regions, their IDs, and the scene name.",inputSchema:c.object({identifiers:c.array(c.string().max(256)).min(1).max(100).describe("Region IDs to delete")})}];var[Pk,ug,dg,pg,mg]=uN;function GJ(e,t){e.registerTool(Pk.name,{description:Pk.description,annotations:Pk.annotations},async()=>v(await t.query("list-regions"))),e.registerTool(ug.name,{description:ug.description,annotations:ug.annotations,inputSchema:ug.inputSchema.shape},async({identifier:r})=>v(await t.query("get-region",{identifier:r})))}function JJ(e,t){e.registerTool(dg.name,{description:dg.description,annotations:dg.annotations,inputSchema:dg.inputSchema.shape},async({identifier:r,name:i,color:n,shapes:o,behaviors:a})=>{let s={};return i!==void 0&&(s.name=i),n!==void 0&&(s.color=n),o!==void 0&&(s.shapes=o),a!==void 0&&(s.behaviors=a),v(await t.query("update-region",{identifier:r,updates:s}))})}function KJ(e,t){e.registerTool(pg.name,{description:pg.description,annotations:pg.annotations,inputSchema:pg.inputSchema.shape},async({name:r,shapes:i,color:n,behaviors:o})=>v(await t.query("create-region",{name:r,shapes:i,color:n,behaviors:o}))),e.registerTool(mg.name,{description:mg.description,annotations:mg.annotations,inputSchema:mg.inputSchema.shape},async({identifiers:r})=>v(await t.query("delete-regions",{identifiers:r})))}function dN(e,t){GJ(e,t),JJ(e,t),KJ(e,t)}import{readFile as QJ}from"node:fs/promises";import YJ from"node:path";var pN=[{annotations:hm,name:"generate-image",description:"Generate an image and display it in the chat. Use for character portraits, item illustrations, concept art, or any visual the user wants to SEE in conversation. Does NOT create or modify scenes \u2014 for battle maps and scene backgrounds, use generate-scene instead. Returns the image inline as base64. Be specific in prompts about subject, style, composition, lighting, and mood. Requires image generation to be enabled with a valid API key.",inputSchema:c.object({prompt:c.string().max(1e4).describe("Detailed image generation prompt. Be specific about subject, style, composition, lighting, and mood. For fantasy RPG content, include relevant details like setting, character features, and atmosphere."),size:c.enum(["square","landscape","portrait"]).optional().describe("Image aspect ratio (default: square). Use landscape for wide scenes, portrait for characters."),style:c.string().max(256).optional().describe('Optional style modifier (e.g., "oil painting", "watercolor", "pixel art", "photorealistic"). Appended to the prompt to influence the visual style.')})}];var XJ=5e3,e8=24e4,t8=c.object({imagePath:c.string(),mimeType:c.string(),provider:c.string(),model:c.string()}),[fg]=pN;function r8(e){if(e.includes("\0")||e.includes("\\"))return!1;let t=e.split("/");return t.includes("..")?!1:t[0]==="familiar"}async function n8(e){let{imagePath:t,mimeType:r,provider:i,model:n,foundryDataPath:o}=e;if(!r8(t))return{content:[{type:"text",text:"Image generation failed: invalid image path"}],isError:!0};try{let a=YJ.join(o,t);return{content:[{type:"image",data:(await QJ(a)).toString("base64"),mimeType:r},{type:"text",text:`Image generated by ${i} (${n})`}]}}catch(a){return{content:[{type:"text",text:`Image saved to Foundry (${t}) but could not be read: ${yr(a)}`}],isError:!0}}}function mN(e,t,{foundryDataPath:r}){e.registerTool(fg.name,{description:fg.description,annotations:fg.annotations,inputSchema:fg.inputSchema.shape},async({prompt:i,size:n,style:o},a)=>{let s=st(a,"Generating image\u2026",XJ);try{let l=await li("Image generation",t8,()=>t.query("generate-image",{prompt:i,size:n,style:o},e8));return l.ok?r==null?{content:[{type:"text",text:`Image saved to Foundry (${l.data.imagePath}) but cannot be returned \u2014 set FAMILIAR_FOUNDRY_DATA env var`}],isError:!0}:await n8({imagePath:l.data.imagePath,mimeType:l.data.mimeType,provider:l.data.provider,model:l.data.model,foundryDataPath:r}):l.envelope}finally{s()}})}var fN=[{annotations:ik,name:"generate-voice",description:"Generate speech audio (text-to-speech) from text using the configured TTS provider (ElevenLabs, Cartesia, or OpenAI TTS). Returns audio that plays in Foundry for the GM. Use for NPC dialogue, dramatic narration, or reading text aloud. Keep text under 500 characters for best results. Requires voice generation to be enabled and a voice provider configured in Foundry module settings.",inputSchema:c.object({text:c.string().max(1e4).describe("The text to convert to speech. Keep it concise for best results \u2014 under 500 characters."),voice:c.string().max(256).optional().describe("Optional voice ID to use instead of the configured default. Each provider has different voice IDs \u2014 check provider documentation."),characterId:c.string().max(256).optional().describe("Optional actor name or ID. Resolves the voice from the character\u2019s stored voice (set via set-character-voice). Ignored if voice is explicitly provided."),language:c.string().max(16).optional().describe('Optional ISO 639-1 language code (e.g. "en", "nl", "de", "fr"). Currently consumed by Cartesia (42 languages); ignored by providers that auto-detect language. Defaults to "en".'),emotion:c.string().max(64).optional().describe('Optional emotion/tone hint. Use sparingly \u2014 one word per call, only when the line needs distinct emotional colouring. Reliable values: "angry", "sad", "happy", "excited", "calm", "scared", "whispering", "shouting", "nervous", "tired". Applied on Cartesia (generation_config.emotion) and OpenAI (instructions prefix). Has NO effect on ElevenLabs under the default model: ElevenLabs emotion needs audio tags, which only render on the eleven_v3 model (not the default eleven_multilingual_v2). Unknown emotions are silently ignored on all providers \u2014 pick a value from the reliable list above.')})},{annotations:ik,name:"generate-dialogue",description:"Generate multi-voice dialogue with different voices for DM narration and character speech. Each segment specifies text and a role (dm or character) to select the corresponding voice provider from settings. All segments generate in parallel for speed, then play sequentially as an audio queue with no gaps between lines. Use this instead of multiple generate-voice calls when you have a conversation between the DM and characters. Max 20 segments per call.",inputSchema:c.object({segments:c.array(c.object({text:c.string().max(1e4).describe("The text to speak."),role:c.enum(["dm","character"]).describe('Voice slot: "dm" uses DM/Narrator voice settings, "character" uses Character voice settings.'),voice:c.string().max(256).optional().describe("Optional voice ID override for this segment."),characterId:c.string().max(256).optional().describe("Optional actor name or ID. Resolves the voice from the character\u2019s stored voice (set via set-character-voice). Ignored if voice is explicitly provided."),instructions:c.string().max(1e3).optional().describe('Tone/style instructions for this segment (OpenAI TTS only). E.g. "Speak in a menacing whisper".'),speed:c.number().min(.25).max(4).optional().describe("Speech speed for this segment (OpenAI TTS only). 0.25\u20134.0, default 1.0."),stability:c.number().min(0).max(1).optional().describe("Voice stability for this segment (ElevenLabs only). Lower = more expressive."),similarityBoost:c.number().min(0).max(1).optional().describe("Similarity boost for this segment (ElevenLabs only). Higher = closer to original voice."),language:c.string().max(16).optional().describe('Optional ISO 639-1 language code (e.g. "en", "nl", "de"). Consumed by Cartesia; ignored by auto-detecting providers.'),emotion:c.string().max(64).optional().describe('Optional emotion/tone hint for this segment (e.g. "angry", "sad", "whispering"). Applied on Cartesia/OpenAI; no effect on ElevenLabs under the default model \u2014 see generate-voice.emotion for the full guidance.')})).min(1).max(20).describe("Dialogue segments in playback order. Each uses the DM or character voice slot.")})},{annotations:H,name:"set-character-voice",description:"Assign a TTS voice to a specific character or NPC. The voice ID is stored on the actor and automatically used when that character speaks via generate-voice or generate-dialogue. Voice IDs are provider-specific (e.g. ElevenLabs voice_id, OpenAI voice name). Pass an empty voiceId to clear the assignment.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or ID to assign the voice to."),voiceId:c.string().max(256).describe("Provider-specific voice ID to assign. Pass empty string to clear the voice assignment.")})},{annotations:Ne,name:"get-character-voice",description:"Get the TTS voice ID assigned to a specific character or NPC. Returns the voice ID if one is configured, or null if the character uses the default voice slot.",inputSchema:c.object({identifier:c.string().max(256).describe("Actor name or ID to look up.")})}];var hN=5e3,bg=6e4,i8=c.object({provider:c.string(),model:c.string(),text:c.string()}),o8=c.object({successCount:c.number(),failureCount:c.number(),segments:c.array(c.object({text:c.string(),role:c.string(),index:c.number(),provider:c.string(),model:c.string(),error:c.string().optional()}))}),a8=c.object({voiceId:c.string().optional()}),[hg,gg,yg,vg]=fN;function s8(e,t){e.registerTool(hg.name,{description:hg.description,annotations:hg.annotations,inputSchema:hg.inputSchema.shape},async({text:r,voice:i,characterId:n,language:o,emotion:a},s)=>{let l=st(s,"Generating audio\u2026",hN);try{let u=await li("Voice generation",i8,()=>t.query("generate-voice",{text:r,voice:i,characterId:n,language:o,emotion:a},bg));return u.ok?{content:[{type:"text",text:`Audio generated by ${u.data.provider} (${u.data.model}) and playing in Foundry VTT. Spoken text: "${u.data.text}"`}]}:u.envelope}finally{l()}})}function c8(e,t){e.registerTool(gg.name,{description:gg.description,annotations:gg.annotations,inputSchema:gg.inputSchema.shape},async({segments:r},i)=>{let n=st(i,"Generating dialogue\u2026",hN);try{let o=await li("Dialogue generation",o8,()=>t.query("generate-dialogue",{segments:r},bg*2));if(!o.ok)return o.envelope;let a=o.data.segments.map(s=>s.error!=null&&s.error!==""?` [${s.role}] FAILED: ${s.error}`:` [${s.role}] "${s.text}" \u2014 ${s.provider}`);return{content:[{type:"text",text:`Dialogue (${String(o.data.successCount)}/${String(o.data.segments.length)} segments) playing sequentially in Foundry:
135
135
  ${a.join(`
136
136
  `)}`}]}}finally{n()}})}function l8(e,t){e.registerTool(yg.name,{description:yg.description,annotations:yg.annotations,inputSchema:yg.inputSchema.shape},async({identifier:r,voiceId:i})=>{let n=await t.query("set-character-voice",{identifier:r,voiceId:i},bg);return n.success?{content:[{type:"text",text:`Voice ${i===""?"cleared":`set to "${i}"`} for character "${r}".`}]}:{content:[{type:"text",text:`Failed to set character voice: ${on(n.error)}`}],isError:!0}})}function u8(e,t){e.registerTool(vg.name,{description:vg.description,annotations:vg.annotations,inputSchema:vg.inputSchema.shape},async({identifier:r})=>{let i=await t.query("get-character-voice",{identifier:r},bg);if(!i.success)return{content:[{type:"text",text:`Failed to get character voice: ${on(i.error)}`}],isError:!0};let n=a8.safeParse(i.result);if(!n.success)return{content:[{type:"text",text:`Failed to get character voice: unexpected response format \u2014 ${n.error.message}`}],isError:!0};let o=n.data.voiceId;return{content:[{type:"text",text:o!==void 0&&o!==""?`Character "${r}" has voice ID: "${o}"`:`Character "${r}" has no custom voice assigned (uses default voice slot).`}]}})}function gN(e,t){s8(e,t),c8(e,t),l8(e,t),u8(e,t)}var Rk=["character","quest","npc","relationship","world_state","location","item","combat","effect"];var yN=[{annotations:Ne,name:"search-lore",description:"Search the campaign knowledge base (journals, characters, scenes, items) using semantic search. Returns relevant text chunks with source references.",inputSchema:c.object({query:c.string().max(256).describe("Search query to find relevant campaign lore"),type:c.enum(["journal","character","scene","item"]).optional().describe("Filter results to a specific document type"),limit:c.number().int().min(1).max(20).optional().describe("Maximum number of results to return (default: 5)")})},{annotations:Ne,name:"get-campaign-memories",description:"Retrieve stored campaign memories \u2014 persistent facts about characters, quests, NPCs, world state, and items that persist across sessions.",inputSchema:c.object({category:c.enum(Rk).optional().describe("Filter memories by category"),query:c.string().max(256).optional().describe("Search term to filter memories by content"),includeSuperseded:c.boolean().optional().describe("Include soft-deleted (superseded) facts in the results (default: false)")})},{annotations:C,name:"save-campaign-memory",description:"Save a new fact to persistent campaign memory. Facts persist across sessions and are automatically injected into future conversations. Only save facts useful in FUTURE sessions \u2014 not dice rolls, temporary combat state, or data Foundry already tracks (HP, conditions, inventory). Use for important plot developments, character changes, NPC interactions, locations, and relationships.",inputSchema:c.object({category:c.enum(Rk).describe("Category for this memory"),content:c.string().max(1e3).describe("The fact to remember \u2014 one atomic piece of information per call"),relatedEntities:c.array(c.string().max(100)).max(5).optional().describe("Names of related characters, locations, or items")})},{annotations:H,name:"update-campaign-memory",description:"Update an existing campaign memory fact. Use when correcting or refining a previously stored fact. Provide the memory ID and the updated content.",inputSchema:c.object({id:c.string().max(100).describe("The ID of the memory fact to update"),content:c.string().min(20).max(1e3).describe("The updated fact content (minimum 20 characters)"),relatedEntities:c.array(c.string().max(100)).max(5).optional().describe("Character, NPC, location, or item names related to this fact")})},{annotations:ee,name:"delete-campaign-memory",description:"Soft-delete a campaign memory fact. The fact is marked as superseded but kept in history. Use when a fact is outdated or incorrect.",inputSchema:c.object({id:c.string().max(100).describe("The ID of the memory fact to delete"),reason:c.string().max(256).optional().describe("Brief reason for deletion")})}];var[_g,Sg,xg,wg,kg]=yN;function vN(e,t){e.registerTool(_g.name,{description:_g.description,annotations:_g.annotations,inputSchema:_g.inputSchema.shape},async({query:r,type:i,limit:n})=>v(await t.query("search-lore",{query:r,type:i,limit:n}))),e.registerTool(Sg.name,{description:Sg.description,annotations:Sg.annotations,inputSchema:Sg.inputSchema.shape},async({category:r,query:i})=>v(await t.query("get-campaign-memories",{category:r,query:i}))),e.registerTool(xg.name,{description:xg.description,annotations:xg.annotations,inputSchema:xg.inputSchema.shape},async({category:r,content:i,relatedEntities:n})=>v(await t.query("save-campaign-memory",{category:r,content:i,relatedEntities:n}))),e.registerTool(wg.name,{description:wg.description,annotations:wg.annotations,inputSchema:wg.inputSchema.shape},async({id:r,content:i,relatedEntities:n})=>v(await t.query("update-campaign-memory",{id:r,content:i,relatedEntities:n}))),e.registerTool(kg.name,{description:kg.description,annotations:kg.annotations,inputSchema:kg.inputSchema.shape},async({id:r,reason:i})=>v(await t.query("delete-campaign-memory",{id:r,reason:i})))}var bN=c.object({id:c.string().max(64).describe("Unique room identifier"),name:c.string().max(128).describe("Display name for the room \u2014 also injected into the image prompt"),x:c.number().int().min(0).max(200).optional().describe("Grid column (top-left). Optional \u2014 auto-placed linearly when omitted"),y:c.number().int().min(0).max(200).optional().describe("Grid row (top-left). Optional \u2014 auto-placed linearly when omitted"),width:c.number().int().min(2).max(50).describe("Room width in grid squares"),height:c.number().int().min(2).max(50).describe("Room height in grid squares")});var _N=[{annotations:hm,name:"generate-scene",description:"Create a scene with an AI-generated battle map background image. ALWAYS use this instead of create-scene when the user wants a battle map, dungeon, tavern, or any scene with a background \u2014 never combine create-scene + generate-image separately. Provide rooms with dimensions to set scene grid size; room names are injected into the image prompt as atmosphere keywords. NO walls, tokens, or structural data are generated \u2014 this is image-only. If the user wants walls, they place them with Foundry's native wall tool or import via a UVTT importer. If no backgroundPrompt is given, one is auto-generated from the scene name, rooms, and environment. The generated image is automatically uploaded and applied as the scene background. Returns the created scene ID with background status.",inputSchema:c.object({name:c.string().max(128).describe("Scene name"),gridWidth:c.number().int().min(10).max(200).optional().describe("Scene width in grid squares. Auto-calculated from rooms when omitted"),gridHeight:c.number().int().min(10).max(200).optional().describe("Scene height in grid squares. Auto-calculated from rooms when omitted"),environment:c.enum(["indoor","outdoor","underground","mixed"]).optional().describe("Scene environment type \u2014 added to the image prompt"),gridDistance:c.number().min(1).max(100).optional().describe("Feet per grid square (default: 5)"),weather:c.enum(["none","rain","fog","snow","leaves","rainStorm"]).optional().describe("Weather particle effect"),backgroundColor:c.string().max(7).optional().describe("Hex color for scene background"),rooms:c.array(bN).min(1).max(50).describe("Room definitions \u2014 used for grid sizing and prompt enrichment"),backgroundPrompt:c.string().max(1024).optional().describe("AI image generation prompt for scene background. Generates a top-down battlemap image using the configured image provider and applies it as the scene background. Requires image generation to be enabled in settings with a valid API key."),candidates:c.number().int().min(1).max(4).optional().describe("Number of candidate background images to generate (1-4, default 1). When >1, the tool returns a candidate envelope instead of applying a background \u2014 the built-in chat renders a 2\xD72 picker; MCP clients with vision can pick by calling apply-scene-candidate. Costs N\xD7 base credits; only the chosen candidate is upscaled.")})},{annotations:Qj,name:"apply-scene-candidate",description:"Apply a previously-generated scene candidate as the scene background. Call after generate-scene with candidates>1 returns a candidate envelope. Uploads the chosen image, upscales it when imageQuality=high (Leonardo + fal.ai only), and sets it as the scene background. Re-callable with a different candidateId to swap the chosen background.",inputSchema:c.object({sceneId:c.string().max(256).describe("Scene ID returned by generate-scene"),candidateId:c.string().max(256).describe("Candidate ID from the candidates array of the generate-scene result")})}];var d8=12e4,p8=12e4,SN=5e3,[$g,Tg]=_N;function xN(e,t){e.registerTool($g.name,{description:$g.description,annotations:$g.annotations,inputSchema:$g.inputSchema.shape},async(r,i)=>{let n=st(i,"Generating scene\u2026",SN);try{return v(await t.query("generate-scene",r,d8))}finally{n()}}),e.registerTool(Tg.name,{description:Tg.description,annotations:Tg.annotations,inputSchema:Tg.inputSchema.shape},async(r,i)=>{let n=st(i,"Applying scene candidate\u2026",SN);try{return v(await t.query("apply-scene-candidate",r,p8))}finally{n()}})}var m8=[["dice",kD],["world",ND],["chat",FD],["combat",uD],["combat-ai",_D],["characters",sD],["scenes",jD],["compendium",xD],["journals",ED],["canvas",AD],["canvas-environment",qD],["playlists",HD],["tables",GD],["cards",KD],["macros",YD],["effects",rN],["items",iN],["folders",sN],["regions",dN],["ember-events",eN],["image-generation",mN],["voice-generation",gN],["knowledge",vN],["scene-generator",xN]],f8=new Set(gm.map(e=>e.name));function h8(e,t,r,i){let n=Yo(e),o=new Set(Object.keys(n));return t(e,r,i),Object.keys(n).filter(a=>!o.has(a))}function zk(e,t,r){let{eagerLoadAll:i=!1,metrics:n,foundryDataPath:o}=r??{};eD(e),tD($);let a=new Sm(e),s=new Map,l={foundryDataPath:o},u=oD(t,a,f8);for(let[d,p]of m8)try{let h=h8(e,p,d==="combat"?u:t,l);s.set(d,h)}catch(f){throw $.error({bundleId:d,error:f},`Failed to register tool bundle: ${d}`),f}return iD(e,a,n),a.initialize(s,i),a}async function wN(e){let{connector:t,queryable:r,clientTracker:i,httpPort:n,httpHost:o,httpSecret:a,corsOrigins:s,httpAllowedHosts:l,webhookHandler:u,metrics:d,foundryDataPath:p}=e,f=new Map,h=async y=>{let _=new hc({name:"familiar",version:"0.1.0"},{instructions:Jw});zk(_,r,{eagerLoadAll:!0,metrics:d,foundryDataPath:p}),await _.connect(y);let x=y.sessionId;x!==void 0&&x!==""&&f.set(x,_),_.server.oninitialized=()=>{let k=_.server.getClientVersion();i.add({name:k?.name??"Unknown HTTP client",version:k?.version??"unknown",transport:"http",sessionId:x??void 0,connectedAt:new Date().toISOString()})},y.onclose=()=>{x!==void 0&&x!==""&&(i.removeBySessionId(x),f.delete(x))},$.info("New MCP server instance connected to transport")},{server:m,closeAllSessions:g}=await Kj(n,h,s,{webhookHandler:u,secret:a,host:o,allowedHosts:l});$.info("MCP Streamable HTTP transport ready (multi-session)"),SS(async()=>{await new Promise(_=>{let x=setTimeout(()=>{$.warn("Graceful shutdown timed out (25s) \u2014 proceeding with force-close"),_()},25e3);x.unref(),m.close(()=>{clearTimeout(x),_()})}),await g();let y=[...f.values()].map(async _=>{try{await _.close()}catch(x){$.warn({err:x},"Error closing MCP server instance")}});await Promise.all(y),f.clear(),await _S(t)})}async function kN(e){let{connector:t,queryable:r,clientTracker:i,metrics:n,foundryDataPath:o}=e,a=new hc({name:"familiar",version:"0.1.0"},{instructions:Jw});zk(a,r,{metrics:n,foundryDataPath:o});let s=new um;await a.connect(s),a.server.oninitialized=()=>{let l=a.server.getClientVersion();i.add({name:l?.name??"Unknown stdio client",version:l?.version??"unknown",transport:"stdio",connectedAt:new Date().toISOString()})},s.onclose=()=>{i.removeByTransport("stdio")},$.info("MCP stdio transport connected"),SS(async()=>{await _S(t),await a.close()})}var Ig=class{stats=new Map;record(t,r,i){let n=this.stats.get(t);n||(n={calls:0,errors:0,totalMs:0,maxMs:0,lastCallAt:0},this.stats.set(t,n)),n.calls++,i||n.errors++,n.totalMs+=r,r>n.maxMs&&(n.maxMs=r),n.lastCallAt=Date.now()}getSummary(){let t=0,r=0,i={};for(let[n,o]of this.stats)t+=o.calls,r+=o.errors,i[n]={calls:o.calls,errors:o.errors,totalMs:o.totalMs,maxMs:o.maxMs};return{totalCalls:t,totalErrors:r,methods:i}}};function $N(e,t){return{query:async(r,i,n)=>{let o=Date.now(),a=await e.query(r,i,n);return t.record(r,Date.now()-o,a.success),a}}}process.on("uncaughtException",e=>{$.fatal({err:e},"Uncaught exception"),process.exit(1)});process.on("unhandledRejection",e=>{$.fatal({err:e},"Unhandled rejection"),process.exit(1)});async function g8(){let e=GE();RR(e);let t=ER(e),r=new Fd,i,n,o=!1,a=fR(r,()=>o?{valid:!1,status:"unknown"}:i),s=new Ig,l=$N(a,s),u=new Mc(h=>{r.pushClientUpdate(h,i)}),d=(h,m)=>{t&&(o=!0,(async()=>{try{i=await t.validate(h)}catch(g){$.error({err:g},m),i={valid:!1,status:"unknown"}}finally{o=!1}u.broadcastNow()})())},f=PR(e,t,()=>{n==null||n===""||(i=void 0,d(n,"License re-validation after webhook failed"))});r.onModuleConnect(h=>{if(n=h,i=void 0,!t){i={valid:!0,status:"active"},u.broadcastNow();return}if(h==null||h===""){i={valid:!1,status:"unknown"},u.broadcastNow();return}d(h,"License validation failed during module connect")}),await r.start(e.wsPort,e.wsHost,e.wsAllowedOrigins,e.wsSecret),$.info({host:e.wsHost,port:e.wsPort},"Familiar server starting"),e.httpPort!=null?await wN({connector:r,queryable:l,clientTracker:u,httpPort:e.httpPort,httpHost:e.httpHost,httpSecret:e.httpSecret,corsOrigins:e.corsOrigins??[],httpAllowedHosts:e.httpAllowedHosts,webhookHandler:f,metrics:s,foundryDataPath:e.foundryDataPath}):await kN({connector:r,queryable:l,clientTracker:u,metrics:s,foundryDataPath:e.foundryDataPath})}g8().catch(e=>{$.fatal({err:e},"Fatal error"),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "familiar-vtt",
3
- "version": "2.12.0",
3
+ "version": "2.13.0",
4
+ "mcpName": "com.familiarvtt/familiar",
4
5
  "description": "Your AI co-pilot for Foundry VTT. 193 tools across 24 domains — characters, combat, scenes, canvas, audio, voices, images, and more.",
5
6
  "type": "module",
6
7
  "engines": {