playingpack 0.2.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/LICENSE +90 -0
- package/README.md +500 -0
- package/dist/config-helper.d.ts +46 -0
- package/dist/config-helper.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -0
- package/package.json +81 -0
- package/public/assets/index-D239zB28.css +1 -0
- package/public/assets/index-D6X1cCDc.js +86 -0
- package/public/favicon.svg +4 -0
- package/public/index.html +14 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{program as w}from"commander";import Ye from"fastify";import Be from"@fastify/cors";import Ve from"@fastify/websocket";import{fastifyTRPCPlugin as Ze}from"@trpc/server/adapters/fastify";import Qe from"get-port";var v=class{sessions=new Map;settings;listeners=new Set;pendingResolvers=new Map;constructor(e){this.settings={pause:"off",...e}}createSession(e,n,o,r){let a={id:e,state:"LOOKUP",timestamp:new Date().toISOString(),method:n,path:o,body:r,toolCalls:[],responseContent:""};if(typeof r=="object"&&r!==null){let i=r;typeof i.model=="string"&&(a.model=i.model)}return this.sessions.set(e,a),this.emit({type:"request_update",session:a}),a}getSession(e){return this.sessions.get(e)}getAllSessions(){return Array.from(this.sessions.values())}updateState(e,n){let o=this.sessions.get(e);o&&(o.state=n,this.emit({type:"request_update",session:o}))}addToolCall(e,n){let o=this.sessions.get(e);o&&(o.toolCalls.push(n),this.emit({type:"request_update",session:o}))}updateContent(e,n){let o=this.sessions.get(e);o&&(o.responseContent+=n)}setRawResponse(e,n){let o=this.sessions.get(e);o&&(o.responseContent=n)}complete(e,n,o){let r=this.sessions.get(e);r&&(r.state="COMPLETE",r.statusCode=n,this.emit({type:"request_update",session:r}),this.emit({type:"request_complete",requestId:e,statusCode:n,cached:o}))}error(e,n){let o=this.sessions.get(e);o&&(o.state="ERROR",o.error=n,this.emit({type:"request_update",session:o}))}shouldIntercept(e){if(this.settings.pause==="off")return!1;let n=this.sessions.get(e);return n?this.settings.pause==="tool-calls"?n.toolCalls.length>0:!0:!1}async intercept(e){let n=this.sessions.get(e);if(!n)throw new Error(`Session ${e} not found`);if(this.updateState(e,"INTERCEPT"),n.toolCalls.length>0){let o=n.toolCalls[0];this.emit({type:"intercept",requestId:e,toolCall:{name:o?.name||"unknown",arguments:o?.arguments||"{}"},status:"paused"})}return new Promise(o=>{this.pendingResolvers.set(e,{resolve:r=>{let a=this.pendingResolvers.get(e);this.pendingResolvers.delete(e),o({action:r,mockContent:a?.mockContent})}})})}allowRequest(e){let n=this.pendingResolvers.get(e);return n?(this.updateState(e,"FLUSH"),n.resolve("allow"),!0):!1}mockRequest(e,n){let o=this.pendingResolvers.get(e);return o?(o.mockContent=n,this.updateState(e,"INJECT"),o.resolve("mock"),!0):!1}getSettings(){return{...this.settings}}updateSettings(e){this.settings={...this.settings,...e}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}emit(e){for(let n of this.listeners)try{n(e)}catch{}}cleanup(){let e=Array.from(this.sessions.entries());if(e.length>100){let n=e.filter(([o,r])=>r.state==="COMPLETE"||r.state==="ERROR").slice(0,e.length-100);for(let[o]of n)this.sessions.delete(o)}}removeSession(e){this.sessions.delete(e),this.pendingResolvers.delete(e)}},S=null;function A(t){return S=new v(t),S}function d(){return S||(S=new v),S}function N(t){return{sessionManager:d(),req:t?.req,res:t?.res}}import{initTRPC as Se}from"@trpc/server";import{z as L}from"zod";import{z as s}from"zod";var dt=s.object({model:s.string(),messages:s.array(s.object({role:s.enum(["system","user","assistant","tool"]),content:s.union([s.string(),s.null(),s.array(s.any())]).optional(),name:s.string().optional(),tool_calls:s.array(s.any()).optional(),tool_call_id:s.string().optional()})),temperature:s.number().optional(),top_p:s.number().optional(),n:s.number().optional(),stream:s.boolean().optional(),stop:s.union([s.string(),s.array(s.string())]).optional(),max_tokens:s.number().optional(),presence_penalty:s.number().optional(),frequency_penalty:s.number().optional(),logit_bias:s.record(s.number()).optional(),user:s.string().optional(),tools:s.array(s.object({type:s.literal("function"),function:s.object({name:s.string(),description:s.string().optional(),parameters:s.any().optional()})})).optional(),tool_choice:s.union([s.literal("none"),s.literal("auto"),s.literal("required"),s.object({type:s.literal("function"),function:s.object({name:s.string()})})]).optional(),response_format:s.object({type:s.enum(["text","json_object"])}).optional(),seed:s.number().optional()}).passthrough(),U=s.object({requestId:s.string(),type:s.enum(["text","error","tool_result"]),content:s.string()}),F=s.object({requestId:s.string()}),fe=s.object({pause:s.enum(["off","tool-calls","all"])}),q=s.object({settings:fe.partial()}),me=s.object({c:s.string(),d:s.number()}),he=s.object({id:s.string(),hash:s.string(),timestamp:s.string(),model:s.string(),endpoint:s.string()}),ft=s.object({meta:he,request:s.object({body:s.unknown()}),response:s.object({status:s.number(),chunks:s.array(me)})}),ye=s.enum(["auto","off","replay-only"]),ke=s.enum(["off","tool-calls","all"]),P=s.object({upstream:s.string().url().optional(),tapesDir:s.string().optional(),logsDir:s.string().optional(),record:ye.optional(),headless:s.boolean().optional(),port:s.number().int().min(1).max(65535).optional(),host:s.string().optional(),pause:ke.optional()});var y=Se.context().create(),$=y.router({getSessions:y.procedure.query(({ctx:t})=>({sessions:t.sessionManager.getAllSessions()})),getSession:y.procedure.input(L.object({id:L.string()})).query(({ctx:t,input:e})=>({session:t.sessionManager.getSession(e.id)||null})),getSettings:y.procedure.query(({ctx:t})=>({settings:t.sessionManager.getSettings()})),updateSettings:y.procedure.input(q).mutation(({ctx:t,input:e})=>(t.sessionManager.updateSettings(e.settings),{settings:t.sessionManager.getSettings()})),allowRequest:y.procedure.input(F).mutation(({ctx:t,input:e})=>({success:t.sessionManager.allowRequest(e.requestId)})),mockRequest:y.procedure.input(U).mutation(({ctx:t,input:e})=>({success:t.sessionManager.mockRequest(e.requestId,e.content)})),health:y.procedure.query(()=>({status:"ok",timestamp:new Date().toISOString()}))});import{createParser as be}from"eventsource-parser";var T=class{callbacks;toolCalls=new Map;accumulatedContent="";parser;constructor(e={}){this.callbacks=e,this.parser=be(n=>{n.type==="event"&&this.handleEvent(n.data)})}feed(e){this.parser.feed(e)}getContent(){return this.accumulatedContent}getToolCalls(){return Array.from(this.toolCalls.values())}hasToolCalls(){return this.toolCalls.size>0}getAssembledMessage(){let e=this.getToolCalls();return e.length>0?{role:"assistant",content:null,tool_calls:e.map(n=>({id:n.id,type:"function",function:{name:n.name,arguments:n.arguments}}))}:{role:"assistant",content:this.accumulatedContent||null}}reset(){this.toolCalls.clear(),this.accumulatedContent="",this.parser.reset()}handleEvent(e){if(e==="[DONE]"){this.callbacks.onDone?.();return}try{let n=JSON.parse(e);for(let o of n.choices){let r=o.delta;if(r.content&&(this.accumulatedContent+=r.content,this.callbacks.onContent?.(r.content)),r.tool_calls)for(let a of r.tool_calls){let i=this.toolCalls.get(a.index);if(i)a.function?.arguments&&(i.arguments+=a.function.arguments,this.callbacks.onToolCallUpdate?.(a.index,a.function.arguments));else{let c={index:a.index,id:a.id||"",name:a.function?.name||"",arguments:a.function?.arguments||""};this.toolCalls.set(a.index,c),this.callbacks.onToolCall?.(c)}}}}catch(n){this.callbacks.onError?.(n instanceof Error?n:new Error(String(n)))}}};function E(t){return new T(t)}var we="https://api.openai.com";function J(t){return!t||t.length<8?"****":`${t.substring(0,4)}...${t.substring(t.length-4)}`}function ve(t){let e=new Headers,n=["authorization","content-type","accept","openai-organization","openai-project","user-agent"];for(let o of n){let r=t[o];if(r){let a=Array.isArray(r)?r[0]:r;a&&e.set(o,a)}}return e.set("accept","text/event-stream"),e}async function M(t){let n=`${t.upstreamUrl||we}${t.path}`,o=ve(t.headers),r=typeof t.body=="object"&&t.body!==null?{...t.body,stream:!0}:t.body,a=await fetch(n,{method:t.method,headers:o,body:JSON.stringify(r)});return{status:a.status,headers:a.headers,body:a.body,ok:a.ok}}async function*H(t){let e=t.getReader(),n=new TextDecoder;try{for(;;){let{done:r,value:a}=await e.read();if(r)break;yield n.decode(a,{stream:!0})}let o=n.decode();o&&(yield o)}finally{e.releaseLock()}}import{mkdir as Re,writeFile as xe}from"fs/promises";import{dirname as Pe,join as Te}from"path";import{createHash as Ce}from"crypto";function I(t){if(t==null)return null;if(Array.isArray(t))return t.map(I);if(typeof t=="object"){let e=t,n={},o=Object.keys(e).filter(r=>!["stream","request_id","timestamp"].includes(r)).sort();for(let r of o)n[r]=I(e[r]);return n}return t}function b(t){let e=I(t),n=JSON.stringify(e);return Ce("sha256").update(n).digest("hex")}var Ee=".playingpack/tapes",C=class{tapesDir;chunks=[];lastChunkTime=0;requestBody;model="unknown";endpoint;hash="";constructor(e=Ee){this.tapesDir=e,this.endpoint="/v1/chat/completions"}start(e){if(this.requestBody=e,this.chunks=[],this.lastChunkTime=Date.now(),this.hash=b(e),typeof e=="object"&&e!==null){let n=e;typeof n.model=="string"&&(this.model=n.model)}}recordChunk(e){let n=Date.now(),o=this.chunks.length===0?0:n-this.lastChunkTime;this.chunks.push({c:e,d:o}),this.lastChunkTime=n}async save(e=200){let n={meta:{id:crypto.randomUUID(),hash:this.hash,timestamp:new Date().toISOString(),model:this.model,endpoint:this.endpoint},request:{body:this.requestBody},response:{status:e,chunks:this.chunks}},o=this.getTapePath();return await Re(Pe(o),{recursive:!0}),await xe(o,JSON.stringify(n,null,2),"utf-8"),o}getTapePath(){return Te(this.tapesDir,`${this.hash}.json`)}getHash(){return this.hash}getChunkCount(){return this.chunks.length}};import{readFile as Me,access as Ie}from"fs/promises";import{join as W}from"path";var G=".playingpack/tapes";async function z(t,e=G){let n=b(t),o=W(e,`${n}.json`);try{return await Ie(o),!0}catch{return!1}}async function _e(t,e=G){let n=b(t),o=W(e,`${n}.json`);try{let r=await Me(o,"utf-8");return JSON.parse(r)}catch{return null}}var _=class{tape;currentIndex=0;aborted=!1;constructor(e){this.tape=e}getMeta(){return this.tape.meta}getStatus(){return this.tape.response.status}abort(){this.aborted=!0}async*replay(){this.currentIndex=0,this.aborted=!1;for(let e of this.tape.response.chunks){if(this.aborted||(e.d>0&&await this.delay(e.d),this.aborted))break;yield e.c,this.currentIndex++}}async*replayFast(){for(let e of this.tape.response.chunks){if(this.aborted)break;yield e.c}}delay(e){return new Promise(n=>setTimeout(n,e))}};async function K(t,e){let n=await _e(t,e);return n?new _(n):null}function V(){return`chatcmpl-mock-${Date.now()}`}function Z(t,e=4){let n=[];for(let o=0;o<t.length;o+=e)n.push(t.slice(o,o+e));return n}function m(t){return`data: ${t}
|
|
3
|
+
|
|
4
|
+
`}function Y(t,e,n,o=null){let r={id:t,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:e,choices:[{index:0,delta:n!==null?{content:n}:{},finish_reason:o}]};return JSON.stringify(r)}function B(t,e,n,o,r,a=!1,i=null){let c={index:0};a?(c.id=n,c.type="function",c.function={name:o,arguments:r}):c.function={arguments:r};let g={id:t,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:e,choices:[{index:0,delta:{tool_calls:[c]},finish_reason:i}]};return JSON.stringify(g)}async function*Q(t,e={}){let{model:n="gpt-4",delayMs:o=20}=e,r=V(),a=Z(t),i={id:r,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:n,choices:[{index:0,delta:{role:"assistant",content:""},finish_reason:null}]};yield m(JSON.stringify(i));for(let c of a)o>0&&await ne(o),yield m(Y(r,n,c));yield m(Y(r,n,null,"stop")),yield m("[DONE]")}async function*X(t,e,n={}){let{model:o="gpt-4",delayMs:r=10}=n,a=V(),i=`call_mock_${Date.now()}`,c=Z(e,10),g={id:a,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:o,choices:[{index:0,delta:{role:"assistant",content:null},finish_reason:null}]};yield m(JSON.stringify(g)),yield m(B(a,o,i,t,c[0]||"",!0));for(let u=1;u<c.length;u++)r>0&&await ne(r),yield m(B(a,o,i,t,c[u]||""));let p={id:a,object:"chat.completion.chunk",created:Math.floor(Date.now()/1e3),model:o,choices:[{index:0,delta:{},finish_reason:"tool_calls"}]};yield m(JSON.stringify(p)),yield m("[DONE]")}function ee(t,e="invalid_request_error",n=null){return JSON.stringify({error:{message:t,type:e,param:null,code:n}})}function te(t){let e=t.trim();if(e.startsWith("ERROR:"))return{type:"error",content:e.slice(6).trim()};try{let n=JSON.parse(e);if(n&&typeof n=="object"&&"function"in n)return{type:"tool_call",functionName:n.function,content:JSON.stringify(n.arguments||{})}}catch{}return{type:"text",content:e}}function ne(t){return new Promise(e=>setTimeout(e,t))}import{appendFile as je,mkdir as De}from"fs/promises";import{join as Oe}from"path";var j=null,D=null;async function oe(t){j=t,await De(j,{recursive:!0});let e=new Date().toISOString().split("T")[0];D=Oe(j,`server-${e}.log`)}function Ae(t){let e=`[${t.timestamp}] [${t.level.toUpperCase()}] ${t.message}`;return t.data!==void 0?`${e} ${JSON.stringify(t.data)}
|
|
5
|
+
`:`${e}
|
|
6
|
+
`}async function R(t,e,n){if(!D)return;let o={timestamp:new Date().toISOString(),level:t,message:e,data:n};try{await je(D,Ae(o))}catch{}}var f={info:(t,e)=>R("info",t,e),warn:(t,e)=>R("warn",t,e),error:(t,e)=>R("error",t,e),debug:(t,e)=>R("debug",t,e)};var h={upstream:"https://api.openai.com",tapesDir:".playingpack/tapes",record:"auto"};function re(t,e){e&&(h=e),t.post("/v1/chat/completions",async(n,o)=>{await Ne(n,o)}),t.get("/health",async(n,o)=>o.send({status:"ok"})),t.all("/v1/*",async(n,o)=>{await Le(n,o)})}async function Ne(t,e){let n=d(),o=crypto.randomUUID(),r=t.body,a=t.headers.authorization||"";console.log(`[${o.slice(0,8)}] POST /v1/chat/completions`),console.log(` Model: ${r.model||"unknown"}`),console.log(` Auth: ${J(a.replace("Bearer ",""))}`),f.info("Request received",{requestId:o,path:"/v1/chat/completions",model:r.model}),n.createSession(o,"POST","/v1/chat/completions",r),n.updateState(o,"LOOKUP");let i=h.record!=="off",c=h.record==="auto",g=h.record==="replay-only";if(i&&await z(r,h.tapesDir)){console.log(" [CACHE HIT] Replaying from tape"),f.info("Cache hit",{requestId:o,model:r.model}),await Fe(t,e,o,r);return}if(g){console.log(" [REPLAY-ONLY] No tape found, rejecting request"),n.error(o,"No tape found (replay-only mode)"),e.code(404).send({error:{message:"No recorded tape found for this request (replay-only mode)",type:"tape_not_found"}});return}console.log(" [CACHE MISS] Forwarding to upstream"),f.info("Cache miss",{requestId:o,model:r.model}),n.updateState(o,"CONNECT");try{let u=await M({method:"POST",path:"/v1/chat/completions",headers:t.headers,body:r,upstreamUrl:h.upstream});if(!u.ok||!u.body){n.error(o,`Upstream error: ${u.status}`),e.code(u.status).header("content-type","application/json").send(await se(u.body));return}await Ue(t,e,o,u,r,c)}catch(u){let k=u instanceof Error?u.message:"Unknown error";console.error(` [ERROR] ${k}`),f.error("Request failed",{requestId:o,error:k}),n.error(o,k),e.code(500).send({error:{message:k,type:"proxy_error"}})}}async function Ue(t,e,n,o,r,a){let i=d(),c=a?new C(h.tapesDir):null;c?.start(r),i.updateState(n,"STREAMING");let g=E({onToolCall:l=>{i.addToolCall(n,l),console.log(` [TOOL CALL] ${l.name}`)},onContent:l=>{i.updateContent(n,l)}});if(!o.body){e.raw.end();return}let p=[],u=H(o.body);for await(let l of u)g.feed(l),c?.recordChunk(l),p.push(l);let k=g.getAssembledMessage();if(i.setRawResponse(n,JSON.stringify(k,null,2)),c)try{let l=await c.save(o.status);console.log(` [TAPE] Saved to ${l}`)}catch(l){console.error(" [TAPE ERROR] Failed to save tape:",l)}if(i.shouldIntercept(n)){console.log(" [INTERCEPT] Pausing for user action");let l=await i.intercept(n);if(console.log(` [ACTION] ${l.action}`),l.action==="mock"){await qe(e,n,l.mockContent||"");return}}e.code(o.status).header("content-type","text/event-stream").header("cache-control","no-cache").header("connection","keep-alive");for(let l of p)e.raw.write(l);i.complete(n,o.status,!1),f.info("Request completed",{requestId:n,status:o.status,cached:!1}),e.raw.end()}async function Fe(t,e,n,o){let r=d();r.updateState(n,"REPLAY");let a=await K(o,h.tapesDir);if(!a){r.error(n,"Failed to load tape"),e.code(500).send({error:{message:"Tape not found",type:"proxy_error"}});return}e.code(a.getStatus()).header("content-type","text/event-stream").header("cache-control","no-cache").header("connection","keep-alive").header("x-playingpack-cached","true");let i=E({onToolCall:p=>{r.addToolCall(n,p),console.log(` [TOOL CALL] ${p.name}`)},onContent:p=>{r.updateContent(n,p)}}),c=[];for await(let p of a.replay())i.feed(p),c.push(p),e.raw.write(p);let g=i.getAssembledMessage();r.setRawResponse(n,JSON.stringify(g,null,2)),r.complete(n,a.getStatus(),!0),f.info("Request completed",{requestId:n,status:a.getStatus(),cached:!0}),e.raw.end()}async function qe(t,e,n){let o=d(),r=te(n);if(r.type==="error"){t.code(400).header("content-type","application/json").send(ee(r.content)),o.complete(e,400,!1);return}t.code(200).header("content-type","text/event-stream").header("cache-control","no-cache").header("connection","keep-alive").header("x-playingpack-mocked","true");let a=r.type==="tool_call"?X(r.functionName||"mock_function",r.content):Q(r.content);for await(let i of a)t.raw.write(i);o.complete(e,200,!1),t.raw.end()}async function Le(t,e){try{let n=await M({method:t.method,path:t.url,headers:t.headers,body:t.body,upstreamUrl:h.upstream});if(e.code(n.status),n.headers.forEach((o,r)=>{["content-encoding","transfer-encoding"].includes(r.toLowerCase())||e.header(r,o)}),n.body){let o=await se(n.body);e.send(o)}else e.send()}catch(n){let o=n instanceof Error?n.message:"Unknown error";e.code(500).send({error:{message:o,type:"proxy_error"}})}}async function se(t){if(!t)return"";let e=t.getReader(),n=new TextDecoder,o="";for(;;){let{done:r,value:a}=await e.read();if(r)break;o+=n.decode(a,{stream:!0})}return o}import{join as ae,dirname as $e}from"path";import{fileURLToPath as Je}from"url";import{access as He}from"fs/promises";import We from"@fastify/static";var Ge=$e(Je(import.meta.url));function ie(){return ae(Ge,"../public")}async function ze(){try{return await He(ae(ie(),"index.html")),!0}catch{return!1}}async function ce(t){let e=ie();return await ze()?(await t.register(We,{root:e,prefix:"/",wildcard:!1}),t.setNotFoundHandler((o,r)=>o.url.startsWith("/v1")||o.url.startsWith("/api")||o.url.startsWith("/ws")?r.code(404).send({error:"Not Found"}):r.sendFile("index.html")),console.log(" UI available at root path"),!0):(console.log(" UI not available (development mode)"),console.log(" Run the UI separately: cd packages/web && pnpm dev"),!1)}var x=new Set;function le(t){x.add(t);let e=d(),n=e.getAllSessions();for(let r of n)O(t,{type:"request_update",session:r});let o=e.subscribe(r=>{O(t,r)});t.on("message",r=>{try{let a=JSON.parse(r.toString());Ke(t,a)}catch{}}),t.on("close",()=>{x.delete(t),o()}),t.on("error",()=>{x.delete(t),o()})}function Ke(t,e){if(typeof e!="object"||e===null)return;let n=e,o=d();switch(n.type){case"allow":typeof n.requestId=="string"&&o.allowRequest(n.requestId);break;case"mock":typeof n.requestId=="string"&&typeof n.content=="string"&&o.mockRequest(n.requestId,n.content);break;case"ping":O(t,{type:"pong"});break}}function O(t,e){if(t.readyState===t.OPEN)try{t.send(JSON.stringify(e))}catch{x.delete(t)}}async function ue(t){let e=t.port,n=t.host;A({pause:t.pause}),await oe(t.logsDir),await f.info("Server starting",{upstream:t.upstream,tapesDir:t.tapesDir,record:t.record,headless:t.headless});let o=await Qe({port:e});o!==e&&console.log(` Port ${e} in use, using ${o}`);let r=Ye({logger:!1,bodyLimit:50*1024*1024});return await r.register(Be,{origin:!0,credentials:!0}),await r.register(Ve),r.get("/ws",{websocket:!0},a=>{le(a)}),await r.register(Ze,{prefix:"/api/trpc",trpcOptions:{router:$,createContext:N}}),re(r,{upstream:t.upstream,tapesDir:t.tapesDir,record:t.record}),t.headless?console.log(" Running in headless mode (no UI)"):await ce(r),await r.listen({port:o,host:n}),await f.info("Server listening",{port:o,host:n}),{server:r,port:o,host:n}}import{readFile as Xe}from"fs/promises";import{existsSync as et}from"fs";import{join as pe}from"path";import{createJiti as tt}from"jiti";var nt=["playingpack.config.ts","playingpack.config.mts","playingpack.config.js","playingpack.config.mjs"],ot=["playingpack.config.jsonc","playingpack.config.json",".playingpackrc.json",".playingpackrc"],rt={upstream:"https://api.openai.com",tapesDir:".playingpack/tapes",logsDir:".playingpack/logs",record:"auto",headless:!1,port:4747,host:"0.0.0.0",pause:"off"};function st(t){let e=t.replace(/\/\*[\s\S]*?\*\//g,"");return e=e.replace(/(?<!["'])\/\/.*$/gm,""),e}async function at(t){for(let e of nt){let n=pe(t,e);if(et(n))try{let r=await tt(import.meta.url,{interopDefault:!0}).import(n),a=r&&typeof r=="object"&&"default"in r?r.default:r;if(!a||typeof a!="object"){console.warn(` Warning: ${e} must export a config object`);continue}return{config:P.parse(a),filename:e}}catch(o){console.warn(` Warning: Error loading ${e}:`,o.message)}}return null}async function it(t){for(let e of ot){let n=pe(t,e);try{let o=await Xe(n,"utf-8"),r=JSON.parse(st(o));return{config:P.parse(r),filename:e}}catch(o){o.code!=="ENOENT"&&(o instanceof SyntaxError||o.name==="ZodError")&&console.warn(` Warning: Invalid config in ${e}:`,o.message)}}return null}async function ct(t){let e=await at(t);if(e)return console.log(` Config loaded from ${e.filename}`),e.config;let n=await it(t);return n?(console.log(` Config loaded from ${n.filename}`),n.config):{}}async function ge(t={}){let e=process.cwd(),n=await ct(e),o={...rt,...n};return t.port!==void 0&&(o.port=t.port),t.host!==void 0&&(o.host=t.host),t.ui!==void 0&&(o.headless=!t.ui),t.upstream!==void 0&&(o.upstream=t.upstream),t.tapesDir!==void 0&&(o.tapesDir=t.tapesDir),t.record!==void 0&&(o.record=t.record),o}var de="1.0.0";w.name("playingpack").description("Chrome DevTools for AI Agents - Local reverse proxy and debugger").version(de);w.command("start").description("Start the PlayingPack proxy server").option("-p, --port <port>","Port to listen on").option("-h, --host <host>","Host to bind to").option("--no-ui","Run without UI server (headless mode for CI/CD)").option("--upstream <url>","Upstream API URL (default: https://api.openai.com)").option("--tapes-dir <path>","Directory for tape storage (default: .playingpack/tapes)").option("--record <mode>","Recording mode: auto, off, replay-only (default: auto)").action(async t=>{console.log(),console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),console.log(" \u2551 \u2551"),console.log(" \u2551 \u2593\u2593\u2593\u2593 PlayingPack - The Flight Simulator \u2593\u2593\u2593\u2593 \u2551"),console.log(" \u2551 Chrome DevTools for AI Agents \u2551"),console.log(" \u2551 \u2551"),console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),console.log();try{let e=await ge({port:t.port?parseInt(t.port,10):void 0,host:t.host,ui:t.ui,upstream:t.upstream,tapesDir:t.tapesDir,record:t.record}),{port:n,host:o}=await ue(e),r=`http://localhost:${n}`,a=o==="0.0.0.0"?`http://<your-ip>:${n}`:`http://${o}:${n}`;console.log(),console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),console.log(" \u2502 Server running! \u2502"),console.log(" \u2502 \u2502"),console.log(` \u2502 Local: ${r.padEnd(44)}\u2502`),console.log(` \u2502 Network: ${a.padEnd(44)}\u2502`),console.log(" \u2502 \u2502"),console.log(" \u2502 To use with your AI agent, set: \u2502"),console.log(` \u2502 baseURL = "${r}/v1"`.padEnd(60)+"\u2502"),e.headless||(console.log(" \u2502 \u2502"),console.log(" \u2502 Dashboard: Open the local URL in your browser \u2502")),console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"),console.log(),console.log(" Waiting for requests..."),console.log();let i=async()=>{console.log(`
|
|
7
|
+
Shutting down...`),process.exit(0)};process.on("SIGINT",i),process.on("SIGTERM",i)}catch(e){console.error("Failed to start server:",e),process.exit(1)}});w.command("version").description("Show version").action(()=>{console.log(`playingpack v${de}`)});process.argv.length===2?w.parse(["node","playingpack","start"]):w.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "playingpack",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Chrome DevTools for AI Agents - Local reverse proxy and debugger for LLM API calls",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"playingpack": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/config-helper.js",
|
|
10
|
+
"types": "./dist/config-helper.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/config-helper.d.ts",
|
|
14
|
+
"import": "./dist/config-helper.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"public",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/playingpack/playingpack.git"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/playingpack/playingpack#readme",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/playingpack/playingpack/issues"
|
|
30
|
+
},
|
|
31
|
+
"author": "Geoptly Intelligence Inc.",
|
|
32
|
+
"scripts": {
|
|
33
|
+
"dev": "tsx watch src/index.ts",
|
|
34
|
+
"build": "tsup",
|
|
35
|
+
"start": "node dist/index.js",
|
|
36
|
+
"test": "vitest",
|
|
37
|
+
"test:run": "vitest run",
|
|
38
|
+
"test:coverage": "vitest run --coverage",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"prepublishOnly": "cd ../.. && pnpm run build:all"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"jiti": "^2.4.2",
|
|
44
|
+
"@fastify/cors": "^9.0.1",
|
|
45
|
+
"@fastify/static": "^7.0.1",
|
|
46
|
+
"@fastify/websocket": "^10.0.1",
|
|
47
|
+
"@trpc/server": "^10.45.0",
|
|
48
|
+
"commander": "^12.0.0",
|
|
49
|
+
"eventsource-parser": "^1.1.2",
|
|
50
|
+
"fastify": "^4.26.0",
|
|
51
|
+
"get-port": "^7.0.0",
|
|
52
|
+
"ws": "^8.16.0",
|
|
53
|
+
"zod": "^3.22.4"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^20.11.0",
|
|
57
|
+
"@types/ws": "^8.5.10",
|
|
58
|
+
"tsup": "^8.0.1",
|
|
59
|
+
"tsx": "^4.7.0",
|
|
60
|
+
"typescript": "^5.3.3",
|
|
61
|
+
"@vitest/coverage-v8": "^1.2.0",
|
|
62
|
+
"vitest": "^1.2.0"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=20.0.0"
|
|
66
|
+
},
|
|
67
|
+
"keywords": [
|
|
68
|
+
"ai",
|
|
69
|
+
"llm",
|
|
70
|
+
"openai",
|
|
71
|
+
"proxy",
|
|
72
|
+
"debugger",
|
|
73
|
+
"mock",
|
|
74
|
+
"testing"
|
|
75
|
+
],
|
|
76
|
+
"license": "BUSL-1.1",
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public",
|
|
79
|
+
"registry": "https://registry.npmjs.org/"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap";*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,system-ui,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,SF Mono,Monaco,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-1\.5{margin-left:.375rem}.ml-2{margin-left:.5rem}.mt-1{margin-top:.25rem}.flex{display:flex}.inline-flex{display:inline-flex}.hidden{display:none}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-64{height:16rem}.h-full{height:100%}.h-screen{height:100vh}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-80{width:20rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-amber-500\/30{border-color:#f59e0b4d}.border-blue-400{--tw-border-opacity: 1;border-color:rgb(96 165 250 / var(--tw-border-opacity, 1))}.border-blue-500\/30{border-color:#3b82f64d}.border-cyan-500\/30{border-color:#06b6d44d}.border-gray-500\/30{border-color:#6b72804d}.border-green-500\/30{border-color:#22c55e4d}.border-pp-light{--tw-border-opacity: 1;border-color:rgb(42 42 42 / var(--tw-border-opacity, 1))}.border-purple-500\/20{border-color:#a855f733}.border-purple-500\/30{border-color:#a855f74d}.border-red-500\/20{border-color:#ef444433}.border-red-500\/30{border-color:#ef44444d}.border-teal-500\/30{border-color:#14b8a64d}.border-l-amber-500{--tw-border-opacity: 1;border-left-color:rgb(245 158 11 / var(--tw-border-opacity, 1))}.border-l-blue-500{--tw-border-opacity: 1;border-left-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-l-transparent{border-left-color:transparent}.bg-amber-500{--tw-bg-opacity: 1;background-color:rgb(245 158 11 / var(--tw-bg-opacity, 1))}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-cyan-500\/10{background-color:#06b6d41a}.bg-gray-500\/10{background-color:#6b72801a}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-pp-accent{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-pp-dark{--tw-bg-opacity: 1;background-color:rgb(15 15 15 / var(--tw-bg-opacity, 1))}.bg-pp-darker{--tw-bg-opacity: 1;background-color:rgb(10 10 10 / var(--tw-bg-opacity, 1))}.bg-pp-gray{--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1))}.bg-purple-500\/10{background-color:#a855f71a}.bg-purple-600{--tw-bg-opacity: 1;background-color:rgb(147 51 234 / var(--tw-bg-opacity, 1))}.bg-purple-900\/50{background-color:#581c8780}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-teal-500\/10{background-color:#14b8a61a}.p-1{padding:.25rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.text-center{text-align:center}.font-mono{font-family:JetBrains Mono,SF Mono,Monaco,monospace}.font-sans{font-family:Inter,system-ui,sans-serif}.text-\[10px\]{font-size:10px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.tracking-tight{letter-spacing:-.025em}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-pp-accent{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-teal-400{--tw-text-opacity: 1;color:rgb(45 212 191 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-50{opacity:.5}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}:root{font-size:14px;line-height:1.5;font-weight:400;color:#e0e0e0;background-color:#0f0f0f;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;min-height:100vh}#root{min-height:100vh}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#1a1a1a}::-webkit-scrollbar-thumb{background:#3a3a3a;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#4a4a4a}.json-key{color:#7dd3fc}.json-string{color:#86efac}.json-number{color:#fbbf24}.json-boolean{color:#c084fc}.json-null{color:#94a3b8}.status-pending{color:#fbbf24}.status-success{color:#22c55e}.status-error{color:#ef4444}.status-paused{color:#3b82f6}@keyframes pulse-border{0%,to{border-color:#3b82f680}50%{border-color:#3b82f6}}.paused-border{animation:pulse-border 2s ease-in-out infinite}.hover\:bg-green-700:hover{--tw-bg-opacity: 1;background-color:rgb(21 128 61 / var(--tw-bg-opacity, 1))}.hover\:bg-pp-gray\/50:hover{background-color:#1a1a1a80}.hover\:bg-pp-light:hover{--tw-bg-opacity: 1;background-color:rgb(42 42 42 / var(--tw-bg-opacity, 1))}.hover\:bg-purple-700:hover{--tw-bg-opacity: 1;background-color:rgb(126 34 206 / var(--tw-bg-opacity, 1))}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-300:hover{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}
|