publishport-opencli 1.8.5-pp.26 → 1.8.5-pp.27

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.
@@ -1 +1 @@
1
- import{openSync as $,closeSync as g,writeSync as B,readFileSync as q,unlinkSync as V,statSync as H,mkdirSync as j}from"node:fs";import{join as G}from"node:path";import{homedir as v}from"node:os";const J=G(v(),".opencli"),W=G(J,"browser-adapter-session.lock");function w(x){if(!x)return W;const b=x.replace(/[^A-Za-z0-9_-]/g,"_");return G(J,`browser-adapter-session.${b}.lock`)}const N=180000,X=600000,D=60;function Q(x){return new Promise((b)=>setTimeout(b,x))}function m(x){try{process.kill(x,0);return!0}catch(b){return b?.code==="EPERM"}}function u(x){try{const b=parseInt(q(x,"utf8").trim(),10);return Number.isFinite(b)?b:null}catch{return null}}function Y(x){try{const b=$(x,"wx");try{B(b,String(process.pid))}finally{g(b)}return!0}catch(b){if(b?.code==="EEXIST")return!1;throw b}}function F(x){try{const b=u(x),z=Date.now()-H(x).mtimeMs;if(b!==null&&!m(b)||z>X)V(x)}catch{}}export async function withBrowserSessionLock(x,b){try{j(J,{recursive:!0})}catch{}const z=w(b),Z=Date.now()+N;while(!Y(z)){F(z);if(Y(z))break;if(Date.now()>=Z)throw Error(`browser session lock busy: waited ${Math.round(N/1000)}s for another browser command to finish`);await Q(D)}try{return await x()}finally{try{V(z)}catch{}}}export async function withBrowserSessionLockIf(x,b,z){return x?withBrowserSessionLock(b,z):b()}export const __lockInternals={LOCK_PATH:W,ACQUIRE_TIMEOUT_MS:N,STALE_AFTER_MS:X};
1
+ import{openSync as q,closeSync as H,writeSync as b,readFileSync as j,unlinkSync as Y,statSync as v,mkdirSync as D,utimesSync as Q}from"node:fs";import{join as N}from"node:path";import{homedir as F}from"node:os";const V=N(F(),".opencli"),Z=N(V,"browser-adapter-session.lock");function m(z){if(!z)return Z;const x=z.replace(/[^A-Za-z0-9_-]/g,"_");return N(V,`browser-adapter-session.${x}.lock`)}const W=180000,$=600000,u=60;function w(z){return new Promise((x)=>setTimeout(x,z))}function K(z){try{process.kill(z,0);return!0}catch(x){return x?.code==="EPERM"}}function U(z){try{const x=parseInt(j(z,"utf8").trim(),10);return Number.isFinite(x)?x:null}catch{return null}}function g(z){try{const x=q(z,"wx");try{b(x,String(process.pid))}finally{H(x)}return!0}catch(x){if(x?.code==="EEXIST")return!1;throw x}}function C(z){try{const x=U(z),G=Date.now()-v(z).mtimeMs;if(x!==null&&!K(x)||G>$)Y(z)}catch{}}export async function withBrowserSessionLock(z,x){try{D(V,{recursive:!0})}catch{}const G=m(x),B=Date.now()+W;while(!g(G)){C(G);if(g(G))break;if(Date.now()>=B)throw Error(`browser session lock busy: waited ${Math.round(W/1000)}s for another browser command to finish`);await w(u)}const J=setInterval(()=>{try{const X=new Date;Q(G,X,X)}catch{}},60000);if(typeof J.unref==="function")J.unref();try{return await z()}finally{clearInterval(J);try{Y(G)}catch{}}}export async function withBrowserSessionLockIf(z,x,G){return z?withBrowserSessionLock(x,G):x()}export const __lockInternals={LOCK_PATH:Z,ACQUIRE_TIMEOUT_MS:W,STALE_AFTER_MS:$};
@@ -1,3 +1,3 @@
1
- import{mkdir as q,readFile as N,writeFile as H}from"node:fs/promises";import{homedir as P}from"node:os";import{dirname as b,join as _}from"node:path";import{pathToFileURL as u}from"node:url";import{InvalidArgumentError as I,Option as l}from"commander";import{AuthRequiredError as F,CliError as x,getErrorMessage as v}from"../errors.js";import{executeCommand as Q}from"../execution.js";import{fullName as n,getRegistry as T}from"../registry.js";import{render as C}from"../output.js";const z=1,U=86400000;function j(B,D,G){if(B===void 0||B===null||B==="")return G;const X=Number(B);if(!Number.isInteger(X)||X<=0)throw new I(`${D} must be a positive integer. Received: "${String(B)}"`);return X}function E(B){if(!B||!B.trim())return null;const D=B.split(",").map((G)=>G.trim()).filter(Boolean);return D.length>0?new Set(D):null}function p(){return _(P(),".opencli","auth-refresh.json")}function i(){return{version:z,sites:{}}}async function m(B){try{const D=JSON.parse(await N(B,"utf8"));if(D&&D.version===z&&D.sites&&typeof D.sites==="object")return{version:z,sites:D.sites}}catch(D){if(D.code!=="ENOENT")throw D}return i()}async function c(B,D){await q(b(B),{recursive:!0});await H(B,`${JSON.stringify(D,null,2)}
2
- `,"utf8")}function k(B){if(!B?.last_touched_at)return null;const D=Date.parse(B.last_touched_at);return Number.isFinite(D)?D:null}function a(B,D){const G=k(B);return G!==null&&D.getTime()-G<U}function L(B){const D=k(B);return D===null?"":new Date(D+U).toISOString()}function A(){const B=new Set;return[...T().values()].filter((D)=>{if(B.has(D))return!1;B.add(D);return D.name==="whoami"&&D.access==="read"}).sort((D,G)=>D.site.localeCompare(G.site))}async function V(B){const D=B;if(!D._lazy||!D._modulePath)return B;await import(u(D._modulePath).href);return T().get(n(B))??B}function M(B,D){const G=B.args.some((X)=>X.name==="timeout");return{...B,args:G?B.args:[...B.args,{name:"timeout",type:"int",default:D,help:"Per-site auth command timeout in seconds"}]}}function d(B,D){if(B.browser!==!0||typeof B.authStatus?.quickCheck!=="function")return null;return M({...B,func:B.authStatus.quickCheck,navigateBefore:!1,siteSession:"ephemeral",defaultWindowMode:"background"},D)}function R(B){if(typeof B==="boolean")return B;if(B&&typeof B==="object"&&!Array.isArray(B)){const D=B.logged_in;if(typeof D==="boolean")return D}return null}function S(B){if(B===void 0||B===null)return"";if(typeof B==="string"||typeof B==="number"||typeof B==="boolean")return String(B);return""}function w(B){if(!B||typeof B!=="object"||Array.isArray(B))return"";const D=B,G=/(?:email|phone|real.?name|first.?name|last.?name|cookie|token|session|secret|password|csrf|jwt|bearer|wt2)/i;for(const X of["username","handle","user_id","id","name","nickname","user_type","url"]){if(G.test(X))continue;const J=S(D[X]);if(J)return J}for(const[X,J]of Object.entries(D)){if(X==="site"||X==="logged_in"||G.test(X))continue;const Y=S(J);if(Y)return Y}return""}function O(B){return B instanceof F||typeof B==="object"&&B!==null&&B.code==="AUTH_REQUIRED"}function f(B,D,G){if(O(G))return{site:B,status:"not_logged_in",logged_in:!1,identity:"",checked:D,error:""};const X=G instanceof x?G.code:"",J=v(G);return{site:B,status:"error",logged_in:"",identity:"",checked:D,error:X?`${X}: ${J}`:J}}function o(B,D){if(B.browser!==!0)return null;let G=B.authStatus?.refresh;if(typeof G!=="function"){const X=B.authStatus?.quickCheck;if(typeof X!=="function"||!B.domain)return null;const J=B.domain.startsWith("http://")||B.domain.startsWith("https://")?B.domain:`https://${B.domain}`;G=async(Y,Z,$)=>{await Y.goto(J);await Y.wait(1);if(R(await X(Y,Z,$))!==!0)throw new F(B.domain??B.site,`Auth refresh quickCheck failed for ${B.site}`);return{status:"touched"}}}return M({...B,func:G,navigateBefore:!1,siteSession:"persistent",defaultWindowMode:"background"},D)}function s(B){if(B&&typeof B==="object"&&!Array.isArray(B)){const D=B;if(D.status==="refreshed"||D.refreshed===!0)return"refreshed"}return"touched"}function r(B,D,G){if(O(G))return{site:B,status:"not_logged_in",last_touched_at:D?.last_touched_at??"",next_refresh_at:L(D),error:""};const X=G instanceof x?G.code:"",J=v(G);return{site:B,status:"error",last_touched_at:D?.last_touched_at??"",next_refresh_at:L(D),error:X?`${X}: ${J}`:J}}function y(){return process.env.PUBLISHPORT_SITE_SESSION==="ephemeral"?{siteSession:"ephemeral",keepTab:"false"}:{siteSession:"persistent",keepTab:"true"}}function g(B){return _(P(),".opencli","sites",B,"quickcheck-cache.json")}async function t(B,D,G){try{const J=JSON.parse(await N(g(B),"utf8"))?.[G??"default"];if(!J||J.logged_in!==!0)return!1;const Y=Date.now()-Date.parse(J.checked_at);return Number.isFinite(Y)&&Y>=0&&Y<D*1000}catch{return!1}}async function e(B,D,G){const X=g(B),J=G??"default";let Y={};try{Y=JSON.parse(await N(X,"utf8"))}catch{}if(D)Y[J]={logged_in:!0,checked_at:new Date().toISOString()};else delete Y[J];try{await q(b(X),{recursive:!0});await H(X,`${JSON.stringify(Y,null,2)}
3
- `,"utf8")}catch{}}async function BB(B,D){try{const G=await V(B);if(G.browser!==!0){const $=M(G,D.timeoutSeconds),W=await Q($,{timeout:D.timeoutSeconds},!1,{...D.profile?{profile:D.profile}:{}});return{site:B.site,status:"logged_in",logged_in:!0,identity:w(W),checked:"quick",error:""}}const X=G.authStatus?.quickCheckCacheTtlSeconds;if(typeof X==="number"&&X>0&&await t(B.site,X,D.profile))return{site:B.site,status:"logged_in",logged_in:!0,identity:"",checked:"quick",error:""};const J=d(G,D.timeoutSeconds);if(!J)return{site:B.site,status:"unknown",logged_in:"",identity:"",checked:"skipped",error:"quickCheck not implemented; use --full to run whoami"};const Y=await Q(J,{timeout:D.timeoutSeconds},!1,{...y(),windowMode:"background",...D.profile?{profile:D.profile}:{}}),Z=R(Y);if(typeof X==="number"&&X>0&&typeof Z==="boolean")await e(B.site,Z,D.profile);if(Z===!0)return{site:B.site,status:"logged_in",logged_in:!0,identity:"",checked:"quick",error:""};if(Z===!1)return{site:B.site,status:"not_logged_in",logged_in:!1,identity:"",checked:"quick",error:""};return{site:B.site,status:"unknown",logged_in:"",identity:"",checked:"quick",error:"quickCheck returned no boolean logged_in signal"}}catch(G){return f(B.site,"quick",G)}}async function DB(B,D){try{const G=await V(B),X=M(G,D.timeoutSeconds),J=await Q(X,{timeout:D.timeoutSeconds},!1,{...y(),windowMode:"background",...D.profile?{profile:D.profile}:{}});return{site:B.site,status:"logged_in",logged_in:!0,identity:w(J),checked:"full",error:""}}catch(G){return f(B.site,"full",G)}}function GB(B,D){return D?`${B}@${D}`:B}async function JB(B,D){const G=GB(B.site,D.profile),X=D.state.sites[G];if(!D.force&&a(X,D.now))return{site:B.site,status:"skipped",last_touched_at:X?.last_touched_at??"",next_refresh_at:L(X),error:""};const J=D.now.toISOString();try{const Y=await V(B),Z=o(Y,D.timeoutSeconds);if(!Z){D.state.sites[G]={...X,last_attempt_at:J,last_status:"unsupported"};return{site:B.site,status:"unsupported",last_touched_at:X?.last_touched_at??"",next_refresh_at:L(X),error:"refresh probe is not available for this site"}}const $=await Q(Z,{timeout:D.timeoutSeconds},!1,{siteSession:"persistent",keepTab:"true",windowMode:"background",...D.profile?{profile:D.profile}:{}}),W=s($);D.state.sites[G]={...X,last_attempt_at:J,last_touched_at:J,last_status:W};return{site:B.site,status:W,last_touched_at:J,next_refresh_at:new Date(D.now.getTime()+U).toISOString(),error:""}}catch(Y){const Z=O(Y)?"not_logged_in":"error";D.state.sites[G]={...X,last_attempt_at:J,last_status:Z};return r(B.site,X,Y)}}async function h(B,D,G){const X=Array(B.length);let J=0;const Y=Array.from({length:Math.min(D,B.length)},async()=>{while(J<B.length){const Z=J++;X[Z]=await G(B[Z])}});await Promise.all(Y);return X}export async function collectAuthStatus(B){const D=E(B.sites),G=B.full?"full":"quick",X=j(B.concurrency,"--concurrency",G==="full"?3:8),J=j(B.timeout,"--timeout",G==="full"?20:8),Y=String(B.only??"all");if(!["all","logged-in","not-logged-in","unknown","error"].includes(Y))throw new I("--only must be one of: all, logged-in, not-logged-in, unknown, error");const Z=A().filter((K)=>!D||D.has(K.site)),$=await h(Z,X,(K)=>G==="full"?DB(K,{timeoutSeconds:J,profile:B.profile}):BB(K,{timeoutSeconds:J,profile:B.profile})),W=Y.replace(/-/g,"_");return W==="all"?$:$.filter((K)=>K.status===W)}export async function collectAuthRefresh(B){const D=E(B.sites),G=j(B.concurrency,"--concurrency",3),X=j(B.timeout,"--timeout",20),J=B.statePath??p(),Y=B.now??new Date,Z=await m(J),$=A().filter((K)=>!D||D.has(K.site)),W=await h($,G,(K)=>JB(K,{timeoutSeconds:X,profile:B.profile,now:Y,state:Z,force:B.all===!0}));await c(J,Z);return W}export function registerAuthCommands(B){const D=B.command("auth").description("Inspect website login status"),G=D.command("status").description("Show login status for sites with auth adapters").option("--site <sites>","Comma-separated site names to check, e.g. github,chatgpt").option("--full","Run full per-site whoami probes instead of quick no-navigation checks",!1).option("--concurrency <n>","Maximum sites to check at once").option("--timeout <seconds>","Per-site timeout in seconds").addOption(new l("--only <status>","Filter rows by status").choices(["all","logged-in","not-logged-in","unknown","error"]).default("all")).option("-f, --format <fmt>","Output format: table, plain, json, yaml, md, csv","table").action(async(J)=>{const Y=typeof G.optsWithGlobals==="function"?G.optsWithGlobals():{},Z=await collectAuthStatus({sites:J.site,full:J.full===!0,concurrency:J.concurrency,timeout:J.timeout,only:J.only,profile:typeof Y.profile==="string"&&Y.profile.trim()?Y.profile.trim():void 0}),$=typeof J.format==="string"?J.format:"table";C(Z,{fmt:$,fmtExplicit:G.getOptionValueSource("format")==="cli",columns:["site","status","identity","checked","error"],title:"opencli/auth status",source:J.full?"full whoami probe":"quick auth check"})}),X=D.command("refresh").description("Touch logged-in site sessions to keep browser auth fresh").option("--site <sites>","Comma-separated site names to refresh, e.g. github,claude").option("--all","Ignore the 24h refresh throttle and force every selected site",!1).option("--concurrency <n>","Maximum sites to refresh at once").option("--timeout <seconds>","Per-site timeout in seconds").option("-f, --format <fmt>","Output format: table, plain, json, yaml, md, csv","table").action(async(J)=>{const Y=typeof X.optsWithGlobals==="function"?X.optsWithGlobals():{},Z=await collectAuthRefresh({sites:J.site,all:J.all===!0,concurrency:J.concurrency,timeout:J.timeout,profile:typeof Y.profile==="string"&&Y.profile.trim()?Y.profile.trim():void 0}),$=typeof J.format==="string"?J.format:"table";C(Z,{fmt:$,fmtExplicit:X.getOptionValueSource("format")==="cli",columns:["site","status","last_touched_at","next_refresh_at","error"],title:"opencli/auth refresh",source:J.all?"forced persistent touch":"persistent touch with 24h throttle"})});return D}
1
+ import{mkdir as I,readFile as U,writeFile as _}from"node:fs/promises";import{homedir as F}from"node:os";import{dirname as x,join as b}from"node:path";import{pathToFileURL as p}from"node:url";import{InvalidArgumentError as v,Option as i}from"commander";import{AuthRequiredError as T,CliError as C,getErrorMessage as V}from"../errors.js";import{executeCommand as N}from"../execution.js";import{ensureBrowserBridgeReady as a}from"../browser/daemon-lifecycle.js";import{resolveProfile as m}from"../browser/profile.js";import{isElectronApp as j}from"../electron-apps.js";import{fullName as c,getRegistry as k}from"../registry.js";import{render as A}from"../output.js";const O=1,q=86400000;function z(G,J,X){if(G===void 0||G===null||G==="")return X;const Z=Number(G);if(!Number.isInteger(Z)||Z<=0)throw new v(`${J} must be a positive integer. Received: "${String(G)}"`);return Z}function E(G){if(!G||!G.trim())return null;const J=G.split(",").map((X)=>X.trim()).filter(Boolean);return J.length>0?new Set(J):null}function d(){return b(F(),".opencli","auth-refresh.json")}function o(){return{version:O,sites:{}}}async function s(G){try{const J=JSON.parse(await U(G,"utf8"));if(J&&J.version===O&&J.sites&&typeof J.sites==="object")return{version:O,sites:J.sites}}catch(J){if(J.code!=="ENOENT")throw J}return o()}async function r(G,J){await I(x(G),{recursive:!0});await _(G,`${JSON.stringify(J,null,2)}
2
+ `,"utf8")}function S(G){if(!G?.last_touched_at)return null;const J=Date.parse(G.last_touched_at);return Number.isFinite(J)?J:null}function t(G,J){const X=S(G);return X!==null&&J.getTime()-X<q}function B(G){const J=S(G);return J===null?"":new Date(J+q).toISOString()}function R(){const G=new Set;return[...k().values()].filter((J)=>{if(G.has(J))return!1;G.add(J);return J.name==="whoami"&&J.access==="read"}).sort((J,X)=>J.site.localeCompare(X.site))}async function H(G){const J=G;if(!J._lazy||!J._modulePath)return G;await import(p(J._modulePath).href);return k().get(c(G))??G}function M(G,J){const X=G.args.some((Z)=>Z.name==="timeout");return{...G,args:X?G.args:[...G.args,{name:"timeout",type:"int",default:J,help:"Per-site auth command timeout in seconds"}]}}function e(G,J){if(G.browser!==!0||typeof G.authStatus?.quickCheck!=="function")return null;return M({...G,func:G.authStatus.quickCheck,navigateBefore:!1,siteSession:"ephemeral",defaultWindowMode:"background"},J)}function w(G){if(typeof G==="boolean")return G;if(G&&typeof G==="object"&&!Array.isArray(G)){const J=G.logged_in;if(typeof J==="boolean")return J}return null}function f(G){if(G===void 0||G===null)return"";if(typeof G==="string"||typeof G==="number"||typeof G==="boolean")return String(G);return""}function y(G){if(!G||typeof G!=="object"||Array.isArray(G))return"";const J=G,X=/(?:email|phone|real.?name|first.?name|last.?name|cookie|token|session|secret|password|csrf|jwt|bearer|wt2)/i;for(const Z of["username","handle","user_id","id","name","nickname","user_type","url"]){if(X.test(Z))continue;const Y=f(J[Z]);if(Y)return Y}for(const[Z,Y]of Object.entries(J)){if(Z==="site"||Z==="logged_in"||X.test(Z))continue;const $=f(Y);if($)return $}return""}function P(G){return G instanceof T||typeof G==="object"&&G!==null&&G.code==="AUTH_REQUIRED"}function g(G,J,X){if(P(X))return{site:G,status:"not_logged_in",logged_in:!1,identity:"",checked:J,error:""};const Z=X instanceof C?X.code:"",Y=V(X);return{site:G,status:"error",logged_in:"",identity:"",checked:J,error:Z?`${Z}: ${Y}`:Y}}function GG(G,J){if(G.browser!==!0)return null;let X=G.authStatus?.refresh;if(typeof X!=="function"){const Z=G.authStatus?.quickCheck;if(typeof Z!=="function"||!G.domain)return null;const Y=G.domain.startsWith("http://")||G.domain.startsWith("https://")?G.domain:`https://${G.domain}`;X=async($,D,K)=>{await $.goto(Y);await $.wait(1);if(w(await Z($,D,K))!==!0)throw new T(G.domain??G.site,`Auth refresh quickCheck failed for ${G.site}`);return{status:"touched"}}}return M({...G,func:X,navigateBefore:!1,siteSession:"persistent",defaultWindowMode:"background"},J)}function JG(G){if(G&&typeof G==="object"&&!Array.isArray(G)){const J=G;if(J.status==="refreshed"||J.refreshed===!0)return"refreshed"}return"touched"}function XG(G,J,X){if(P(X))return{site:G,status:"not_logged_in",last_touched_at:J?.last_touched_at??"",next_refresh_at:B(J),error:""};const Z=X instanceof C?X.code:"",Y=V(X);return{site:G,status:"error",last_touched_at:J?.last_touched_at??"",next_refresh_at:B(J),error:Z?`${Z}: ${Y}`:Y}}function h(){return process.env.PUBLISHPORT_SITE_SESSION==="ephemeral"?{siteSession:"ephemeral",keepTab:"false"}:{siteSession:"persistent",keepTab:"true"}}function u(G){return b(F(),".opencli","sites",G,"quickcheck-cache.json")}async function YG(G,J,X){try{const Y=JSON.parse(await U(u(G),"utf8"))?.[X??"default"];if(!Y||Y.logged_in!==!0)return!1;const $=Date.now()-Date.parse(Y.checked_at);return Number.isFinite($)&&$>=0&&$<J*1000}catch{return!1}}async function ZG(G,J,X){const Z=u(G),Y=X??"default";let $={};try{$=JSON.parse(await U(Z,"utf8"))}catch{}if(J)$[Y]={logged_in:!0,checked_at:new Date().toISOString()};else delete $[Y];try{await I(x(Z),{recursive:!0});await _(Z,`${JSON.stringify($,null,2)}
3
+ `,"utf8")}catch{}}async function $G(G,J){try{const X=await H(G);if(X.browser!==!0){const K=M(X,J.timeoutSeconds),Q=await N(K,{timeout:J.timeoutSeconds},!1,{...J.profile?{profile:J.profile}:{}});return{site:G.site,status:"logged_in",logged_in:!0,identity:y(Q),checked:"quick",error:""}}const Z=X.authStatus?.quickCheckCacheTtlSeconds;if(typeof Z==="number"&&Z>0&&await YG(G.site,Z,J.profile))return{site:G.site,status:"logged_in",logged_in:!0,identity:"",checked:"quick",error:""};const Y=e(X,J.timeoutSeconds);if(!Y)return{site:G.site,status:"unknown",logged_in:"",identity:"",checked:"skipped",error:"quickCheck not implemented; use --full to run whoami"};const $=await N(Y,{timeout:J.timeoutSeconds},!1,{...h(),windowMode:"background",...J.profile?{profile:J.profile}:{}}),D=w($);if(typeof Z==="number"&&Z>0&&typeof D==="boolean")await ZG(G.site,D,J.profile);if(D===!0)return{site:G.site,status:"logged_in",logged_in:!0,identity:"",checked:"quick",error:""};if(D===!1)return{site:G.site,status:"not_logged_in",logged_in:!1,identity:"",checked:"quick",error:""};return{site:G.site,status:"unknown",logged_in:"",identity:"",checked:"quick",error:"quickCheck returned no boolean logged_in signal"}}catch(X){return g(G.site,"quick",X)}}async function DG(G,J){try{const X=await H(G),Z=M(X,J.timeoutSeconds),Y=await N(Z,{timeout:J.timeoutSeconds},!1,{...h(),windowMode:"background",...J.profile?{profile:J.profile}:{}});return{site:G.site,status:"logged_in",logged_in:!0,identity:y(Y),checked:"full",error:""}}catch(X){return g(G.site,"full",X)}}function WG(G,J){return J?`${G}@${J}`:G}async function KG(G,J){const X=WG(G.site,J.profile),Z=J.state.sites[X];if(!J.force&&t(Z,J.now))return{site:G.site,status:"skipped",last_touched_at:Z?.last_touched_at??"",next_refresh_at:B(Z),error:""};const Y=J.now.toISOString();try{const $=await H(G),D=GG($,J.timeoutSeconds);if(!D){J.state.sites[X]={...Z,last_attempt_at:Y,last_status:"unsupported"};return{site:G.site,status:"unsupported",last_touched_at:Z?.last_touched_at??"",next_refresh_at:B(Z),error:"refresh probe is not available for this site"}}const K=await N(D,{timeout:J.timeoutSeconds},!1,{siteSession:"persistent",keepTab:"true",windowMode:"background",...J.profile?{profile:J.profile}:{}}),Q=JG(K);J.state.sites[X]={...Z,last_attempt_at:Y,last_touched_at:Y,last_status:Q};return{site:G.site,status:Q,last_touched_at:Y,next_refresh_at:new Date(J.now.getTime()+q).toISOString(),error:""}}catch($){const D=P($)?"not_logged_in":"error";J.state.sites[X]={...Z,last_attempt_at:Y,last_status:D};return XG(G.site,Z,$)}}async function l(G,J){const X=m(J);if(X.kind!=="extension")return null;if(!G.some((Y)=>Y.browser===!0&&!j(Y.site)))return null;try{await a({timeoutSeconds:15,contextId:X.contextId,verbose:!1});return null}catch(Y){return V(Y)}}function QG(G,J,X){return{site:G,status:"error",logged_in:"",identity:"",checked:J,error:`BROWSER_CONNECT: ${X}`}}async function n(G,J,X){const Z=Array(G.length);let Y=0;const $=Array.from({length:Math.min(J,G.length)},async()=>{while(Y<G.length){const D=Y++;Z[D]=await X(G[D])}});await Promise.all($);return Z}export async function collectAuthStatus(G){const J=E(G.sites),X=G.full?"full":"quick",Z=z(G.concurrency,"--concurrency",X==="full"?3:8),Y=z(G.timeout,"--timeout",X==="full"?20:8),$=String(G.only??"all");if(!["all","logged-in","not-logged-in","unknown","error"].includes($))throw new v("--only must be one of: all, logged-in, not-logged-in, unknown, error");const D=R().filter((W)=>!J||J.has(W.site)),K=await l(D,G.profile),Q=await n(D,Z,(W)=>K&&W.browser===!0&&!j(W.site)?Promise.resolve(QG(W.site,X,K)):X==="full"?DG(W,{timeoutSeconds:Y,profile:G.profile}):$G(W,{timeoutSeconds:Y,profile:G.profile})),L=$.replace(/-/g,"_");return L==="all"?Q:Q.filter((W)=>W.status===L)}export async function collectAuthRefresh(G){const J=E(G.sites),X=z(G.concurrency,"--concurrency",3),Z=z(G.timeout,"--timeout",20),Y=G.statePath??d(),$=G.now??new Date,D=await s(Y),K=R().filter((W)=>!J||J.has(W.site)),Q=await l(K,G.profile),L=await n(K,X,(W)=>Q&&W.browser===!0&&!j(W.site)?Promise.resolve({site:W.site,status:"error",last_touched_at:D.sites[W.site]?.last_touched_at??"",next_refresh_at:B(D.sites[W.site]),error:`BROWSER_CONNECT: ${Q}`}):KG(W,{timeoutSeconds:Z,profile:G.profile,now:$,state:D,force:G.all===!0}));await r(Y,D);return L}export function registerAuthCommands(G){const J=G.command("auth").description("Inspect website login status"),X=J.command("status").description("Show login status for sites with auth adapters").option("--site <sites>","Comma-separated site names to check, e.g. github,chatgpt").option("--full","Run full per-site whoami probes instead of quick no-navigation checks",!1).option("--concurrency <n>","Maximum sites to check at once").option("--timeout <seconds>","Per-site timeout in seconds").addOption(new i("--only <status>","Filter rows by status").choices(["all","logged-in","not-logged-in","unknown","error"]).default("all")).option("-f, --format <fmt>","Output format: table, plain, json, yaml, md, csv","table").action(async(Y)=>{const $=typeof X.optsWithGlobals==="function"?X.optsWithGlobals():{},D=await collectAuthStatus({sites:Y.site,full:Y.full===!0,concurrency:Y.concurrency,timeout:Y.timeout,only:Y.only,profile:typeof $.profile==="string"&&$.profile.trim()?$.profile.trim():void 0}),K=typeof Y.format==="string"?Y.format:"table";A(D,{fmt:K,fmtExplicit:X.getOptionValueSource("format")==="cli",columns:["site","status","identity","checked","error"],title:"opencli/auth status",source:Y.full?"full whoami probe":"quick auth check"})}),Z=J.command("refresh").description("Touch logged-in site sessions to keep browser auth fresh").option("--site <sites>","Comma-separated site names to refresh, e.g. github,claude").option("--all","Ignore the 24h refresh throttle and force every selected site",!1).option("--concurrency <n>","Maximum sites to refresh at once").option("--timeout <seconds>","Per-site timeout in seconds").option("-f, --format <fmt>","Output format: table, plain, json, yaml, md, csv","table").action(async(Y)=>{const $=typeof Z.optsWithGlobals==="function"?Z.optsWithGlobals():{},D=await collectAuthRefresh({sites:Y.site,all:Y.all===!0,concurrency:Y.concurrency,timeout:Y.timeout,profile:typeof $.profile==="string"&&$.profile.trim()?$.profile.trim():void 0}),K=typeof Y.format==="string"?Y.format:"table";A(D,{fmt:K,fmtExplicit:Z.getOptionValueSource("format")==="cli",columns:["site","status","last_touched_at","next_refresh_at","error"],title:"opencli/auth refresh",source:Y.all?"forced persistent touch":"persistent touch with 24h throttle"})});return J}
@@ -1 +1 @@
1
- import{createServer as P}from"node:http";import{WebSocketServer as g,WebSocket as E}from"ws";import{DEFAULT_DAEMON_PORT as u,PUBLISHPORT_DAEMON_PORT as p,unsupportedDaemonPortEnvMessage as l}from"./constants.js";import{EXIT_CODES as S}from"./errors.js";import{log as V}from"./logger.js";import{PKG_VERSION as c}from"./version.js";import{DEFAULT_CONTEXT_ID as d}from"./browser/profile.js";import{recordExtensionVersion as a}from"./update-check.js";import{buildCommandDispatchFailure as i,buildExtensionDisconnectFailure as t,getResponseCorsHeaders as n}from"./daemon-utils.js";import*as L from"./browser/trace.js";const k=u,h=p;if(process.env.OPENCLI_DAEMON_PORT){V.error(l(process.env.OPENCLI_DAEMON_PORT));process.exit(S.USAGE_ERROR)}const W=new Map,N=new Map;let y=0;const s=200,F=[];class v extends Error{errorCode;errorHint;status;constructor(J,K,z,G=400){super(J);this.errorCode=K;this.errorHint=z;this.status=G;this.name="DaemonCommandFailure"}}function o(J){F.push(J);if(F.length>s)F.shift()}function I(){return[...W.values()].filter((J)=>J.ws.readyState===E.OPEN)}function q(J){const K=typeof J==="string"&&J.trim()?J.trim():void 0;if(K){const G=W.get(K);if(G?.ws.readyState===E.OPEN)return{connection:G};return{errorCode:"profile_disconnected",error:`Browser profile "${K}" is not connected.`,errorHint:"Open that Chrome profile and make sure the OpenCLI extension is enabled, or choose another profile with opencli profile use <name>."}}const z=I();if(z.length===1)return{connection:z[0]};if(z.length>1)return{errorCode:"profile_required",error:"Multiple Browser Bridge profiles are connected; choose one with --profile.",errorHint:"Run opencli profile list, then use opencli --profile <name> ... or opencli profile use <name>."};return{errorCode:"extension_not_connected",error:"Extension not connected. Please install the opencli Browser Bridge extension."}}function r(J,K){const z=typeof K==="string"&&K.trim()?K.trim():d,G=W.get(z);if(G&&G.ws!==J)G.ws.close();const $=[...W.entries()].find(([,A])=>A.ws===J);if($&&$[0]!==z)W.delete($[0]);const Q=W.get(z),Z={contextId:z,ws:J,extensionVersion:Q?.ws===J?Q.extensionVersion:null,extensionCompatRange:Q?.ws===J?Q.extensionCompatRange:null,lastSeenAt:Date.now()};W.set(z,Z);return Z}function w(J){for(const[K,z]of W.entries()){if(z.ws!==J)continue;W.delete(K);L.traceExtDisconnect(K);for(const[G,$]of N){if($.contextId!==K)continue;clearTimeout($.timer);const Q=t({contextId:K,action:$.action,dispatched:$.dispatched});if(Q.countAsCommandResultUnknown){y++;V.warn(`[daemon] Command result unknown after extension disconnect (id=${G}, action=${$.action}, context=${K})`)}L.traceResult(G,!1,Date.now()-$.dispatchTs,Q.errorCode);$.reject(new v(Q.message,Q.errorCode,Q.errorHint,Q.status));N.delete(G)}}}const x=33554432;class R extends Error{status=413;constructor(){super(`请求体超过 ${x/1024/1024} MB 上限(正文内联图片过多/过大?可减少单次发布的图片体积)`);this.name="BodyTooLargeError"}}function e(J){return new Promise((K,z)=>{const G=[];let $=0,Q=!1;J.on("data",(Z)=>{if(Q)return;$+=Z.length;if($>x){Q=!0;G.length=0;z(new R);return}G.push(Z)});J.on("end",()=>{if(!Q)K(Buffer.concat(G).toString("utf-8"))});J.on("error",(Z)=>{if(!Q)z(Z)})})}function Y(J,K,z,G){J.writeHead(K,{"Content-Type":"application/json",...G});J.end(JSON.stringify(z))}async function f(J,K){const z=J.headers.origin;if(z&&!z.startsWith("chrome-extension://")){Y(K,403,{ok:!1,error:"Forbidden: cross-origin request blocked"});return}if(J.method==="OPTIONS"){K.writeHead(204);K.end();return}const G=J.url??"/",$=G.split("?")[0];if(J.method==="GET"&&$==="/ping"){Y(K,200,{ok:!0},n($,z));return}if(!J.headers["x-opencli"]){Y(K,403,{ok:!1,error:"Forbidden: missing X-OpenCLI header"});return}if(J.method==="GET"&&$==="/status"){const Q=process.uptime(),Z=process.memoryUsage(),B=new URL(G,`http://localhost:${k}`).searchParams.get("contextId")?.trim()||void 0,X=q(B),_=I().map((M)=>({contextId:M.contextId,extensionConnected:!0,extensionVersion:M.extensionVersion??void 0,extensionCompatRange:M.extensionCompatRange??void 0,pending:[...N.values()].filter((H)=>H.contextId===M.contextId).length,lastSeenAt:M.lastSeenAt}));Y(K,200,{ok:!0,pid:process.pid,uptime:Q,daemonVersion:c,extensionConnected:!!X.connection,extensionVersion:X.connection?.extensionVersion??void 0,extensionCompatRange:X.connection?.extensionCompatRange??void 0,contextId:X.connection?.contextId??B,profileRequired:X.errorCode==="profile_required",profileDisconnected:X.errorCode==="profile_disconnected",profiles:_,pending:N.size,commandResultUnknown:y,memoryMB:Math.round(Z.rss/1024/1024*10)/10,port:k});return}if(J.method==="GET"&&$==="/logs"){const Z=new URL(G,`http://localhost:${k}`).searchParams.get("level"),A=Z?F.filter((B)=>B.level===Z):F;Y(K,200,{ok:!0,logs:A});return}if(J.method==="DELETE"&&$==="/logs"){F.length=0;Y(K,200,{ok:!0});return}if(J.method==="POST"&&$==="/shutdown"){Y(K,200,{ok:!0,message:"Shutting down"});setTimeout(()=>C(),100);return}if(J.method==="POST"&&G==="/command"){try{const Q=JSON.parse(await e(J));if(!Q.id){Y(K,400,{ok:!1,error:"Missing command id"});return}const Z=q(typeof Q.contextId==="string"?Q.contextId:void 0);if(!Z.connection){Y(K,Z.errorCode==="profile_required"?409:503,{id:Q.id,ok:!1,errorCode:Z.errorCode,error:Z.error,...Z.errorHint?{errorHint:Z.errorHint}:{}});return}const A=typeof Q.timeout==="number"&&Q.timeout>0?Q.timeout*1000:120000;if(N.has(Q.id)){Y(K,409,{id:Q.id,ok:!1,error:"Duplicate command id already pending; retry"});return}const B=await new Promise((X,_)=>{const M=setTimeout(()=>{N.delete(Q.id);L.traceTimeout(Q.id,A,typeof Q.action==="string"?Q.action:void 0);_(Error(`Command timeout (${A/1000}s)`))},A),H={contextId:Z.connection.contextId,action:typeof Q.action==="string"?Q.action:"unknown",dispatched:!1,dispatchTs:Date.now(),resolve:X,reject:_,timer:M};N.set(Q.id,H);const b=(U)=>{if(N.get(Q.id)!==H)return;const D=i(H.contextId);clearTimeout(M);N.delete(Q.id);_(new v(D.message,D.errorCode,D.errorHint,D.status));V.warn(`[daemon] Failed to dispatch command ${Q.id}: ${U instanceof Error?U.message:String(U)}`)};try{Z.connection.ws.send(JSON.stringify(Q),(U)=>{if(U&&!H.dispatched)b(U)});H.dispatched=!0;L.traceDispatch(Q)}catch(U){b(U)}});Y(K,200,B)}catch(Q){const Z=Q instanceof v?Q:null;if(Q instanceof R){Y(K,413,{ok:!1,error:Q.message,errorCode:"body_too_large"});return}Y(K,Z?.status??(Q instanceof Error&&Q.message.includes("timeout")?408:400),{ok:!1,error:Q instanceof Error?Q.message:"Invalid request",...Z?.errorCode?{errorCode:Z.errorCode}:{},...Z?.errorHint?{errorHint:Z.errorHint}:{}})}return}Y(K,404,{error:"Not found"})}const O=P((J,K)=>{f(J,K).catch(()=>{K.writeHead(500);K.end()})}),T=P((J,K)=>{f(J,K).catch(()=>{K.writeHead(500);K.end()})}),j=new g({noServer:!0});function m(J,K,z){if((J.url??"/").split("?")[0]!=="/ext"){K.destroy();return}const $=J.headers.origin;if($&&!$.startsWith("chrome-extension://")){K.destroy();return}j.handleUpgrade(J,K,z,(Q)=>{j.emit("connection",Q,J)})}O.on("upgrade",m);T.on("upgrade",m);j.on("connection",(J)=>{V.info("[daemon] Extension connected");let K=0;const z=setInterval(()=>{if(J.readyState!==E.OPEN){clearInterval(z);return}if(K>=2){V.warn("[daemon] Extension heartbeat lost, closing connection");clearInterval(z);J.terminate();return}K++;J.ping()},15000);J.on("pong",()=>{K=0});J.on("message",(G)=>{try{const $=JSON.parse(G.toString());if($.type==="hello"){const Z=r(J,$.contextId);Z.extensionVersion=typeof $.version==="string"?$.version:null;Z.extensionCompatRange=typeof $.compatRange==="string"?$.compatRange:null;Z.lastSeenAt=Date.now();if(Z.extensionVersion)a(Z.extensionVersion);V.info(`[daemon] Extension profile connected: ${Z.contextId}`);L.traceExtConnect(Z.contextId,Z.extensionVersion);return}if($.type==="log"){if($.level==="error")V.error(`[ext] ${$.msg}`);else if($.level==="warn")V.warn(`[ext] ${$.msg}`);else V.info(`[ext] ${$.msg}`);o({level:$.level,msg:$.msg,ts:$.ts??Date.now()});L.traceExtLog($.level,$.msg);return}const Q=N.get($.id);if(Q){clearTimeout(Q.timer);N.delete($.id);L.traceResult($.id,!!$.ok,Date.now()-Q.dispatchTs,typeof $.errorCode==="string"?$.errorCode:void 0,typeof $.page==="string"?$.page:void 0);Q.resolve($)}}catch($){const Q=G.toString().slice(0,200);V.warn(`[daemon] Ignoring malformed WS message from extension: ${$ instanceof Error?$.message:String($)} (first 200 chars: ${JSON.stringify(Q)})`)}});J.on("close",()=>{V.info("[daemon] Extension disconnected");clearInterval(z);w(J)});J.on("error",()=>{clearInterval(z);w(J)})});O.listen(k,"127.0.0.1",()=>{V.info(`[daemon] Listening on http://127.0.0.1:${k}`)});O.on("error",(J)=>{if(J.code==="EADDRINUSE"){V.error(`[daemon] Port ${k} already in use — another daemon is likely running. Exiting.`);process.exit(S.SERVICE_UNAVAIL)}V.error(`[daemon] Server error: ${J.message}`);process.exit(S.GENERIC_ERROR)});T.listen(h,"127.0.0.1",()=>{V.info(`[daemon] Listening on http://127.0.0.1:${h} (PublishPort extension)`)});T.on("error",(J)=>{V.warn(`[daemon] PublishPort port ${h} unavailable (${J.message}) — extension will fall back to ${k}`)});function C(){for(const[,J]of N){clearTimeout(J.timer);J.reject(Error("Daemon shutting down"))}N.clear();for(const J of W.values())J.ws.close();O.close();T.close();process.exit(S.SUCCESS)}process.on("SIGTERM",C);process.on("SIGINT",C);
1
+ import{createServer as b}from"node:http";import{WebSocketServer as g,WebSocket as v}from"ws";import{DEFAULT_DAEMON_PORT as u,PUBLISHPORT_DAEMON_PORT as p,unsupportedDaemonPortEnvMessage as l}from"./constants.js";import{EXIT_CODES as S}from"./errors.js";import{log as V}from"./logger.js";import{PKG_VERSION as c}from"./version.js";import{DEFAULT_CONTEXT_ID as d}from"./browser/profile.js";import{recordExtensionVersion as a}from"./update-check.js";import{buildCommandDispatchFailure as i,buildExtensionDisconnectFailure as t,getResponseCorsHeaders as n}from"./daemon-utils.js";import*as L from"./browser/trace.js";const B=u,h=p;if(process.env.OPENCLI_DAEMON_PORT){V.error(l(process.env.OPENCLI_DAEMON_PORT));process.exit(S.USAGE_ERROR)}const W=new Map,N=new Map;let I=0;const s=200,_=[];class E extends Error{errorCode;errorHint;status;constructor(J,K,z,G=400){super(J);this.errorCode=K;this.errorHint=z;this.status=G;this.name="DaemonCommandFailure"}}function o(J){_.push(J);if(_.length>s)_.shift()}function j(){return[...W.values()].filter((J)=>J.ws.readyState===v.OPEN)}function q(J){const K=typeof J==="string"&&J.trim()?J.trim():void 0;if(K){const G=W.get(K);if(G?.ws.readyState===v.OPEN)return{connection:G};const Q=j();if(Q.length===1){V.warn(`[daemon] Requested profile "${K}" not connected; falling back to the only connected profile "${Q[0].contextId}"`);return{connection:Q[0]}}return{errorCode:"profile_disconnected",error:`Browser profile "${K}" is not connected.`,errorHint:"Open that Chrome profile and make sure the OpenCLI extension is enabled, or choose another profile with opencli profile use <name>."}}const z=j();if(z.length===1)return{connection:z[0]};if(z.length>1)return{errorCode:"profile_required",error:"Multiple Browser Bridge profiles are connected; choose one with --profile.",errorHint:"Run opencli profile list, then use opencli --profile <name> ... or opencli profile use <name>."};return{errorCode:"extension_not_connected",error:"Extension not connected. Please install the opencli Browser Bridge extension."}}function r(J,K){const z=typeof K==="string"&&K.trim()?K.trim():d,G=W.get(z);if(G&&G.ws!==J)G.ws.close();const Q=[...W.entries()].find(([,A])=>A.ws===J);if(Q&&Q[0]!==z)W.delete(Q[0]);const $=W.get(z),Z={contextId:z,ws:J,extensionVersion:$?.ws===J?$.extensionVersion:null,extensionCompatRange:$?.ws===J?$.extensionCompatRange:null,lastSeenAt:Date.now()};W.set(z,Z);return Z}function w(J){for(const[K,z]of W.entries()){if(z.ws!==J)continue;W.delete(K);L.traceExtDisconnect(K);for(const[G,Q]of N){if(Q.contextId!==K)continue;clearTimeout(Q.timer);const $=t({contextId:K,action:Q.action,dispatched:Q.dispatched});if($.countAsCommandResultUnknown){I++;V.warn(`[daemon] Command result unknown after extension disconnect (id=${G}, action=${Q.action}, context=${K})`)}L.traceResult(G,!1,Date.now()-Q.dispatchTs,$.errorCode);Q.reject(new E($.message,$.errorCode,$.errorHint,$.status));N.delete(G)}}}const x=33554432;class P extends Error{status=413;constructor(){super(`请求体超过 ${x/1024/1024} MB 上限(正文内联图片过多/过大?可减少单次发布的图片体积)`);this.name="BodyTooLargeError"}}function e(J){return new Promise((K,z)=>{const G=[];let Q=0,$=!1;J.on("data",(Z)=>{if($)return;Q+=Z.length;if(Q>x){$=!0;G.length=0;z(new P);return}G.push(Z)});J.on("end",()=>{if(!$)K(Buffer.concat(G).toString("utf-8"))});J.on("error",(Z)=>{if(!$)z(Z)})})}function Y(J,K,z,G){J.writeHead(K,{"Content-Type":"application/json",...G});J.end(JSON.stringify(z))}async function f(J,K){const z=J.headers.origin;if(z&&!z.startsWith("chrome-extension://")){Y(K,403,{ok:!1,error:"Forbidden: cross-origin request blocked"});return}if(J.method==="OPTIONS"){K.writeHead(204);K.end();return}const G=J.url??"/",Q=G.split("?")[0];if(J.method==="GET"&&Q==="/ping"){Y(K,200,{ok:!0},n(Q,z));return}if(!J.headers["x-opencli"]){Y(K,403,{ok:!1,error:"Forbidden: missing X-OpenCLI header"});return}if(J.method==="GET"&&Q==="/status"){const $=process.uptime(),Z=process.memoryUsage(),F=new URL(G,`http://localhost:${B}`).searchParams.get("contextId")?.trim()||void 0,X=q(F),k=j().map((M)=>({contextId:M.contextId,extensionConnected:!0,extensionVersion:M.extensionVersion??void 0,extensionCompatRange:M.extensionCompatRange??void 0,pending:[...N.values()].filter((H)=>H.contextId===M.contextId).length,lastSeenAt:M.lastSeenAt}));Y(K,200,{ok:!0,pid:process.pid,uptime:$,daemonVersion:c,extensionConnected:!!X.connection,extensionVersion:X.connection?.extensionVersion??void 0,extensionCompatRange:X.connection?.extensionCompatRange??void 0,contextId:X.connection?.contextId??F,profileRequired:X.errorCode==="profile_required",profileDisconnected:X.errorCode==="profile_disconnected",profiles:k,pending:N.size,commandResultUnknown:I,memoryMB:Math.round(Z.rss/1024/1024*10)/10,port:B});return}if(J.method==="GET"&&Q==="/logs"){const Z=new URL(G,`http://localhost:${B}`).searchParams.get("level"),A=Z?_.filter((F)=>F.level===Z):_;Y(K,200,{ok:!0,logs:A});return}if(J.method==="DELETE"&&Q==="/logs"){_.length=0;Y(K,200,{ok:!0});return}if(J.method==="POST"&&Q==="/shutdown"){Y(K,200,{ok:!0,message:"Shutting down"});setTimeout(()=>R(),100);return}if(J.method==="POST"&&G==="/command"){try{const $=JSON.parse(await e(J));if(!$.id){Y(K,400,{ok:!1,error:"Missing command id"});return}const Z=q(typeof $.contextId==="string"?$.contextId:void 0);if(!Z.connection){Y(K,Z.errorCode==="profile_required"?409:503,{id:$.id,ok:!1,errorCode:Z.errorCode,error:Z.error,...Z.errorHint?{errorHint:Z.errorHint}:{}});return}const A=typeof $.timeout==="number"&&$.timeout>0?$.timeout*1000:120000;if(N.has($.id)){Y(K,409,{id:$.id,ok:!1,error:"Duplicate command id already pending; retry"});return}const F=await new Promise((X,k)=>{const M=setTimeout(()=>{N.delete($.id);L.traceTimeout($.id,A,typeof $.action==="string"?$.action:void 0);k(Error(`Command timeout (${A/1000}s)`))},A),H={contextId:Z.connection.contextId,action:typeof $.action==="string"?$.action:"unknown",dispatched:!1,dispatchTs:Date.now(),resolve:X,reject:k,timer:M};N.set($.id,H);const y=(U)=>{if(N.get($.id)!==H)return;const D=i(H.contextId);clearTimeout(M);N.delete($.id);k(new E(D.message,D.errorCode,D.errorHint,D.status));V.warn(`[daemon] Failed to dispatch command ${$.id}: ${U instanceof Error?U.message:String(U)}`)};try{Z.connection.ws.send(JSON.stringify($),(U)=>{if(U&&!H.dispatched)y(U)});H.dispatched=!0;L.traceDispatch($)}catch(U){y(U)}});Y(K,200,F)}catch($){const Z=$ instanceof E?$:null;if($ instanceof P){Y(K,413,{ok:!1,error:$.message,errorCode:"body_too_large"});return}Y(K,Z?.status??($ instanceof Error&&$.message.includes("timeout")?408:400),{ok:!1,error:$ instanceof Error?$.message:"Invalid request",...Z?.errorCode?{errorCode:Z.errorCode}:{},...Z?.errorHint?{errorHint:Z.errorHint}:{}})}return}Y(K,404,{error:"Not found"})}const O=b((J,K)=>{f(J,K).catch(()=>{K.writeHead(500);K.end()})}),T=b((J,K)=>{f(J,K).catch(()=>{K.writeHead(500);K.end()})}),C=new g({noServer:!0});function m(J,K,z){if((J.url??"/").split("?")[0]!=="/ext"){K.destroy();return}const Q=J.headers.origin;if(Q&&!Q.startsWith("chrome-extension://")){K.destroy();return}C.handleUpgrade(J,K,z,($)=>{C.emit("connection",$,J)})}O.on("upgrade",m);T.on("upgrade",m);C.on("connection",(J)=>{V.info("[daemon] Extension connected");let K=0;const z=setInterval(()=>{if(J.readyState!==v.OPEN){clearInterval(z);return}if(K>=2){V.warn("[daemon] Extension heartbeat lost, closing connection");clearInterval(z);J.terminate();return}K++;J.ping()},15000);J.on("pong",()=>{K=0});J.on("message",(G)=>{try{const Q=JSON.parse(G.toString());if(Q.type==="hello"){const Z=r(J,Q.contextId);Z.extensionVersion=typeof Q.version==="string"?Q.version:null;Z.extensionCompatRange=typeof Q.compatRange==="string"?Q.compatRange:null;Z.lastSeenAt=Date.now();if(Z.extensionVersion)a(Z.extensionVersion);V.info(`[daemon] Extension profile connected: ${Z.contextId}`);L.traceExtConnect(Z.contextId,Z.extensionVersion);return}if(Q.type==="log"){if(Q.level==="error")V.error(`[ext] ${Q.msg}`);else if(Q.level==="warn")V.warn(`[ext] ${Q.msg}`);else V.info(`[ext] ${Q.msg}`);o({level:Q.level,msg:Q.msg,ts:Q.ts??Date.now()});L.traceExtLog(Q.level,Q.msg);return}const $=N.get(Q.id);if($){clearTimeout($.timer);N.delete(Q.id);L.traceResult(Q.id,!!Q.ok,Date.now()-$.dispatchTs,typeof Q.errorCode==="string"?Q.errorCode:void 0,typeof Q.page==="string"?Q.page:void 0);$.resolve(Q)}}catch(Q){const $=G.toString().slice(0,200);V.warn(`[daemon] Ignoring malformed WS message from extension: ${Q instanceof Error?Q.message:String(Q)} (first 200 chars: ${JSON.stringify($)})`)}});J.on("close",()=>{V.info("[daemon] Extension disconnected");clearInterval(z);w(J)});J.on("error",()=>{clearInterval(z);w(J)})});O.listen(B,"127.0.0.1",()=>{V.info(`[daemon] Listening on http://127.0.0.1:${B}`)});O.on("error",(J)=>{if(J.code==="EADDRINUSE"){V.error(`[daemon] Port ${B} already in use — another daemon is likely running. Exiting.`);process.exit(S.SERVICE_UNAVAIL)}V.error(`[daemon] Server error: ${J.message}`);process.exit(S.GENERIC_ERROR)});T.listen(h,"127.0.0.1",()=>{V.info(`[daemon] Listening on http://127.0.0.1:${h} (PublishPort extension)`)});T.on("error",(J)=>{V.warn(`[daemon] PublishPort port ${h} unavailable (${J.message}) — extension will fall back to ${B}`)});function R(){for(const[,J]of N){clearTimeout(J.timer);J.reject(Error("Daemon shutting down"))}N.clear();for(const J of W.values())J.ws.close();O.close();T.close();process.exit(S.SUCCESS)}process.on("SIGTERM",R);process.on("SIGINT",R);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "publishport-opencli",
3
- "version": "1.8.5-pp.26",
3
+ "version": "1.8.5-pp.27",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": false