figura-cli 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +102 -93
- package/dist/mcp.js +1 -1
- package/package.json +1 -1
- package/skills/fig/SKILL.md +60 -18
package/dist/mcp.js
CHANGED
|
@@ -65,4 +65,4 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
65
65
|
`:`[${Y[b]}\r
|
|
66
66
|
]`;continue}if(X+=Y[b],Y[b]==="\\")J=!0;else if(G&&Y[b]==="]")G=!1;else if(!G&&Y[b]==="[")G=!0}try{new RegExp(X)}catch{return console.warn(`Could not convert regex pattern at ${Q.currentPath.join("/")} to a flag-independent form! Falling back to the flag-ignorant source`),x.source}return X}function Z7(x,Q){if(Q.target==="openAi")console.warn("Warning: OpenAI may not support records in schemas! Try an array of key-value pairs instead.");if(Q.target==="openApi3"&&x.keyType?._def.typeName===I.ZodEnum)return{type:"object",required:x.keyType._def.values,properties:x.keyType._def.values.reduce((Y,X)=>({...Y,[X]:m(x.valueType._def,{...Q,currentPath:[...Q.currentPath,"properties",X]})??j0(Q)}),{}),additionalProperties:Q.rejectedAdditionalProperties};let W={type:"object",additionalProperties:m(x.valueType._def,{...Q,currentPath:[...Q.currentPath,"additionalProperties"]})??Q.allowedAdditionalProperties};if(Q.target==="openApi3")return W;if(x.keyType?._def.typeName===I.ZodString&&x.keyType._def.checks?.length){let{type:Y,...X}=i7(x.keyType._def,Q);return{...W,propertyNames:X}}else if(x.keyType?._def.typeName===I.ZodEnum)return{...W,propertyNames:{enum:x.keyType._def.values}};else if(x.keyType?._def.typeName===I.ZodBranded&&x.keyType._def.type._def.typeName===I.ZodString&&x.keyType._def.type._def.checks?.length){let{type:Y,...X}=P7(x.keyType._def,Q);return{...W,propertyNames:X}}return W}function VB(x,Q){if(Q.mapStrategy==="record")return Z7(x,Q);let W=m(x.keyType._def,{...Q,currentPath:[...Q.currentPath,"items","items","0"]})||j0(Q),Y=m(x.valueType._def,{...Q,currentPath:[...Q.currentPath,"items","items","1"]})||j0(Q);return{type:"array",maxItems:125,items:{type:"array",items:[W,Y],minItems:2,maxItems:2}}}function qB(x){let Q=x.values,Y=Object.keys(x.values).filter((J)=>{return typeof Q[Q[J]]!=="number"}).map((J)=>Q[J]),X=Array.from(new Set(Y.map((J)=>typeof J)));return{type:X.length===1?X[0]==="string"?"string":"number":["string","number"],enum:Y}}function NB(x){return x.target==="openAi"?void 0:{not:j0({...x,currentPath:[...x.currentPath,"not"]})}}function FB(x){return x.target==="openApi3"?{enum:["null"],nullable:!0}:{type:"null"}}var Z6={ZodString:"string",ZodNumber:"number",ZodBigInt:"integer",ZodBoolean:"boolean",ZodNull:"null"};function LB(x,Q){if(Q.target==="openApi3")return UB(x,Q);let W=x.options instanceof Map?Array.from(x.options.values()):x.options;if(W.every((Y)=>(Y._def.typeName in Z6)&&(!Y._def.checks||!Y._def.checks.length))){let Y=W.reduce((X,J)=>{let G=Z6[J._def.typeName];return G&&!X.includes(G)?[...X,G]:X},[]);return{type:Y.length>1?Y:Y[0]}}else if(W.every((Y)=>Y._def.typeName==="ZodLiteral"&&!Y.description)){let Y=W.reduce((X,J)=>{let G=typeof J._def.value;switch(G){case"string":case"number":case"boolean":return[...X,G];case"bigint":return[...X,"integer"];case"object":if(J._def.value===null)return[...X,"null"];case"symbol":case"undefined":case"function":default:return X}},[]);if(Y.length===W.length){let X=Y.filter((J,G,$)=>$.indexOf(J)===G);return{type:X.length>1?X:X[0],enum:W.reduce((J,G)=>{return J.includes(G._def.value)?J:[...J,G._def.value]},[])}}}else if(W.every((Y)=>Y._def.typeName==="ZodEnum"))return{type:"string",enum:W.reduce((Y,X)=>[...Y,...X._def.values.filter((J)=>!Y.includes(J))],[])};return UB(x,Q)}var UB=(x,Q)=>{let W=(x.options instanceof Map?Array.from(x.options.values()):x.options).map((Y,X)=>m(Y._def,{...Q,currentPath:[...Q.currentPath,"anyOf",`${X}`]})).filter((Y)=>!!Y&&(!Q.strictUnions||typeof Y==="object"&&Object.keys(Y).length>0));return W.length?{anyOf:W}:void 0};function cB(x,Q){if(["ZodString","ZodNumber","ZodBigInt","ZodBoolean","ZodNull"].includes(x.innerType._def.typeName)&&(!x.innerType._def.checks||!x.innerType._def.checks.length)){if(Q.target==="openApi3")return{type:Z6[x.innerType._def.typeName],nullable:!0};return{type:[Z6[x.innerType._def.typeName],"null"]}}if(Q.target==="openApi3"){let Y=m(x.innerType._def,{...Q,currentPath:[...Q.currentPath]});if(Y&&"$ref"in Y)return{allOf:[Y],nullable:!0};return Y&&{...Y,nullable:!0}}let W=m(x.innerType._def,{...Q,currentPath:[...Q.currentPath,"anyOf","0"]});return W&&{anyOf:[W,{type:"null"}]}}function MB(x,Q){let W={type:"number"};if(!x.checks)return W;for(let Y of x.checks)switch(Y.kind){case"int":W.type="integer",BY(W,"type",Y.message,Q);break;case"min":if(Q.target==="jsonSchema7")if(Y.inclusive)Y0(W,"minimum",Y.value,Y.message,Q);else Y0(W,"exclusiveMinimum",Y.value,Y.message,Q);else{if(!Y.inclusive)W.exclusiveMinimum=!0;Y0(W,"minimum",Y.value,Y.message,Q)}break;case"max":if(Q.target==="jsonSchema7")if(Y.inclusive)Y0(W,"maximum",Y.value,Y.message,Q);else Y0(W,"exclusiveMaximum",Y.value,Y.message,Q);else{if(!Y.inclusive)W.exclusiveMaximum=!0;Y0(W,"maximum",Y.value,Y.message,Q)}break;case"multipleOf":Y0(W,"multipleOf",Y.value,Y.message,Q);break}return W}function DB(x,Q){let W=Q.target==="openAi",Y={type:"object",properties:{}},X=[],J=x.shape();for(let $ in J){let b=J[$];if(b===void 0||b._def===void 0)continue;let z=A2(b);if(z&&W){if(b._def.typeName==="ZodOptional")b=b._def.innerType;if(!b.isNullable())b=b.nullable();z=!1}let v=m(b._def,{...Q,currentPath:[...Q.currentPath,"properties",$],propertyPath:[...Q.currentPath,"properties",$]});if(v===void 0)continue;if(Y.properties[$]=v,!z)X.push($)}if(X.length)Y.required=X;let G=O2(x,Q);if(G!==void 0)Y.additionalProperties=G;return Y}function O2(x,Q){if(x.catchall._def.typeName!=="ZodNever")return m(x.catchall._def,{...Q,currentPath:[...Q.currentPath,"additionalProperties"]});switch(x.unknownKeys){case"passthrough":return Q.allowedAdditionalProperties;case"strict":return Q.rejectedAdditionalProperties;case"strip":return Q.removeAdditionalStrategy==="strict"?Q.allowedAdditionalProperties:Q.rejectedAdditionalProperties}}function A2(x){try{return x.isOptional()}catch{return!0}}var jB=(x,Q)=>{if(Q.currentPath.toString()===Q.propertyPath?.toString())return m(x.innerType._def,Q);let W=m(x.innerType._def,{...Q,currentPath:[...Q.currentPath,"anyOf","1"]});return W?{anyOf:[{not:j0(Q)},W]}:j0(Q)};var OB=(x,Q)=>{if(Q.pipeStrategy==="input")return m(x.in._def,Q);else if(Q.pipeStrategy==="output")return m(x.out._def,Q);let W=m(x.in._def,{...Q,currentPath:[...Q.currentPath,"allOf","0"]}),Y=m(x.out._def,{...Q,currentPath:[...Q.currentPath,"allOf",W?"1":"0"]});return{allOf:[W,Y].filter((X)=>X!==void 0)}};function AB(x,Q){return m(x.type._def,Q)}function IB(x,Q){let Y={type:"array",uniqueItems:!0,items:m(x.valueType._def,{...Q,currentPath:[...Q.currentPath,"items"]})};if(x.minSize)Y0(Y,"minItems",x.minSize.value,x.minSize.message,Q);if(x.maxSize)Y0(Y,"maxItems",x.maxSize.value,x.maxSize.message,Q);return Y}function SB(x,Q){if(x.rest)return{type:"array",minItems:x.items.length,items:x.items.map((W,Y)=>m(W._def,{...Q,currentPath:[...Q.currentPath,"items",`${Y}`]})).reduce((W,Y)=>Y===void 0?W:[...W,Y],[]),additionalItems:m(x.rest._def,{...Q,currentPath:[...Q.currentPath,"additionalItems"]})};else return{type:"array",minItems:x.items.length,maxItems:x.items.length,items:x.items.map((W,Y)=>m(W._def,{...Q,currentPath:[...Q.currentPath,"items",`${Y}`]})).reduce((W,Y)=>Y===void 0?W:[...W,Y],[])}}function EB(x){return{not:j0(x)}}function RB(x){return j0(x)}var PB=(x,Q)=>{return m(x.innerType._def,Q)};var iB=(x,Q,W)=>{switch(Q){case I.ZodString:return i7(x,W);case I.ZodNumber:return MB(x,W);case I.ZodObject:return DB(x,W);case I.ZodBigInt:return GB(x,W);case I.ZodBoolean:return $B();case I.ZodDate:return wY(x,W);case I.ZodUndefined:return EB(W);case I.ZodNull:return FB(W);case I.ZodArray:return JB(x,W);case I.ZodUnion:case I.ZodDiscriminatedUnion:return LB(x,W);case I.ZodIntersection:return wB(x,W);case I.ZodTuple:return SB(x,W);case I.ZodRecord:return Z7(x,W);case I.ZodLiteral:return KB(x,W);case I.ZodEnum:return BB(x);case I.ZodNativeEnum:return qB(x);case I.ZodNullable:return cB(x,W);case I.ZodOptional:return jB(x,W);case I.ZodMap:return VB(x,W);case I.ZodSet:return IB(x,W);case I.ZodLazy:return()=>x.getter()._def;case I.ZodPromise:return AB(x,W);case I.ZodNaN:case I.ZodNever:return NB(W);case I.ZodEffects:return vB(x,W);case I.ZodAny:return j0(W);case I.ZodUnknown:return RB(W);case I.ZodDefault:return zB(x,W);case I.ZodBranded:return P7(x,W);case I.ZodReadonly:return PB(x,W);case I.ZodCatch:return bB(x,W);case I.ZodPipeline:return OB(x,W);case I.ZodFunction:case I.ZodVoid:case I.ZodSymbol:return;default:return((Y)=>{return})(Q)}};function m(x,Q,W=!1){let Y=Q.seen.get(x);if(Q.override){let $=Q.override?.(x,Q,Y,W);if($!==WB)return $}if(Y&&!W){let $=I2(Y,Q);if($!==void 0)return $}let X={def:x,path:Q.currentPath,jsonSchema:void 0};Q.seen.set(x,X);let J=iB(x,x.typeName,Q),G=typeof J==="function"?m(J(),Q):J;if(G)S2(x,Q,G);if(Q.postProcess){let $=Q.postProcess(G,x,Q);return X.jsonSchema=G,$}return X.jsonSchema=G,G}var I2=(x,Q)=>{switch(Q.$refStrategy){case"root":return{$ref:x.path.join("/")};case"relative":return{$ref:R7(Q.currentPath,x.path)};case"none":case"seen":{if(x.path.length<Q.currentPath.length&&x.path.every((W,Y)=>Q.currentPath[Y]===W))return console.warn(`Recursive reference detected at ${Q.currentPath.join("/")}! Defaulting to any`),j0(Q);return Q.$refStrategy==="seen"?j0(Q):void 0}}},S2=(x,Q,W)=>{if(x.description){if(W.description=x.description,Q.markdownDescription)W.markdownDescription=x.description}return W};var VY=(x,Q)=>{let W=XB(Q),Y=typeof Q==="object"&&Q.definitions?Object.entries(Q.definitions).reduce((b,[z,v])=>({...b,[z]:m(v._def,{...W,currentPath:[...W.basePath,W.definitionPath,z]},!0)??j0(W)}),{}):void 0,X=typeof Q==="string"?Q:Q?.nameStrategy==="title"?void 0:Q?.name,J=m(x._def,X===void 0?W:{...W,currentPath:[...W.basePath,W.definitionPath,X]},!1)??j0(W),G=typeof Q==="object"&&Q.name!==void 0&&Q.nameStrategy==="title"?Q.name:void 0;if(G!==void 0)J.title=G;if(W.flags.hasReferencedOpenAiAnyType){if(!Y)Y={};if(!Y[W.openAiAnyTypeName])Y[W.openAiAnyTypeName]={type:["string","number","integer","boolean","array","null"],items:{$ref:W.$refStrategy==="relative"?"1":[...W.basePath,W.definitionPath,W.openAiAnyTypeName].join("/")}}}let $=X===void 0?Y?{...J,[W.definitionPath]:Y}:J:{$ref:[...W.$refStrategy==="relative"?[]:W.basePath,W.definitionPath,X].join("/"),[W.definitionPath]:{...Y,[X]:J}};if(W.target==="jsonSchema7")$.$schema="http://json-schema.org/draft-07/schema#";else if(W.target==="jsonSchema2019-09"||W.target==="openAi")$.$schema="https://json-schema.org/draft/2019-09/schema#";if(W.target==="openAi"&&(("anyOf"in $)||("oneOf"in $)||("allOf"in $)||("type"in $)&&Array.isArray($.type)))console.warn("Warning: OpenAI may not support schemas with unions as roots! Try wrapping it in an object property.");return $};function E2(x){if(!x)return"draft-7";if(x==="jsonSchema7"||x==="draft-7")return"draft-7";if(x==="jsonSchema2019-09"||x==="draft-2020-12")return"draft-2020-12";return"draft-7"}function qY(x,Q){if(P1(x))return lW(x,{target:E2(Q?.target),io:Q?.pipeStrategy??"input"});return VY(x,{strictUnions:Q?.strictUnions??!0,pipeStrategy:Q?.pipeStrategy??"input"})}function NY(x){let W=C4(x)?.method;if(!W)throw Error("Schema is missing a method literal");let Y=G7(W);if(typeof Y!=="string")throw Error("Schema method literal must be a string");return Y}function FY(x,Q){let W=Z4(x,Q);if(!W.success)throw W.error;return W.data}var R2=60000;class UY{constructor(x){if(this._options=x,this._requestMessageId=0,this._requestHandlers=new Map,this._requestHandlerAbortControllers=new Map,this._notificationHandlers=new Map,this._responseHandlers=new Map,this._progressHandlers=new Map,this._timeoutInfo=new Map,this._pendingDebouncedNotifications=new Set,this._taskProgressTokens=new Map,this._requestResolvers=new Map,this.setNotificationHandler(K7,(Q)=>{this._oncancel(Q)}),this.setNotificationHandler(V7,(Q)=>{this._onprogress(Q)}),this.setRequestHandler(H7,(Q)=>({})),this._taskStore=x?.taskStore,this._taskMessageQueue=x?.taskMessageQueue,this._taskStore)this.setRequestHandler(q7,async(Q,W)=>{let Y=await this._taskStore.getTask(Q.params.taskId,W.sessionId);if(!Y)throw new k(l.InvalidParams,"Failed to retrieve task: Task not found");return{...Y}}),this.setRequestHandler(F7,async(Q,W)=>{let Y=async()=>{let X=Q.params.taskId;if(this._taskMessageQueue){let G;while(G=await this._taskMessageQueue.dequeue(X,W.sessionId)){if(G.type==="response"||G.type==="error"){let $=G.message,b=$.id,z=this._requestResolvers.get(b);if(z)if(this._requestResolvers.delete(b),G.type==="response")z($);else{let v=$,B=new k(v.error.code,v.error.message,v.error.data);z(B)}else{let v=G.type==="response"?"Response":"Error";this._onerror(Error(`${v} handler missing for request ${b}`))}continue}await this._transport?.send(G.message,{relatedRequestId:W.requestId})}}let J=await this._taskStore.getTask(X,W.sessionId);if(!J)throw new k(l.InvalidParams,`Task not found: ${X}`);if(!k4(J.status))return await this._waitForTaskUpdate(X,W.signal),await Y();if(k4(J.status)){let G=await this._taskStore.getTaskResult(X,W.sessionId);return this._clearTaskQueue(X),{...G,_meta:{...G._meta,[T4]:{taskId:X}}}}return await Y()};return await Y()}),this.setRequestHandler(U7,async(Q,W)=>{try{let{tasks:Y,nextCursor:X}=await this._taskStore.listTasks(Q.params?.cursor,W.sessionId);return{tasks:Y,nextCursor:X,_meta:{}}}catch(Y){throw new k(l.InvalidParams,`Failed to list tasks: ${Y instanceof Error?Y.message:String(Y)}`)}}),this.setRequestHandler(c7,async(Q,W)=>{try{let Y=await this._taskStore.getTask(Q.params.taskId,W.sessionId);if(!Y)throw new k(l.InvalidParams,`Task not found: ${Q.params.taskId}`);if(k4(Y.status))throw new k(l.InvalidParams,`Cannot cancel task in terminal status: ${Y.status}`);await this._taskStore.updateTaskStatus(Q.params.taskId,"cancelled","Client cancelled task execution.",W.sessionId),this._clearTaskQueue(Q.params.taskId);let X=await this._taskStore.getTask(Q.params.taskId,W.sessionId);if(!X)throw new k(l.InvalidParams,`Task not found after cancellation: ${Q.params.taskId}`);return{_meta:{},...X}}catch(Y){if(Y instanceof k)throw Y;throw new k(l.InvalidRequest,`Failed to cancel task: ${Y instanceof Error?Y.message:String(Y)}`)}})}async _oncancel(x){if(!x.params.requestId)return;this._requestHandlerAbortControllers.get(x.params.requestId)?.abort(x.params.reason)}_setupTimeout(x,Q,W,Y,X=!1){this._timeoutInfo.set(x,{timeoutId:setTimeout(Y,Q),startTime:Date.now(),timeout:Q,maxTotalTimeout:W,resetTimeoutOnProgress:X,onTimeout:Y})}_resetTimeout(x){let Q=this._timeoutInfo.get(x);if(!Q)return!1;let W=Date.now()-Q.startTime;if(Q.maxTotalTimeout&&W>=Q.maxTotalTimeout)throw this._timeoutInfo.delete(x),k.fromError(l.RequestTimeout,"Maximum total timeout exceeded",{maxTotalTimeout:Q.maxTotalTimeout,totalElapsed:W});return clearTimeout(Q.timeoutId),Q.timeoutId=setTimeout(Q.onTimeout,Q.timeout),!0}_cleanupTimeout(x){let Q=this._timeoutInfo.get(x);if(Q)clearTimeout(Q.timeoutId),this._timeoutInfo.delete(x)}async connect(x){if(this._transport)throw Error("Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection.");this._transport=x;let Q=this.transport?.onclose;this._transport.onclose=()=>{Q?.(),this._onclose()};let W=this.transport?.onerror;this._transport.onerror=(X)=>{W?.(X),this._onerror(X)};let Y=this._transport?.onmessage;this._transport.onmessage=(X,J)=>{if(Y?.(X,J),j6(X)||fv(X))this._onresponse(X);else if(sW(X))this._onrequest(X,J);else if(mv(X))this._onnotification(X);else this._onerror(Error(`Unknown message type: ${JSON.stringify(X)}`))},await this._transport.start()}_onclose(){let x=this._responseHandlers;this._responseHandlers=new Map,this._progressHandlers.clear(),this._taskProgressTokens.clear(),this._pendingDebouncedNotifications.clear();for(let W of this._timeoutInfo.values())clearTimeout(W.timeoutId);this._timeoutInfo.clear();for(let W of this._requestHandlerAbortControllers.values())W.abort();this._requestHandlerAbortControllers.clear();let Q=k.fromError(l.ConnectionClosed,"Connection closed");this._transport=void 0,this.onclose?.();for(let W of x.values())W(Q)}_onerror(x){this.onerror?.(x)}_onnotification(x){let Q=this._notificationHandlers.get(x.method)??this.fallbackNotificationHandler;if(Q===void 0)return;Promise.resolve().then(()=>Q(x)).catch((W)=>this._onerror(Error(`Uncaught error in notification handler: ${W}`)))}_onrequest(x,Q){let W=this._requestHandlers.get(x.method)??this.fallbackRequestHandler,Y=this._transport,X=x.params?._meta?.[T4]?.taskId;if(W===void 0){let z={jsonrpc:"2.0",id:x.id,error:{code:l.MethodNotFound,message:"Method not found"}};if(X&&this._taskMessageQueue)this._enqueueTaskMessage(X,{type:"error",message:z,timestamp:Date.now()},Y?.sessionId).catch((v)=>this._onerror(Error(`Failed to enqueue error response: ${v}`)));else Y?.send(z).catch((v)=>this._onerror(Error(`Failed to send an error response: ${v}`)));return}let J=new AbortController;this._requestHandlerAbortControllers.set(x.id,J);let G=lv(x.params)?x.params.task:void 0,$=this._taskStore?this.requestTaskStore(x,Y?.sessionId):void 0,b={signal:J.signal,sessionId:Y?.sessionId,_meta:x.params?._meta,sendNotification:async(z)=>{if(J.signal.aborted)return;let v={relatedRequestId:x.id};if(X)v.relatedTask={taskId:X};await this.notification(z,v)},sendRequest:async(z,v,B)=>{if(J.signal.aborted)throw new k(l.ConnectionClosed,"Request was cancelled");let K={...B,relatedRequestId:x.id};if(X&&!K.relatedTask)K.relatedTask={taskId:X};let w=K.relatedTask?.taskId??X;if(w&&$)await $.updateTaskStatus(w,"input_required");return await this.request(z,v,K)},authInfo:Q?.authInfo,requestId:x.id,requestInfo:Q?.requestInfo,taskId:X,taskStore:$,taskRequestedTtl:G?.ttl,closeSSEStream:Q?.closeSSEStream,closeStandaloneSSEStream:Q?.closeStandaloneSSEStream};Promise.resolve().then(()=>{if(G)this.assertTaskHandlerCapability(x.method)}).then(()=>W(x,b)).then(async(z)=>{if(J.signal.aborted)return;let v={result:z,jsonrpc:"2.0",id:x.id};if(X&&this._taskMessageQueue)await this._enqueueTaskMessage(X,{type:"response",message:v,timestamp:Date.now()},Y?.sessionId);else await Y?.send(v)},async(z)=>{if(J.signal.aborted)return;let v={jsonrpc:"2.0",id:x.id,error:{code:Number.isSafeInteger(z.code)?z.code:l.InternalError,message:z.message??"Internal error",...z.data!==void 0&&{data:z.data}}};if(X&&this._taskMessageQueue)await this._enqueueTaskMessage(X,{type:"error",message:v,timestamp:Date.now()},Y?.sessionId);else await Y?.send(v)}).catch((z)=>this._onerror(Error(`Failed to send response: ${z}`))).finally(()=>{if(this._requestHandlerAbortControllers.get(x.id)===J)this._requestHandlerAbortControllers.delete(x.id)})}_onprogress(x){let{progressToken:Q,...W}=x.params,Y=Number(Q),X=this._progressHandlers.get(Y);if(!X){this._onerror(Error(`Received a progress notification for an unknown token: ${JSON.stringify(x)}`));return}let J=this._responseHandlers.get(Y),G=this._timeoutInfo.get(Y);if(G&&J&&G.resetTimeoutOnProgress)try{this._resetTimeout(Y)}catch($){this._responseHandlers.delete(Y),this._progressHandlers.delete(Y),this._cleanupTimeout(Y),J($);return}X(W)}_onresponse(x){let Q=Number(x.id),W=this._requestResolvers.get(Q);if(W){if(this._requestResolvers.delete(Q),j6(x))W(x);else{let J=new k(x.error.code,x.error.message,x.error.data);W(J)}return}let Y=this._responseHandlers.get(Q);if(Y===void 0){this._onerror(Error(`Received a response for an unknown message ID: ${JSON.stringify(x)}`));return}this._responseHandlers.delete(Q),this._cleanupTimeout(Q);let X=!1;if(j6(x)&&x.result&&typeof x.result==="object"){let J=x.result;if(J.task&&typeof J.task==="object"){let G=J.task;if(typeof G.taskId==="string")X=!0,this._taskProgressTokens.set(G.taskId,Q)}}if(!X)this._progressHandlers.delete(Q);if(j6(x))Y(x);else{let J=k.fromError(x.error.code,x.error.message,x.error.data);Y(J)}}get transport(){return this._transport}async close(){await this._transport?.close()}async*requestStream(x,Q,W){let{task:Y}=W??{};if(!Y){try{yield{type:"result",result:await this.request(x,Q,W)}}catch(J){yield{type:"error",error:J instanceof k?J:new k(l.InternalError,String(J))}}return}let X;try{let J=await this.request(x,z8,W);if(J.task)X=J.task.taskId,yield{type:"taskCreated",task:J.task};else throw new k(l.InternalError,"Task creation did not return a task");while(!0){let G=await this.getTask({taskId:X},W);if(yield{type:"taskStatus",task:G},k4(G.status)){if(G.status==="completed")yield{type:"result",result:await this.getTaskResult({taskId:X},Q,W)};else if(G.status==="failed")yield{type:"error",error:new k(l.InternalError,`Task ${X} failed`)};else if(G.status==="cancelled")yield{type:"error",error:new k(l.InternalError,`Task ${X} was cancelled`)};return}if(G.status==="input_required"){yield{type:"result",result:await this.getTaskResult({taskId:X},Q,W)};return}let $=G.pollInterval??this._options?.defaultTaskPollInterval??1000;await new Promise((b)=>setTimeout(b,$)),W?.signal?.throwIfAborted()}}catch(J){yield{type:"error",error:J instanceof k?J:new k(l.InternalError,String(J))}}}request(x,Q,W){let{relatedRequestId:Y,resumptionToken:X,onresumptiontoken:J,task:G,relatedTask:$}=W??{};return new Promise((b,z)=>{let v=(q)=>{z(q)};if(!this._transport){v(Error("Not connected"));return}if(this._options?.enforceStrictCapabilities===!0)try{if(this.assertCapabilityForMethod(x.method),G)this.assertTaskCapability(x.method)}catch(q){v(q);return}W?.signal?.throwIfAborted();let B=this._requestMessageId++,K={...x,jsonrpc:"2.0",id:B};if(W?.onprogress)this._progressHandlers.set(B,W.onprogress),K.params={...x.params,_meta:{...x.params?._meta||{},progressToken:B}};if(G)K.params={...K.params,task:G};if($)K.params={...K.params,_meta:{...K.params?._meta||{},[T4]:$}};let w=(q)=>{this._responseHandlers.delete(B),this._progressHandlers.delete(B),this._cleanupTimeout(B),this._transport?.send({jsonrpc:"2.0",method:"notifications/cancelled",params:{requestId:B,reason:String(q)}},{relatedRequestId:Y,resumptionToken:X,onresumptiontoken:J}).catch((M)=>this._onerror(Error(`Failed to send cancellation: ${M}`)));let U=q instanceof k?q:new k(l.RequestTimeout,String(q));z(U)};this._responseHandlers.set(B,(q)=>{if(W?.signal?.aborted)return;if(q instanceof Error)return z(q);try{let U=Z4(Q,q.result);if(!U.success)z(U.error);else b(U.data)}catch(U){z(U)}}),W?.signal?.addEventListener("abort",()=>{w(W?.signal?.reason)});let V=W?.timeout??R2,N=()=>w(k.fromError(l.RequestTimeout,"Request timed out",{timeout:V}));this._setupTimeout(B,V,W?.maxTotalTimeout,N,W?.resetTimeoutOnProgress??!1);let F=$?.taskId;if(F){let q=(U)=>{let M=this._responseHandlers.get(B);if(M)M(U);else this._onerror(Error(`Response handler missing for side-channeled request ${B}`))};this._requestResolvers.set(B,q),this._enqueueTaskMessage(F,{type:"request",message:K,timestamp:Date.now()}).catch((U)=>{this._cleanupTimeout(B),z(U)})}else this._transport.send(K,{relatedRequestId:Y,resumptionToken:X,onresumptiontoken:J}).catch((q)=>{this._cleanupTimeout(B),z(q)})})}async getTask(x,Q){return this.request({method:"tasks/get",params:x},N7,Q)}async getTaskResult(x,Q,W){return this.request({method:"tasks/result",params:x},Q,W)}async listTasks(x,Q){return this.request({method:"tasks/list",params:x},L7,Q)}async cancelTask(x,Q){return this.request({method:"tasks/cancel",params:x},hv,Q)}async notification(x,Q){if(!this._transport)throw Error("Not connected");this.assertNotificationCapability(x.method);let W=Q?.relatedTask?.taskId;if(W){let G={...x,jsonrpc:"2.0",params:{...x.params,_meta:{...x.params?._meta||{},[T4]:Q.relatedTask}}};await this._enqueueTaskMessage(W,{type:"notification",message:G,timestamp:Date.now()});return}if((this._options?.debouncedNotificationMethods??[]).includes(x.method)&&!x.params&&!Q?.relatedRequestId&&!Q?.relatedTask){if(this._pendingDebouncedNotifications.has(x.method))return;this._pendingDebouncedNotifications.add(x.method),Promise.resolve().then(()=>{if(this._pendingDebouncedNotifications.delete(x.method),!this._transport)return;let G={...x,jsonrpc:"2.0"};if(Q?.relatedTask)G={...G,params:{...G.params,_meta:{...G.params?._meta||{},[T4]:Q.relatedTask}}};this._transport?.send(G,Q).catch(($)=>this._onerror($))});return}let J={...x,jsonrpc:"2.0"};if(Q?.relatedTask)J={...J,params:{...J.params,_meta:{...J.params?._meta||{},[T4]:Q.relatedTask}}};await this._transport.send(J,Q)}setRequestHandler(x,Q){let W=NY(x);this.assertRequestHandlerCapability(W),this._requestHandlers.set(W,(Y,X)=>{let J=FY(x,Y);return Promise.resolve(Q(J,X))})}removeRequestHandler(x){this._requestHandlers.delete(x)}assertCanSetRequestHandler(x){if(this._requestHandlers.has(x))throw Error(`A request handler for ${x} already exists, which would be overridden`)}setNotificationHandler(x,Q){let W=NY(x);this._notificationHandlers.set(W,(Y)=>{let X=FY(x,Y);return Promise.resolve(Q(X))})}removeNotificationHandler(x){this._notificationHandlers.delete(x)}_cleanupTaskProgressHandler(x){let Q=this._taskProgressTokens.get(x);if(Q!==void 0)this._progressHandlers.delete(Q),this._taskProgressTokens.delete(x)}async _enqueueTaskMessage(x,Q,W){if(!this._taskStore||!this._taskMessageQueue)throw Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");let Y=this._options?.maxTaskQueueSize;await this._taskMessageQueue.enqueue(x,Q,W,Y)}async _clearTaskQueue(x,Q){if(this._taskMessageQueue){let W=await this._taskMessageQueue.dequeueAll(x,Q);for(let Y of W)if(Y.type==="request"&&sW(Y.message)){let X=Y.message.id,J=this._requestResolvers.get(X);if(J)J(new k(l.InternalError,"Task cancelled or completed")),this._requestResolvers.delete(X);else this._onerror(Error(`Resolver missing for request ${X} during task ${x} cleanup`))}}}async _waitForTaskUpdate(x,Q){let W=this._options?.defaultTaskPollInterval??1000;try{let Y=await this._taskStore?.getTask(x);if(Y?.pollInterval)W=Y.pollInterval}catch{}return new Promise((Y,X)=>{if(Q.aborted){X(new k(l.InvalidRequest,"Request cancelled"));return}let J=setTimeout(Y,W);Q.addEventListener("abort",()=>{clearTimeout(J),X(new k(l.InvalidRequest,"Request cancelled"))},{once:!0})})}requestTaskStore(x,Q){let W=this._taskStore;if(!W)throw Error("No task store configured");return{createTask:async(Y)=>{if(!x)throw Error("No request provided");return await W.createTask(Y,x.id,{method:x.method,params:x.params},Q)},getTask:async(Y)=>{let X=await W.getTask(Y,Q);if(!X)throw new k(l.InvalidParams,"Failed to retrieve task: Task not found");return X},storeTaskResult:async(Y,X,J)=>{await W.storeTaskResult(Y,X,J,Q);let G=await W.getTask(Y,Q);if(G){let $=E6.parse({method:"notifications/tasks/status",params:G});if(await this.notification($),k4(G.status))this._cleanupTaskProgressHandler(Y)}},getTaskResult:(Y)=>{return W.getTaskResult(Y,Q)},updateTaskStatus:async(Y,X,J)=>{let G=await W.getTask(Y,Q);if(!G)throw new k(l.InvalidParams,`Task "${Y}" not found - it may have been cleaned up`);if(k4(G.status))throw new k(l.InvalidParams,`Cannot update task "${Y}" from terminal status "${G.status}" to "${X}". Terminal states (completed, failed, cancelled) cannot transition to other states.`);await W.updateTaskStatus(Y,X,J,Q);let $=await W.getTask(Y,Q);if($){let b=E6.parse({method:"notifications/tasks/status",params:$});if(await this.notification(b),k4($.status))this._cleanupTaskProgressHandler(Y)}},listTasks:(Y)=>{return W.listTasks(Y,Q)}}}}function ZB(x){return x!==null&&typeof x==="object"&&!Array.isArray(x)}function CB(x,Q){let W={...x};for(let Y in Q){let X=Y,J=Q[X];if(J===void 0)continue;let G=W[X];if(ZB(G)&&ZB(J))W[X]={...G,...J};else W[X]=J}return W}var qq=S8(HX(),1),Nq=S8(Vq(),1);function tC(){let x=new qq.default({strict:!1,validateFormats:!0,validateSchema:!1,allErrors:!0});return Nq.default(x),x}class jX{constructor(x){this._ajv=x??tC()}getValidator(x){let Q="$id"in x&&typeof x.$id==="string"?this._ajv.getSchema(x.$id)??this._ajv.compile(x):this._ajv.compile(x);return(W)=>{if(Q(W))return{valid:!0,data:W,errorMessage:void 0};else return{valid:!1,data:void 0,errorMessage:this._ajv.errorsText(Q.errors)}}}}class OX{constructor(x){this._server=x}requestStream(x,Q,W){return this._server.requestStream(x,Q,W)}createMessageStream(x,Q){let W=this._server.getClientCapabilities();if((x.tools||x.toolChoice)&&!W?.sampling?.tools)throw Error("Client does not support sampling tools capability.");if(x.messages.length>0){let Y=x.messages[x.messages.length-1],X=Array.isArray(Y.content)?Y.content:[Y.content],J=X.some((z)=>z.type==="tool_result"),G=x.messages.length>1?x.messages[x.messages.length-2]:void 0,$=G?Array.isArray(G.content)?G.content:[G.content]:[],b=$.some((z)=>z.type==="tool_use");if(J){if(X.some((z)=>z.type!=="tool_result"))throw Error("The last message must contain only tool_result content if any is present");if(!b)throw Error("tool_result blocks are not matching any tool_use from the previous message")}if(b){let z=new Set($.filter((B)=>B.type==="tool_use").map((B)=>B.id)),v=new Set(X.filter((B)=>B.type==="tool_result").map((B)=>B.toolUseId));if(z.size!==v.size||![...z].every((B)=>v.has(B)))throw Error("ids of tool_result blocks and tool_use blocks from previous message do not match")}}return this.requestStream({method:"sampling/createMessage",params:x},i6,Q)}elicitInputStream(x,Q){let W=this._server.getClientCapabilities(),Y=x.mode??"form";switch(Y){case"url":{if(!W?.elicitation?.url)throw Error("Client does not support url elicitation.");break}case"form":{if(!W?.elicitation?.form)throw Error("Client does not support form elicitation.");break}}let X=Y==="form"&&x.mode===void 0?{...x,mode:"form"}:x;return this.requestStream({method:"elicitation/create",params:X},w8,Q)}async getTask(x,Q){return this._server.getTask({taskId:x},Q)}async getTaskResult(x,Q,W){return this._server.getTaskResult({taskId:x},Q,W)}async listTasks(x,Q){return this._server.listTasks(x?{cursor:x}:void 0,Q)}async cancelTask(x,Q){return this._server.cancelTask({taskId:x},Q)}}function Fq(x,Q,W){if(!x)throw Error(`${W} does not support task creation (required for ${Q})`);switch(Q){case"tools/call":if(!x.tools?.call)throw Error(`${W} does not support task creation for tools/call (required for ${Q})`);break;default:break}}function Uq(x,Q,W){if(!x)throw Error(`${W} does not support task creation (required for ${Q})`);switch(Q){case"sampling/createMessage":if(!x.sampling?.createMessage)throw Error(`${W} does not support task creation for sampling/createMessage (required for ${Q})`);break;case"elicitation/create":if(!x.elicitation?.create)throw Error(`${W} does not support task creation for elicitation/create (required for ${Q})`);break;default:break}}class AX extends UY{constructor(x,Q){super(Q);if(this._serverInfo=x,this._loggingLevels=new Map,this.LOG_LEVEL_SEVERITY=new Map(P6.options.map((W,Y)=>[W,Y])),this.isMessageIgnored=(W,Y)=>{let X=this._loggingLevels.get(Y);return X?this.LOG_LEVEL_SEVERITY.get(W)<this.LOG_LEVEL_SEVERITY.get(X):!1},this._capabilities=Q?.capabilities??{},this._instructions=Q?.instructions,this._jsonSchemaValidator=Q?.jsonSchemaValidator??new jX,this.setRequestHandler(xY,(W)=>this._oninitialize(W)),this.setNotificationHandler(QY,()=>this.oninitialized?.()),this._capabilities.logging)this.setRequestHandler(bY,async(W,Y)=>{let X=Y.sessionId||Y.requestInfo?.headers["mcp-session-id"]||void 0,{level:J}=W.params,G=P6.safeParse(J);if(G.success)this._loggingLevels.set(X,G.data);return{}})}get experimental(){if(!this._experimental)this._experimental={tasks:new OX(this)};return this._experimental}registerCapabilities(x){if(this.transport)throw Error("Cannot register capabilities after connecting to transport");this._capabilities=CB(this._capabilities,x)}setRequestHandler(x,Q){let Y=C4(x)?.method;if(!Y)throw Error("Schema is missing a method literal");let X;if(P1(Y)){let G=Y;X=G._zod?.def?.value??G.value}else{let G=Y;X=G._def?.value??G.value}if(typeof X!=="string")throw Error("Schema method literal must be a string");if(X==="tools/call"){let G=async($,b)=>{let z=Z4(B8,$);if(!z.success){let w=z.error instanceof Error?z.error.message:String(z.error);throw new k(l.InvalidParams,`Invalid tools/call request: ${w}`)}let{params:v}=z.data,B=await Promise.resolve(Q($,b));if(v.task){let w=Z4(z8,B);if(!w.success){let V=w.error instanceof Error?w.error.message:String(w.error);throw new k(l.InvalidParams,`Invalid task creation result: ${V}`)}return w.data}let K=Z4(S7,B);if(!K.success){let w=K.error instanceof Error?K.error.message:String(K.error);throw new k(l.InvalidParams,`Invalid tools/call result: ${w}`)}return K.data};return super.setRequestHandler(x,G)}return super.setRequestHandler(x,Q)}assertCapabilityForMethod(x){switch(x){case"sampling/createMessage":if(!this._clientCapabilities?.sampling)throw Error(`Client does not support sampling (required for ${x})`);break;case"elicitation/create":if(!this._clientCapabilities?.elicitation)throw Error(`Client does not support elicitation (required for ${x})`);break;case"roots/list":if(!this._clientCapabilities?.roots)throw Error(`Client does not support listing roots (required for ${x})`);break;case"ping":break}}assertNotificationCapability(x){switch(x){case"notifications/message":if(!this._capabilities.logging)throw Error(`Server does not support logging (required for ${x})`);break;case"notifications/resources/updated":case"notifications/resources/list_changed":if(!this._capabilities.resources)throw Error(`Server does not support notifying about resources (required for ${x})`);break;case"notifications/tools/list_changed":if(!this._capabilities.tools)throw Error(`Server does not support notifying of tool list changes (required for ${x})`);break;case"notifications/prompts/list_changed":if(!this._capabilities.prompts)throw Error(`Server does not support notifying of prompt list changes (required for ${x})`);break;case"notifications/elicitation/complete":if(!this._clientCapabilities?.elicitation?.url)throw Error(`Client does not support URL elicitation (required for ${x})`);break;case"notifications/cancelled":break;case"notifications/progress":break}}assertRequestHandlerCapability(x){if(!this._capabilities)return;switch(x){case"completion/complete":if(!this._capabilities.completions)throw Error(`Server does not support completions (required for ${x})`);break;case"logging/setLevel":if(!this._capabilities.logging)throw Error(`Server does not support logging (required for ${x})`);break;case"prompts/get":case"prompts/list":if(!this._capabilities.prompts)throw Error(`Server does not support prompts (required for ${x})`);break;case"resources/list":case"resources/templates/list":case"resources/read":if(!this._capabilities.resources)throw Error(`Server does not support resources (required for ${x})`);break;case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Server does not support tools (required for ${x})`);break;case"tasks/get":case"tasks/list":case"tasks/result":case"tasks/cancel":if(!this._capabilities.tasks)throw Error(`Server does not support tasks capability (required for ${x})`);break;case"ping":case"initialize":break}}assertTaskCapability(x){Uq(this._clientCapabilities?.tasks?.requests,x,"Client")}assertTaskHandlerCapability(x){if(!this._capabilities)return;Fq(this._capabilities.tasks?.requests,x,"Server")}async _oninitialize(x){let Q=x.params.protocolVersion;return this._clientCapabilities=x.params.capabilities,this._clientVersion=x.params.clientInfo,{protocolVersion:kv.includes(Q)?Q:dW,capabilities:this.getCapabilities(),serverInfo:this._serverInfo,...this._instructions&&{instructions:this._instructions}}}getClientCapabilities(){return this._clientCapabilities}getClientVersion(){return this._clientVersion}getCapabilities(){return this._capabilities}async ping(){return this.request({method:"ping"},w7)}async createMessage(x,Q){if(x.tools||x.toolChoice){if(!this._clientCapabilities?.sampling?.tools)throw Error("Client does not support sampling tools capability.")}if(x.messages.length>0){let W=x.messages[x.messages.length-1],Y=Array.isArray(W.content)?W.content:[W.content],X=Y.some((b)=>b.type==="tool_result"),J=x.messages.length>1?x.messages[x.messages.length-2]:void 0,G=J?Array.isArray(J.content)?J.content:[J.content]:[],$=G.some((b)=>b.type==="tool_use");if(X){if(Y.some((b)=>b.type!=="tool_result"))throw Error("The last message must contain only tool_result content if any is present");if(!$)throw Error("tool_result blocks are not matching any tool_use from the previous message")}if($){let b=new Set(G.filter((v)=>v.type==="tool_use").map((v)=>v.id)),z=new Set(Y.filter((v)=>v.type==="tool_result").map((v)=>v.toolUseId));if(b.size!==z.size||![...b].every((v)=>z.has(v)))throw Error("ids of tool_result blocks and tool_use blocks from previous message do not match")}}if(x.tools)return this.request({method:"sampling/createMessage",params:x},zY,Q);return this.request({method:"sampling/createMessage",params:x},i6,Q)}async elicitInput(x,Q){switch(x.mode??"form"){case"url":{if(!this._clientCapabilities?.elicitation?.url)throw Error("Client does not support url elicitation.");let Y=x;return this.request({method:"elicitation/create",params:Y},w8,Q)}case"form":{if(!this._clientCapabilities?.elicitation?.form)throw Error("Client does not support form elicitation.");let Y=x.mode==="form"?x:{...x,mode:"form"},X=await this.request({method:"elicitation/create",params:Y},w8,Q);if(X.action==="accept"&&X.content&&Y.requestedSchema)try{let G=this._jsonSchemaValidator.getValidator(Y.requestedSchema)(X.content);if(!G.valid)throw new k(l.InvalidParams,`Elicitation response content does not match requested schema: ${G.errorMessage}`)}catch(J){if(J instanceof k)throw J;throw new k(l.InternalError,`Error validating elicitation response: ${J instanceof Error?J.message:String(J)}`)}return X}}}createElicitationCompletionNotifier(x,Q){if(!this._clientCapabilities?.elicitation?.url)throw Error("Client does not support URL elicitation (required for notifications/elicitation/complete)");return()=>this.notification({method:"notifications/elicitation/complete",params:{elicitationId:x}},Q)}async listRoots(x,Q){return this.request({method:"roots/list",params:x},vY,Q)}async sendLoggingMessage(x,Q){if(this._capabilities.logging){if(!this.isMessageIgnored(x.level,Q))return this.notification({method:"notifications/message",params:x})}}async sendResourceUpdated(x){return this.notification({method:"notifications/resources/updated",params:x})}async sendResourceListChanged(){return this.notification({method:"notifications/resources/list_changed"})}async sendToolListChanged(){return this.notification({method:"notifications/tools/list_changed"})}async sendPromptListChanged(){return this.notification({method:"notifications/prompts/list_changed"})}}var cq=Symbol.for("mcp.completable");function IX(x){return!!x&&typeof x==="object"&&cq in x}function Mq(x){return x[cq]?.complete}var Lq;(function(x){x.Completable="McpCompletable"})(Lq||(Lq={}));var eC=/^[A-Za-z0-9._-]{1,128}$/;function xT(x){let Q=[];if(x.length===0)return{isValid:!1,warnings:["Tool name cannot be empty"]};if(x.length>128)return{isValid:!1,warnings:[`Tool name exceeds maximum length of 128 characters (current: ${x.length})`]};if(x.includes(" "))Q.push("Tool name contains spaces, which may cause parsing issues");if(x.includes(","))Q.push("Tool name contains commas, which may cause parsing issues");if(x.startsWith("-")||x.endsWith("-"))Q.push("Tool name starts or ends with a dash, which may cause parsing issues in some contexts");if(x.startsWith(".")||x.endsWith("."))Q.push("Tool name starts or ends with a dot, which may cause parsing issues in some contexts");if(!eC.test(x)){let W=x.split("").filter((Y)=>!/[A-Za-z0-9._-]/.test(Y)).filter((Y,X,J)=>J.indexOf(Y)===X);return Q.push(`Tool name contains invalid characters: ${W.map((Y)=>`"${Y}"`).join(", ")}`,"Allowed characters are: A-Z, a-z, 0-9, underscore (_), dash (-), and dot (.)"),{isValid:!1,warnings:Q}}return{isValid:!0,warnings:Q}}function QT(x,Q){if(Q.length>0){console.warn(`Tool name validation warning for "${x}":`);for(let W of Q)console.warn(` - ${W}`);console.warn("Tool registration will proceed, but this may cause compatibility issues."),console.warn("Consider updating the tool name to conform to the MCP tool naming standard."),console.warn("See SEP: Specify Format for Tool Names (https://github.com/modelcontextprotocol/modelcontextprotocol/issues/986) for more details.")}}function SX(x){let Q=xT(x);return QT(x,Q.warnings),Q.isValid}class EX{constructor(x){this._mcpServer=x}registerToolTask(x,Q,W){let Y={taskSupport:"required",...Q.execution};if(Y.taskSupport==="forbidden")throw Error(`Cannot register task-based tool '${x}' with taskSupport 'forbidden'. Use registerTool() instead.`);return this._mcpServer._createRegisteredTool(x,Q.title,Q.description,Q.inputSchema,Q.outputSchema,Q.annotations,Y,Q._meta,W)}}class PX{constructor(x,Q){this._registeredResources={},this._registeredResourceTemplates={},this._registeredTools={},this._registeredPrompts={},this._toolHandlersInitialized=!1,this._completionHandlerInitialized=!1,this._resourceHandlersInitialized=!1,this._promptHandlersInitialized=!1,this.server=new AX(x,Q)}get experimental(){if(!this._experimental)this._experimental={tasks:new EX(this)};return this._experimental}async connect(x){return await this.server.connect(x)}async close(){await this.server.close()}setToolRequestHandlers(){if(this._toolHandlersInitialized)return;this.server.assertCanSetRequestHandler(g4(I7)),this.server.assertCanSetRequestHandler(g4(B8)),this.server.registerCapabilities({tools:{listChanged:!0}}),this.server.setRequestHandler(I7,()=>({tools:Object.entries(this._registeredTools).filter(([,x])=>x.enabled).map(([x,Q])=>{let W={name:x,title:Q.title,description:Q.description,inputSchema:(()=>{let Y=$8(Q.inputSchema);return Y?qY(Y,{strictUnions:!0,pipeStrategy:"input"}):WT})(),annotations:Q.annotations,execution:Q.execution,_meta:Q._meta};if(Q.outputSchema){let Y=$8(Q.outputSchema);if(Y)W.outputSchema=qY(Y,{strictUnions:!0,pipeStrategy:"output"})}return W})})),this.server.setRequestHandler(B8,async(x,Q)=>{try{let W=this._registeredTools[x.params.name];if(!W)throw new k(l.InvalidParams,`Tool ${x.params.name} not found`);if(!W.enabled)throw new k(l.InvalidParams,`Tool ${x.params.name} disabled`);let Y=!!x.params.task,X=W.execution?.taskSupport,J="createTask"in W.handler;if((X==="required"||X==="optional")&&!J)throw new k(l.InternalError,`Tool ${x.params.name} has taskSupport '${X}' but was not registered with registerToolTask`);if(X==="required"&&!Y)throw new k(l.MethodNotFound,`Tool ${x.params.name} requires task augmentation (taskSupport: 'required')`);if(X==="optional"&&!Y&&J)return await this.handleAutomaticTaskPolling(W,x,Q);let G=await this.validateToolInput(W,x.params.arguments,x.params.name),$=await this.executeToolHandler(W,G,Q);if(Y)return $;return await this.validateToolOutput(W,$,x.params.name),$}catch(W){if(W instanceof k){if(W.code===l.UrlElicitationRequired)throw W}return this.createToolError(W instanceof Error?W.message:String(W))}}),this._toolHandlersInitialized=!0}createToolError(x){return{content:[{type:"text",text:x}],isError:!0}}async validateToolInput(x,Q,W){if(!x.inputSchema)return;let X=$8(x.inputSchema)??x.inputSchema,J=await X7(X,Q);if(!J.success){let G="error"in J?J.error:"Unknown error",$=J7(G);throw new k(l.InvalidParams,`Input validation error: Invalid arguments for tool ${W}: ${$}`)}return J.data}async validateToolOutput(x,Q,W){if(!x.outputSchema)return;if(!("content"in Q))return;if(Q.isError)return;if(!Q.structuredContent)throw new k(l.InvalidParams,`Output validation error: Tool ${W} has an output schema but no structured content was provided`);let Y=$8(x.outputSchema),X=await X7(Y,Q.structuredContent);if(!X.success){let J="error"in X?X.error:"Unknown error",G=J7(J);throw new k(l.InvalidParams,`Output validation error: Invalid structured content for tool ${W}: ${G}`)}}async executeToolHandler(x,Q,W){let Y=x.handler;if("createTask"in Y){if(!W.taskStore)throw Error("No task store provided.");let J={...W,taskStore:W.taskStore};if(x.inputSchema)return await Promise.resolve(Y.createTask(Q,J));else return await Promise.resolve(Y.createTask(J))}if(x.inputSchema)return await Promise.resolve(Y(Q,W));else return await Promise.resolve(Y(W))}async handleAutomaticTaskPolling(x,Q,W){if(!W.taskStore)throw Error("No task store provided for task-capable tool.");let Y=await this.validateToolInput(x,Q.params.arguments,Q.params.name),X=x.handler,J={...W,taskStore:W.taskStore},G=Y?await Promise.resolve(X.createTask(Y,J)):await Promise.resolve(X.createTask(J)),$=G.task.taskId,b=G.task,z=b.pollInterval??5000;while(b.status!=="completed"&&b.status!=="failed"&&b.status!=="cancelled"){await new Promise((B)=>setTimeout(B,z));let v=await W.taskStore.getTask($);if(!v)throw new k(l.InternalError,`Task ${$} not found during polling`);b=v}return await W.taskStore.getTaskResult($)}setCompletionRequestHandler(){if(this._completionHandlerInitialized)return;this.server.assertCanSetRequestHandler(g4(E7)),this.server.registerCapabilities({completions:{}}),this.server.setRequestHandler(E7,async(x)=>{switch(x.params.ref.type){case"ref/prompt":return tv(x),this.handlePromptCompletion(x,x.params.ref);case"ref/resource":return ev(x),this.handleResourceCompletion(x,x.params.ref);default:throw new k(l.InvalidParams,`Invalid completion reference: ${x.params.ref}`)}}),this._completionHandlerInitialized=!0}async handlePromptCompletion(x,Q){let W=this._registeredPrompts[Q.name];if(!W)throw new k(l.InvalidParams,`Prompt ${Q.name} not found`);if(!W.enabled)throw new k(l.InvalidParams,`Prompt ${Q.name} disabled`);if(!W.argsSchema)return Y9;let X=C4(W.argsSchema)?.[x.params.argument.name];if(!IX(X))return Y9;let J=Mq(X);if(!J)return Y9;let G=await J(x.params.argument.value,x.params.context);return jq(G)}async handleResourceCompletion(x,Q){let W=Object.values(this._registeredResourceTemplates).find((J)=>J.resourceTemplate.uriTemplate.toString()===Q.uri);if(!W){if(this._registeredResources[Q.uri])return Y9;throw new k(l.InvalidParams,`Resource template ${x.params.ref.uri} not found`)}let Y=W.resourceTemplate.completeCallback(x.params.argument.name);if(!Y)return Y9;let X=await Y(x.params.argument.value,x.params.context);return jq(X)}setResourceRequestHandlers(){if(this._resourceHandlersInitialized)return;this.server.assertCanSetRequestHandler(g4(M7)),this.server.assertCanSetRequestHandler(g4(D7)),this.server.assertCanSetRequestHandler(g4(j7)),this.server.registerCapabilities({resources:{listChanged:!0}}),this.server.setRequestHandler(M7,async(x,Q)=>{let W=Object.entries(this._registeredResources).filter(([X,J])=>J.enabled).map(([X,J])=>({uri:X,name:J.name,...J.metadata})),Y=[];for(let X of Object.values(this._registeredResourceTemplates)){if(!X.resourceTemplate.listCallback)continue;let J=await X.resourceTemplate.listCallback(Q);for(let G of J.resources)Y.push({...X.metadata,...G})}return{resources:[...W,...Y]}}),this.server.setRequestHandler(D7,async()=>{return{resourceTemplates:Object.entries(this._registeredResourceTemplates).map(([Q,W])=>({name:Q,uriTemplate:W.resourceTemplate.uriTemplate.toString(),...W.metadata}))}}),this.server.setRequestHandler(j7,async(x,Q)=>{let W=new URL(x.params.uri),Y=this._registeredResources[W.toString()];if(Y){if(!Y.enabled)throw new k(l.InvalidParams,`Resource ${W} disabled`);return Y.readCallback(W,Q)}for(let X of Object.values(this._registeredResourceTemplates)){let J=X.resourceTemplate.uriTemplate.match(W.toString());if(J)return X.readCallback(W,J,Q)}throw new k(l.InvalidParams,`Resource ${W} not found`)}),this._resourceHandlersInitialized=!0}setPromptRequestHandlers(){if(this._promptHandlersInitialized)return;this.server.assertCanSetRequestHandler(g4(O7)),this.server.assertCanSetRequestHandler(g4(A7)),this.server.registerCapabilities({prompts:{listChanged:!0}}),this.server.setRequestHandler(O7,()=>({prompts:Object.entries(this._registeredPrompts).filter(([,x])=>x.enabled).map(([x,Q])=>{return{name:x,title:Q.title,description:Q.description,arguments:Q.argsSchema?YT(Q.argsSchema):void 0}})})),this.server.setRequestHandler(A7,async(x,Q)=>{let W=this._registeredPrompts[x.params.name];if(!W)throw new k(l.InvalidParams,`Prompt ${x.params.name} not found`);if(!W.enabled)throw new k(l.InvalidParams,`Prompt ${x.params.name} disabled`);if(W.argsSchema){let Y=$8(W.argsSchema),X=await X7(Y,x.params.arguments);if(!X.success){let $="error"in X?X.error:"Unknown error",b=J7($);throw new k(l.InvalidParams,`Invalid arguments for prompt ${x.params.name}: ${b}`)}let J=X.data,G=W.callback;return await Promise.resolve(G(J,Q))}else{let Y=W.callback;return await Promise.resolve(Y(Q))}}),this._promptHandlersInitialized=!0}resource(x,Q,...W){let Y;if(typeof W[0]==="object")Y=W.shift();let X=W[0];if(typeof Q==="string"){if(this._registeredResources[Q])throw Error(`Resource ${Q} is already registered`);let J=this._createRegisteredResource(x,void 0,Q,Y,X);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),J}else{if(this._registeredResourceTemplates[x])throw Error(`Resource template ${x} is already registered`);let J=this._createRegisteredResourceTemplate(x,void 0,Q,Y,X);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),J}}registerResource(x,Q,W,Y){if(typeof Q==="string"){if(this._registeredResources[Q])throw Error(`Resource ${Q} is already registered`);let X=this._createRegisteredResource(x,W.title,Q,W,Y);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),X}else{if(this._registeredResourceTemplates[x])throw Error(`Resource template ${x} is already registered`);let X=this._createRegisteredResourceTemplate(x,W.title,Q,W,Y);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),X}}_createRegisteredResource(x,Q,W,Y,X){let J={name:x,title:Q,metadata:Y,readCallback:X,enabled:!0,disable:()=>J.update({enabled:!1}),enable:()=>J.update({enabled:!0}),remove:()=>J.update({uri:null}),update:(G)=>{if(typeof G.uri<"u"&&G.uri!==W){if(delete this._registeredResources[W],G.uri)this._registeredResources[G.uri]=J}if(typeof G.name<"u")J.name=G.name;if(typeof G.title<"u")J.title=G.title;if(typeof G.metadata<"u")J.metadata=G.metadata;if(typeof G.callback<"u")J.readCallback=G.callback;if(typeof G.enabled<"u")J.enabled=G.enabled;this.sendResourceListChanged()}};return this._registeredResources[W]=J,J}_createRegisteredResourceTemplate(x,Q,W,Y,X){let J={resourceTemplate:W,title:Q,metadata:Y,readCallback:X,enabled:!0,disable:()=>J.update({enabled:!1}),enable:()=>J.update({enabled:!0}),remove:()=>J.update({name:null}),update:(b)=>{if(typeof b.name<"u"&&b.name!==x){if(delete this._registeredResourceTemplates[x],b.name)this._registeredResourceTemplates[b.name]=J}if(typeof b.title<"u")J.title=b.title;if(typeof b.template<"u")J.resourceTemplate=b.template;if(typeof b.metadata<"u")J.metadata=b.metadata;if(typeof b.callback<"u")J.readCallback=b.callback;if(typeof b.enabled<"u")J.enabled=b.enabled;this.sendResourceListChanged()}};this._registeredResourceTemplates[x]=J;let G=W.uriTemplate.variableNames;if(Array.isArray(G)&&G.some((b)=>!!W.completeCallback(b)))this.setCompletionRequestHandler();return J}_createRegisteredPrompt(x,Q,W,Y,X){let J={title:Q,description:W,argsSchema:Y===void 0?void 0:Nx(Y),callback:X,enabled:!0,disable:()=>J.update({enabled:!1}),enable:()=>J.update({enabled:!0}),remove:()=>J.update({name:null}),update:(G)=>{if(typeof G.name<"u"&&G.name!==x){if(delete this._registeredPrompts[x],G.name)this._registeredPrompts[G.name]=J}if(typeof G.title<"u")J.title=G.title;if(typeof G.description<"u")J.description=G.description;if(typeof G.argsSchema<"u")J.argsSchema=Nx(G.argsSchema);if(typeof G.callback<"u")J.callback=G.callback;if(typeof G.enabled<"u")J.enabled=G.enabled;this.sendPromptListChanged()}};if(this._registeredPrompts[x]=J,Y){if(Object.values(Y).some(($)=>{let b=$ instanceof M1?$._def?.innerType:$;return IX(b)}))this.setCompletionRequestHandler()}return J}_createRegisteredTool(x,Q,W,Y,X,J,G,$,b){SX(x);let z={title:Q,description:W,inputSchema:Dq(Y),outputSchema:Dq(X),annotations:J,execution:G,_meta:$,handler:b,enabled:!0,disable:()=>z.update({enabled:!1}),enable:()=>z.update({enabled:!0}),remove:()=>z.update({name:null}),update:(v)=>{if(typeof v.name<"u"&&v.name!==x){if(typeof v.name==="string")SX(v.name);if(delete this._registeredTools[x],v.name)this._registeredTools[v.name]=z}if(typeof v.title<"u")z.title=v.title;if(typeof v.description<"u")z.description=v.description;if(typeof v.paramsSchema<"u")z.inputSchema=Nx(v.paramsSchema);if(typeof v.outputSchema<"u")z.outputSchema=Nx(v.outputSchema);if(typeof v.callback<"u")z.handler=v.callback;if(typeof v.annotations<"u")z.annotations=v.annotations;if(typeof v._meta<"u")z._meta=v._meta;if(typeof v.enabled<"u")z.enabled=v.enabled;this.sendToolListChanged()}};return this._registeredTools[x]=z,this.setToolRequestHandlers(),this.sendToolListChanged(),z}tool(x,...Q){if(this._registeredTools[x])throw Error(`Tool ${x} is already registered`);let W,Y,X,J;if(typeof Q[0]==="string")W=Q.shift();if(Q.length>1){let $=Q[0];if(RX($)){if(Y=Q.shift(),Q.length>1&&typeof Q[0]==="object"&&Q[0]!==null&&!RX(Q[0]))J=Q.shift()}else if(typeof $==="object"&&$!==null){if(Object.values($).some((b)=>typeof b==="object"&&b!==null))throw Error(`Tool ${x} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);J=Q.shift()}}let G=Q[0];return this._createRegisteredTool(x,void 0,W,Y,X,J,{taskSupport:"forbidden"},void 0,G)}registerTool(x,Q,W){if(this._registeredTools[x])throw Error(`Tool ${x} is already registered`);let{title:Y,description:X,inputSchema:J,outputSchema:G,annotations:$,_meta:b}=Q;return this._createRegisteredTool(x,Y,X,J,G,$,{taskSupport:"forbidden"},b,W)}prompt(x,...Q){if(this._registeredPrompts[x])throw Error(`Prompt ${x} is already registered`);let W;if(typeof Q[0]==="string")W=Q.shift();let Y;if(Q.length>1)Y=Q.shift();let X=Q[0],J=this._createRegisteredPrompt(x,void 0,W,Y,X);return this.setPromptRequestHandlers(),this.sendPromptListChanged(),J}registerPrompt(x,Q,W){if(this._registeredPrompts[x])throw Error(`Prompt ${x} is already registered`);let{title:Y,description:X,argsSchema:J}=Q,G=this._createRegisteredPrompt(x,Y,X,J,W);return this.setPromptRequestHandlers(),this.sendPromptListChanged(),G}isConnected(){return this.server.transport!==void 0}async sendLoggingMessage(x,Q){return this.server.sendLoggingMessage(x,Q)}sendResourceListChanged(){if(this.isConnected())this.server.sendResourceListChanged()}sendToolListChanged(){if(this.isConnected())this.server.sendToolListChanged()}sendPromptListChanged(){if(this.isConnected())this.server.sendPromptListChanged()}}var WT={type:"object",properties:{}};function Oq(x){return x!==null&&typeof x==="object"&&"parse"in x&&typeof x.parse==="function"&&"safeParse"in x&&typeof x.safeParse==="function"}function Aq(x){return"_def"in x||"_zod"in x||Oq(x)}function RX(x){if(typeof x!=="object"||x===null)return!1;if(Aq(x))return!1;if(Object.keys(x).length===0)return!0;return Object.values(x).some(Oq)}function Dq(x){if(!x)return;if(RX(x))return Nx(x);if(!Aq(x))throw Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");return x}function YT(x){let Q=C4(x);if(!Q)return[];return Object.entries(Q).map(([W,Y])=>{let X=wv(Y),J=Kv(Y);return{name:W,description:X,required:!J}})}function g4(x){let W=C4(x)?.method;if(!W)throw Error("Schema is missing a method literal");let Y=G7(W);if(typeof Y==="string")return Y;throw Error("Schema method literal must be a string")}function jq(x){return{completion:{values:x.slice(0,100),total:x.length,hasMore:x.length>100}}}var Y9={completion:{values:[],hasMore:!1}};import Sq from"node:process";class iX{append(x){this._buffer=this._buffer?Buffer.concat([this._buffer,x]):x}readMessage(){if(!this._buffer)return null;let x=this._buffer.indexOf(`
|
|
67
67
|
`);if(x===-1)return null;let Q=this._buffer.toString("utf8",0,x).replace(/\r$/,"");return this._buffer=this._buffer.subarray(x+1),XT(Q)}clear(){this._buffer=void 0}}function XT(x){return gv.parse(JSON.parse(x))}function Iq(x){return JSON.stringify(x)+`
|
|
68
|
-
`}class ZX{constructor(x=Sq.stdin,Q=Sq.stdout){this._stdin=x,this._stdout=Q,this._readBuffer=new iX,this._started=!1,this._ondata=(W)=>{this._readBuffer.append(W),this.processReadBuffer()},this._onerror=(W)=>{this.onerror?.(W)}}async start(){if(this._started)throw Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){while(!0)try{let x=this._readBuffer.readMessage();if(x===null)break;this.onmessage?.(x)}catch(x){this.onerror?.(x)}}async close(){if(this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror),this._stdin.listenerCount("data")===0)this._stdin.pause();this._readBuffer.clear(),this.onclose?.()}send(x){return new Promise((Q)=>{let W=Iq(x);if(this._stdout.write(W))Q();else this._stdout.once("drain",Q)})}}function Eq(x,Q,W){return x.toUpperCase()!=="GET"&&Q==="global"&&!W}var $T="https://api.figura.so",bT="mcp",Rq=TX(Tq(),".figura","config.json"),zT=TX(".figura","config.json");async function Pq(x){try{let Q=JSON.parse(await X9.readFile(x,"utf8"));if(Q&&typeof Q==="object")return Q}catch{}return{}}async function vT(x=process.cwd()){let Q=Tq(),W=x;for(;;){if(W!==Q){let X=TX(W,zT);try{return await X9.access(X),X}catch{}}let Y=GT(W);if(Y===W)return null;W=Y}}async function kq(){let x=await vT(),Q=x?await Pq(x):{},W=await Pq(Rq),Y={...W,...Q},X=process.env.FIGURA_TOKEN||Y.token||void 0,J=process.env.FIGURA_TOKEN?"env":Q.token?"project":W.token?"global":"none",G=(process.env.FIGURA_API_URL||Y.apiUrl||$T).replace(/\/+$/,"");return{token:X,apiUrl:G,team:Y.team,source:J,configPath:J==="project"?x:J==="global"?Rq:null}}async function u0(x,Q={}){let W=await kq(),Y=W.token;if(!Y)throw Error("No Figura token. Run `figura link` in this repo (or `figura login`, or set FIGURA_TOKEN).");let X=(Q.method??"GET").toUpperCase();if(Eq(X,W.source,process.env.FIGURA_ALLOW_GLOBAL==="1"))throw Error(`this project isn't linked to a Figura team — the global credential${W.team?` (team '${W.team}')`:""} would receive this write. Ask the user to run \`figura link\` in this repo (or set FIGURA_ALLOW_GLOBAL=1 to use the global team deliberately).`);let J=`${W.apiUrl}${x.startsWith("/")?"":"/"}${x}`,G={Accept:"application/json",Authorization:`Bearer ${Y}`,"X-Figura-Source":bT},$;if(Q.body!==void 0)G["Content-Type"]="application/json",$=JSON.stringify(Q.body);let b=await k9.request({url:J,method:Q.method??"GET",headers:G,data:$,validateStatus:()=>!0,transformRequest:[(B)=>B],transformResponse:[(B)=>B]}),z=typeof b.data==="string"?b.data:"",v=void 0;if(z)try{v=JSON.parse(z)}catch{v=z}if(b.status<200||b.status>=300){let B=v&&typeof v==="object"&&v!==null&&"error"in v?String(v.error):typeof v==="string"&&v?v:b.statusText||`HTTP ${b.status}`;throw Error(`${B} (HTTP ${b.status})`)}return v}var iq=2500,BT=600000,wT=60000,Zq=(x)=>new Promise((Q)=>setTimeout(Q,x));async function KT(x){let Q=Date.now(),W=null;for(;;){let Y;try{Y=await u0(`/v1/jobs/${encodeURIComponent(x)}`),W=null}catch(X){if(W??=Date.now(),Date.now()-W>=wT){let J=X instanceof Error?X.message:String(X);throw Error(`Lost contact with the Figura API while waiting for job ${x} (${J}). `+`The job may still complete — resume by checking GET /v1/jobs/${x} instead of regenerating.`)}await Zq(iq);continue}if(Y.status==="done")return Y;if(Y.status==="error")throw Error(Y.error||"The generation job failed. Please retry.");if(Date.now()-Q>BT)throw Error(`Timed out waiting for job ${x} — it may still complete; `+`check GET /v1/jobs/${x} instead of regenerating.`);await Zq(iq)}}function g0(x){return{content:[{type:"text",text:JSON.stringify(x,null,2)}]}}function X1(x){return{content:[{type:"text",text:x instanceof Error?x.message:String(x)}],isError:!0}}var q3=()=>i.string().optional().describe("Product area / surface the fig belongs to, e.g. 'dashboard', 'settings', 'onboarding'. Free text."),kX=["portrait","landscape","both"],_X=["single","compare","variations"],HT=(()=>{try{let x=JSON.parse(JT(new URL("../package.json",import.meta.url),"utf8"));return typeof x.version==="string"?x.version:"0.0.0"}catch{return"0.0.0"}})(),t0=new PX({name:"figura",version:HT});t0.registerTool("figura_whoami",{title:"Figura: whoami",description:"Return the identity (team, actor, scopes) for the configured Figura token, plus `binding` — which config tier supplied it (env | project | global). A `global` binding means this project is not linked to a team and writes will be refused; ask the user to run `figura link` in the repo.",inputSchema:{}},async()=>{try{let x=await u0("/v1/whoami"),{source:Q,configPath:W}=await kq();return g0({...x,binding:{source:Q,configPath:W}})}catch(x){return X1(x)}});t0.registerTool("figura_set_brand",{title:"Figura: set brand profile",description:'Set (upsert) the team brand profile that the fig generator renders against. Use this to "introduce a repo to Figura": scan the codebase for its design system, synthesize the brand, then push it here. `name` is required; `appDescription` is a one-clause product summary; `tokensBlock` is the freeform design-token reference (colors + hex + role, type scale + faces, spacing, the brand gradient, plus voice/conventions); `surfaces` is the product\'s own key areas/sections (e.g. "Feed", "Settings", "Checkout"), NOT another product\'s — drives the Studio Area picker. This is a partial merge — fields you omit keep their saved value (pass an explicit empty string / empty array to clear one), so a name-only update will not wipe existing tokens or surfaces. Editing the team brand — confirm with the user before calling.',inputSchema:{name:i.string().describe("Brand / app name (required)."),appDescription:i.string().optional().describe("One clause on what the product is + who it is for."),tokensBlock:i.string().optional().describe("Freeform plain-text design-token reference: colors (hex + role), type scale + faces, spacing, the brand gradient, and voice/conventions. Injected verbatim into the fig-generation prompt."),surfaces:i.array(i.string()).optional().describe(`The product's own key areas/sections (e.g. ["Feed", "Settings", "Checkout"]) — read from the repo's real navigation/routes, never invented or copied from another product. Drives the Studio Area picker; capped at 24 areas / 40 characters each. Pass [] to clear.`)}},async(x)=>{try{let Q={name:x.name};if(x.appDescription!==void 0)Q.appDescription=x.appDescription;if(x.tokensBlock!==void 0)Q.tokensBlock=x.tokensBlock;if(x.surfaces!==void 0)Q.surfaces=x.surfaces;return g0(await u0("/brand",{method:"PUT",body:Q}))}catch(Q){return X1(Q)}});var VT={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",avif:"image/avif",svg:"image/svg+xml"},CX=5242880;function Cq(x){let Q=x.split(/[?#]/)[0].split(".").pop()?.toLowerCase()??"";return VT[Q]}async function _q(x,Q){let W,Y=Q;if(/^https?:\/\//i.test(x)){let X=await k9.get(x,{responseType:"arraybuffer",maxContentLength:CX,timeout:30000});if(W=Buffer.from(X.data).toString("base64"),!Y){let J=String(X.headers["content-type"]??"").split(";")[0].trim().toLowerCase();Y=J.startsWith("image/")?J:Cq(x)}}else{let{size:X}=await X9.stat(x);if(X>CX)throw Error(`'${x}' is ${(X/1048576).toFixed(1)}MB — over the ${CX/1048576}MB asset limit.`);if(W=(await X9.readFile(x)).toString("base64"),!Y)Y=Cq(x)}if(!Y)throw Error(`Could not determine an image content-type for '${x}' — pass contentType (e.g. image/png).`);return{data:W,contentType:Y}}async function qT(x,Q){let{data:W,contentType:Y}=await _q(x,Q),X=await u0("/v1/assets",{method:"POST",body:{data:W,contentType:Y}});if(!X||typeof X.url!=="string")throw Error("Asset upload did not return a URL.");return X.url}var uq=()=>i.array(i.object({source:i.string().describe("The image — a local file path or an http(s) URL (e.g. a screenshot)."),match:i.string().optional().describe('Exact string in the HTML to replace with the hosted URL (defaults to `source` — i.e. write <img src="<source>"> in the HTML).')})).optional().describe("Images to attach: each is uploaded to durable figura hosting (stable non-expiring URL, allowed by the fig CSP) and substituted into the HTML in place of `match`/`source`. Use for real screenshots/photos/logos instead of base64 data-URIs (HTML size cap) or external URLs (expire/CSP-blocked).");async function lq(x,Q){if(!Q?.length)return{html:x,uploaded:[]};let W=x,Y=[];for(let X of Q){let J=X.match??X.source;if(!W.includes(J))throw Error(`images: '${J}' does not occur in the html — reference it (e.g. <img src="${J}">) so it can be swapped for the hosted URL, or set 'match'.`);let G=await qT(X.source);W=W.split(J).join(G),Y.push({source:X.source,url:G})}return{html:W,uploaded:Y}}function pq(x,Q){if(!Q.length)return x;if(x&&typeof x==="object"&&!Array.isArray(x))return{...x,assets:Q};return{result:x,assets:Q}}t0.registerTool("figura_create_fig",{title:"Figura: create fig",description:'Create a new visualization from an HTML document. Returns the created fig (id, slug); with publish:true it is published and the result includes a shareable public url. The response also carries `brandWarnings` — a deterministic (no-LLM) colors/fonts/radii/spacing diff against the team\'s brand tokens, WARN-only (never blocks the create). If it comes back non-empty, read it and consider a figura_revise_fig call that binds the missing tokens before telling the user you\'re done. To include REAL images (screenshots/photos/logos), pass them via `images` — they are uploaded to durable figura hosting and their stable URLs substituted into the HTML. TIP — injected components: if your HTML shows phone frames (a compare/multi-screen mock), wrap each state in <div class="fig-device portrait|landscape"><div class="fig-screen">…</div><div class="fig-di"></div><div class="fig-homebar"></div></div> and do NOT style those classes — Figura injects accurate iPhone frame CSS at save. Same for an iOS status bar (<div class="fig-statusbar" data-time="9:41"></div>), a compare control bar (.fig-controls/.fig-seg), and the .frame-web browser window.',inputSchema:{html:i.string().describe("The full HTML document for the visualization."),images:uq(),title:i.string().optional().describe("Title (otherwise derived from the HTML)."),description:i.string().optional(),area:q3(),tags:i.array(i.string()).optional(),orientation:i.enum(kX).optional(),mode:i.enum(_X).optional(),model:i.string().optional().describe("Model attribution."),device:i.string().optional().describe("Catalog iPhone id the fig targets (e.g. 'iphone-16', 'iphone-17-pro', 'iphone-se-3') — keys the injected frame/safe-area geometry."),issue:i.number().optional().describe("Linked issue number."),publish:i.boolean().optional().describe("Publish to a shareable public URL on create (requires a paid plan)."),prompt:i.string().optional().describe("OPTIONAL: the brief that produced this HTML, if there was one — you wrote the HTML yourself, so Figura never sees a prompt unless you pass it here. Stored on the fig (DB-only, never stamped into the HTML), and returned by figura_get_fig — lets teammates see 'why' this fig exists.")}},async(x)=>{try{let{html:Q,uploaded:W}=await lq(x.html,x.images),Y={html:Q};for(let X of["title","description","area","tags","orientation","mode","model","device","issue","publish","prompt"])if(x[X]!==void 0)Y[X]=x[X];return g0(pq(await u0("/v1/figs",{method:"POST",body:Y}),W))}catch(Q){return X1(Q)}});t0.registerTool("figura_upload_asset",{title:"Figura: upload asset",description:"Upload a durable image asset and get back a STABLE, figura-hosted URL that never expires — use it in a fig's <img src> instead of a base64 data-URI (bloats the HTML past the publish size) or an external URL that expires (Instagram/CDN signed links). The fig CSP allows figura-hosted images. Provide `path` (a local image file), `url` (an http(s) image to fetch and re-host), or `data` (base64 bytes) + `contentType`. Accepts png/jpeg/gif/webp/avif/svg, up to 5MB. Returns `{ url }`.",inputSchema:{path:i.string().optional().describe("Local path to an image file to upload (content-type inferred from the extension)."),url:i.string().optional().describe("http(s) URL of an image to fetch and re-host (alternative to `path`) — use for expiring/signed source URLs so the fig keeps rendering after they die."),data:i.string().optional().describe("Base64-encoded image bytes (alternative to `path`/`url`). Requires `contentType`."),contentType:i.string().optional().describe("MIME type, e.g. image/png. Required with `data`; inferred from the extension/response for `path`/`url`.")}},async(x)=>{try{let Q=x.path??x.url;if(Q){let{data:W,contentType:Y}=await _q(Q,x.contentType);return g0(await u0("/v1/assets",{method:"POST",body:{data:W,contentType:Y}}))}if(!x.data)throw Error("Provide `path` (a local image file), `url` (an http(s) image), or `data` (base64 bytes).");if(!x.contentType)throw Error("`contentType` is required with `data` — e.g. image/png.");return g0(await u0("/v1/assets",{method:"POST",body:{data:x.data,contentType:x.contentType}}))}catch(Q){return X1(Q)}});t0.registerTool("figura_generate_fig",{title:"Figura: generate fig",description:"Generate a new fig server-side from a natural-language brief, then persist it. The engine runs Figura's on-brand generation — you do NOT write the HTML; just describe the screen. SPENDS TEAM CREDITS: only use when the user explicitly opts for a server render; otherwise default to writing the HTML yourself and landing it with figura_create_fig (free, uses the calling model). Runs as an async server job; this call polls until the fig lands (typically 30–90s). Returns the created fig (id, slug); publish:true publishes it with a shareable url. Requires an active paid subscription.",inputSchema:{prompt:i.string().describe("A natural-language brief describing the screen/UI to generate."),area:q3(),orientation:i.enum(kX).optional(),mode:i.enum(_X).optional(),platform:i.enum(["mobile","web"]).optional().describe("Output platform: 'mobile' (iPhone, default) or 'web' (browser frame)."),device:i.string().optional().describe("Catalog iPhone id to target (e.g. 'iphone-16', 'iphone-17-pro', 'iphone-se-3'). Mobile only; default iphone-16."),title:i.string().optional().describe("Title (otherwise derived from the generated HTML)."),fidelity:i.enum(["fast","high"]).optional().describe("Generation fidelity (default fast)."),publish:i.boolean().optional().describe("Publish to a shareable public URL on create (requires a paid plan).")}},async(x)=>{try{let Q={prompt:x.prompt};for(let Y of["area","orientation","mode","platform","device","title","publish"])if(x[Y]!==void 0)Q[Y]=x[Y];if(x.fidelity!==void 0)Q.model=x.fidelity;Q.async=!0;let W=await u0("/v1/figs/generate",{method:"POST",body:Q});if(W&&typeof W==="object"&&typeof W.jobId==="string"){let Y=await KT(W.jobId),X=Y.result??{};return g0(X.fig??Y.result)}return g0(W)}catch(Q){return X1(Q)}});t0.registerTool("figura_revise_fig",{title:"Figura: revise fig",description:"Create a new revision (version) of an existing fig from updated HTML. Returns the new fig (id, slug, version); publish:true publishes it with a shareable url. The new version joins the original fig's chain — it does not replace it in place. Use list_revisions for the chain (rootFig + every version). The response also carries `brandWarnings` — a deterministic (no-LLM) colors/fonts/radii/spacing diff against the team's brand tokens, WARN-only. If it comes back non-empty, consider one more revision that binds the missing tokens. To include REAL images (screenshots/photos/logos), pass them via `images` — uploaded to durable figura hosting, stable URLs substituted into the HTML.",inputSchema:{id:i.string().describe("The fig to revise — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),html:i.string().describe("The full updated HTML document."),images:uq(),title:i.string().optional().describe("Title (otherwise derived from the HTML)."),description:i.string().optional(),area:i.string().optional().describe("Product area / surface; free text. Inherited from the parent if omitted."),tags:i.array(i.string()).optional(),orientation:i.enum(kX).optional().describe("Inherited from the parent if omitted."),mode:i.enum(_X).optional(),model:i.string().optional().describe("Model attribution."),issue:i.number().optional().describe("Linked issue number."),publish:i.boolean().optional().describe("Publish the new revision to a shareable public URL (requires a paid plan)."),prompt:i.string().optional().describe("OPTIONAL: the brief/ask that produced this revision, if there was one. Stored on the fig (DB-only, never stamped into the HTML), and returned by figura_get_fig.")}},async(x)=>{try{let{html:Q,uploaded:W}=await lq(x.html,x.images),Y={html:Q};for(let X of["title","description","area","tags","orientation","mode","model","issue","publish","prompt"])if(x[X]!==void 0)Y[X]=x[X];return g0(pq(await u0(`/v1/figs/${encodeURIComponent(x.id)}/revise`,{method:"POST",body:Y}),W))}catch(Q){return X1(Q)}});t0.registerTool("figura_list_revisions",{title:"Figura: list revisions",description:"List the full revision chain for a fig (all versions sharing a root), oldest to newest.",inputSchema:{id:i.string().describe("Any fig in the chain — its id (`fig_…`), a UUID, share link/URL, or publicId.")}},async(x)=>{try{return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}/revisions`))}catch(Q){return X1(Q)}});t0.registerTool("figura_publish_fig",{title:"Figura: publish fig",description:"Publish (or unpublish) an existing fig to its shareable public URL. Requires a paid plan. Returns { isPublic, url, publicId }.",inputSchema:{id:i.string().describe("The fig to publish — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),isPublic:i.boolean().optional().describe("Set false to unpublish (make private). Defaults to true (publish).")}},async(x)=>{try{let Q={};if(x.isPublic!==void 0)Q.isPublic=x.isPublic;return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}/publish`,{method:"POST",body:Q}))}catch(Q){return X1(Q)}});t0.registerTool("figura_list_figs",{title:"Figura: list figs",description:"List visualizations for the team, optionally filtered by area or tag.",inputSchema:{area:q3(),tag:i.string().optional(),limit:i.number().optional(),cursor:i.string().optional()}},async(x)=>{try{let Q=new URLSearchParams;if(x.area)Q.set("area",x.area);if(x.tag)Q.set("tag",x.tag);if(x.limit!==void 0)Q.set("limit",String(x.limit));if(x.cursor)Q.set("cursor",x.cursor);let W=Q.toString();return g0(await u0(`/v1/figs${W?`?${W}`:""}`))}catch(Q){return X1(Q)}});t0.registerTool("figura_get_fig",{title:"Figura: get fig",description:"Fetch a single visualization by id.",inputSchema:{id:i.string().describe("The fig — its id (`fig_…`), a UUID, a share link/URL, or its publicId.")}},async(x)=>{try{return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}`))}catch(Q){return X1(Q)}});t0.registerTool("figura_comment",{title:"Figura: comment",description:"Add a comment to a visualization.",inputSchema:{fig_id:i.string().describe("The fig to comment on — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),body:i.string().describe("Comment text.")}},async(x)=>{try{return g0(await u0(`/v1/figs/${encodeURIComponent(x.fig_id)}/comments`,{method:"POST",body:{body:x.body}}))}catch(Q){return X1(Q)}});t0.registerTool("figura_request_feedback",{title:"Figura: request feedback",description:"Ask a teammate to review a fig. Resolve `from` to a team member (by name or email); they get a notification (in-app, push, email) and the discussion happens in the fig's comment thread. Use this when the user says they want a specific person's feedback/eyes/review on a fig. Returns { ok, request: { to, status }, figUrl }.",inputSchema:{id:i.string().describe("The fig to request feedback on — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),from:i.string().describe("The teammate to ask, by name or email (resolved within the team)."),note:i.string().optional().describe("Optional message for them (e.g. what to look at).")}},async(x)=>{try{let Q={from:x.from};if(x.note!==void 0)Q.note=x.note;return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}/request-feedback`,{method:"POST",body:Q}))}catch(Q){return X1(Q)}});t0.registerTool("figura_list_sources",{title:"Figura: list sources",description:"List the company-brain source-spine connectors for the team, with event counts and last-synced times.",inputSchema:{}},async()=>{try{return g0(await u0("/v1/sources"))}catch(x){return X1(x)}});t0.registerTool("figura_sync_source",{title:"Figura: sync source",description:"Ingest new events from a connected source into the spine. Returns { ingested, fetched, nextCursor }.",inputSchema:{id:i.string().describe("The source id to sync (from figura_list_sources).")}},async(x)=>{try{return g0(await u0(`/v1/sources/${encodeURIComponent(x.id)}/sync`,{method:"POST"}))}catch(Q){return X1(Q)}});t0.registerTool("figura_update_fig",{title:"Figura: update fig",description:"Update a fig's METADATA only (title/description/area/tags/status). Set status='archived' to soft-delete. To change the fig's HTML, use figura_revise_fig — it saves a new version and never destroys the old one. update cannot touch the HTML.",inputSchema:{id:i.string().describe("The fig to update — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),title:i.string().optional(),description:i.string().optional(),area:q3(),tags:i.array(i.string()).optional().describe("Replaces the existing tags."),status:i.enum(["draft","published","archived"]).optional()}},async(x)=>{try{let Q={};if(x.title!==void 0)Q.title=x.title;if(x.description!==void 0)Q.description=x.description;if(x.area!==void 0)Q.area=x.area;if(x.tags!==void 0)Q.tags=x.tags;if(x.status!==void 0)Q.status=x.status;return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}`,{method:"PATCH",body:Q}))}catch(Q){return X1(Q)}});t0.registerTool("figura_delete_fig",{title:"Figura: delete fig",description:"Archive a fig (soft-delete; leaves the library, public link stops resolving) or permanently delete it with hard=true.",inputSchema:{id:i.string().describe("The fig to delete — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),hard:i.boolean().optional().describe("Permanently delete instead of archive.")}},async(x)=>{try{return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}${x.hard?"?hard=true":""}`,{method:"DELETE"}))}catch(Q){return X1(Q)}});await t0.connect(new ZX);
|
|
68
|
+
`}class ZX{constructor(x=Sq.stdin,Q=Sq.stdout){this._stdin=x,this._stdout=Q,this._readBuffer=new iX,this._started=!1,this._ondata=(W)=>{this._readBuffer.append(W),this.processReadBuffer()},this._onerror=(W)=>{this.onerror?.(W)}}async start(){if(this._started)throw Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){while(!0)try{let x=this._readBuffer.readMessage();if(x===null)break;this.onmessage?.(x)}catch(x){this.onerror?.(x)}}async close(){if(this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror),this._stdin.listenerCount("data")===0)this._stdin.pause();this._readBuffer.clear(),this.onclose?.()}send(x){return new Promise((Q)=>{let W=Iq(x);if(this._stdout.write(W))Q();else this._stdout.once("drain",Q)})}}function Eq(x,Q,W){return x.toUpperCase()!=="GET"&&Q==="global"&&!W}var $T="https://api.figura.so",bT="mcp",Rq=TX(Tq(),".figura","config.json"),zT=TX(".figura","config.json");async function Pq(x){try{let Q=JSON.parse(await X9.readFile(x,"utf8"));if(Q&&typeof Q==="object")return Q}catch{}return{}}async function vT(x=process.cwd()){let Q=Tq(),W=x;for(;;){if(W!==Q){let X=TX(W,zT);try{return await X9.access(X),X}catch{}}let Y=GT(W);if(Y===W)return null;W=Y}}async function kq(){let x=await vT(),Q=x?await Pq(x):{},W=await Pq(Rq),Y={...W,...Q},X=process.env.FIGURA_TOKEN||Y.token||void 0,J=process.env.FIGURA_TOKEN?"env":Q.token?"project":W.token?"global":"none",G=(process.env.FIGURA_API_URL||Y.apiUrl||$T).replace(/\/+$/,"");return{token:X,apiUrl:G,team:Y.team,source:J,configPath:J==="project"?x:J==="global"?Rq:null}}async function u0(x,Q={}){let W=await kq(),Y=W.token;if(!Y)throw Error("No Figura token. Run `figura link` in this repo (or `figura login`, or set FIGURA_TOKEN).");let X=(Q.method??"GET").toUpperCase();if(Eq(X,W.source,process.env.FIGURA_ALLOW_GLOBAL==="1"))throw Error(`this project isn't linked to a Figura team — the global credential${W.team?` (team '${W.team}')`:""} would receive this write. Ask the user to run \`figura link\` in this repo (or set FIGURA_ALLOW_GLOBAL=1 to use the global team deliberately).`);let J=`${W.apiUrl}${x.startsWith("/")?"":"/"}${x}`,G={Accept:"application/json",Authorization:`Bearer ${Y}`,"X-Figura-Source":bT},$;if(Q.body!==void 0)G["Content-Type"]="application/json",$=JSON.stringify(Q.body);let b=await k9.request({url:J,method:Q.method??"GET",headers:G,data:$,validateStatus:()=>!0,transformRequest:[(B)=>B],transformResponse:[(B)=>B]}),z=typeof b.data==="string"?b.data:"",v=void 0;if(z)try{v=JSON.parse(z)}catch{v=z}if(b.status<200||b.status>=300){let B=v&&typeof v==="object"&&v!==null&&"error"in v?String(v.error):typeof v==="string"&&v?v:b.statusText||`HTTP ${b.status}`;throw Error(`${B} (HTTP ${b.status})`)}return v}var iq=2500,BT=600000,wT=60000,Zq=(x)=>new Promise((Q)=>setTimeout(Q,x));async function KT(x){let Q=Date.now(),W=null;for(;;){let Y;try{Y=await u0(`/v1/jobs/${encodeURIComponent(x)}`),W=null}catch(X){if(W??=Date.now(),Date.now()-W>=wT){let J=X instanceof Error?X.message:String(X);throw Error(`Lost contact with the Figura API while waiting for job ${x} (${J}). `+`The job may still complete — resume by checking GET /v1/jobs/${x} instead of regenerating.`)}await Zq(iq);continue}if(Y.status==="done")return Y;if(Y.status==="error")throw Error(Y.error||"The generation job failed. Please retry.");if(Date.now()-Q>BT)throw Error(`Timed out waiting for job ${x} — it may still complete; `+`check GET /v1/jobs/${x} instead of regenerating.`);await Zq(iq)}}function g0(x){return{content:[{type:"text",text:JSON.stringify(x,null,2)}]}}function X1(x){return{content:[{type:"text",text:x instanceof Error?x.message:String(x)}],isError:!0}}var q3=()=>i.string().optional().describe("Product area / surface the fig belongs to, e.g. 'dashboard', 'settings', 'onboarding'. Free text."),kX=["portrait","landscape","both"],_X=["single","compare","variations"],HT=(()=>{try{let x=JSON.parse(JT(new URL("../package.json",import.meta.url),"utf8"));return typeof x.version==="string"?x.version:"0.0.0"}catch{return"0.0.0"}})(),t0=new PX({name:"figura",version:HT});t0.registerTool("figura_whoami",{title:"Figura: whoami",description:"Return the identity (team, actor, scopes) for the configured Figura token, plus `binding` — which config tier supplied it (env | project | global). A `global` binding means this project is not linked to a team and writes will be refused; ask the user to run `figura link` in the repo.",inputSchema:{}},async()=>{try{let x=await u0("/v1/whoami"),{source:Q,configPath:W}=await kq();return g0({...x,binding:{source:Q,configPath:W}})}catch(x){return X1(x)}});t0.registerTool("figura_set_brand",{title:"Figura: set brand profile",description:'Set (upsert) the team brand profile that the fig generator renders against. Use this to "introduce a repo to Figura": scan the codebase for its design system, synthesize the brand, then push it here. `name` is required; `appDescription` is a one-clause product summary; `tokensBlock` is the freeform design-token reference (colors + hex + role, type scale + faces, spacing, the brand gradient, plus voice/conventions); `surfaces` is the product\'s own key areas/sections (e.g. "Feed", "Settings", "Checkout"), NOT another product\'s — drives the Studio Area picker. This is a partial merge — fields you omit keep their saved value (pass an explicit empty string / empty array to clear one), so a name-only update will not wipe existing tokens or surfaces. Editing the team brand — confirm with the user before calling.',inputSchema:{name:i.string().describe("Brand / app name (required)."),appDescription:i.string().optional().describe("One clause on what the product is + who it is for."),tokensBlock:i.string().optional().describe("Freeform plain-text design-token reference: colors (hex + role), type scale + faces, spacing, the brand gradient, and voice/conventions. Injected verbatim into the fig-generation prompt."),surfaces:i.array(i.string()).optional().describe(`The product's own key areas/sections (e.g. ["Feed", "Settings", "Checkout"]) — read from the repo's real navigation/routes, never invented or copied from another product. Drives the Studio Area picker; capped at 24 areas / 40 characters each. Pass [] to clear.`)}},async(x)=>{try{let Q={name:x.name};if(x.appDescription!==void 0)Q.appDescription=x.appDescription;if(x.tokensBlock!==void 0)Q.tokensBlock=x.tokensBlock;if(x.surfaces!==void 0)Q.surfaces=x.surfaces;return g0(await u0("/brand",{method:"PUT",body:Q}))}catch(Q){return X1(Q)}});var VT={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",avif:"image/avif",svg:"image/svg+xml"},CX=5242880;function Cq(x){let Q=x.split(/[?#]/)[0].split(".").pop()?.toLowerCase()??"";return VT[Q]}async function _q(x,Q){let W,Y=Q;if(/^https?:\/\//i.test(x)){let X=await k9.get(x,{responseType:"arraybuffer",maxContentLength:CX,timeout:30000});if(W=Buffer.from(X.data).toString("base64"),!Y){let J=String(X.headers["content-type"]??"").split(";")[0].trim().toLowerCase();Y=J.startsWith("image/")?J:Cq(x)}}else{let{size:X}=await X9.stat(x);if(X>CX)throw Error(`'${x}' is ${(X/1048576).toFixed(1)}MB — over the ${CX/1048576}MB asset limit.`);if(W=(await X9.readFile(x)).toString("base64"),!Y)Y=Cq(x)}if(!Y)throw Error(`Could not determine an image content-type for '${x}' — pass contentType (e.g. image/png).`);return{data:W,contentType:Y}}async function qT(x,Q){let{data:W,contentType:Y}=await _q(x,Q),X=await u0("/v1/assets",{method:"POST",body:{data:W,contentType:Y}});if(!X||typeof X.url!=="string")throw Error("Asset upload did not return a URL.");return X.url}var uq=()=>i.array(i.object({source:i.string().describe("The image — a local file path or an http(s) URL (e.g. a screenshot)."),match:i.string().optional().describe('Exact string in the HTML to replace with the hosted URL (defaults to `source` — i.e. write <img src="<source>"> in the HTML).')})).optional().describe("Images to attach: each is uploaded to durable figura hosting (stable non-expiring URL, allowed by the fig CSP) and substituted into the HTML in place of `match`/`source`. Use for real screenshots/photos/logos instead of base64 data-URIs (HTML size cap) or external URLs (expire/CSP-blocked).");async function lq(x,Q){if(!Q?.length)return{html:x,uploaded:[]};let W=x,Y=[];for(let X of Q){let J=X.match??X.source;if(!W.includes(J))throw Error(`images: '${J}' does not occur in the html — reference it (e.g. <img src="${J}">) so it can be swapped for the hosted URL, or set 'match'.`);let G=await qT(X.source);W=W.split(J).join(G),Y.push({source:X.source,url:G})}return{html:W,uploaded:Y}}function pq(x,Q){if(!Q.length)return x;if(x&&typeof x==="object"&&!Array.isArray(x))return{...x,assets:Q};return{result:x,assets:Q}}t0.registerTool("figura_create_fig",{title:"Figura: create fig",description:"Create a new visualization from an HTML document — this HOSTS it (the create IS the hosting; there is no separate \"land it in Figura\" step). Every create returns `viewUrl`: ALWAYS a link you can open for approval, whether or not the fig is published — the public share url once published, else a team-scoped viewer link only signed-in teammates can open. Do NOT pass publish:true just to get something viewable — omit it (default false) so the fig lands team-private, open `viewUrl` for review, and only publish when the human wants a link they can share outside the team (figura_publish_fig, once approved). The response also carries `brandWarnings` — a deterministic (no-LLM) colors/fonts/radii/spacing diff against the team's brand tokens, WARN-only (never blocks the create). If it comes back non-empty, read it and consider a figura_revise_fig call that binds the missing tokens before telling the user you're done. To include REAL images (screenshots/photos/logos), pass them via `images` — they are uploaded to durable figura hosting and their stable URLs substituted into the HTML. TIP — injected components: if your HTML shows phone frames (a compare/multi-screen mock), wrap each state in <div class=\"fig-device portrait|landscape\"><div class=\"fig-screen\">…</div><div class=\"fig-di\"></div><div class=\"fig-homebar\"></div></div> and do NOT style those classes — Figura injects accurate iPhone frame CSS at save. Same for an iOS status bar (<div class=\"fig-statusbar\" data-time=\"9:41\"></div>), a compare control bar (.fig-controls/.fig-seg), and the .frame-web browser window.",inputSchema:{html:i.string().describe("The full HTML document for the visualization."),images:uq(),title:i.string().optional().describe("Title (otherwise derived from the HTML)."),description:i.string().optional(),area:q3(),tags:i.array(i.string()).optional(),orientation:i.enum(kX).optional(),mode:i.enum(_X).optional(),model:i.string().optional().describe("Model attribution."),device:i.string().optional().describe("Catalog iPhone id the fig targets (e.g. 'iphone-16', 'iphone-17-pro', 'iphone-se-3') — keys the injected frame/safe-area geometry."),issue:i.number().optional().describe("Linked issue number."),publish:i.boolean().optional().describe("Publish to a shareable public URL on create (requires a paid plan). Default false — the response's `viewUrl` is already open-able team-privately, so only set this when the human explicitly wants a link they can share outside the team."),prompt:i.string().optional().describe("OPTIONAL: the brief that produced this HTML, if there was one — you wrote the HTML yourself, so Figura never sees a prompt unless you pass it here. Stored on the fig (DB-only, never stamped into the HTML), and returned by figura_get_fig — lets teammates see 'why' this fig exists.")}},async(x)=>{try{let{html:Q,uploaded:W}=await lq(x.html,x.images),Y={html:Q};for(let X of["title","description","area","tags","orientation","mode","model","device","issue","publish","prompt"])if(x[X]!==void 0)Y[X]=x[X];return g0(pq(await u0("/v1/figs",{method:"POST",body:Y}),W))}catch(Q){return X1(Q)}});t0.registerTool("figura_upload_asset",{title:"Figura: upload asset",description:"Upload a durable image asset and get back a STABLE, figura-hosted URL that never expires — use it in a fig's <img src> instead of a base64 data-URI (bloats the HTML past the publish size) or an external URL that expires (Instagram/CDN signed links). The fig CSP allows figura-hosted images. Provide `path` (a local image file), `url` (an http(s) image to fetch and re-host), or `data` (base64 bytes) + `contentType`. Accepts png/jpeg/gif/webp/avif/svg, up to 5MB. Returns `{ url }`.",inputSchema:{path:i.string().optional().describe("Local path to an image file to upload (content-type inferred from the extension)."),url:i.string().optional().describe("http(s) URL of an image to fetch and re-host (alternative to `path`) — use for expiring/signed source URLs so the fig keeps rendering after they die."),data:i.string().optional().describe("Base64-encoded image bytes (alternative to `path`/`url`). Requires `contentType`."),contentType:i.string().optional().describe("MIME type, e.g. image/png. Required with `data`; inferred from the extension/response for `path`/`url`.")}},async(x)=>{try{let Q=x.path??x.url;if(Q){let{data:W,contentType:Y}=await _q(Q,x.contentType);return g0(await u0("/v1/assets",{method:"POST",body:{data:W,contentType:Y}}))}if(!x.data)throw Error("Provide `path` (a local image file), `url` (an http(s) image), or `data` (base64 bytes).");if(!x.contentType)throw Error("`contentType` is required with `data` — e.g. image/png.");return g0(await u0("/v1/assets",{method:"POST",body:{data:x.data,contentType:x.contentType}}))}catch(Q){return X1(Q)}});t0.registerTool("figura_generate_fig",{title:"Figura: generate fig",description:"Generate a new fig server-side from a natural-language brief, then persist it — this HOSTS it (the create IS the hosting). The engine runs Figura's on-brand generation — you do NOT write the HTML; just describe the screen. SPENDS TEAM CREDITS: only use when the user explicitly opts for a server render; otherwise default to writing the HTML yourself and landing it with figura_create_fig (free, uses the calling model). Runs as an async server job; this call polls until the fig lands (typically 30–90s). Returns the created fig (id, slug) and `viewUrl` — ALWAYS a link you can open for approval (a team-scoped viewer when unpublished, the public share url once published). Requires an active paid subscription.",inputSchema:{prompt:i.string().describe("A natural-language brief describing the screen/UI to generate."),area:q3(),orientation:i.enum(kX).optional(),mode:i.enum(_X).optional(),platform:i.enum(["mobile","web"]).optional().describe("Output platform: 'mobile' (iPhone, default) or 'web' (browser frame)."),device:i.string().optional().describe("Catalog iPhone id to target (e.g. 'iphone-16', 'iphone-17-pro', 'iphone-se-3'). Mobile only; default iphone-16."),title:i.string().optional().describe("Title (otherwise derived from the generated HTML)."),fidelity:i.enum(["fast","high"]).optional().describe("Generation fidelity (default fast)."),publish:i.boolean().optional().describe("Publish to a shareable public URL on create (requires a paid plan). Default false — the response's `viewUrl` is already open-able team-privately, so only set this when the human explicitly wants a link they can share outside the team.")}},async(x)=>{try{let Q={prompt:x.prompt};for(let Y of["area","orientation","mode","platform","device","title","publish"])if(x[Y]!==void 0)Q[Y]=x[Y];if(x.fidelity!==void 0)Q.model=x.fidelity;Q.async=!0;let W=await u0("/v1/figs/generate",{method:"POST",body:Q});if(W&&typeof W==="object"&&typeof W.jobId==="string"){let Y=await KT(W.jobId),X=Y.result??{};return g0(X.fig??Y.result)}return g0(W)}catch(Q){return X1(Q)}});t0.registerTool("figura_revise_fig",{title:"Figura: revise fig",description:"Create a new revision (version) of an existing fig from updated HTML — this HOSTS it, same as create. Returns the new fig (id, slug, version) and `viewUrl` — ALWAYS a link to open and re-review. Publishing is NOT inherited from the fig being revised: the new revision defaults to unpublished (a team-scoped viewer `viewUrl`) even if the fig it revises is currently live — the existing public link keeps serving the OLD version until you pass `publish: true` on THIS call, which republishes at the SAME stable link (same publicId across the chain). The new version joins the original fig's chain — it does not replace it in place. Use list_revisions for the chain (rootFig + every version). The response also carries `brandWarnings` — a deterministic (no-LLM) colors/fonts/radii/spacing diff against the team's brand tokens, WARN-only. If it comes back non-empty, consider one more revision that binds the missing tokens. To include REAL images (screenshots/photos/logos), pass them via `images` — uploaded to durable figura hosting, stable URLs substituted into the HTML.",inputSchema:{id:i.string().describe("The fig to revise — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),html:i.string().describe("The full updated HTML document."),images:uq(),title:i.string().optional().describe("Title (otherwise derived from the HTML)."),description:i.string().optional(),area:i.string().optional().describe("Product area / surface; free text. Inherited from the parent if omitted."),tags:i.array(i.string()).optional(),orientation:i.enum(kX).optional().describe("Inherited from the parent if omitted."),mode:i.enum(_X).optional(),model:i.string().optional().describe("Model attribution."),issue:i.number().optional().describe("Linked issue number."),publish:i.boolean().optional().describe("Publish the new revision to a shareable public URL (requires a paid plan). Default false — the response's `viewUrl` is already open-able team-privately. If the fig being revised is ALREADY published, pass true here too or its public link keeps serving the prior version."),prompt:i.string().optional().describe("OPTIONAL: the brief/ask that produced this revision, if there was one. Stored on the fig (DB-only, never stamped into the HTML), and returned by figura_get_fig.")}},async(x)=>{try{let{html:Q,uploaded:W}=await lq(x.html,x.images),Y={html:Q};for(let X of["title","description","area","tags","orientation","mode","model","issue","publish","prompt"])if(x[X]!==void 0)Y[X]=x[X];return g0(pq(await u0(`/v1/figs/${encodeURIComponent(x.id)}/revise`,{method:"POST",body:Y}),W))}catch(Q){return X1(Q)}});t0.registerTool("figura_list_revisions",{title:"Figura: list revisions",description:"List the full revision chain for a fig (all versions sharing a root), oldest to newest.",inputSchema:{id:i.string().describe("Any fig in the chain — its id (`fig_…`), a UUID, share link/URL, or publicId.")}},async(x)=>{try{return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}/revisions`))}catch(Q){return X1(Q)}});t0.registerTool("figura_publish_fig",{title:"Figura: publish fig",description:"Publish (or unpublish) an existing fig to its shareable public URL. The fig is ALREADY hosted and viewable team-privately without this (figura_create_fig/figura_revise_fig's `viewUrl`) — only call this once the human wants a link they can share outside the team. Requires a paid plan. Returns { isPublic, url, publicId, viewUrl }.",inputSchema:{id:i.string().describe("The fig to publish — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),isPublic:i.boolean().optional().describe("Set false to unpublish (make private). Defaults to true (publish).")}},async(x)=>{try{let Q={};if(x.isPublic!==void 0)Q.isPublic=x.isPublic;return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}/publish`,{method:"POST",body:Q}))}catch(Q){return X1(Q)}});t0.registerTool("figura_list_figs",{title:"Figura: list figs",description:"List visualizations for the team, optionally filtered by area or tag.",inputSchema:{area:q3(),tag:i.string().optional(),limit:i.number().optional(),cursor:i.string().optional()}},async(x)=>{try{let Q=new URLSearchParams;if(x.area)Q.set("area",x.area);if(x.tag)Q.set("tag",x.tag);if(x.limit!==void 0)Q.set("limit",String(x.limit));if(x.cursor)Q.set("cursor",x.cursor);let W=Q.toString();return g0(await u0(`/v1/figs${W?`?${W}`:""}`))}catch(Q){return X1(Q)}});t0.registerTool("figura_get_fig",{title:"Figura: get fig",description:"Fetch a single visualization by id. The response carries `viewUrl` — ALWAYS a link you can open for approval (a team-scoped viewer when unpublished, the public share url once published).",inputSchema:{id:i.string().describe("The fig — its id (`fig_…`), a UUID, a share link/URL, or its publicId.")}},async(x)=>{try{return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}`))}catch(Q){return X1(Q)}});t0.registerTool("figura_comment",{title:"Figura: comment",description:"Add a comment to a visualization.",inputSchema:{fig_id:i.string().describe("The fig to comment on — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),body:i.string().describe("Comment text.")}},async(x)=>{try{return g0(await u0(`/v1/figs/${encodeURIComponent(x.fig_id)}/comments`,{method:"POST",body:{body:x.body}}))}catch(Q){return X1(Q)}});t0.registerTool("figura_request_feedback",{title:"Figura: request feedback",description:"Ask a teammate to review a fig. Resolve `from` to a team member (by name or email); they get a notification (in-app, push, email) and the discussion happens in the fig's comment thread. Use this when the user says they want a specific person's feedback/eyes/review on a fig. Returns { ok, request: { to, status }, figUrl }.",inputSchema:{id:i.string().describe("The fig to request feedback on — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),from:i.string().describe("The teammate to ask, by name or email (resolved within the team)."),note:i.string().optional().describe("Optional message for them (e.g. what to look at).")}},async(x)=>{try{let Q={from:x.from};if(x.note!==void 0)Q.note=x.note;return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}/request-feedback`,{method:"POST",body:Q}))}catch(Q){return X1(Q)}});t0.registerTool("figura_list_sources",{title:"Figura: list sources",description:"List the company-brain source-spine connectors for the team, with event counts and last-synced times.",inputSchema:{}},async()=>{try{return g0(await u0("/v1/sources"))}catch(x){return X1(x)}});t0.registerTool("figura_sync_source",{title:"Figura: sync source",description:"Ingest new events from a connected source into the spine. Returns { ingested, fetched, nextCursor }.",inputSchema:{id:i.string().describe("The source id to sync (from figura_list_sources).")}},async(x)=>{try{return g0(await u0(`/v1/sources/${encodeURIComponent(x.id)}/sync`,{method:"POST"}))}catch(Q){return X1(Q)}});t0.registerTool("figura_update_fig",{title:"Figura: update fig",description:"Update a fig's METADATA only (title/description/area/tags/status). Set status='archived' to soft-delete. To change the fig's HTML, use figura_revise_fig — it saves a new version and never destroys the old one. update cannot touch the HTML.",inputSchema:{id:i.string().describe("The fig to update — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),title:i.string().optional(),description:i.string().optional(),area:q3(),tags:i.array(i.string()).optional().describe("Replaces the existing tags."),status:i.enum(["draft","published","archived"]).optional()}},async(x)=>{try{let Q={};if(x.title!==void 0)Q.title=x.title;if(x.description!==void 0)Q.description=x.description;if(x.area!==void 0)Q.area=x.area;if(x.tags!==void 0)Q.tags=x.tags;if(x.status!==void 0)Q.status=x.status;return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}`,{method:"PATCH",body:Q}))}catch(Q){return X1(Q)}});t0.registerTool("figura_delete_fig",{title:"Figura: delete fig",description:"Archive a fig (soft-delete; leaves the library, public link stops resolving) or permanently delete it with hard=true.",inputSchema:{id:i.string().describe("The fig to delete — its id (`fig_…`), a UUID, a share link/URL, or its publicId."),hard:i.boolean().optional().describe("Permanently delete instead of archive.")}},async(x)=>{try{return g0(await u0(`/v1/figs/${encodeURIComponent(x.id)}${x.hard?"?hard=true":""}`,{method:"DELETE"}))}catch(Q){return X1(Q)}});await t0.connect(new ZX);
|
package/package.json
CHANGED
package/skills/fig/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: fig
|
|
3
3
|
description: "Mock up a screen as an on-brand, shareable HTML fig via your Figura team — render a UI state in seconds before writing the real code. Use when asked to visualize a screen, mock a UI, or 'make a fig'. iPhone-framed by default; browser frame for web/desktop."
|
|
4
4
|
argument-hint: '<what to visualize>'
|
|
5
|
-
allowed-tools: 'mcp__figura__figura_whoami, mcp__figura__figura_generate_fig, mcp__figura__figura_create_fig, Bash(figura *), Bash(bunx figura-cli *)'
|
|
5
|
+
allowed-tools: 'mcp__figura__figura_whoami, mcp__figura__figura_generate_fig, mcp__figura__figura_create_fig, mcp__figura__figura_revise_fig, mcp__figura__figura_publish_fig, Bash(figura *), Bash(bunx figura-cli *)'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# /fig — figure it out before you build it
|
|
@@ -65,30 +65,63 @@ greenfield.
|
|
|
65
65
|
|
|
66
66
|
2. **Default — build it yourself, with YOUR Claude.** Fetch the team's design
|
|
67
67
|
system — `bunx figura-cli brand --json` — and hand-build the fig's HTML against
|
|
68
|
-
it, then land it with **`figura_create_fig
|
|
68
|
+
it, then land it with **`figura_create_fig`**, setting
|
|
69
69
|
`area`, `orientation` (`portrait` / `landscape` / `both`), `mode` (`single` /
|
|
70
70
|
`compare` / `variations` — default to `compare` when Existing-first turned up a
|
|
71
71
|
baseline), `title`, and a real description. This uses the
|
|
72
72
|
caller's own Claude (the model running this skill) — no extra cost, full control
|
|
73
73
|
for precise broken-vs-proposed diptychs — and applies the team's brand from the
|
|
74
74
|
fetched design system. **Follow that design system verbatim; never invent brand
|
|
75
|
-
values.**
|
|
75
|
+
values.**
|
|
76
|
+
|
|
77
|
+
**Landing IS hosting — leave `publish` unset.** Creating a fig always persists
|
|
78
|
+
it to Figura (there is no separate "save it for real" step); the response's
|
|
79
|
+
**`viewUrl`** is a link you can open right now, whether or not anyone has
|
|
80
|
+
decided to share it wider. Don't pass `publish: true` just to get something
|
|
81
|
+
viewable — that makes a first-draft iteration PUBLIC. Publishing is a
|
|
82
|
+
deliberate, later step (see step 5).
|
|
76
83
|
|
|
77
84
|
3. **Server render — the user's call, never yours.** Figura can also render the
|
|
78
85
|
fig server-side against the team brand: **`figura_generate_fig`** with a
|
|
79
|
-
natural-language brief (same `area` / `orientation` / `mode` / `title
|
|
80
|
-
`publish
|
|
81
|
-
render ("let Figura render it", headless/CI
|
|
82
|
-
when you've offered both paths and they
|
|
83
|
-
**async job the tool polls for you** — expect
|
|
84
|
-
returns; don't re-call while one is in flight.
|
|
85
|
-
**spends the team's Figura credits** (a `403`
|
|
86
|
-
Pass **`fidelity: 'high'`** to render on the
|
|
87
|
-
`'fast'` is lighter — fine for rough states, weaker
|
|
88
|
-
this path on your own to save effort: it costs credits
|
|
89
|
-
track the brand better.
|
|
90
|
-
|
|
91
|
-
4. **
|
|
86
|
+
natural-language brief (same `area` / `orientation` / `mode` / `title`, and
|
|
87
|
+
again leave `publish` unset by default). Use it only when the **user
|
|
88
|
+
explicitly asks** for a server render ("let Figura render it", headless/CI
|
|
89
|
+
flows with no local model) — or when you've offered both paths and they
|
|
90
|
+
picked this one. It runs as an **async job the tool polls for you** — expect
|
|
91
|
+
~30–90s before the landed fig returns; don't re-call while one is in flight.
|
|
92
|
+
Unlike the default path, this **spends the team's Figura credits** (a `403`
|
|
93
|
+
means no active subscription). Pass **`fidelity: 'high'`** to render on the
|
|
94
|
+
better model (the default `'fast'` is lighter — fine for rough states, weaker
|
|
95
|
+
on brand). Never pick this path on your own to save effort: it costs credits
|
|
96
|
+
and hand-built figs track the brand better.
|
|
97
|
+
|
|
98
|
+
4. **Open the HOSTED fig for approval — never a local file.** Run
|
|
99
|
+
`figura fig open <id>` (the id `figura_create_fig`/`figura_generate_fig` just
|
|
100
|
+
returned). It opens **`viewUrl`** in the reviewer's browser — the actual
|
|
101
|
+
Figura render (brand chrome, CSP, favicon, responsive frame), team-scoped so
|
|
102
|
+
any signed-in teammate can open it even though nothing was published. That's
|
|
103
|
+
what gets approved, and it's the exact page that ships once published — not a
|
|
104
|
+
`file://` reconstruction that can drift from it. No display reachable (a
|
|
105
|
+
headless/CI session)? Paste the printed `viewUrl` into the conversation
|
|
106
|
+
instead, or fall back to `figura fig open <id> --local` for a local sandboxed
|
|
107
|
+
HTML preview.
|
|
108
|
+
|
|
109
|
+
5. **Iterate against the SAME fig, not a fresh one.** Changes from review go
|
|
110
|
+
through **`figura_revise_fig`** (`id`, the updated `html`) — it joins the
|
|
111
|
+
existing chain as a new version and returns a fresh `viewUrl`. Publishing is
|
|
112
|
+
NOT inherited automatically: a revision of an already-published fig still
|
|
113
|
+
lands unpublished by default (`viewUrl` is a team-scoped view, and the
|
|
114
|
+
existing public link keeps serving the OLD version) — pass `publish: true`
|
|
115
|
+
on the revise call itself if the fig is already live and the public link
|
|
116
|
+
needs to move to this version. Re-open it (`figura fig open <id>`) and
|
|
117
|
+
repeat until approved.
|
|
118
|
+
|
|
119
|
+
6. **Approve, then share.** Once the human signs off AND wants a link people
|
|
120
|
+
outside the team can open, publish it explicitly —
|
|
121
|
+
**`figura_publish_fig`** (or `figura fig publish <id>`) — Figura's
|
|
122
|
+
`figura.so/f/<id>` share link. Team-only review never needed this; treat
|
|
123
|
+
publishing as a deliberate call the human makes, not something create/revise
|
|
124
|
+
does on its own.
|
|
92
125
|
|
|
93
126
|
## Presenting a redesign — overview ⇄ focus
|
|
94
127
|
|
|
@@ -124,8 +157,10 @@ CSS-only radio tabs, since figs carry no `<script>`:
|
|
|
124
157
|
**deterministic**, project-scoped path — the repo's `.figura/figs/` (already
|
|
125
158
|
gitignored), or `~/.figura/figs/` outside a bound repo — never `/tmp` or
|
|
126
159
|
`mktemp`. A stable path means a rerun overwrites the same file instead of
|
|
127
|
-
littering volatile temp locations, and matches where `figura fig open`
|
|
128
|
-
(`<repo>/.figura/figs/figura-fig-<id>.html`)
|
|
160
|
+
littering volatile temp locations, and matches where `figura fig open --local`
|
|
161
|
+
writes (`<repo>/.figura/figs/figura-fig-<id>.html`) — this is YOUR headless
|
|
162
|
+
self-check copy, separate from the hosted `viewUrl` a human reviewer opens
|
|
163
|
+
(step 4 above).
|
|
129
164
|
|
|
130
165
|
A **wipe-slider** stays the narrow tool for exactly two states of the same
|
|
131
166
|
layout with pixel-level tweaks. Never ship a squeezed side-by-side as the
|
|
@@ -164,5 +199,12 @@ So when you **hand-build** a fig (the default path), do your part of the contrac
|
|
|
164
199
|
- **MCP unavailable** (headless / cron, or no interactive auth): fall back to the
|
|
165
200
|
CLI — `bunx figura-cli fig create` / `fig publish`, with `fig revise` /
|
|
166
201
|
`fig revisions` for versions, and `bunx figura-cli brand --json` for the brand.
|
|
202
|
+
- **`figura_create_fig`/`figura_revise_fig` itself fails** (no network, API down,
|
|
203
|
+
auth expired): the fig never entered Figura, so there is no `id`/`viewUrl` to
|
|
204
|
+
open. Only THEN write the HTML to a local scratch file (project-scoped —
|
|
205
|
+
`.figura/figs/`, never `/tmp`) and tell the user it's a local-only draft that
|
|
206
|
+
still needs landing once connectivity is back — this is the degraded fallback,
|
|
207
|
+
not the default path. Don't reach for it just because publishing felt like
|
|
208
|
+
an extra step; `figura_create_fig` hosts on the first call, every time.
|
|
167
209
|
- **One job, one place.** Brand = the Figura brand profile. Generation = the Figura
|
|
168
210
|
engine. This skill only decides *when* to fig and *which MCP call* to make.
|