publishport-opencli 1.0.27 → 1.0.28

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,3 +1,3 @@
1
- import{mkdir as W,readFile as D,rename as ce,writeFile as U}from"node:fs/promises";import{homedir as H}from"node:os";import{dirname as V,join as z}from"node:path";import{pathToFileURL as le}from"node:url";import{InvalidArgumentError as y,Option as fe}from"commander";import{AuthRequiredError as L,CliError as G,getErrorMessage as R}from"../errors.js";import{executeCommand as _}from"../execution.js";import{ensureBrowserBridgeReady as de}from"../browser/daemon-lifecycle.js";import{resolveProfile as he}from"../browser/profile.js";import{isElectronApp as E}from"../electron-apps.js";import{fullName as ge,getRegistry as J}from"../registry.js";import{render as K}from"../output.js";const x=1,T=86400000;function w(e,t,r){if(e===void 0||e===null||e==="")return r;const i=Number(e);if(!Number.isInteger(i)||i<=0)throw new y(`${t} must be a positive integer. Received: "${String(e)}"`);return i}function Q(e,t,r){if(e===void 0||e===null||e==="")return r;const i=Number(e);if(!Number.isInteger(i)||i<0)throw new y(`${t} must be a non-negative integer. Received: "${String(e)}"`);return i}function Y(e){if(!e||!e.trim())return null;const t=e.split(",").map((r)=>r.trim()).filter(Boolean);return t.length>0?new Set(t):null}function me(){return z(H(),".ppcli","auth-refresh.json")}const C=1,Ke=600000;function pe(){return z(H(),".ppcli","auth-state.json")}async function ye(e){try{const t=JSON.parse(await D(e,"utf8"));if(t&&t.version===C&&t.profiles&&typeof t.profiles==="object")return{version:C,profiles:t.profiles}}catch{}return{version:C,profiles:{}}}async function _e(e,t){await W(V(e),{recursive:!0});const r=`${e}.${process.pid}.tmp`;await U(r,`${JSON.stringify(t,null,2)}
2
- `,"utf8");await ce(r,e)}function we(e,t){return{site:e,status:t.status,logged_in:t.logged_in,identity:t.identity,checked:"cached",error:t.error,checked_at:new Date(t.at).toISOString(),verified:t.verifiedAt!==void 0}}function ke(e,t,r,i){return{status:e.status,logged_in:e.logged_in,identity:e.identity,checked:e.checked,error:e.error,at:t,...r!==void 0?{verifiedAt:r}:{},...i!==void 0?{verifyAttemptAt:i}:{}}}function Se(){return{version:x,sites:{}}}async function be(e){try{const t=JSON.parse(await D(e,"utf8"));if(t&&t.version===x&&t.sites&&typeof t.sites==="object")return{version:x,sites:t.sites}}catch(t){if(t.code!=="ENOENT")throw t}return Se()}async function Ae(e,t){await W(V(e),{recursive:!0});await U(e,`${JSON.stringify(t,null,2)}
3
- `,"utf8")}function X(e){if(!e?.last_touched_at)return null;const t=Date.parse(e.last_touched_at);return Number.isFinite(t)?t:null}function ve(e,t){const r=X(e);return r!==null&&t.getTime()-r<T}function h(e){const t=X(e);return t===null?"":new Date(t+T).toISOString()}function Z(){const e=new Set;return[...J().values()].filter((t)=>{if(e.has(t))return!1;e.add(t);return t.name==="whoami"&&t.access==="read"}).sort((t,r)=>t.site.localeCompare(r.site))}async function O(e){const t=e;if(!t._lazy||!t._modulePath)return e;await import(le(t._modulePath).href);return J().get(ge(e))??e}function k(e,t){const r=e.args.some((i)=>i.name==="timeout");return{...e,args:r?e.args:[...e.args,{name:"timeout",type:"int",default:t,help:"Per-site auth command timeout in seconds"}]}}function Re(e,t){if(e.browser!==!0||typeof e.authStatus?.quickCheck!=="function")return null;return k({...e,func:e.authStatus.quickCheck,navigateBefore:!1,siteSession:"ephemeral",defaultWindowMode:"background"},t)}function ee(e){if(typeof e==="boolean")return e;if(e&&typeof e==="object"&&!Array.isArray(e)){const t=e.logged_in;if(typeof t==="boolean")return t}return null}function Ee(e){const t=Array.isArray(e)?e[0]:e;if(t&&typeof t==="object"&&!Array.isArray(t)){const r=t.logged_in;if(typeof r==="boolean")return r}return null}function xe(e){const t=Array.isArray(e)?e[0]:e;if(t&&typeof t==="object"&&!Array.isArray(t)){const r=t.error;if(typeof r==="string"&&r.trim())return r.trim()}return""}function te(e){if(e===void 0||e===null)return"";if(typeof e==="string"||typeof e==="number"||typeof e==="boolean")return String(e);return""}function re(e){const t=Array.isArray(e)?e[0]:e;if(!t||typeof t!=="object"||Array.isArray(t))return"";const r=t,i=/(?:email|phone|real.?name|first.?name|last.?name|cookie|token|session|secret|password|csrf|jwt|bearer|wt2)/i;for(const n of["username","handle","user_id","id","name","nickname","user_type","url"]){if(i.test(n))continue;const s=te(r[n]);if(s)return s}for(const[n,s]of Object.entries(r)){if(n==="site"||n==="logged_in"||i.test(n))continue;const a=te(s);if(a)return a}return""}function I(e){return e instanceof L||typeof e==="object"&&e!==null&&e.code==="AUTH_REQUIRED"}function ne(e,t,r){if(I(r))return{site:e,status:"not_logged_in",logged_in:!1,identity:"",checked:t,error:""};const i=r instanceof G?r.code:"",n=R(r);return{site:e,status:"error",logged_in:"",identity:"",checked:t,error:i?`${i}: ${n}`:n}}function Te(e,t){if(e.browser!==!0)return null;let r=e.authStatus?.refresh;if(typeof r!=="function"){const i=e.authStatus?.quickCheck;if(typeof i!=="function"||!e.domain)return null;const n=e.domain.startsWith("http://")||e.domain.startsWith("https://")?e.domain:`https://${e.domain}`;r=async(s,a,u)=>{await s.goto(n);await s.wait(1);if(ee(await i(s,a,u))!==!0)throw new L(e.domain??e.site,`Auth refresh quickCheck failed for ${e.site}`);return{status:"touched"}}}return k({...e,func:r,navigateBefore:!1,siteSession:"persistent",defaultWindowMode:"background"},t)}function Ce(e){if(e&&typeof e==="object"&&!Array.isArray(e)){const t=e;if(t.status==="refreshed"||t.refreshed===!0)return"refreshed"}return"touched"}function Oe(e,t,r){if(I(r))return{site:e,status:"not_logged_in",last_touched_at:t?.last_touched_at??"",next_refresh_at:h(t),error:""};const i=r instanceof G?r.code:"",n=R(r);return{site:e,status:"error",last_touched_at:t?.last_touched_at??"",next_refresh_at:h(t),error:i?`${i}: ${n}`:n}}function ie(){return process.env.PUBLISHPORT_SITE_SESSION==="ephemeral"?{siteSession:"ephemeral",keepTab:"false"}:{siteSession:"persistent",keepTab:"true"}}async function Ie(e,t){try{const r=await O(e);if(r.browser!==!0){const a=k(r,t.timeoutSeconds),u=await _(a,{timeout:t.timeoutSeconds},!1,{...t.profile?{profile:t.profile}:{}});return{site:e.site,status:"logged_in",logged_in:!0,identity:re(u),checked:"quick",error:""}}if(r.authStatus?.quickCheckRequiresNavigation===!0)return{site:e.site,status:"unknown",logged_in:"",identity:"",checked:"navigation_required",error:""};const i=Re(r,t.timeoutSeconds);if(!i)return{site:e.site,status:"unknown",logged_in:"",identity:"",checked:"skipped",error:"quickCheck not implemented; use --full to run whoami"};const n=await _(i,{timeout:t.timeoutSeconds},!1,{...ie(),windowMode:"background",...t.profile?{profile:t.profile}:{}}),s=ee(n);if(s===!0)return{site:e.site,status:"logged_in",logged_in:!0,identity:"",checked:"quick",error:""};if(s===!1)return{site:e.site,status:"not_logged_in",logged_in:!1,identity:"",checked:"quick",error:""};return{site:e.site,status:"unknown",logged_in:"",identity:"",checked:"quick",error:"quickCheck returned no boolean logged_in signal"}}catch(r){return ne(e.site,"quick",r)}}async function qe(e,t){try{const r=await O(e),i=k(r,t.timeoutSeconds),n=await _(i,{timeout:t.timeoutSeconds},!1,{...ie(),windowMode:"background",...t.profile?{profile:t.profile}:{}});if(Ee(n)===!1)return{site:e.site,status:"not_logged_in",logged_in:!1,identity:"",checked:"full",error:xe(n)};return{site:e.site,status:"logged_in",logged_in:!0,identity:re(n),checked:"full",error:""}}catch(r){return ne(e.site,"full",r)}}function Ne(e,t){return t?`${e}@${t}`:e}async function Me(e,t){const r=Ne(e.site,t.profile),i=t.state.sites[r];if(!t.force&&ve(i,t.now))return{site:e.site,status:"skipped",last_touched_at:i?.last_touched_at??"",next_refresh_at:h(i),error:""};const n=t.now.toISOString();try{const s=await O(e),a=Te(s,t.timeoutSeconds);if(!a){t.state.sites[r]={...i,last_attempt_at:n,last_status:"unsupported"};return{site:e.site,status:"unsupported",last_touched_at:i?.last_touched_at??"",next_refresh_at:h(i),error:"refresh probe is not available for this site"}}const u=await _(a,{timeout:t.timeoutSeconds},!1,{siteSession:"persistent",keepTab:"true",windowMode:"background",...t.profile?{profile:t.profile}:{}}),f=Ce(u);t.state.sites[r]={...i,last_attempt_at:n,last_touched_at:n,last_status:f};return{site:e.site,status:f,last_touched_at:n,next_refresh_at:new Date(t.now.getTime()+T).toISOString(),error:""}}catch(s){const a=I(s)?"not_logged_in":"error";t.state.sites[r]={...i,last_attempt_at:n,last_status:a};return Oe(e.site,i,s)}}async function oe(e,t){const r=he(t);if(r.kind!=="extension")return null;if(!e.some((n)=>n.browser===!0&&!E(n.site)))return null;try{await de({timeoutSeconds:15,contextId:r.contextId,verbose:!1});return null}catch(n){return R(n)}}function $e(e,t,r){return{site:e,status:"error",logged_in:"",identity:"",checked:t,error:`BROWSER_CONNECT: ${r}`}}async function se(e,t,r){const i=Array(e.length);let n=0;const s=Array.from({length:Math.min(t,e.length)},async()=>{while(n<e.length){const a=n++;i[a]=await r(e[a])}});await Promise.all(s);return i}export async function collectAuthStatus(e){const t=Y(e.sites),r=e.full?"full":"quick",i=w(e.concurrency,"--concurrency",r==="full"?3:8),n=w(e.timeout,"--timeout",r==="full"?20:8),s=String(e.only??"all");if(!["all","logged-in","not-logged-in","unknown","error"].includes(s))throw new y("--only must be one of: all, logged-in, not-logged-in, unknown, error");if(r==="full"&&!t)throw new y("--full 会对每个站点真实打开页面,不允许全量使用。"+"要实证某个站点请点名:auth status --site <site> --full;"+"想看全部站点就用默认的 quick(纯 cookie,不开任何页面)。");const a=Q(e.maxAge,"--max-age",0)*1000,u=Q(e.verify,"--verify",0),f=e.statePath??pe(),d=(e.now??new Date).getTime(),c=e.profile??"",S=Z().filter((o)=>!t||t.has(o.site)),b=await ye(f),g=b.profiles[c]??{},A=new Map,m=[];for(const o of S){const l=g[o.site];if(a>0&&l&&d-l.at<a)A.set(o.site,we(o.site,l));else m.push(o)}const q=m.length>0?await oe(m,e.profile):null,ae=await se(m,i,(o)=>q&&o.browser===!0&&!E(o.site)?Promise.resolve($e(o.site,r,q)):r==="full"?qe(o,{timeoutSeconds:n,profile:e.profile}):Ie(o,{timeoutSeconds:n,profile:e.profile})),Fe=new Map(S.map((o)=>[o.site,o])),p=new Map(A),N=new Map,M=new Map;for(const o of ae){p.set(o.site,o);if(r==="full"){M.set(o.site,d);if(o.status!=="error")N.set(o.site,d)}}const F=(o)=>{const l=N.get(o.site);if(l!==void 0)return l;const v=g[o.site];return v&&v.status===o.status?v.verifiedAt:void 0},ue=(o)=>M.get(o.site)??g[o.site]?.verifyAttemptAt,P={...g};for(const o of p.keys()){if(A.has(o))continue;const l=p.get(o);if(!l)continue;P[o]=ke(l,d,F(l),ue(l))}b.profiles[c]=P;await _e(f,b).catch(()=>{});const j=S.map((o)=>p.get(o.site)).filter((o)=>o!==void 0).map((o)=>({...o,checked_at:o.checked_at??new Date(d).toISOString(),verified:o.verified??F(o)!==void 0})),B=s.replace(/-/g,"_");return B==="all"?j:j.filter((o)=>o.status===B)}export async function collectAuthRefresh(e){const t=Y(e.sites),r=w(e.concurrency,"--concurrency",3),i=w(e.timeout,"--timeout",20),n=e.statePath??me(),s=e.now??new Date,a=await be(n),u=Z().filter((c)=>!t||t.has(c.site)),f=await oe(u,e.profile),d=await se(u,r,(c)=>f&&c.browser===!0&&!E(c.site)?Promise.resolve({site:c.site,status:"error",last_touched_at:a.sites[c.site]?.last_touched_at??"",next_refresh_at:h(a.sites[c.site]),error:`BROWSER_CONNECT: ${f}`}):Me(c,{timeoutSeconds:i,profile:e.profile,now:s,state:a,force:e.all===!0}));await Ae(n,a);return d}export function registerAuthCommands(e){const t=e.command("auth").description("Inspect website login status"),r=t.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").option("--max-age <seconds>","Reuse a local cached verdict younger than this instead of probing").option("--verify <n>","Deprecated and ignored: batch auth status never navigates. Use --site <s> --full to confirm one site with a real whoami").addOption(new fe("--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(n)=>{const s=typeof r.optsWithGlobals==="function"?r.optsWithGlobals():{},a=await collectAuthStatus({sites:n.site,full:n.full===!0,concurrency:n.concurrency,timeout:n.timeout,only:n.only,maxAge:n.maxAge,verify:n.verify,profile:typeof s.profile==="string"&&s.profile.trim()?s.profile.trim():void 0}),u=typeof n.format==="string"?n.format:"table";K(a,{fmt:u,fmtExplicit:r.getOptionValueSource("format")==="cli",columns:["site","status","identity","checked","error"],title:"ppcli/auth status",source:n.full?"full whoami probe":"quick auth check"})}),i=t.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(n)=>{const s=typeof i.optsWithGlobals==="function"?i.optsWithGlobals():{},a=await collectAuthRefresh({sites:n.site,all:n.all===!0,concurrency:n.concurrency,timeout:n.timeout,profile:typeof s.profile==="string"&&s.profile.trim()?s.profile.trim():void 0}),u=typeof n.format==="string"?n.format:"table";K(a,{fmt:u,fmtExplicit:i.getOptionValueSource("format")==="cli",columns:["site","status","last_touched_at","next_refresh_at","error"],title:"ppcli/auth refresh",source:n.all?"forced persistent touch":"persistent touch with 24h throttle"})});return t}
1
+ import{mkdir as W,readFile as D,rename as le,writeFile as U}from"node:fs/promises";import{homedir as H}from"node:os";import{dirname as V,join as z}from"node:path";import{pathToFileURL as fe}from"node:url";import{InvalidArgumentError as _,Option as de}from"commander";import{AuthRequiredError as L,CliError as G,getErrorMessage as R}from"../errors.js";import{executeCommand as w}from"../execution.js";import{ensureBrowserBridgeReady as he}from"../browser/daemon-lifecycle.js";import{resolveProfile as ge}from"../browser/profile.js";import{isElectronApp as E}from"../electron-apps.js";import{fullName as me,getRegistry as J}from"../registry.js";import{render as K}from"../output.js";const x=1,T=86400000;function k(e,t,r){if(e===void 0||e===null||e==="")return r;const i=Number(e);if(!Number.isInteger(i)||i<=0)throw new _(`${t} must be a positive integer. Received: "${String(e)}"`);return i}function Q(e,t,r){if(e===void 0||e===null||e==="")return r;const i=Number(e);if(!Number.isInteger(i)||i<0)throw new _(`${t} must be a non-negative integer. Received: "${String(e)}"`);return i}function Y(e){if(!e||!e.trim())return null;const t=e.split(",").map((r)=>r.trim()).filter(Boolean);return t.length>0?new Set(t):null}function pe(){return z(H(),".ppcli","auth-refresh.json")}const C=1,ye=600000;function _e(){return z(H(),".ppcli","auth-state.json")}async function we(e){try{const t=JSON.parse(await D(e,"utf8"));if(t&&t.version===C&&t.profiles&&typeof t.profiles==="object")return{version:C,profiles:t.profiles}}catch{}return{version:C,profiles:{}}}async function ke(e,t){await W(V(e),{recursive:!0});const r=`${e}.${process.pid}.tmp`;await U(r,`${JSON.stringify(t,null,2)}
2
+ `,"utf8");await le(r,e)}function X(e,t){return e!==void 0&&t>=e&&t-e<ye}function Se(e,t,r){return{site:e,status:t.status,logged_in:t.logged_in,identity:t.identity,checked:"cached",error:t.error,checked_at:new Date(t.at).toISOString(),verified:X(t.verifiedAt,r)}}function be(e,t,r,i){return{status:e.status,logged_in:e.logged_in,identity:e.identity,checked:e.checked,error:e.error,at:t,...r!==void 0?{verifiedAt:r}:{},...i!==void 0?{verifyAttemptAt:i}:{}}}function Ae(){return{version:x,sites:{}}}async function ve(e){try{const t=JSON.parse(await D(e,"utf8"));if(t&&t.version===x&&t.sites&&typeof t.sites==="object")return{version:x,sites:t.sites}}catch(t){if(t.code!=="ENOENT")throw t}return Ae()}async function Re(e,t){await W(V(e),{recursive:!0});await U(e,`${JSON.stringify(t,null,2)}
3
+ `,"utf8")}function Z(e){if(!e?.last_touched_at)return null;const t=Date.parse(e.last_touched_at);return Number.isFinite(t)?t:null}function Ee(e,t){const r=Z(e);return r!==null&&t.getTime()-r<T}function h(e){const t=Z(e);return t===null?"":new Date(t+T).toISOString()}function ee(){const e=new Set;return[...J().values()].filter((t)=>{if(e.has(t))return!1;e.add(t);return t.name==="whoami"&&t.access==="read"}).sort((t,r)=>t.site.localeCompare(r.site))}async function O(e){const t=e;if(!t._lazy||!t._modulePath)return e;await import(fe(t._modulePath).href);return J().get(me(e))??e}function S(e,t){const r=e.args.some((i)=>i.name==="timeout");return{...e,args:r?e.args:[...e.args,{name:"timeout",type:"int",default:t,help:"Per-site auth command timeout in seconds"}]}}function xe(e,t){if(e.browser!==!0||typeof e.authStatus?.quickCheck!=="function")return null;return S({...e,func:e.authStatus.quickCheck,navigateBefore:!1,siteSession:"ephemeral",defaultWindowMode:"background"},t)}function te(e){if(typeof e==="boolean")return e;if(e&&typeof e==="object"&&!Array.isArray(e)){const t=e.logged_in;if(typeof t==="boolean")return t}return null}function Te(e){const t=Array.isArray(e)?e[0]:e;if(t&&typeof t==="object"&&!Array.isArray(t)){const r=t.logged_in;if(typeof r==="boolean")return r}return null}function Ce(e){const t=Array.isArray(e)?e[0]:e;if(t&&typeof t==="object"&&!Array.isArray(t)){const r=t.error;if(typeof r==="string"&&r.trim())return r.trim()}return""}function re(e){if(e===void 0||e===null)return"";if(typeof e==="string"||typeof e==="number"||typeof e==="boolean")return String(e);return""}function ne(e){const t=Array.isArray(e)?e[0]:e;if(!t||typeof t!=="object"||Array.isArray(t))return"";const r=t,i=/(?:email|phone|real.?name|first.?name|last.?name|cookie|token|session|secret|password|csrf|jwt|bearer|wt2)/i;for(const n of["username","handle","user_id","id","name","nickname","user_type","url"]){if(i.test(n))continue;const s=re(r[n]);if(s)return s}for(const[n,s]of Object.entries(r)){if(n==="site"||n==="logged_in"||i.test(n))continue;const a=re(s);if(a)return a}return""}function I(e){return e instanceof L||typeof e==="object"&&e!==null&&e.code==="AUTH_REQUIRED"}function ie(e,t,r){if(I(r))return{site:e,status:"not_logged_in",logged_in:!1,identity:"",checked:t,error:""};const i=r instanceof G?r.code:"",n=R(r);return{site:e,status:"error",logged_in:"",identity:"",checked:t,error:i?`${i}: ${n}`:n}}function Oe(e,t){if(e.browser!==!0)return null;let r=e.authStatus?.refresh;if(typeof r!=="function"){const i=e.authStatus?.quickCheck;if(typeof i!=="function"||!e.domain)return null;const n=e.domain.startsWith("http://")||e.domain.startsWith("https://")?e.domain:`https://${e.domain}`;r=async(s,a,u)=>{await s.goto(n);await s.wait(1);if(te(await i(s,a,u))!==!0)throw new L(e.domain??e.site,`Auth refresh quickCheck failed for ${e.site}`);return{status:"touched"}}}return S({...e,func:r,navigateBefore:!1,siteSession:"persistent",defaultWindowMode:"background"},t)}function Ie(e){if(e&&typeof e==="object"&&!Array.isArray(e)){const t=e;if(t.status==="refreshed"||t.refreshed===!0)return"refreshed"}return"touched"}function qe(e,t,r){if(I(r))return{site:e,status:"not_logged_in",last_touched_at:t?.last_touched_at??"",next_refresh_at:h(t),error:""};const i=r instanceof G?r.code:"",n=R(r);return{site:e,status:"error",last_touched_at:t?.last_touched_at??"",next_refresh_at:h(t),error:i?`${i}: ${n}`:n}}function oe(){return process.env.PUBLISHPORT_SITE_SESSION==="ephemeral"?{siteSession:"ephemeral",keepTab:"false"}:{siteSession:"persistent",keepTab:"true"}}async function Ne(e,t){try{const r=await O(e);if(r.browser!==!0){const a=S(r,t.timeoutSeconds),u=await w(a,{timeout:t.timeoutSeconds},!1,{...t.profile?{profile:t.profile}:{}});return{site:e.site,status:"logged_in",logged_in:!0,identity:ne(u),checked:"quick",error:""}}if(r.authStatus?.quickCheckRequiresNavigation===!0)return{site:e.site,status:"unknown",logged_in:"",identity:"",checked:"navigation_required",error:""};const i=xe(r,t.timeoutSeconds);if(!i)return{site:e.site,status:"unknown",logged_in:"",identity:"",checked:"skipped",error:"quickCheck not implemented; use --full to run whoami"};const n=await w(i,{timeout:t.timeoutSeconds},!1,{...oe(),windowMode:"background",...t.profile?{profile:t.profile}:{}}),s=te(n);if(s===!0)return{site:e.site,status:"logged_in",logged_in:!0,identity:"",checked:"quick",error:""};if(s===!1)return{site:e.site,status:"not_logged_in",logged_in:!1,identity:"",checked:"quick",error:""};return{site:e.site,status:"unknown",logged_in:"",identity:"",checked:"quick",error:"quickCheck returned no boolean logged_in signal"}}catch(r){return ie(e.site,"quick",r)}}async function Me(e,t){try{const r=await O(e),i=S(r,t.timeoutSeconds),n=await w(i,{timeout:t.timeoutSeconds},!1,{...oe(),windowMode:"background",...t.profile?{profile:t.profile}:{}});if(Te(n)===!1)return{site:e.site,status:"not_logged_in",logged_in:!1,identity:"",checked:"full",error:Ce(n)};return{site:e.site,status:"logged_in",logged_in:!0,identity:ne(n),checked:"full",error:""}}catch(r){return ie(e.site,"full",r)}}function Fe(e,t){return t?`${e}@${t}`:e}async function $e(e,t){const r=Fe(e.site,t.profile),i=t.state.sites[r];if(!t.force&&Ee(i,t.now))return{site:e.site,status:"skipped",last_touched_at:i?.last_touched_at??"",next_refresh_at:h(i),error:""};const n=t.now.toISOString();try{const s=await O(e),a=Oe(s,t.timeoutSeconds);if(!a){t.state.sites[r]={...i,last_attempt_at:n,last_status:"unsupported"};return{site:e.site,status:"unsupported",last_touched_at:i?.last_touched_at??"",next_refresh_at:h(i),error:"refresh probe is not available for this site"}}const u=await w(a,{timeout:t.timeoutSeconds},!1,{siteSession:"persistent",keepTab:"true",windowMode:"background",...t.profile?{profile:t.profile}:{}}),d=Ie(u);t.state.sites[r]={...i,last_attempt_at:n,last_touched_at:n,last_status:d};return{site:e.site,status:d,last_touched_at:n,next_refresh_at:new Date(t.now.getTime()+T).toISOString(),error:""}}catch(s){const a=I(s)?"not_logged_in":"error";t.state.sites[r]={...i,last_attempt_at:n,last_status:a};return qe(e.site,i,s)}}async function se(e,t){const r=ge(t);if(r.kind!=="extension")return null;if(!e.some((n)=>n.browser===!0&&!E(n.site)))return null;try{await he({timeoutSeconds:15,contextId:r.contextId,verbose:!1});return null}catch(n){return R(n)}}function Pe(e,t,r){return{site:e,status:"error",logged_in:"",identity:"",checked:t,error:`BROWSER_CONNECT: ${r}`}}async function ae(e,t,r){const i=Array(e.length);let n=0;const s=Array.from({length:Math.min(t,e.length)},async()=>{while(n<e.length){const a=n++;i[a]=await r(e[a])}});await Promise.all(s);return i}export async function collectAuthStatus(e){const t=Y(e.sites),r=e.full?"full":"quick",i=k(e.concurrency,"--concurrency",r==="full"?3:8),n=k(e.timeout,"--timeout",r==="full"?20:8),s=String(e.only??"all");if(!["all","logged-in","not-logged-in","unknown","error"].includes(s))throw new _("--only must be one of: all, logged-in, not-logged-in, unknown, error");if(r==="full"&&!t)throw new _("--full 会对每个站点真实打开页面,不允许全量使用。"+"要实证某个站点请点名:auth status --site <site> --full;"+"想看全部站点就用默认的 quick(纯 cookie,不开任何页面)。");const a=Q(e.maxAge,"--max-age",0)*1000,u=Q(e.verify,"--verify",0),d=e.statePath??_e(),f=(e.now??new Date).getTime(),l=e.profile??"",b=ee().filter((o)=>!t||t.has(o.site)),A=await we(d),g=A.profiles[l]??{},v=new Map,m=[];for(const o of b){const c=g[o.site];if(r!=="full"&&a>0&&c&&f>=c.at&&f-c.at<a)v.set(o.site,Se(o.site,c,f));else m.push(o)}const q=m.length>0?await se(m,e.profile):null,ue=await ae(m,i,(o)=>q&&o.browser===!0&&!E(o.site)?Promise.resolve(Pe(o.site,r,q)):r==="full"?Me(o,{timeoutSeconds:n,profile:e.profile}):Ne(o,{timeoutSeconds:n,profile:e.profile})),je=new Map(b.map((o)=>[o.site,o])),p=new Map(v),N=new Map,M=new Map;for(const o of ue){p.set(o.site,o);if(r==="full"){M.set(o.site,f);if(o.status!=="error")N.set(o.site,f)}}const F=(o)=>{const c=N.get(o.site);if(c!==void 0)return c;const y=g[o.site];return y&&y.status===o.status&&X(y.verifiedAt,f)?y.verifiedAt:void 0},ce=(o)=>M.get(o.site)??g[o.site]?.verifyAttemptAt,P={...g};for(const o of p.keys()){if(v.has(o))continue;const c=p.get(o);if(!c)continue;P[o]=be(c,f,F(c),ce(c))}A.profiles[l]=P;await ke(d,A).catch(()=>{});const j=b.map((o)=>p.get(o.site)).filter((o)=>o!==void 0).map((o)=>({...o,checked_at:o.checked_at??new Date(f).toISOString(),verified:o.verified??F(o)!==void 0})),B=s.replace(/-/g,"_");return B==="all"?j:j.filter((o)=>o.status===B)}export async function collectAuthRefresh(e){const t=Y(e.sites),r=k(e.concurrency,"--concurrency",3),i=k(e.timeout,"--timeout",20),n=e.statePath??pe(),s=e.now??new Date,a=await ve(n),u=ee().filter((l)=>!t||t.has(l.site)),d=await se(u,e.profile),f=await ae(u,r,(l)=>d&&l.browser===!0&&!E(l.site)?Promise.resolve({site:l.site,status:"error",last_touched_at:a.sites[l.site]?.last_touched_at??"",next_refresh_at:h(a.sites[l.site]),error:`BROWSER_CONNECT: ${d}`}):$e(l,{timeoutSeconds:i,profile:e.profile,now:s,state:a,force:e.all===!0}));await Re(n,a);return f}export function registerAuthCommands(e){const t=e.command("auth").description("Inspect website login status"),r=t.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").option("--max-age <seconds>","Reuse a local cached verdict younger than this instead of probing").option("--verify <n>","Deprecated and ignored: batch auth status never navigates. Use --site <s> --full to confirm one site with a real whoami").addOption(new de("--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(n)=>{const s=typeof r.optsWithGlobals==="function"?r.optsWithGlobals():{},a=await collectAuthStatus({sites:n.site,full:n.full===!0,concurrency:n.concurrency,timeout:n.timeout,only:n.only,maxAge:n.maxAge,verify:n.verify,profile:typeof s.profile==="string"&&s.profile.trim()?s.profile.trim():void 0}),u=typeof n.format==="string"?n.format:"table";K(a,{fmt:u,fmtExplicit:r.getOptionValueSource("format")==="cli",columns:["site","status","identity","checked","error"],title:"ppcli/auth status",source:n.full?"full whoami probe":"quick auth check"})}),i=t.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(n)=>{const s=typeof i.optsWithGlobals==="function"?i.optsWithGlobals():{},a=await collectAuthRefresh({sites:n.site,all:n.all===!0,concurrency:n.concurrency,timeout:n.timeout,profile:typeof s.profile==="string"&&s.profile.trim()?s.profile.trim():void 0}),u=typeof n.format==="string"?n.format:"table";K(a,{fmt:u,fmtExplicit:i.getOptionValueSource("format")==="cli",columns:["site","status","last_touched_at","next_refresh_at","error"],title:"ppcli/auth refresh",source:n.all?"forced persistent touch":"persistent touch with 24h throttle"})});return t}
@@ -8,6 +8,6 @@ If the extension is not installed, install the PublishPort browser extension
8
8
  from the PublishPort desktop app.`);if(u&&!a)t.push(`Extension is connected but did not report a version.
