localdeck 1.7.1 → 1.8.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/CHANGELOG.md +12 -0
- package/dashboard/assets/{index-2gSOxBaK.js → index-BNbydS4x.js} +1 -1
- package/dashboard/index.html +1 -1
- package/dist/{index-scnd2pq4.js → index-hdw8ggnb.js} +2 -2
- package/dist/{index-jtd14k8h.js → index-xhf909hw.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/{mcp-w45qck6z.js → mcp-yqgy25c4.js} +1 -1
- package/dist/quarry-31n76q0s.js +5 -0
- package/dist/{update-2nttbg13.js → update-c2m151t6.js} +1 -1
- package/docs/quarry.md +11 -2
- package/package.json +1 -1
- package/dist/quarry-ysrktgjh.js +0 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{I as N,e as Y}from"./index-
|
|
1
|
+
import{I as N,e as Y}from"./index-xhf909hw.js";import{va as C,wa as J}from"./index-hdw8ggnb.js";import{parseArgs as Z}from"node:util";import y from"node:process";var P=10485760;class Q{constructor(b){this._maxBufferSize=b?.maxBufferSize??P}append(b){if((this._buffer?.length??0)+b.length>this._maxBufferSize)throw this.clear(),Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);this._buffer=this._buffer?Buffer.concat([this._buffer,b]):b}readMessage(){if(!this._buffer)return null;let b=this._buffer.indexOf(`
|
|
2
2
|
`);if(b===-1)return null;let q=this._buffer.toString("utf8",0,b).replace(/\r$/,"");return this._buffer=this._buffer.subarray(b+1),X(q)}clear(){this._buffer=void 0}}function X(b){return Y.parse(JSON.parse(b))}function $(b){return JSON.stringify(b)+`
|
|
3
3
|
`}class V{constructor(b=y.stdin,q=y.stdout,w){this._stdin=b,this._stdout=q,this._started=!1,this._ondata=(K)=>{try{this._readBuffer.append(K),this.processReadBuffer()}catch(H){this.onerror?.(H),this.close().catch(()=>{})}},this._onerror=(K)=>{this.onerror?.(K)},this._readBuffer=new Q({maxBufferSize:w?.maxBufferSize})}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 b=this._readBuffer.readMessage();if(b===null)break;this.onmessage?.(b)}catch(b){this.onerror?.(b)}}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(b){return new Promise((q)=>{let w=$(b);if(this._stdout.write(w))q();else this._stdout.once("drain",q)})}}async function T(b){let{values:q}=Z({args:b,strict:!0,allowPositionals:!1,options:{project:{type:"string"},help:{type:"boolean",short:"h"}}});if(q.help){console.log(`Usage: localdeck mcp [--project <id>]
|
|
4
4
|
Runs an MCP server over stdio for inspection, service control, recorded GET/HEAD replay and quick sharing.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{A as b,B as gZ,C as bZ,D as kZ,E as hZ,F as uZ,G as lZ,H as dZ,I as K0,a as P,b as PZ,c as CZ,d as M,f as u,g as jZ,h as FZ,i as LZ,j as zZ,k as AZ,l as _Z,m as qZ,n as xZ,o as MZ,p as TZ,q as r,r as IZ,s as yZ,t as EZ,u as SZ,v as fZ,w as wZ,x as RZ,y as vZ,z as A}from"./index-xhf909hw.js";import{N as sZ,U as rZ,X as eZ,ra as GZ,ta as Z0,va as $0,wa as J0}from"./index-hdw8ggnb.js";import mZ from"node:crypto";import c from"node:fs";import p from"node:path";class QZ{constructor(Z){this._client=Z}async*callToolStream(Z,H=r,Q){let B=this._client,G={...Q,task:Q?.task??(B.isToolTask(Z.name)?{}:void 0)},W=B.requestStream({method:"tools/call",params:Z},H,G),O=B.getToolOutputValidator(Z.name);for await(let N of W){if(N.type==="result"&&O){let C=N.result;if(!C.structuredContent&&!C.isError){yield{type:"error",error:new A(M.InvalidRequest,`Tool ${Z.name} has an output schema but did not return structured content`)};return}if(C.structuredContent)try{let V=O(C.structuredContent);if(!V.valid){yield{type:"error",error:new A(M.InvalidParams,`Structured content does not match the tool's output schema: ${V.errorMessage}`)};return}}catch(V){if(V instanceof A){yield{type:"error",error:V};return}yield{type:"error",error:new A(M.InvalidParams,`Failed to validate structured content: ${V instanceof Error?V.message:String(V)}`)};return}}yield N}}async getTask(Z,H){return this._client.getTask({taskId:Z},H)}async getTaskResult(Z,H,Q){return this._client.getTaskResult({taskId:Z},H,Q)}async listTasks(Z,H){return this._client.listTasks(Z?{cursor:Z}:void 0,H)}async cancelTask(Z,H){return this._client.cancelTask({taskId:Z},H)}requestStream(Z,H,Q){return this._client.requestStream(Z,H,Q)}}function e(Z,H){if(!Z||H===null||typeof H!=="object")return;if(Z.type==="object"&&Z.properties&&typeof Z.properties==="object"){let Q=H,B=Z.properties;for(let G of Object.keys(B)){let W=B[G];if(Q[G]===void 0&&Object.prototype.hasOwnProperty.call(W,"default"))Q[G]=W.default;if(Q[G]!==void 0)e(W,Q[G])}}if(Array.isArray(Z.anyOf)){for(let Q of Z.anyOf)if(typeof Q!=="boolean")e(Q,H)}if(Array.isArray(Z.oneOf)){for(let Q of Z.oneOf)if(typeof Q!=="boolean")e(Q,H)}}function L0(Z){if(!Z)return{supportsFormMode:!1,supportsUrlMode:!1};let H=Z.form!==void 0,Q=Z.url!==void 0;return{supportsFormMode:H||!H&&!Q,supportsUrlMode:Q}}class WZ extends kZ{constructor(Z,H){super(H);if(this._clientInfo=Z,this._cachedToolOutputValidators=new Map,this._cachedKnownTaskTools=new Set,this._cachedRequiredTaskTools=new Set,this._listChangedDebounceTimers=new Map,this._capabilities=H?.capabilities??{},this._jsonSchemaValidator=H?.jsonSchemaValidator??new uZ,H?.listChanged)this._pendingListChangedConfig=H.listChanged}_setupListChangedHandlers(Z){if(Z.tools&&this._serverCapabilities?.tools?.listChanged)this._setupListChangedHandler("tools",IZ,Z.tools,async()=>{return(await this.listTools()).tools});if(Z.prompts&&this._serverCapabilities?.prompts?.listChanged)this._setupListChangedHandler("prompts",MZ,Z.prompts,async()=>{return(await this.listPrompts()).prompts});if(Z.resources&&this._serverCapabilities?.resources?.listChanged)this._setupListChangedHandler("resources",_Z,Z.resources,async()=>{return(await this.listResources()).resources})}get experimental(){if(!this._experimental)this._experimental={tasks:new QZ(this)};return this._experimental}registerCapabilities(Z){if(this.transport)throw Error("Cannot register capabilities after connecting to transport");this._capabilities=hZ(this._capabilities,Z)}setRequestHandler(Z,H){let B=gZ(Z)?.method;if(!B)throw Error("Schema is missing a method literal");let G=bZ(B);if(typeof G!=="string")throw Error("Schema method literal must be a string");let W=G;if(W==="elicitation/create"){let O=async(N,C)=>{let V=b(wZ,N);if(!V.success){let x=V.error instanceof Error?V.error.message:String(V.error);throw new A(M.InvalidParams,`Invalid elicitation request: ${x}`)}let{params:U}=V.data;U.mode=U.mode??"form";let{supportsFormMode:j,supportsUrlMode:y}=L0(this._capabilities.elicitation);if(U.mode==="form"&&!j)throw new A(M.InvalidParams,"Client does not support form-mode elicitation requests");if(U.mode==="url"&&!y)throw new A(M.InvalidParams,"Client does not support URL-mode elicitation requests");let f=await Promise.resolve(H(N,C));if(U.task){let x=b(FZ,f);if(!x.success){let n=x.error instanceof Error?x.error.message:String(x.error);throw new A(M.InvalidParams,`Invalid task creation result: ${n}`)}return x.data}let T=b(RZ,f);if(!T.success){let x=T.error instanceof Error?T.error.message:String(T.error);throw new A(M.InvalidParams,`Invalid elicitation result: ${x}`)}let z=T.data,R=U.mode==="form"?U.requestedSchema:void 0;if(U.mode==="form"&&z.action==="accept"&&z.content&&R){if(this._capabilities.elicitation?.form?.applyDefaults)try{e(R,z.content)}catch{}}return z};return super.setRequestHandler(Z,O)}if(W==="sampling/createMessage"){let O=async(N,C)=>{let V=b(EZ,N);if(!V.success){let z=V.error instanceof Error?V.error.message:String(V.error);throw new A(M.InvalidParams,`Invalid sampling request: ${z}`)}let{params:U}=V.data,j=await Promise.resolve(H(N,C));if(U.task){let z=b(FZ,j);if(!z.success){let R=z.error instanceof Error?z.error.message:String(z.error);throw new A(M.InvalidParams,`Invalid task creation result: ${R}`)}return z.data}let f=U.tools||U.toolChoice?fZ:SZ,T=b(f,j);if(!T.success){let z=T.error instanceof Error?T.error.message:String(T.error);throw new A(M.InvalidParams,`Invalid sampling result: ${z}`)}return T.data};return super.setRequestHandler(Z,O)}return super.setRequestHandler(Z,H)}assertCapability(Z,H){if(!this._serverCapabilities?.[Z])throw Error(`Server does not support ${Z} (required for ${H})`)}async connect(Z,H){if(await super.connect(Z),Z.sessionId!==void 0)return;try{let Q=await this.request({method:"initialize",params:{protocolVersion:PZ,capabilities:this._capabilities,clientInfo:this._clientInfo}},jZ,H);if(Q===void 0)throw Error(`Server sent invalid initialize result: ${Q}`);if(!CZ.includes(Q.protocolVersion))throw Error(`Server's protocol version is not supported: ${Q.protocolVersion}`);if(this._serverCapabilities=Q.capabilities,this._serverVersion=Q.serverInfo,Z.setProtocolVersion)Z.setProtocolVersion(Q.protocolVersion);if(this._instructions=Q.instructions,await this.notification({method:"notifications/initialized"}),this._pendingListChangedConfig)this._setupListChangedHandlers(this._pendingListChangedConfig),this._pendingListChangedConfig=void 0}catch(Q){throw this.close(),Q}}getServerCapabilities(){return this._serverCapabilities}getServerVersion(){return this._serverVersion}getInstructions(){return this._instructions}assertCapabilityForMethod(Z){switch(Z){case"logging/setLevel":if(!this._serverCapabilities?.logging)throw Error(`Server does not support logging (required for ${Z})`);break;case"prompts/get":case"prompts/list":if(!this._serverCapabilities?.prompts)throw Error(`Server does not support prompts (required for ${Z})`);break;case"resources/list":case"resources/templates/list":case"resources/read":case"resources/subscribe":case"resources/unsubscribe":if(!this._serverCapabilities?.resources)throw Error(`Server does not support resources (required for ${Z})`);if(Z==="resources/subscribe"&&!this._serverCapabilities.resources.subscribe)throw Error(`Server does not support resource subscriptions (required for ${Z})`);break;case"tools/call":case"tools/list":if(!this._serverCapabilities?.tools)throw Error(`Server does not support tools (required for ${Z})`);break;case"completion/complete":if(!this._serverCapabilities?.completions)throw Error(`Server does not support completions (required for ${Z})`);break;case"initialize":break;case"ping":break}}assertNotificationCapability(Z){switch(Z){case"notifications/roots/list_changed":if(!this._capabilities.roots?.listChanged)throw Error(`Client does not support roots list changed notifications (required for ${Z})`);break;case"notifications/initialized":break;case"notifications/cancelled":break;case"notifications/progress":break}}assertRequestHandlerCapability(Z){if(!this._capabilities)return;switch(Z){case"sampling/createMessage":if(!this._capabilities.sampling)throw Error(`Client does not support sampling capability (required for ${Z})`);break;case"elicitation/create":if(!this._capabilities.elicitation)throw Error(`Client does not support elicitation capability (required for ${Z})`);break;case"roots/list":if(!this._capabilities.roots)throw Error(`Client does not support roots capability (required for ${Z})`);break;case"tasks/get":case"tasks/list":case"tasks/result":case"tasks/cancel":if(!this._capabilities.tasks)throw Error(`Client does not support tasks capability (required for ${Z})`);break;case"ping":break}}assertTaskCapability(Z){lZ(this._serverCapabilities?.tasks?.requests,Z,"Server")}assertTaskHandlerCapability(Z){if(!this._capabilities)return;dZ(this._capabilities.tasks?.requests,Z,"Client")}async ping(Z){return this.request({method:"ping"},u,Z)}async complete(Z,H){return this.request({method:"completion/complete",params:Z},vZ,H)}async setLoggingLevel(Z,H){return this.request({method:"logging/setLevel",params:{level:Z}},u,H)}async getPrompt(Z,H){return this.request({method:"prompts/get",params:Z},xZ,H)}async listPrompts(Z,H){return this.request({method:"prompts/list",params:Z},qZ,H)}async listResources(Z,H){return this.request({method:"resources/list",params:Z},LZ,H)}async listResourceTemplates(Z,H){return this.request({method:"resources/templates/list",params:Z},zZ,H)}async readResource(Z,H){return this.request({method:"resources/read",params:Z},AZ,H)}async subscribeResource(Z,H){return this.request({method:"resources/subscribe",params:Z},u,H)}async unsubscribeResource(Z,H){return this.request({method:"resources/unsubscribe",params:Z},u,H)}async callTool(Z,H=r,Q){if(this.isToolTaskRequired(Z.name))throw new A(M.InvalidRequest,`Tool "${Z.name}" requires task-based execution. Use client.experimental.tasks.callToolStream() instead.`);let B=await this.request({method:"tools/call",params:Z},H,Q),G=this.getToolOutputValidator(Z.name);if(G){if(!B.structuredContent&&!B.isError)throw new A(M.InvalidRequest,`Tool ${Z.name} has an output schema but did not return structured content`);if(B.structuredContent)try{let W=G(B.structuredContent);if(!W.valid)throw new A(M.InvalidParams,`Structured content does not match the tool's output schema: ${W.errorMessage}`)}catch(W){if(W instanceof A)throw W;throw new A(M.InvalidParams,`Failed to validate structured content: ${W instanceof Error?W.message:String(W)}`)}}return B}isToolTask(Z){if(!this._serverCapabilities?.tasks?.requests?.tools?.call)return!1;return this._cachedKnownTaskTools.has(Z)}isToolTaskRequired(Z){return this._cachedRequiredTaskTools.has(Z)}cacheToolMetadata(Z){this._cachedToolOutputValidators.clear(),this._cachedKnownTaskTools.clear(),this._cachedRequiredTaskTools.clear();for(let H of Z){if(H.outputSchema){let B=this._jsonSchemaValidator.getValidator(H.outputSchema);this._cachedToolOutputValidators.set(H.name,B)}let Q=H.execution?.taskSupport;if(Q==="required"||Q==="optional")this._cachedKnownTaskTools.add(H.name);if(Q==="required")this._cachedRequiredTaskTools.add(H.name)}}getToolOutputValidator(Z){return this._cachedToolOutputValidators.get(Z)}async listTools(Z,H){let Q=await this.request({method:"tools/list",params:Z},TZ,H);return this.cacheToolMetadata(Q.tools),Q}_setupListChangedHandler(Z,H,Q,B){let G=yZ.safeParse(Q);if(!G.success)throw Error(`Invalid ${Z} listChanged options: ${G.error.message}`);if(typeof Q.onChanged!=="function")throw Error(`Invalid ${Z} listChanged options: onChanged must be a function`);let{autoRefresh:W,debounceMs:O}=G.data,{onChanged:N}=Q,C=async()=>{if(!W){N(null,null);return}try{let U=await B();N(null,U)}catch(U){let j=U instanceof Error?U:Error(String(U));N(j,null)}},V=()=>{if(O){let U=this._listChangedDebounceTimers.get(Z);if(U)clearTimeout(U);let j=setTimeout(C,O);this._listChangedDebounceTimers.set(Z,j)}else C()};this.setNotificationHandler(H,V)}async sendRootsListChanged(){return this.notification({method:"notifications/roots/list_changed"})}}class l{constructor(){this._messageQueue=[]}static createLinkedPair(){let Z=new l,H=new l;return Z._otherTransport=H,H._otherTransport=Z,[Z,H]}async start(){while(this._messageQueue.length>0){let Z=this._messageQueue.shift();this.onmessage?.(Z.message,Z.extra)}}async close(){let Z=this._otherTransport;this._otherTransport=void 0,await Z?.close(),this.onclose?.()}async send(Z,H){if(!this._otherTransport)throw Error("Not connected");if(this._otherTransport.onmessage)this._otherTransport.onmessage(Z,{authInfo:H?.authInfo});else this._otherTransport._messageQueue.push({message:Z,extra:{authInfo:H?.authInfo}})}}var z0=1,ZZ=1048576,cZ=["list_services","get_service","get_logs","start_service","stop_service","restart_service"],A0="Restart it in the terminal that started it",_0="LocalDeck isn't responding; retrying",pZ=16384,q0=200,nZ=64,m=5000,H0=ZZ-65536,x0=[1000,2000,5000,1e4],M0=/^[a-zA-Z0-9_-]+:[a-zA-Z0-9_-]+$/;class I extends Error{code;constructor(Z,H){super(H);this.code=Z}}var v=P.string().min(1).max(200),iZ=P.string().regex(M0,"Use the exact projectId:service id from services/list"),E={initialize:P.object({protocolVersion:P.number(),quarryVersion:P.string().max(100).optional()}),open:P.object({workspaceId:v,path:P.string().min(1).max(4096).refine((Z)=>p.isAbsolute(Z),"path must be absolute")}),workspace:P.object({workspaceId:v}),action:P.object({workspaceId:v,serviceId:iZ,action:P.enum(["start","stop","restart","share","unshare","open"])}),subscribe:P.object({workspaceId:v,serviceId:iZ,from:P.string().max(200).optional(),limit:P.number().int().min(1).max(m).default(1000)}),unsubscribe:P.object({subscription:P.string().min(1).max(200)}),page:P.object({workspaceId:v,pageId:P.string().min(1).max(100)}),call:P.object({workspaceId:v,name:P.string().min(1).max(100),arguments:P.record(P.string(),P.unknown()).optional()}),profiles:P.object({workspaceId:v,profiles:P.array(P.string().min(1).max(100)).max(50)}),actionAll:P.object({workspaceId:v,action:P.enum(["start","stop"])})};function oZ(Z){return{profiles:Z.profiles,active:Z.active,clashes:Z.clashes,...Z.error?{error:Z.error}:{}}}function S(Z,H){let Q=Z.safeParse(H??{});if(!Q.success)throw new I(-32602,Q.error.issues.map((B)=>`${B.path.join(".")||"params"}: ${B.message}`).join("; "));return Q.data}function d(Z){return Z instanceof TypeError||Z instanceof Error&&(Z.name==="TimeoutError"||Z.name==="AbortError"||/ECONNREFUSED|ECONNRESET|did not start/.test(Z.message))}function T0(Z){if(Buffer.byteLength(Z)<=pZ)return Z;return new TextDecoder().decode(Buffer.from(Z).subarray(0,pZ-3)).replace(/�+$/,"")+"…"}function Q0(Z){return{cursor:Z.id,ts:Z.ts,stream:Z.stream,text:T0(Z.line)}}function aZ(Z,H,Q){let B=0,G=!1;if(H!==void 0){let U=Z.findIndex((j)=>j.id===H);if(U===-1)G=Z.length>0;else B=U+1}let W=Z.slice(B),O=W.slice(-Q).map(Q0),N=0,C=O.length;while(C>0){if(N+=Buffer.byteLength(JSON.stringify(O[C-1]))+1,N>H0)break;C--}let V=O.slice(C);return{lines:V,truncated:G||V.length<W.length}}function I0(Z){for(let H of Z){let Q=H.replace(/^(http:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)?\.localhost):\d+(?=\/|$)/i,"$1");if(Q!==H)return Q}return}function y0(Z,H,Q,B={}){let G=Q?.status==="running"||Q?.status==="starting",W;if(!Q||Q.status==="stopped")W=Q&&(Q.readinessStatus==="failed"||Q.lastError)?"failed":"stopped";else if(Q.status==="stopping")W="stopping";else if(Q.readinessStatus==="failed")W="failed";else if(Q.status==="starting"||!Q.upstreamReady||Q.readinessStatus==="checking")W="starting";else W="running";let O=Q?.stablePort??B.command?.port,N=!(Q?.serviceType??B.command?.serviceType)||(Q?.serviceType??B.command?.serviceType)==="http",C=N&&O?(B.cleanUrls&&Q?I0(Q.urls):void 0)??`http://localhost:${O}`:void 0,V=Boolean(Q?.share||Q?.shareStartup?.status==="starting"),U=Boolean(G&&Q?.configFingerprint&&B.fingerprint&&Q.configFingerprint!==B.fingerprint),j=[];if(!G&&W!=="stopping")j.push("start");if(G)j.push("stop","restart");if(W==="running"&&N&&B.cloudflared&&!V)j.push("share");if(V)j.push("unshare");if(C)j.push("open");return{id:`${Z}:${H}`,name:H,status:W,...O?{detail:`:${O}`}:{},...C?{url:C}:{},...U?{restartNeeded:U}:{},actions:j}}function E0(Z){return`${Z.filter((H)=>H.status==="running").length} of ${Z.length} services running`}function S0(Z,H=process.env){let Q=(G)=>{try{return c.accessSync(G,c.constants.X_OK),c.statSync(G).isFile()}catch{return!1}};if(Z.includes("/")||Z.includes("\\"))return Q(Z);let B=process.platform==="win32"?["",...(H.PATHEXT??".EXE;.CMD;.BAT").split(";")]:[""];return(H.PATH??"").split(p.delimiter).filter(Boolean).some((G)=>B.some((W)=>Q(p.join(G,Z+W))))}function f0(Z){let H=Z.coalesceMs??150,Q=Z.flushMs??50,B=Z.retryMs??x0,G=Z.warn??(()=>{}),W=new Map,O=new Map,N,C,V=!1,U,j=0,y=!1,f,T;function z(K){if(y)return;let $=JSON.stringify({jsonrpc:"2.0",...K});if(Buffer.byteLength($)>ZZ){if(!("id"in K)){G(`dropped an oversized ${String(K.method)} notification`);return}$=JSON.stringify({jsonrpc:"2.0",id:K.id,error:{code:-32603,message:"Result exceeds 1 MiB"}})}Z.write($+`
|
|
2
|
+
`)}let R=(K,$)=>z({method:K,params:$}),x=(K,$,J)=>z({id:K,error:{code:$,message:J}});function n(K,$){let J={workspaceId:K.id,...$},F=JSON.stringify(J);if(F===K.lastStatus)return;K.lastStatus=F,R("status",J)}function i(){let K=N;N=void 0,C=void 0;try{K?.close()}catch{}}function $Z(K){if(y||!W.size||N&&C===K)return;i();let $=Z.createWebSocket(`ws://127.0.0.1:${K}/api/events`);N=$,C=K,$.on("open",()=>{if(N!==$)return;if(j=0,V){V=!1;for(let J of O.values())V0(J)}}),$.on("message",(J)=>{if(N===$)F0(J)}),$.on("error",(J)=>{if(N===$)G(`event stream error: ${J.message}`)}),$.on("close",()=>{if(N!==$)return;N=void 0,C=void 0,k()})}function k(){if(y)return;V=!0;for(let K of W.values())n(K,{state:"degraded",message:_0});i(),DZ()}function DZ(){if(y||U||!W.size)return;let K=B[Math.min(j++,B.length-1)];U=setTimeout(async()=>{if(U=void 0,y||!W.size||N)return;let $=await Z.discover().catch(()=>{return});if(y||!W.size||N)return;if($)$Z($.apiPort);else DZ()},K)}function F0(K){let $;try{$=JSON.parse(String(K))}catch{return}if($.type==="snapshot")for(let J of W.values())o(J);else if($.type==="service"){for(let J of W.values())if($.service.projectId===J.projectId)o(J)}else if($.type==="service:removed"){for(let J of W.values())if($.name.startsWith(`${J.projectId}:`))o(J)}else if($.type==="log"){for(let J of O.values())if(J.serviceId===$.log.service)a(J,Q0($.log))}else if($.type==="profiles"){for(let J of W.values())if(J.projectId===$.projectId)z({jsonrpc:"2.0",method:"profiles/changed",params:{workspaceId:J.id}})}else if($.type==="history:cleared"&&$.kind==="logs"){for(let J of O.values())if(J.serviceId===$.service)a(J,{cursor:$.beforeId,ts:Date.now(),stream:"system",text:"Logs cleared in LocalDeck"})}}function o(K){if(K.timer||y)return;K.timer=setTimeout(()=>{K.timer=void 0,W0(K)},H)}async function W0(K){if(W.get(K.id)!==K)return;try{let $=await Z.discover();if(!$)return k();let J=await JZ($.api,K.projectId);if(W.get(K.id)!==K)return;BZ(K,J)}catch($){if(d($))k();else G(`could not refresh services: ${$ instanceof Error?$.message:String($)}`)}}function BZ(K,$){let J=JSON.stringify($);if(J!==K.lastItems)K.lastItems=J,R("services/changed",{workspaceId:K.id,items:$});n(K,{state:"ready",summary:E0($)})}async function G0(K,$){try{let J=await K.project($);return{details:J,runtime:J.services}}catch(J){if(d(J))throw J;return{runtime:(await K.services()).filter((F)=>F.projectId===$)}}}async function D0(K){if(!f||Date.now()-f.at>15000)f={ready:await K.forwarder().then(($)=>$.ready===!0,()=>!1),at:Date.now()};return f.ready}function B0(){if(!T||Date.now()-T.at>60000)T={available:Z.cloudflared?Z.cloudflared():S0(process.env.LOCALDECK_CLOUDFLARED??"cloudflared"),at:Date.now()};return T.available}async function JZ(K,$,J){let[{details:F,runtime:Y},X]=await Promise.all([G0(K,$),D0(K)]),_=B0(),L=new Map(Y.map((q)=>[q.name,q]));for(let q of Y)J?.set(q.key,q);let D=F?.commands??[];return[...D.map((q)=>q.name),...Y.map((q)=>q.name).filter((q)=>!D.some((s)=>s.name===q)).sort()].slice(0,q0).map((q)=>y0($,q,L.get(q),{command:D.find((s)=>s.name===q),fingerprint:F?.fingerprints?.[q],cleanUrls:X,cloudflared:_}))}async function KZ(){let K;try{K=await Z.connect()}catch($){throw k(),new I(-32000,`LocalDeck isn't responding: ${$ instanceof Error?$.message:String($)}`)}return $Z(K.apiPort),K}async function g(K){let $=await KZ();try{return await K($)}catch(J){if(J instanceof I)throw J;if(d(J))throw k(),new I(-32000,"LocalDeck isn't responding");throw new I(-32000,J instanceof Error?J.message:String(J))}}function w(K){let $=W.get(K);if(!$)throw new I(-32602,"Unknown workspace; call workspace/open first");return $}function YZ(K,$){if(!$.startsWith(`${K.projectId}:`))throw new I(-32602,"That service belongs to another project")}async function HZ(K){let $=W.get(K);if(!$)return;W.delete(K),clearTimeout($.timer);for(let F of O.values())if(F.workspaceId===K)UZ(F.id);if(!W.size)i(),clearTimeout(U),U=void 0,V=!1,j=0;let J=$.tools;if($.tools=void 0,J)await J.then((F)=>F.close(),()=>{})}async function Y0(K,$){await HZ(K);let J;try{J=c.realpathSync($)}catch{return{active:!1,reason:"The folder couldn't be found"}}let F=rZ.map((D)=>p.join(J,D)).find((D)=>{try{return c.statSync(D).isFile()}catch{return!1}});if(!F)return{active:!1,reason:"No .localdeck file in this folder"};let Y=p.basename(F);try{eZ(F)}catch(D){let h=(D instanceof Error?D.message:String(D)).replace(new RegExp(`^${Y.replace(/\./g,"\\.")}:\\s*`),"");return{active:!1,reason:`${Y} couldn't be read: ${h}`}}let{projectId:X,apiPort:_}=await g(async({api:D,apiPort:h,controlToken:q})=>{return{projectId:(await D.projects()).find((j0)=>j0.directory===J)?.id??(await D.rememberProject(F,q)).id,apiPort:h}}),L={id:K,path:J,projectId:X};return W.set(K,L),$Z(_),o(L),{active:!0}}async function O0(K,$,J){return YZ(K,$),g(async({api:F})=>{let Y=new Map,X=(await JZ(F,K.projectId,Y)).find((D)=>D.id===$);if(!X)throw new I(-32602,"Unknown service; use services/list");let _=Y.get($),L=X.name;try{if(J==="start"||J==="restart"){if(J==="restart"&&_?.managedBy==="terminal"&&_.status!=="stopped")return{ok:!1,message:A0};let D=await F.serviceAction(K.projectId,L,J);return{ok:D.ok,...D.error?{message:D.error}:{}}}if(J==="stop"){if(!_)return{ok:!1,message:"It isn't running"};let D=await F.stop($);return{ok:D.ok,...D.ok?{}:{message:"It isn't running"}}}if(J==="share"){if(!X.actions.includes("share"))return{ok:!1,message:X.actions.includes("unshare")?"It's already shared":X.status!=="running"?"Start it before sharing":"Sharing needs cloudflared"};let D=mZ.randomBytes(12).toString("base64url");return{ok:!0,url:(await F.share($,D,!0,!1)).url,secret:D}}if(J==="unshare"){let D=await F.unshare($);return{ok:D.ok,...D.ok?{}:{message:"It isn't shared"}}}return X.url?{ok:!0,url:X.url}:{ok:!1,message:"It has no address"}}catch(D){if(d(D))throw D;return{ok:!1,message:D instanceof Error?D.message:String(D)}}})}function a(K,$){if(K.pending){if(K.pending.push($),K.pending.length>m)K.pending.shift();return}if(K.queue.push($),K.lastCursor=$.cursor,K.queue.length>m)K.queue.shift(),K.dropped++;K.timer??=setTimeout(()=>U0(K),Q)}function U0(K){if(K.timer=void 0,O.get(K.id)!==K)return;let $=K.queue.splice(0),J=K.dropped;K.dropped=0;let F=[],Y=0,X=(_)=>{if(!F.length&&!(_&&J))return;R("logs/append",{subscription:K.id,lines:F,..._&&J?{dropped:J}:{}}),F=[],Y=0};for(let _ of $){let L=Buffer.byteLength(JSON.stringify(_))+1;if(Y+L>H0)X(!1);F.push(_),Y+=L}X(!0)}async function OZ(K,$){try{return await K.logs($,m)}catch(J){if(!d(J)&&/unknown service|not found/i.test(J instanceof Error?J.message:""))return[];throw J}}async function X0(K){let $=w(K.workspaceId);if(YZ($,K.serviceId),O.size>=nZ)throw new I(-32000,`At most ${nZ} log subscriptions`);let J={id:mZ.randomUUID(),workspaceId:$.id,serviceId:K.serviceId,pending:[],queue:[],dropped:0};O.set(J.id,J);try{let F=await g(({api:D})=>OZ(D,K.serviceId)),{lines:Y,truncated:X}=aZ(F,K.from,K.limit),_=new Set(F.map((D)=>D.id)),L=J.pending.filter((D)=>!_.has(D.cursor));J.pending=void 0,J.lastCursor=F.at(-1)?.id??K.from;for(let D of L)a(J,D);return{subscription:J.id,lines:Y,truncated:X}}catch(F){throw O.delete(J.id),F}}async function V0(K){try{let $=await Z.discover();if(!$||O.get(K.id)!==K)return;let J=await OZ($.api,K.serviceId);if(O.get(K.id)!==K)return;let{lines:F}=aZ(J,K.lastCursor,m);for(let Y of F)a(K,Y)}catch($){G(`could not catch up logs: ${$ instanceof Error?$.message:String($)}`)}}function UZ(K){let $=O.get(K);if(!$)return;clearTimeout($.timer),O.delete(K)}async function XZ(K){return K.tools??=(async()=>{let $=K0({projectId:K.projectId,projectSecrets:Z.projectSecrets,connect:async()=>{let X=await KZ();return{api:X.api,secrets:[X.controlToken]}}}),[J,F]=l.createLinkedPair(),Y=new WZ({name:"quarry",version:GZ});return await $.connect(F),await Y.connect(J),{client:Y,close:async()=>{await Y.close(),await $.close()}}})(),(await K.tools).client}async function N0(K,$){switch(K){case"initialize":return S(E.initialize,$),{protocolVersion:z0,name:"LocalDeck",version:GZ,capabilities:{profiles:!0,bulkActions:!0}};case"workspace/open":{let J=S(E.open,$);return Y0(J.workspaceId,J.path)}case"workspace/close":return await HZ(S(E.workspace,$).workspaceId),{};case"services/list":{let J=w(S(E.workspace,$).workspaceId),F=await g(({api:Y})=>JZ(Y,J.projectId));if(W.get(J.id)===J)BZ(J,F);return{items:F}}case"services/action":{let J=S(E.action,$);return O0(w(J.workspaceId),J.serviceId,J.action)}case"services/actionAll":{let J=S(E.actionAll,$),F=w(J.workspaceId),Y=await g(({api:X})=>X.projectAction(F.projectId,J.action));return{ok:Y.ok,results:Y.results.map((X)=>({serviceId:`${F.projectId}:${X.name}`,ok:X.ok,...X.error?{message:X.error}:{}}))}}case"profiles/list":{let J=w(S(E.workspace,$).workspaceId);return oZ(await g(({api:F})=>F.profiles(J.projectId)))}case"profiles/set":{let J=S(E.profiles,$),F=w(J.workspaceId);return oZ(await g(({api:Y})=>Y.setActiveProfiles(F.projectId,J.profiles)))}case"logs/subscribe":return X0(S(E.subscribe,$));case"logs/unsubscribe":return UZ(S(E.unsubscribe,$).subscription),{};case"pages/open":{let J=S(E.page,$),F=w(J.workspaceId);if(J.pageId!=="dashboard")throw new I(-32602,`Unknown page "${J.pageId}"`);let{apiPort:Y}=await KZ();return{url:`http://127.0.0.1:${Y}/projects/${encodeURIComponent(F.projectId)}`}}case"tools/list":{let J=w(S(E.workspace,$).workspaceId),F=(await(await XZ(J)).listTools()).tools;return{tools:cZ.flatMap((Y)=>F.filter((X)=>X.name===Y))}}case"tools/call":{let J=S(E.call,$),F=w(J.workspaceId);if(!cZ.includes(J.name))throw new I(-32602,`Unknown tool "${J.name}"`);let Y=await XZ(F);try{return await Y.callTool({name:J.name,arguments:J.arguments??{}})}catch(X){return{content:[{type:"text",text:X instanceof Error?X.message:String(X)}],isError:!0}}}case"shutdown":return{};default:throw new I(-32601,`Method not found: ${K}`)}}function P0(K){let $;try{$=JSON.parse(K)}catch{return x(null,-32700,"Parse error")}if(!$||typeof $!=="object"||Array.isArray($))return x(null,-32600,"Invalid request");let{id:J,method:F,params:Y,jsonrpc:X}=$,_=J===void 0||J===null||typeof J==="string"||typeof J==="number";if(F===void 0&&(("result"in $)||("error"in $)))return;if(X!=="2.0"||typeof F!=="string"||!_)return x(_&&J!==void 0?J:null,-32600,"Invalid request");if(J===void 0)return;N0(F,Y).then((L)=>{if(z({id:J,result:L}),F==="shutdown")NZ().finally(()=>Z.onShutdown?.())},(L)=>{if(L instanceof I)x(J,L.code,L.message);else G(L instanceof Error?L.message:String(L)),x(J,-32603,"Internal error")})}let VZ=Buffer.alloc(0),t=!1;function C0(K){if(y)return;let $=Buffer.concat([VZ,typeof K==="string"?Buffer.from(K):K]);for(let J=$.indexOf(10);J!==-1;J=$.indexOf(10)){let F=$.subarray(0,J);if($=$.subarray(J+1),t){t=!1;continue}if(F.length>ZZ){x(null,-32600,"Message exceeds 1 MiB");continue}let Y=F.toString("utf8").trim();if(Y)P0(Y)}if($.length>ZZ){if(!t)x(null,-32600,"Message exceeds 1 MiB");t=!0,$=Buffer.alloc(0)}VZ=Buffer.from($)}async function NZ(){if(y)return;let K=[...W.keys()];for(let $ of K)await HZ($);y=!0,i(),clearTimeout(U);for(let $ of O.values())clearTimeout($.timer);O.clear()}return{receive:C0,close:NZ}}var tZ=`Usage: localdeck quarry
|
|
3
|
+
Speaks the Quarry Extension Protocol (version 1) over stdio so Quarry can show and control this project's services.
|
|
4
|
+
Started by Quarry; stdout is reserved for protocol messages. Connecting starts no services.`;async function e0(Z){if(Z.includes("--help")||Z.includes("-h")){console.log(tZ);return}if(Z.length)throw Error(tZ);console.log=console.info=(...G)=>console.error(...G);let H=(G)=>({api:new J0(G.apiPort,globalThis.fetch,60000),apiPort:G.apiPort,controlToken:G.controlToken}),Q=()=>{},B=f0({connect:async()=>H(await $0()),discover:async()=>{let G=await Z0();return G?H(G):void 0},createWebSocket:(G)=>new sZ(G),write:(G)=>{process.stdout.write(G)},warn:(G)=>{process.stderr.write(`LocalDeck: ${G}
|
|
5
|
+
`)},onShutdown:()=>Q()});await new Promise((G)=>{let W=(O)=>B.receive(O);if(Q=()=>{Q=()=>{},process.stdin.removeListener("data",W),process.removeListener("SIGINT",Q),process.removeListener("SIGTERM",Q),B.close().then(()=>new Promise((O)=>process.stdout.write("",()=>O()))).finally(G)},process.stdin.on("data",W),process.stdin.once("end",()=>Q()),process.once("SIGINT",Q),process.once("SIGTERM",Q),process.stdin.readableEnded)Q()}),process.stdin.pause(),process.stdin.destroy()}export{Q0 as toLogLine,E0 as summary,aZ as selectLines,e0 as runQuarry,y0 as quarryService,S0 as findExecutable,f0 as createQuarryServer,T0 as clipText,A0 as TERMINAL_RESTART,cZ as QUARRY_TOOLS,z0 as PROTOCOL_VERSION,ZZ as MAX_MESSAGE_BYTES,_0 as DEGRADED};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{P as F,R as H,ta as C,wa as I}from"./index-
|
|
1
|
+
import{P as F,R as H,ta as C,wa as I}from"./index-hdw8ggnb.js";import{spawn as q}from"node:child_process";import X from"node:fs";import N from"node:path";import{fileURLToPath as w}from"node:url";function A(z,J,G=process.platform){let B=G==="win32"?N.win32:N.posix,Q=B.normalize(z),M=B.dirname(Q),O=(K,Y)=>G==="win32"?K.toLowerCase()===Y.toLowerCase():K===Y;if(B.basename(Q)!=="localdeck"||!O(B.basename(M),"node_modules"))throw Error("--update requires a global npm installation. Source checkouts and linked development builds must be updated manually.");if(O(M,B.normalize(J)))return G==="win32"?B.dirname(M):B.dirname(B.dirname(M));if(G!=="win32"&&B.basename(B.dirname(M))==="lib")return B.dirname(B.dirname(M));throw Error("This is not the active global npm installation. Update it with the package manager and prefix used to install it.")}function D(){let z=[N.resolve(N.dirname(process.execPath),"node_modules/npm/bin/npm-cli.js"),N.resolve(N.dirname(process.execPath),"../lib/node_modules/npm/bin/npm-cli.js")];for(let G of(process.env.PATH??"").split(N.delimiter)){if(!G)continue;z.push(N.join(G,"node_modules/npm/bin/npm-cli.js"));try{let B=X.realpathSync(N.join(G,"npm"));if(N.basename(B)==="npm-cli.js")z.push(B)}catch{}}let J=z.find((G)=>X.existsSync(G));if(!J)throw Error("npm was not found. Install Node.js with npm, then run localdeck --update again.");return[process.execPath,J]}async function $(z,J=!1){return new Promise((G,B)=>{let Q=q(z[0],z.slice(1),{stdio:J?["ignore","pipe","inherit"]:"inherit",shell:!1}),M="";Q.stdout?.on("data",(O)=>{M+=O.toString()}),Q.once("error",B),Q.once("close",(O,K)=>O===0?G(M.trim()):B(Error(`Command failed (${K??`exit ${O}`}).`)))})}function E(z,J,G=process.platform){let B=G==="win32"?N.win32:N.posix;for(let Q of[z,B.dirname(z),G==="win32"?J:B.join(J,"bin")])try{X.accessSync(Q,X.constants.W_OK|X.constants.X_OK)}catch(M){let O=M.code;if(O==="EACCES"||O==="EPERM")return!1;throw M}return!0}async function L(z){let J=z.platform??process.platform,G=z.execute??$,B=[...z.npm,"install","--global","--prefix",z.prefix,"localdeck@latest","--registry=https://registry.npmjs.org/","--ignore-scripts"];if((z.writable??E)(z.packageRoot,z.prefix,J)){await G(B);return}if(J!=="darwin"&&J!=="linux")throw Error("The installation requires administrator access. Install localdeck@latest with npm in an administrator terminal, then restart LocalDeck as your normal user.");if(!(z.interactive??Boolean(process.stdin.isTTY&&process.stdout.isTTY)))throw Error("The installation requires administrator access. Run localdeck --update in an interactive terminal so npm can request permission. Do not run LocalDeck with sudo.");(z.log??console.log)(`Administrator access is required to update ${z.prefix}. Only npm will run with sudo; LocalDeck will continue as your normal user.`),await G(["/usr/bin/sudo","-H","--",...B])}async function S(z){await z.install();let J=await z.validate();return await z.stop(),await z.start(),J}async function T(){H();let z=X.realpathSync(N.resolve(N.dirname(w(import.meta.url)),"..")),J=D(),G=await $([...J,"root","--global"],!0),B=A(z,G),Q=N.join(z,"bin/localdeck.js");console.log(`Updating LocalDeck from npm in ${B}…`),console.log("After installation, the daemon will restart. Managed services and public shares will stop; start them again when ready.");let M="",O=await S({install:async()=>{try{await L({npm:J,packageRoot:z,prefix:B})}catch(K){throw Error(`npm update failed. The daemon was not restarted. ${K instanceof Error?K.message:K}`)}},validate:async()=>{let K=await $([process.execPath,Q,"--version"],!0);if(!/^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(K))throw Error("The updated CLI could not be verified. The daemon was not restarted.");return M=K,K},stop:async()=>{let K=await C();if(!K)return;await new I(K.apiPort,globalThis.fetch,30000).shutdown();let Y=Date.now()+30000,W=()=>{try{return JSON.parse(X.readFileSync(F,"utf8")).pid===K.pid}catch(Z){if(Z.code==="ENOENT")return!1;throw Z}};while(W()){if(Date.now()>=Y)throw Error("Package updated, but the daemon did not stop within 30 seconds. Restart it manually.");await new Promise((Z)=>setTimeout(Z,150))}},start:async()=>{try{if(await $([process.execPath,Q,"daemon","start"]),(await C())?.version!==M)throw Error("Unexpected daemon version")}catch{throw Error("Package updated, but the daemon could not restart. Run localdeck daemon start to retry.")}}});console.log(`LocalDeck ${O} installed. The daemon has restarted.`)}export{T as updateLocalDeck,E as installationWritable,A as installationPrefix,L as installUpdate,S as applyUpdate};
|
package/docs/quarry.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
Quarry installs the `quarry-extension.json` manifest (shipped at the root of this repository and in the npm package) and runs `localdeck quarry` when a workspace folder contains `.localdeck`. It needs LocalDeck 1.7.0 or newer on `PATH`.
|
|
10
10
|
|
|
11
11
|
- Opening a workspace finds the remembered project for that folder. A project that isn't remembered yet is remembered the way `localdeck commands` does, without starting services.
|
|
12
|
-
- Quarry lists the services, starts, stops and restarts them, shows their logs, opens the project's dashboard page (`http://127.0.0.1:7777/projects/<id>`) and offers six MCP tools to agents: `list_services`, `get_service`, `get_logs`, `start_service`, `stop_service` and `restart_service`, restricted to that project as with `localdeck mcp --project <id>`.
|
|
12
|
+
- Quarry lists the services, starts, stops and restarts them (one at a time, or the whole project with Start all and Stop all), switches the project's profiles on this computer, shows their logs, opens the project's dashboard page (`http://127.0.0.1:7777/projects/<id>`) and offers six MCP tools to agents: `list_services`, `get_service`, `get_logs`, `start_service`, `stop_service` and `restart_service`, restricted to that project as with `localdeck mcp --project <id>`.
|
|
13
13
|
- Shares created from Quarry are always password-protected. The password is returned once.
|
|
14
14
|
- Services started with `localdeck up` in a terminal must be restarted in that terminal.
|
|
15
15
|
|
|
@@ -21,11 +21,14 @@ The Quarry Extension Protocol, version 1: newline-delimited JSON-RPC 2.0 over st
|
|
|
21
21
|
|
|
22
22
|
| Request | Params | Result |
|
|
23
23
|
| --- | --- | --- |
|
|
24
|
-
| `initialize` | `{protocolVersion: 1, quarryVersion}` | `{protocolVersion: 1, name: "LocalDeck", version}` |
|
|
24
|
+
| `initialize` | `{protocolVersion: 1, quarryVersion}` | `{protocolVersion: 1, name: "LocalDeck", version, capabilities: {profiles: true, bulkActions: true}}` |
|
|
25
25
|
| `workspace/open` | `{workspaceId, path}` | `{active, reason?}` |
|
|
26
26
|
| `workspace/close` | `{workspaceId}` | `{}` |
|
|
27
27
|
| `services/list` | `{workspaceId}` | `{items: Service[]}` |
|
|
28
28
|
| `services/action` | `{workspaceId, serviceId, action}` | `{ok, message?, url?, secret?}` |
|
|
29
|
+
| `services/actionAll` | `{workspaceId, action: "start" \| "stop"}` | `{ok, results: [{serviceId, ok, message?}]}` |
|
|
30
|
+
| `profiles/list` | `{workspaceId}` | `{profiles: [{name, description?, services, active}], active, clashes, error?}` |
|
|
31
|
+
| `profiles/set` | `{workspaceId, profiles: string[]}` (at most 50 names) | Same as `profiles/list` |
|
|
29
32
|
| `logs/subscribe` | `{workspaceId, serviceId, from?, limit?}` (limit 1–5000, default 1000) | `{subscription, lines, truncated}` |
|
|
30
33
|
| `logs/unsubscribe` | `{subscription}` | `{}` |
|
|
31
34
|
| `pages/open` | `{workspaceId, pageId: "dashboard"}` | `{url}` |
|
|
@@ -37,6 +40,7 @@ The Quarry Extension Protocol, version 1: newline-delimited JSON-RPC 2.0 over st
|
|
|
37
40
|
| --- | --- | --- |
|
|
38
41
|
| `services/changed` | `{workspaceId, items}` | The full list, at most once per 150 ms, after the project's services change. |
|
|
39
42
|
| `logs/append` | `{subscription, lines, dropped?}` | New output for a subscribed service. Clearing its logs in LocalDeck sends the system line "Logs cleared in LocalDeck". |
|
|
43
|
+
| `profiles/changed` | `{workspaceId}` | The project's active profiles changed, from Quarry, the dashboard, the CLI or an agent. Call `profiles/list` again. |
|
|
40
44
|
| `status` | `{workspaceId, state, summary?, message?}` | `ready` with "<n> of <m> services running" after changes; `degraded` while the daemon is unreachable. |
|
|
41
45
|
|
|
42
46
|
A `Service` is `{id, name, status, detail?, url?, restartNeeded?, actions}`:
|
|
@@ -47,3 +51,8 @@ A `Service` is `{id, name, status, detail?, url?, restartNeeded?, actions}`:
|
|
|
47
51
|
- `actions`: `start` when stopped or failed, `stop` and `restart` while running or starting, `share` when running and `cloudflared` is available, `unshare` when shared, and `open` when there is a URL.
|
|
48
52
|
|
|
49
53
|
A log line is `{cursor, ts, stream, text}`. The cursor is LocalDeck's log ID; pass the last one as `from` to resubscribe without repeats. `truncated` means older lines were left out, because of `limit`, the message size, or a cursor that is no longer retained. Text is limited to 16 KiB per line.
|
|
54
|
+
|
|
55
|
+
Since 1.8.0, `initialize` announces two optional features, and Quarry uses each only when it is announced:
|
|
56
|
+
|
|
57
|
+
- `bulkActions`: `services/actionAll` runs the dashboard's Start all or Stop all. Stopped services start in dependency order and running ones stop in reverse order. Terminal-owned services behave as they do in the dashboard.
|
|
58
|
+
- `profiles`: `profiles/list` and `profiles/set` read and replace the active profiles, as `localdeck profile` does. Switching never starts, stops or restarts services; running services affected by the change report `restartNeeded`. Unknown names are an error and change nothing.
|
package/package.json
CHANGED
package/dist/quarry-ysrktgjh.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import{A as R,B as gZ,C as bZ,D as kZ,E as hZ,F as uZ,G as lZ,H as dZ,I as J0,a as C,b as PZ,c as CZ,d as M,f as h,g as jZ,h as FZ,i as zZ,j as LZ,k as AZ,l as _Z,m as qZ,n as xZ,o as MZ,p as IZ,q as r,r as TZ,s as yZ,t as EZ,u as SZ,v as fZ,w as wZ,x as RZ,y as vZ,z as L}from"./index-jtd14k8h.js";import{N as tZ,U as sZ,X as rZ,ra as GZ,ta as eZ,va as Z0,wa as $0}from"./index-scnd2pq4.js";import mZ from"node:crypto";import m from"node:fs";import c from"node:path";class QZ{constructor(Z){this._client=Z}async*callToolStream(Z,H=r,Q){let B=this._client,G={...Q,task:Q?.task??(B.isToolTask(Z.name)?{}:void 0)},W=B.requestStream({method:"tools/call",params:Z},H,G),Y=B.getToolOutputValidator(Z.name);for await(let V of W){if(V.type==="result"&&Y){let P=V.result;if(!P.structuredContent&&!P.isError){yield{type:"error",error:new L(M.InvalidRequest,`Tool ${Z.name} has an output schema but did not return structured content`)};return}if(P.structuredContent)try{let X=Y(P.structuredContent);if(!X.valid){yield{type:"error",error:new L(M.InvalidParams,`Structured content does not match the tool's output schema: ${X.errorMessage}`)};return}}catch(X){if(X instanceof L){yield{type:"error",error:X};return}yield{type:"error",error:new L(M.InvalidParams,`Failed to validate structured content: ${X instanceof Error?X.message:String(X)}`)};return}}yield V}}async getTask(Z,H){return this._client.getTask({taskId:Z},H)}async getTaskResult(Z,H,Q){return this._client.getTaskResult({taskId:Z},H,Q)}async listTasks(Z,H){return this._client.listTasks(Z?{cursor:Z}:void 0,H)}async cancelTask(Z,H){return this._client.cancelTask({taskId:Z},H)}requestStream(Z,H,Q){return this._client.requestStream(Z,H,Q)}}function e(Z,H){if(!Z||H===null||typeof H!=="object")return;if(Z.type==="object"&&Z.properties&&typeof Z.properties==="object"){let Q=H,B=Z.properties;for(let G of Object.keys(B)){let W=B[G];if(Q[G]===void 0&&Object.prototype.hasOwnProperty.call(W,"default"))Q[G]=W.default;if(Q[G]!==void 0)e(W,Q[G])}}if(Array.isArray(Z.anyOf)){for(let Q of Z.anyOf)if(typeof Q!=="boolean")e(Q,H)}if(Array.isArray(Z.oneOf)){for(let Q of Z.oneOf)if(typeof Q!=="boolean")e(Q,H)}}function j0(Z){if(!Z)return{supportsFormMode:!1,supportsUrlMode:!1};let H=Z.form!==void 0,Q=Z.url!==void 0;return{supportsFormMode:H||!H&&!Q,supportsUrlMode:Q}}class WZ extends kZ{constructor(Z,H){super(H);if(this._clientInfo=Z,this._cachedToolOutputValidators=new Map,this._cachedKnownTaskTools=new Set,this._cachedRequiredTaskTools=new Set,this._listChangedDebounceTimers=new Map,this._capabilities=H?.capabilities??{},this._jsonSchemaValidator=H?.jsonSchemaValidator??new uZ,H?.listChanged)this._pendingListChangedConfig=H.listChanged}_setupListChangedHandlers(Z){if(Z.tools&&this._serverCapabilities?.tools?.listChanged)this._setupListChangedHandler("tools",TZ,Z.tools,async()=>{return(await this.listTools()).tools});if(Z.prompts&&this._serverCapabilities?.prompts?.listChanged)this._setupListChangedHandler("prompts",MZ,Z.prompts,async()=>{return(await this.listPrompts()).prompts});if(Z.resources&&this._serverCapabilities?.resources?.listChanged)this._setupListChangedHandler("resources",_Z,Z.resources,async()=>{return(await this.listResources()).resources})}get experimental(){if(!this._experimental)this._experimental={tasks:new QZ(this)};return this._experimental}registerCapabilities(Z){if(this.transport)throw Error("Cannot register capabilities after connecting to transport");this._capabilities=hZ(this._capabilities,Z)}setRequestHandler(Z,H){let B=gZ(Z)?.method;if(!B)throw Error("Schema is missing a method literal");let G=bZ(B);if(typeof G!=="string")throw Error("Schema method literal must be a string");let W=G;if(W==="elicitation/create"){let Y=async(V,P)=>{let X=R(wZ,V);if(!X.success){let x=X.error instanceof Error?X.error.message:String(X.error);throw new L(M.InvalidParams,`Invalid elicitation request: ${x}`)}let{params:O}=X.data;O.mode=O.mode??"form";let{supportsFormMode:j,supportsUrlMode:y}=j0(this._capabilities.elicitation);if(O.mode==="form"&&!j)throw new L(M.InvalidParams,"Client does not support form-mode elicitation requests");if(O.mode==="url"&&!y)throw new L(M.InvalidParams,"Client does not support URL-mode elicitation requests");let f=await Promise.resolve(H(V,P));if(O.task){let x=R(FZ,f);if(!x.success){let p=x.error instanceof Error?x.error.message:String(x.error);throw new L(M.InvalidParams,`Invalid task creation result: ${p}`)}return x.data}let I=R(RZ,f);if(!I.success){let x=I.error instanceof Error?I.error.message:String(I.error);throw new L(M.InvalidParams,`Invalid elicitation result: ${x}`)}let A=I.data,w=O.mode==="form"?O.requestedSchema:void 0;if(O.mode==="form"&&A.action==="accept"&&A.content&&w){if(this._capabilities.elicitation?.form?.applyDefaults)try{e(w,A.content)}catch{}}return A};return super.setRequestHandler(Z,Y)}if(W==="sampling/createMessage"){let Y=async(V,P)=>{let X=R(EZ,V);if(!X.success){let A=X.error instanceof Error?X.error.message:String(X.error);throw new L(M.InvalidParams,`Invalid sampling request: ${A}`)}let{params:O}=X.data,j=await Promise.resolve(H(V,P));if(O.task){let A=R(FZ,j);if(!A.success){let w=A.error instanceof Error?A.error.message:String(A.error);throw new L(M.InvalidParams,`Invalid task creation result: ${w}`)}return A.data}let f=O.tools||O.toolChoice?fZ:SZ,I=R(f,j);if(!I.success){let A=I.error instanceof Error?I.error.message:String(I.error);throw new L(M.InvalidParams,`Invalid sampling result: ${A}`)}return I.data};return super.setRequestHandler(Z,Y)}return super.setRequestHandler(Z,H)}assertCapability(Z,H){if(!this._serverCapabilities?.[Z])throw Error(`Server does not support ${Z} (required for ${H})`)}async connect(Z,H){if(await super.connect(Z),Z.sessionId!==void 0)return;try{let Q=await this.request({method:"initialize",params:{protocolVersion:PZ,capabilities:this._capabilities,clientInfo:this._clientInfo}},jZ,H);if(Q===void 0)throw Error(`Server sent invalid initialize result: ${Q}`);if(!CZ.includes(Q.protocolVersion))throw Error(`Server's protocol version is not supported: ${Q.protocolVersion}`);if(this._serverCapabilities=Q.capabilities,this._serverVersion=Q.serverInfo,Z.setProtocolVersion)Z.setProtocolVersion(Q.protocolVersion);if(this._instructions=Q.instructions,await this.notification({method:"notifications/initialized"}),this._pendingListChangedConfig)this._setupListChangedHandlers(this._pendingListChangedConfig),this._pendingListChangedConfig=void 0}catch(Q){throw this.close(),Q}}getServerCapabilities(){return this._serverCapabilities}getServerVersion(){return this._serverVersion}getInstructions(){return this._instructions}assertCapabilityForMethod(Z){switch(Z){case"logging/setLevel":if(!this._serverCapabilities?.logging)throw Error(`Server does not support logging (required for ${Z})`);break;case"prompts/get":case"prompts/list":if(!this._serverCapabilities?.prompts)throw Error(`Server does not support prompts (required for ${Z})`);break;case"resources/list":case"resources/templates/list":case"resources/read":case"resources/subscribe":case"resources/unsubscribe":if(!this._serverCapabilities?.resources)throw Error(`Server does not support resources (required for ${Z})`);if(Z==="resources/subscribe"&&!this._serverCapabilities.resources.subscribe)throw Error(`Server does not support resource subscriptions (required for ${Z})`);break;case"tools/call":case"tools/list":if(!this._serverCapabilities?.tools)throw Error(`Server does not support tools (required for ${Z})`);break;case"completion/complete":if(!this._serverCapabilities?.completions)throw Error(`Server does not support completions (required for ${Z})`);break;case"initialize":break;case"ping":break}}assertNotificationCapability(Z){switch(Z){case"notifications/roots/list_changed":if(!this._capabilities.roots?.listChanged)throw Error(`Client does not support roots list changed notifications (required for ${Z})`);break;case"notifications/initialized":break;case"notifications/cancelled":break;case"notifications/progress":break}}assertRequestHandlerCapability(Z){if(!this._capabilities)return;switch(Z){case"sampling/createMessage":if(!this._capabilities.sampling)throw Error(`Client does not support sampling capability (required for ${Z})`);break;case"elicitation/create":if(!this._capabilities.elicitation)throw Error(`Client does not support elicitation capability (required for ${Z})`);break;case"roots/list":if(!this._capabilities.roots)throw Error(`Client does not support roots capability (required for ${Z})`);break;case"tasks/get":case"tasks/list":case"tasks/result":case"tasks/cancel":if(!this._capabilities.tasks)throw Error(`Client does not support tasks capability (required for ${Z})`);break;case"ping":break}}assertTaskCapability(Z){lZ(this._serverCapabilities?.tasks?.requests,Z,"Server")}assertTaskHandlerCapability(Z){if(!this._capabilities)return;dZ(this._capabilities.tasks?.requests,Z,"Client")}async ping(Z){return this.request({method:"ping"},h,Z)}async complete(Z,H){return this.request({method:"completion/complete",params:Z},vZ,H)}async setLoggingLevel(Z,H){return this.request({method:"logging/setLevel",params:{level:Z}},h,H)}async getPrompt(Z,H){return this.request({method:"prompts/get",params:Z},xZ,H)}async listPrompts(Z,H){return this.request({method:"prompts/list",params:Z},qZ,H)}async listResources(Z,H){return this.request({method:"resources/list",params:Z},zZ,H)}async listResourceTemplates(Z,H){return this.request({method:"resources/templates/list",params:Z},LZ,H)}async readResource(Z,H){return this.request({method:"resources/read",params:Z},AZ,H)}async subscribeResource(Z,H){return this.request({method:"resources/subscribe",params:Z},h,H)}async unsubscribeResource(Z,H){return this.request({method:"resources/unsubscribe",params:Z},h,H)}async callTool(Z,H=r,Q){if(this.isToolTaskRequired(Z.name))throw new L(M.InvalidRequest,`Tool "${Z.name}" requires task-based execution. Use client.experimental.tasks.callToolStream() instead.`);let B=await this.request({method:"tools/call",params:Z},H,Q),G=this.getToolOutputValidator(Z.name);if(G){if(!B.structuredContent&&!B.isError)throw new L(M.InvalidRequest,`Tool ${Z.name} has an output schema but did not return structured content`);if(B.structuredContent)try{let W=G(B.structuredContent);if(!W.valid)throw new L(M.InvalidParams,`Structured content does not match the tool's output schema: ${W.errorMessage}`)}catch(W){if(W instanceof L)throw W;throw new L(M.InvalidParams,`Failed to validate structured content: ${W instanceof Error?W.message:String(W)}`)}}return B}isToolTask(Z){if(!this._serverCapabilities?.tasks?.requests?.tools?.call)return!1;return this._cachedKnownTaskTools.has(Z)}isToolTaskRequired(Z){return this._cachedRequiredTaskTools.has(Z)}cacheToolMetadata(Z){this._cachedToolOutputValidators.clear(),this._cachedKnownTaskTools.clear(),this._cachedRequiredTaskTools.clear();for(let H of Z){if(H.outputSchema){let B=this._jsonSchemaValidator.getValidator(H.outputSchema);this._cachedToolOutputValidators.set(H.name,B)}let Q=H.execution?.taskSupport;if(Q==="required"||Q==="optional")this._cachedKnownTaskTools.add(H.name);if(Q==="required")this._cachedRequiredTaskTools.add(H.name)}}getToolOutputValidator(Z){return this._cachedToolOutputValidators.get(Z)}async listTools(Z,H){let Q=await this.request({method:"tools/list",params:Z},IZ,H);return this.cacheToolMetadata(Q.tools),Q}_setupListChangedHandler(Z,H,Q,B){let G=yZ.safeParse(Q);if(!G.success)throw Error(`Invalid ${Z} listChanged options: ${G.error.message}`);if(typeof Q.onChanged!=="function")throw Error(`Invalid ${Z} listChanged options: onChanged must be a function`);let{autoRefresh:W,debounceMs:Y}=G.data,{onChanged:V}=Q,P=async()=>{if(!W){V(null,null);return}try{let O=await B();V(null,O)}catch(O){let j=O instanceof Error?O:Error(String(O));V(j,null)}},X=()=>{if(Y){let O=this._listChangedDebounceTimers.get(Z);if(O)clearTimeout(O);let j=setTimeout(P,Y);this._listChangedDebounceTimers.set(Z,j)}else P()};this.setNotificationHandler(H,X)}async sendRootsListChanged(){return this.notification({method:"notifications/roots/list_changed"})}}class u{constructor(){this._messageQueue=[]}static createLinkedPair(){let Z=new u,H=new u;return Z._otherTransport=H,H._otherTransport=Z,[Z,H]}async start(){while(this._messageQueue.length>0){let Z=this._messageQueue.shift();this.onmessage?.(Z.message,Z.extra)}}async close(){let Z=this._otherTransport;this._otherTransport=void 0,await Z?.close(),this.onclose?.()}async send(Z,H){if(!this._otherTransport)throw Error("Not connected");if(this._otherTransport.onmessage)this._otherTransport.onmessage(Z,{authInfo:H?.authInfo});else this._otherTransport._messageQueue.push({message:Z,extra:{authInfo:H?.authInfo}})}}var z0=1,ZZ=1048576,cZ=["list_services","get_service","get_logs","start_service","stop_service","restart_service"],L0="Restart it in the terminal that started it",A0="LocalDeck isn't responding; retrying",pZ=16384,_0=200,nZ=64,d=5000,K0=ZZ-65536,q0=[1000,2000,5000,1e4],x0=/^[a-zA-Z0-9_-]+:[a-zA-Z0-9_-]+$/;class T extends Error{code;constructor(Z,H){super(H);this.code=Z}}var g=C.string().min(1).max(200),iZ=C.string().regex(x0,"Use the exact projectId:service id from services/list"),E={initialize:C.object({protocolVersion:C.number(),quarryVersion:C.string().max(100).optional()}),open:C.object({workspaceId:g,path:C.string().min(1).max(4096).refine((Z)=>c.isAbsolute(Z),"path must be absolute")}),workspace:C.object({workspaceId:g}),action:C.object({workspaceId:g,serviceId:iZ,action:C.enum(["start","stop","restart","share","unshare","open"])}),subscribe:C.object({workspaceId:g,serviceId:iZ,from:C.string().max(200).optional(),limit:C.number().int().min(1).max(d).default(1000)}),unsubscribe:C.object({subscription:C.string().min(1).max(200)}),page:C.object({workspaceId:g,pageId:C.string().min(1).max(100)}),call:C.object({workspaceId:g,name:C.string().min(1).max(100),arguments:C.record(C.string(),C.unknown()).optional()})};function S(Z,H){let Q=Z.safeParse(H??{});if(!Q.success)throw new T(-32602,Q.error.issues.map((B)=>`${B.path.join(".")||"params"}: ${B.message}`).join("; "));return Q.data}function l(Z){return Z instanceof TypeError||Z instanceof Error&&(Z.name==="TimeoutError"||Z.name==="AbortError"||/ECONNREFUSED|ECONNRESET|did not start/.test(Z.message))}function M0(Z){if(Buffer.byteLength(Z)<=pZ)return Z;return new TextDecoder().decode(Buffer.from(Z).subarray(0,pZ-3)).replace(/�+$/,"")+"…"}function H0(Z){return{cursor:Z.id,ts:Z.ts,stream:Z.stream,text:M0(Z.line)}}function oZ(Z,H,Q){let B=0,G=!1;if(H!==void 0){let O=Z.findIndex((j)=>j.id===H);if(O===-1)G=Z.length>0;else B=O+1}let W=Z.slice(B),Y=W.slice(-Q).map(H0),V=0,P=Y.length;while(P>0){if(V+=Buffer.byteLength(JSON.stringify(Y[P-1]))+1,V>K0)break;P--}let X=Y.slice(P);return{lines:X,truncated:G||X.length<W.length}}function I0(Z){for(let H of Z){let Q=H.replace(/^(http:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)?\.localhost):\d+(?=\/|$)/i,"$1");if(Q!==H)return Q}return}function T0(Z,H,Q,B={}){let G=Q?.status==="running"||Q?.status==="starting",W;if(!Q||Q.status==="stopped")W=Q&&(Q.readinessStatus==="failed"||Q.lastError)?"failed":"stopped";else if(Q.status==="stopping")W="stopping";else if(Q.readinessStatus==="failed")W="failed";else if(Q.status==="starting"||!Q.upstreamReady||Q.readinessStatus==="checking")W="starting";else W="running";let Y=Q?.stablePort??B.command?.port,V=!(Q?.serviceType??B.command?.serviceType)||(Q?.serviceType??B.command?.serviceType)==="http",P=V&&Y?(B.cleanUrls&&Q?I0(Q.urls):void 0)??`http://localhost:${Y}`:void 0,X=Boolean(Q?.share||Q?.shareStartup?.status==="starting"),O=Boolean(G&&Q?.configFingerprint&&B.fingerprint&&Q.configFingerprint!==B.fingerprint),j=[];if(!G&&W!=="stopping")j.push("start");if(G)j.push("stop","restart");if(W==="running"&&V&&B.cloudflared&&!X)j.push("share");if(X)j.push("unshare");if(P)j.push("open");return{id:`${Z}:${H}`,name:H,status:W,...Y?{detail:`:${Y}`}:{},...P?{url:P}:{},...O?{restartNeeded:O}:{},actions:j}}function y0(Z){return`${Z.filter((H)=>H.status==="running").length} of ${Z.length} services running`}function E0(Z,H=process.env){let Q=(G)=>{try{return m.accessSync(G,m.constants.X_OK),m.statSync(G).isFile()}catch{return!1}};if(Z.includes("/")||Z.includes("\\"))return Q(Z);let B=process.platform==="win32"?["",...(H.PATHEXT??".EXE;.CMD;.BAT").split(";")]:[""];return(H.PATH??"").split(c.delimiter).filter(Boolean).some((G)=>B.some((W)=>Q(c.join(G,Z+W))))}function S0(Z){let H=Z.coalesceMs??150,Q=Z.flushMs??50,B=Z.retryMs??q0,G=Z.warn??(()=>{}),W=new Map,Y=new Map,V,P,X=!1,O,j=0,y=!1,f,I;function A(J){if(y)return;let $=JSON.stringify({jsonrpc:"2.0",...J});if(Buffer.byteLength($)>ZZ){if(!("id"in J)){G(`dropped an oversized ${String(J.method)} notification`);return}$=JSON.stringify({jsonrpc:"2.0",id:J.id,error:{code:-32603,message:"Result exceeds 1 MiB"}})}Z.write($+`
|
|
2
|
-
`)}let w=(J,$)=>A({method:J,params:$}),x=(J,$,K)=>A({id:J,error:{code:$,message:K}});function p(J,$){let K={workspaceId:J.id,...$},F=JSON.stringify(K);if(F===J.lastStatus)return;J.lastStatus=F,w("status",K)}function n(){let J=V;V=void 0,P=void 0;try{J?.close()}catch{}}function $Z(J){if(y||!W.size||V&&P===J)return;n();let $=Z.createWebSocket(`ws://127.0.0.1:${J}/api/events`);V=$,P=J,$.on("open",()=>{if(V!==$)return;if(j=0,X){X=!1;for(let K of Y.values())X0(K)}}),$.on("message",(K)=>{if(V===$)Q0(K)}),$.on("error",(K)=>{if(V===$)G(`event stream error: ${K.message}`)}),$.on("close",()=>{if(V!==$)return;V=void 0,P=void 0,b()})}function b(){if(y)return;X=!0;for(let J of W.values())p(J,{state:"degraded",message:A0});n(),DZ()}function DZ(){if(y||O||!W.size)return;let J=B[Math.min(j++,B.length-1)];O=setTimeout(async()=>{if(O=void 0,y||!W.size||V)return;let $=await Z.discover().catch(()=>{return});if(y||!W.size||V)return;if($)$Z($.apiPort);else DZ()},J)}function Q0(J){let $;try{$=JSON.parse(String(J))}catch{return}if($.type==="snapshot")for(let K of W.values())i(K);else if($.type==="service"){for(let K of W.values())if($.service.projectId===K.projectId)i(K)}else if($.type==="service:removed"){for(let K of W.values())if($.name.startsWith(`${K.projectId}:`))i(K)}else if($.type==="log"){for(let K of Y.values())if(K.serviceId===$.log.service)a(K,H0($.log))}else if($.type==="history:cleared"&&$.kind==="logs"){for(let K of Y.values())if(K.serviceId===$.service)a(K,{cursor:$.beforeId,ts:Date.now(),stream:"system",text:"Logs cleared in LocalDeck"})}}function i(J){if(J.timer||y)return;J.timer=setTimeout(()=>{J.timer=void 0,F0(J)},H)}async function F0(J){if(W.get(J.id)!==J)return;try{let $=await Z.discover();if(!$)return b();let K=await JZ($.api,J.projectId);if(W.get(J.id)!==J)return;BZ(J,K)}catch($){if(l($))b();else G(`could not refresh services: ${$ instanceof Error?$.message:String($)}`)}}function BZ(J,$){let K=JSON.stringify($);if(K!==J.lastItems)J.lastItems=K,w("services/changed",{workspaceId:J.id,items:$});p(J,{state:"ready",summary:y0($)})}async function W0(J,$){try{let K=await J.project($);return{details:K,runtime:K.services}}catch(K){if(l(K))throw K;return{runtime:(await J.services()).filter((F)=>F.projectId===$)}}}async function G0(J){if(!f||Date.now()-f.at>15000)f={ready:await J.forwarder().then(($)=>$.ready===!0,()=>!1),at:Date.now()};return f.ready}function D0(){if(!I||Date.now()-I.at>60000)I={available:Z.cloudflared?Z.cloudflared():E0(process.env.LOCALDECK_CLOUDFLARED??"cloudflared"),at:Date.now()};return I.available}async function JZ(J,$,K){let[{details:F,runtime:U},N]=await Promise.all([W0(J,$),G0(J)]),_=D0(),z=new Map(U.map((q)=>[q.name,q]));for(let q of U)K?.set(q.key,q);let D=F?.commands??[];return[...D.map((q)=>q.name),...U.map((q)=>q.name).filter((q)=>!D.some((s)=>s.name===q)).sort()].slice(0,_0).map((q)=>T0($,q,z.get(q),{command:D.find((s)=>s.name===q),fingerprint:F?.fingerprints?.[q],cleanUrls:N,cloudflared:_}))}async function KZ(){let J;try{J=await Z.connect()}catch($){throw b(),new T(-32000,`LocalDeck isn't responding: ${$ instanceof Error?$.message:String($)}`)}return $Z(J.apiPort),J}async function o(J){let $=await KZ();try{return await J($)}catch(K){if(K instanceof T)throw K;if(l(K))throw b(),new T(-32000,"LocalDeck isn't responding");throw new T(-32000,K instanceof Error?K.message:String(K))}}function v(J){let $=W.get(J);if(!$)throw new T(-32602,"Unknown workspace; call workspace/open first");return $}function YZ(J,$){if(!$.startsWith(`${J.projectId}:`))throw new T(-32602,"That service belongs to another project")}async function HZ(J){let $=W.get(J);if(!$)return;W.delete(J),clearTimeout($.timer);for(let F of Y.values())if(F.workspaceId===J)UZ(F.id);if(!W.size)n(),clearTimeout(O),O=void 0,X=!1,j=0;let K=$.tools;if($.tools=void 0,K)await K.then((F)=>F.close(),()=>{})}async function B0(J,$){await HZ(J);let K;try{K=m.realpathSync($)}catch{return{active:!1,reason:"The folder couldn't be found"}}let F=sZ.map((D)=>c.join(K,D)).find((D)=>{try{return m.statSync(D).isFile()}catch{return!1}});if(!F)return{active:!1,reason:"No .localdeck file in this folder"};let U=c.basename(F);try{rZ(F)}catch(D){let k=(D instanceof Error?D.message:String(D)).replace(new RegExp(`^${U.replace(/\./g,"\\.")}:\\s*`),"");return{active:!1,reason:`${U} couldn't be read: ${k}`}}let{projectId:N,apiPort:_}=await o(async({api:D,apiPort:k,controlToken:q})=>{return{projectId:(await D.projects()).find((C0)=>C0.directory===K)?.id??(await D.rememberProject(F,q)).id,apiPort:k}}),z={id:J,path:K,projectId:N};return W.set(J,z),$Z(_),i(z),{active:!0}}async function Y0(J,$,K){return YZ(J,$),o(async({api:F})=>{let U=new Map,N=(await JZ(F,J.projectId,U)).find((D)=>D.id===$);if(!N)throw new T(-32602,"Unknown service; use services/list");let _=U.get($),z=N.name;try{if(K==="start"||K==="restart"){if(K==="restart"&&_?.managedBy==="terminal"&&_.status!=="stopped")return{ok:!1,message:L0};let D=await F.serviceAction(J.projectId,z,K);return{ok:D.ok,...D.error?{message:D.error}:{}}}if(K==="stop"){if(!_)return{ok:!1,message:"It isn't running"};let D=await F.stop($);return{ok:D.ok,...D.ok?{}:{message:"It isn't running"}}}if(K==="share"){if(!N.actions.includes("share"))return{ok:!1,message:N.actions.includes("unshare")?"It's already shared":N.status!=="running"?"Start it before sharing":"Sharing needs cloudflared"};let D=mZ.randomBytes(12).toString("base64url");return{ok:!0,url:(await F.share($,D,!0,!1)).url,secret:D}}if(K==="unshare"){let D=await F.unshare($);return{ok:D.ok,...D.ok?{}:{message:"It isn't shared"}}}return N.url?{ok:!0,url:N.url}:{ok:!1,message:"It has no address"}}catch(D){if(l(D))throw D;return{ok:!1,message:D instanceof Error?D.message:String(D)}}})}function a(J,$){if(J.pending){if(J.pending.push($),J.pending.length>d)J.pending.shift();return}if(J.queue.push($),J.lastCursor=$.cursor,J.queue.length>d)J.queue.shift(),J.dropped++;J.timer??=setTimeout(()=>O0(J),Q)}function O0(J){if(J.timer=void 0,Y.get(J.id)!==J)return;let $=J.queue.splice(0),K=J.dropped;J.dropped=0;let F=[],U=0,N=(_)=>{if(!F.length&&!(_&&K))return;w("logs/append",{subscription:J.id,lines:F,..._&&K?{dropped:K}:{}}),F=[],U=0};for(let _ of $){let z=Buffer.byteLength(JSON.stringify(_))+1;if(U+z>K0)N(!1);F.push(_),U+=z}N(!0)}async function OZ(J,$){try{return await J.logs($,d)}catch(K){if(!l(K)&&/unknown service|not found/i.test(K instanceof Error?K.message:""))return[];throw K}}async function U0(J){let $=v(J.workspaceId);if(YZ($,J.serviceId),Y.size>=nZ)throw new T(-32000,`At most ${nZ} log subscriptions`);let K={id:mZ.randomUUID(),workspaceId:$.id,serviceId:J.serviceId,pending:[],queue:[],dropped:0};Y.set(K.id,K);try{let F=await o(({api:D})=>OZ(D,J.serviceId)),{lines:U,truncated:N}=oZ(F,J.from,J.limit),_=new Set(F.map((D)=>D.id)),z=K.pending.filter((D)=>!_.has(D.cursor));K.pending=void 0,K.lastCursor=F.at(-1)?.id??J.from;for(let D of z)a(K,D);return{subscription:K.id,lines:U,truncated:N}}catch(F){throw Y.delete(K.id),F}}async function X0(J){try{let $=await Z.discover();if(!$||Y.get(J.id)!==J)return;let K=await OZ($.api,J.serviceId);if(Y.get(J.id)!==J)return;let{lines:F}=oZ(K,J.lastCursor,d);for(let U of F)a(J,U)}catch($){G(`could not catch up logs: ${$ instanceof Error?$.message:String($)}`)}}function UZ(J){let $=Y.get(J);if(!$)return;clearTimeout($.timer),Y.delete(J)}async function XZ(J){return J.tools??=(async()=>{let $=J0({projectId:J.projectId,projectSecrets:Z.projectSecrets,connect:async()=>{let N=await KZ();return{api:N.api,secrets:[N.controlToken]}}}),[K,F]=u.createLinkedPair(),U=new WZ({name:"quarry",version:GZ});return await $.connect(F),await U.connect(K),{client:U,close:async()=>{await U.close(),await $.close()}}})(),(await J.tools).client}async function V0(J,$){switch(J){case"initialize":return S(E.initialize,$),{protocolVersion:z0,name:"LocalDeck",version:GZ};case"workspace/open":{let K=S(E.open,$);return B0(K.workspaceId,K.path)}case"workspace/close":return await HZ(S(E.workspace,$).workspaceId),{};case"services/list":{let K=v(S(E.workspace,$).workspaceId),F=await o(({api:U})=>JZ(U,K.projectId));if(W.get(K.id)===K)BZ(K,F);return{items:F}}case"services/action":{let K=S(E.action,$);return Y0(v(K.workspaceId),K.serviceId,K.action)}case"logs/subscribe":return U0(S(E.subscribe,$));case"logs/unsubscribe":return UZ(S(E.unsubscribe,$).subscription),{};case"pages/open":{let K=S(E.page,$),F=v(K.workspaceId);if(K.pageId!=="dashboard")throw new T(-32602,`Unknown page "${K.pageId}"`);let{apiPort:U}=await KZ();return{url:`http://127.0.0.1:${U}/projects/${encodeURIComponent(F.projectId)}`}}case"tools/list":{let K=v(S(E.workspace,$).workspaceId),F=(await(await XZ(K)).listTools()).tools;return{tools:cZ.flatMap((U)=>F.filter((N)=>N.name===U))}}case"tools/call":{let K=S(E.call,$),F=v(K.workspaceId);if(!cZ.includes(K.name))throw new T(-32602,`Unknown tool "${K.name}"`);let U=await XZ(F);try{return await U.callTool({name:K.name,arguments:K.arguments??{}})}catch(N){return{content:[{type:"text",text:N instanceof Error?N.message:String(N)}],isError:!0}}}case"shutdown":return{};default:throw new T(-32601,`Method not found: ${J}`)}}function N0(J){let $;try{$=JSON.parse(J)}catch{return x(null,-32700,"Parse error")}if(!$||typeof $!=="object"||Array.isArray($))return x(null,-32600,"Invalid request");let{id:K,method:F,params:U,jsonrpc:N}=$,_=K===void 0||K===null||typeof K==="string"||typeof K==="number";if(F===void 0&&(("result"in $)||("error"in $)))return;if(N!=="2.0"||typeof F!=="string"||!_)return x(_&&K!==void 0?K:null,-32600,"Invalid request");if(K===void 0)return;V0(F,U).then((z)=>{if(A({id:K,result:z}),F==="shutdown")NZ().finally(()=>Z.onShutdown?.())},(z)=>{if(z instanceof T)x(K,z.code,z.message);else G(z instanceof Error?z.message:String(z)),x(K,-32603,"Internal error")})}let VZ=Buffer.alloc(0),t=!1;function P0(J){if(y)return;let $=Buffer.concat([VZ,typeof J==="string"?Buffer.from(J):J]);for(let K=$.indexOf(10);K!==-1;K=$.indexOf(10)){let F=$.subarray(0,K);if($=$.subarray(K+1),t){t=!1;continue}if(F.length>ZZ){x(null,-32600,"Message exceeds 1 MiB");continue}let U=F.toString("utf8").trim();if(U)N0(U)}if($.length>ZZ){if(!t)x(null,-32600,"Message exceeds 1 MiB");t=!0,$=Buffer.alloc(0)}VZ=Buffer.from($)}async function NZ(){if(y)return;let J=[...W.keys()];for(let $ of J)await HZ($);y=!0,n(),clearTimeout(O);for(let $ of Y.values())clearTimeout($.timer);Y.clear()}return{receive:P0,close:NZ}}var aZ=`Usage: localdeck quarry
|
|
3
|
-
Speaks the Quarry Extension Protocol (version 1) over stdio so Quarry can show and control this project's services.
|
|
4
|
-
Started by Quarry; stdout is reserved for protocol messages. Connecting starts no services.`;async function r0(Z){if(Z.includes("--help")||Z.includes("-h")){console.log(aZ);return}if(Z.length)throw Error(aZ);console.log=console.info=(...G)=>console.error(...G);let H=(G)=>({api:new $0(G.apiPort,globalThis.fetch,60000),apiPort:G.apiPort,controlToken:G.controlToken}),Q=()=>{},B=S0({connect:async()=>H(await Z0()),discover:async()=>{let G=await eZ();return G?H(G):void 0},createWebSocket:(G)=>new tZ(G),write:(G)=>{process.stdout.write(G)},warn:(G)=>{process.stderr.write(`LocalDeck: ${G}
|
|
5
|
-
`)},onShutdown:()=>Q()});await new Promise((G)=>{let W=(Y)=>B.receive(Y);if(Q=()=>{Q=()=>{},process.stdin.removeListener("data",W),process.removeListener("SIGINT",Q),process.removeListener("SIGTERM",Q),B.close().then(()=>new Promise((Y)=>process.stdout.write("",()=>Y()))).finally(G)},process.stdin.on("data",W),process.stdin.once("end",()=>Q()),process.once("SIGINT",Q),process.once("SIGTERM",Q),process.stdin.readableEnded)Q()}),process.stdin.pause(),process.stdin.destroy()}export{H0 as toLogLine,y0 as summary,oZ as selectLines,r0 as runQuarry,T0 as quarryService,E0 as findExecutable,S0 as createQuarryServer,M0 as clipText,L0 as TERMINAL_RESTART,cZ as QUARRY_TOOLS,z0 as PROTOCOL_VERSION,ZZ as MAX_MESSAGE_BYTES,A0 as DEGRADED};
|