9
9
  This usually means an outdated Browser Bridge extension.
10
10
  Reload or reinstall the PublishPort browser extension via the PublishPort desktop app.`);if(!n.ok)t.push(`Browser connectivity test failed: ${n.error??"unknown"}`);const m=o.status?.extensionCompatRange;if(a&&e.cliVersion&&m){if(!O(e.cliVersion,m))t.push(`CLI version incompatible with extension: extension v${a} requires CLI ${m}, but CLI is v${e.cliVersion}
11
- Update via the PublishPort desktop app (or: npm install -g publishport-ppcli).`)}else if(a&&e.cliVersion){const x=a.split(".")[0],v=e.cliVersion.split(".")[0];if(x!==v)t.push(`Extension major version mismatch: extension v${a} ≠ CLI v${e.cliVersion}
11
+ Update via the PublishPort desktop app (or: npm install -g publishport-opencli).`)}else if(a&&e.cliVersion){const x=a.split(".")[0],v=e.cliVersion.split(".")[0];if(x!==v)t.push(`Extension major version mismatch: extension v${a} ≠ CLI v${e.cliVersion}
12
12
  Update the PublishPort browser extension via the PublishPort desktop app.`)}if(d.length>0)t.push(P(d));return{cliVersion:e.cliVersion,daemonRunning:i,daemonFlaky:r,daemonStale:s,daemonVersion:o.status?.daemonVersion,extensionConnected:u,extensionFlaky:c,extensionVersion:a,connectivity:n,profiles:l,adapterShadows:d,issues:t}}export function renderBrowserDoctorReport(e){const n=[`ppcli v${e.cliVersion??"unknown"} doctor (${k()})`,""],o=e.daemonFlaky?"[WARN]":e.daemonStale?"[WARN]":e.daemonRunning?"[OK]":"[MISSING]",i=e.daemonFlaky?"unstable (running during live check, then stopped)":e.daemonRunning?`running on port ${w} (${e.daemonStale?`${f(e)}, stale; CLI v${e.cliVersion??"unknown"}`:f(e)})`:"not running";n.push(`${o} Daemon: ${i}`);const u=e.extensionFlaky||e.extensionConnected&&!e.extensionVersion?"[WARN]":e.extensionConnected?"[OK]":"[MISSING]",r=!e.extensionConnected?"":e.extensionVersion?` (v${e.extensionVersion})`:" (version unknown)",c=e.extensionFlaky?"unstable (connected during live check, then disconnected)":e.extensionConnected?"connected":"not connected";n.push(`${u} Extension: ${c}${r}`);if(e.profiles&&e.profiles.length>0){const s=I();n.push("","Profiles:");for(const l of e.profiles){const a=C(s,l.contextId),d=a?` (${a})`:"",t=s.defaultContextId===l.contextId?", default":"",m=l.extensionVersion?`v${l.extensionVersion}`:"version unknown";n.push(` • ${l.contextId}${d}: connected ${m}${t}`)}}if(e.connectivity){const s=e.connectivity.ok?"[OK]":"[FAIL]",l=e.connectivity.ok?`connected in ${(e.connectivity.durationMs/1000).toFixed(1)}s`:`failed (${e.connectivity.error??"unknown"})`;n.push(`${s} Connectivity: ${l}`)}if(e.issues.length){n.push("","Issues:");for(const s of e.issues)n.push(` • ${s}`)}else if(e.daemonRunning&&e.extensionConnected)n.push("","Everything looks good!");return n.join(`
13
13
  `)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "publishport-opencli",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": false