publishport-opencli 1.0.24 → 1.0.26

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.
@@ -0,0 +1,59 @@
1
+ import{existsSync as ee,mkdirSync as te,readFileSync as re,writeFileSync as ne}from"node:fs";import{homedir as ae}from"node:os";import{dirname as ie,join as G}from"node:path";import{cli as se,Strategy as oe}from"@jackwener/opencli/registry";import{AuthRequiredError as J,CommandExecutionError as ce}from"@jackwener/opencli/errors";const P=1000,B=300,j=60,D=30,z=2000,le=20;function ue(){return G(ae(),".ppcli","watch")}function H(e,t){const r=String(process.env.PPCLI_ACTIVE_PROFILE??"").trim();return G(ue(),`${e}${r?`@${r}`:""}.${t}.seen.json`)}function de(e,t){const r=H(e,t);try{if(!ee(r))return{ids:[],set:new Set,lastTs:0,firstRun:!0};const i=JSON.parse(re(r,"utf-8")),o=Array.isArray(i?.ids)?i.ids.map(String):[],c=Number.isFinite(i?.lastTs)?Number(i.lastTs):0;return{ids:o,set:new Set(o),lastTs:c,firstRun:c<=0}}catch{return{ids:[],set:new Set,lastTs:0,firstRun:!0}}}function E(e,t,r){const i=H(e,t);try{te(ie(i),{recursive:!0});ne(i,JSON.stringify({ids:r.ids.slice(-z),lastTs:r.lastTs||0}),"utf-8")}catch{}}function fe(e,t){if(e.set.has(t))return!1;e.set.add(t);e.ids.push(t);while(e.ids.length>z){const r=e.ids.shift();e.set.delete(r)}return!0}function R(e){process.stdout.write(`${JSON.stringify(e)}
2
+ `)}function u(e,t,r={}){R({type:"status",site:e,state:t,ts:Date.now(),...r})}function K(e,t){if(!t||typeof t!=="object")return null;const r=t.id==null?"":String(t.id);if(!r)return null;const i=t.sender&&typeof t.sender==="object"?{id:t.sender.id==null?"":String(t.sender.id),name:String(t.sender.name??"")}:{id:"",name:String(t.sender??"")};return{type:"event",site:e,event:String(t.event??"message"),id:r,ts:Number.isFinite(t.ts)?Number(t.ts):Date.now(),conversation_id:t.conversation_id==null?"":String(t.conversation_id),sender:i,text:String(t.text??""),url:t.url==null?"":String(t.url),raw:t.raw??null}}function V(e){if(!e)return[];return Array.isArray(e)?e.filter(Boolean):[e]}function Q(e){try{return new URL(e).origin}catch{return""}}function pe(e){return`(function(){
3
+ if (window.__pp_stream_hooked) return;
4
+ window.__pp_stream_hooked = true;
5
+ var PATTERNS = ${JSON.stringify(String(e||"").split("|").map((r)=>r.trim()).filter(Boolean))};
6
+ var LIMIT = 500;
7
+ var CAP = 262144;
8
+ window.__pp_stream = [];
9
+ function want(u){ if(!PATTERNS.length) return true; for(var i=0;i<PATTERNS.length;i++){ if(String(u).indexOf(PATTERNS[i])>=0) return true; } return false; }
10
+ function push(ch, dir, url, data, name){
11
+ try {
12
+ if (!want(url)) return;
13
+ if (window.__pp_stream.length >= LIMIT) window.__pp_stream.shift();
14
+ var body = typeof data === 'string' ? data : null;
15
+ // 二进制帧(Blob / ArrayBuffer)在页面里同步转不了 base64,如实标记后交给上层,
16
+ // 不猜内容——大多数平台的业务帧是文本,二进制帧靠 CDP 那条通道拿。
17
+ var binary = body === null;
18
+ if (body && body.length > CAP) body = body.slice(0, CAP);
19
+ window.__pp_stream.push({ channel: ch, direction: dir, url: String(url||''), data: body, binary: binary, eventName: name || '', ts: Date.now() });
20
+ } catch (e) {}
21
+ }
22
+ var NativeWS = window.WebSocket;
23
+ if (NativeWS) {
24
+ var WrappedWS = function(url, protocols){
25
+ var ws = protocols === undefined ? new NativeWS(url) : new NativeWS(url, protocols);
26
+ try {
27
+ ws.addEventListener('message', function(ev){ push('websocket','received',url,ev.data); });
28
+ var send = ws.send;
29
+ ws.send = function(d){ push('websocket','sent',url,d); return send.apply(this, arguments); };
30
+ } catch (e) {}
31
+ return ws;
32
+ };
33
+ WrappedWS.prototype = NativeWS.prototype;
34
+ WrappedWS.CONNECTING = NativeWS.CONNECTING; WrappedWS.OPEN = NativeWS.OPEN;
35
+ WrappedWS.CLOSING = NativeWS.CLOSING; WrappedWS.CLOSED = NativeWS.CLOSED;
36
+ window.WebSocket = WrappedWS;
37
+ }
38
+ var NativeES = window.EventSource;
39
+ if (NativeES) {
40
+ var WrappedES = function(url, cfg){
41
+ var es = cfg === undefined ? new NativeES(url) : new NativeES(url, cfg);
42
+ try {
43
+ es.addEventListener('message', function(ev){ push('eventsource','received',url,ev.data,'message'); });
44
+ // 具名 SSE 事件不走 onmessage:劫持 addEventListener,把业务方注册的每个
45
+ // 事件名都顺带旁听一份。
46
+ var addL = es.addEventListener.bind(es);
47
+ es.addEventListener = function(name, fn, opts){
48
+ if (name && name !== 'message' && name !== 'open' && name !== 'error') {
49
+ try { addL(name, function(ev){ push('eventsource','received',url,ev.data,name); }); } catch(e){}
50
+ }
51
+ return addL(name, fn, opts);
52
+ };
53
+ } catch (e) {}
54
+ return es;
55
+ };
56
+ WrappedES.prototype = NativeES.prototype;
57
+ window.EventSource = WrappedES;
58
+ }
59
+ })()`}async function me(e){let t;try{t=await e.evaluate("(function(){ var o = window.__pp_stream || []; window.__pp_stream = []; return JSON.stringify(o); })()")}catch{return[]}let r;try{r=JSON.parse(typeof t==="string"?t:"[]")}catch{return[]}if(!Array.isArray(r))return[];return r.map((i)=>({kind:"page-hook",channel:i.channel,direction:i.direction,url:i.url,method:i.channel==="websocket"?"WS":"SSE",eventName:i.eventName||void 0,responseStatus:200,responseContentType:i.binary?"application/octet-stream":"text/plain",responsePreview:i.binary?"":String(i.data??""),timestamp:i.ts||Date.now()}))}export function registerWatch(e){if(!e?.site||!e?.domain||!e?.watchUrl||typeof e.parse!=="function")throw Error("registerWatch 需要 site、domain、watchUrl 和 parse");const t=e.name??"watch",r=e.site,i=Q(e.watchUrl);se({site:r,name:t,access:"read",description:e.description??`长驻监听${r}的新消息,旁听页面自身的实时通道并按 NDJSON 逐条输出(不对平台发轮询请求)`,domain:e.domain,strategy:oe.COOKIE,browser:!0,navigateBefore:!1,sessionKey:"watch",siteSession:"persistent",defaultWindowMode:"background",args:[{name:"interval",type:"int",required:!1,default:P,help:`本机取帧间隔毫秒(默认 ${P})。纯本机 IPC,不发平台请求`},{name:"reconcile-interval",type:"int",required:!1,default:0,help:`对账间隔秒(0 = 用该平台的默认值:可旁听实时通道的平台 ${B} 秒兜底,只能靠拉取的平台 ${j} 秒一轮);-1 = 彻底关闭对账`},{name:"heartbeat",type:"int",required:!1,default:D,help:`心跳间隔秒(默认 ${D}),无事件时输出一行 status 表示还活着`},{name:"events",type:"str",required:!1,help:"只输出这些事件类型,逗号分隔(如 dm,comment);默认全部"},{name:"once",type:"bool",required:!1,default:!1,help:"只跑一轮对账后退出,不长驻。用于验证解析器和补历史"},{name:"replay-seen",type:"bool",required:!1,default:!1,help:"忽略去重表,把解析到的事件全部重新输出(调试用,会导致重复触发)"},{name:"backfill",type:"bool",required:!1,default:!1,help:"首次运行时把已存在的历史消息也当成新事件输出(默认只静默登记,不触发)"},{name:"dump-frames",type:"bool",required:!1,default:!1,help:"侦察模式:把收到的每一条原始帧(url / 通道 / 内容片段)也打出来,用来给新平台摸清事件通道"},{name:"include-own",type:"bool",required:!1,default:!1,help:"连自己发出去的消息也当成事件(默认不算——否则自动回复会追着自己的回复自我触发)。调试解析用"}],columns:["event","id","sender","text","ts"],func:async(o,c)=>{const X=Math.max(200,Number(c.interval??P)),k=e.mode==="reconcile"?"reconcile":"stream",_=Number(c["reconcile-interval"]??0),b=_<0?0:_>0?_:k==="reconcile"?j:B,U=Math.max(0,Number(c.heartbeat??D)),x=c.once===!0||c.once==="true",S=c["replay-seen"]===!0||c["replay-seen"]==="true",T=c.backfill===!0||c.backfill==="true",Y=c["dump-frames"]===!0||c["dump-frames"]==="true",h=String(c.events??"").split(",").map((l)=>l.trim()).filter(Boolean);if(typeof o.startNetworkCapture!=="function"||typeof o.readNetworkCapture!=="function")throw new ce("当前浏览器通道不支持网络捕获,watch 无法工作(需要 CDP 或扩展路径)");const d=de(r,t),C={site:r,page:o,state:{},includeOwn:c["include-own"]===!0||c["include-own"]==="true"};let p=0,W=0,y=!1;const g=[];let m=d.firstRun&&!T&&!S,O=!1;const w=d.lastTs,F=(l)=>{const a=K(r,l);if(!a)return!1;if(h.length&&!h.includes(a.event))return!1;if(!S&&!fe(d,`${a.event}:${a.id}`))return!1;if(a.ts>d.lastTs)d.lastTs=a.ts;if(m){W++;return!1}if(!T&&!S&&w>0&&a.ts>0&&a.ts<=w){W++;return!1}if(!x)R(a);g.push(a);p++;return!0},Z=(l)=>{let a=0;for(const n of l){const s=K(r,n);if(!s)continue;if(h.length&&!h.includes(s.event))continue;if(d.set.has(`${s.event}:${s.id}`))continue;if(w>0&&s.ts>0&&s.ts<=w)continue;a++}return a},v=async(l,a=!1)=>{if(typeof e.reconcile!=="function")return;try{const n=V(await e.reconcile(o,C));if(a&&!m&&!T&&!S&&Z(n)>le){m=!0;O=!0}let s=0;for(const f of n)if(F(f))s++;u(r,"reconciled",{reason:l,scanned:n.length,new:s})}catch(n){if(n instanceof J)throw n;u(r,"reconcile_failed",{reason:l,error:n?.message??String(n)})}},I=async(l)=>{let a=!1;try{a=await o.startNetworkCapture(e.pattern??"")!==!1}catch{a=!1}const n=pe(e.pattern??"");y=!1;let s=!1;if(typeof o.cdp==="function")try{await o.cdp("Page.enable",{});await o.cdp("Page.addScriptToEvaluateOnNewDocument",{source:n});s=!0;y=!0}catch{}await o.goto(e.watchUrl,{waitUntil:s?"load":"none"});if(!s)try{await o.evaluate(n);y=!0;u(r,"page_hook_late",{note:"浏览器扩展版本较旧,旁听脚本只能在导航后注入;页面在此之前已建立的连接要等它重连才能被看到"})}catch(f){u(r,"page_hook_unavailable",{error:f?.message??String(f)})}if(!a){await o.startNetworkCapture(e.pattern??"");await o.goto(e.watchUrl,{waitUntil:"load"})}if(typeof e.onAttach==="function")await e.onAttach(o,C);u(r,"watching",{reason:l,mode:k,url:e.watchUrl,pattern:e.pattern??"",reconcile_seconds:b})};await I("start");await v("startup",!0);if(m){E(r,t,d);u(r,"seeded",{seeded:W,desynced:O,note:O?"去重表和收件箱对不上(上次没跑完 / 换了账号 / 表被裁剪),本轮把当前收件箱重新登记为「已见过」,不触发任何事件。从现在起只有新消息才会上报":"首次运行:已把当前收件箱登记为「已见过」,不触发任何事件。从现在起只有新消息才会上报(要补历史请加 --backfill)"});m=!1}if(x){E(r,t,d);return g}let A=!1;const N=()=>{A=!0};process.on("SIGINT",N);process.on("SIGTERM",N);let M=Date.now(),L=Date.now(),q=p;try{while(!A){await new Promise((n)=>setTimeout(n,X));if(A)break;let l=[];try{l=await o.readNetworkCapture()??[];if(y)l=l.concat(await me(o))}catch(n){u(r,"reattaching",{error:n?.message??String(n)});try{await I("capture_lost");await v("reattach")}catch(s){if(s instanceof J)throw s;u(r,"reattach_failed",{error:s?.message??String(s)})}continue}for(const n of l){if(Y)R({type:"frame",site:r,via:n?.kind==="page-hook"?"page-hook":"cdp",channel:n?.channel??"http",direction:n?.direction??"",method:n?.method??"",url:String(n?.url??"").slice(0,200),size:n?.responseBodyFullSize??String(n?.responsePreview??"").length,preview:String(n?.responsePreview??"").slice(0,600)});if(n?.direction==="sent")continue;let s;try{s=await e.parse(n,C)}catch(f){u(r,"parse_failed",{url:n?.url??"",error:f?.message??String(f)});continue}for(const f of V(s))F(f)}if(p!==q){E(r,t,d);q=p;L=Date.now()}if(i&&typeof o.getCurrentUrl==="function")try{const n=await o.getCurrentUrl();if(n&&Q(n)!==i){u(r,"page_drifted",{url:n});await I("drift");await v("drift")}}catch{}const a=Date.now();if(b&&a-M>=b*1000){M=a;await v("interval")}if(U&&a-L>=U*1000){L=a;u(r,"alive",{emitted:p})}}}finally{process.off("SIGINT",N);process.off("SIGTERM",N);E(r,t,d);u(r,"stopped",{emitted:p})}return g}})}export function frameJson(e){const t=e?.responsePreview;if(typeof t!=="string"||!t)return null;if(t.startsWith("base64:"))return null;try{return JSON.parse(t)}catch{return null}}
package/clis/band/post.js CHANGED
@@ -1,4 +1,4 @@
1
- import{AuthRequiredError as g,EmptyResultError as C}from"@jackwener/opencli/errors";import{formatCookieHeader as w}from"@jackwener/opencli/download";import{downloadMedia as h}from"@jackwener/opencli/download/media-download";import{cli as x,Strategy as L}from"@jackwener/opencli/registry";x({site:"band",name:"post",access:"read",description:"Export full content of a post including comments",domain:"www.band.us",strategy:L.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"band_no",positional:!0,required:!0,type:"int",help:"Band number"},{name:"post_no",positional:!0,required:!0,type:"int",help:"Post number"},{name:"output",type:"str",default:"",help:"Directory to save attached photos"},{name:"comments",type:"bool",default:!0,help:"Include comments (default: true)"}],columns:["type","author","date","text"],func:async(n,r)=>{const f=Number(r.band_no),y=Number(r.post_no),s=r.output,b=r.comments;await n.goto(`https://www.band.us/band/${f}/post/${y}`);if(!(await n.getCookies({domain:"band.us"})).some((t)=>t.name==="band_session"))throw new g("band.us","Not logged in to Band");const o=await n.evaluate(`
1
+ import{AuthRequiredError as g,EmptyResultError as C}from"@jackwener/opencli/errors";import{formatCookieHeader as w}from"@jackwener/opencli/download";import{downloadMedia as h}from"@jackwener/opencli/download/media-download";import{cli as x,Strategy as L}from"@jackwener/opencli/registry";x({site:"band",name:"post",access:"read",description:"Export full content of a post including comments",domain:"www.band.us",strategy:L.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"band_no",positional:!0,required:!0,type:"int",help:"Band number"},{name:"post_no",positional:!0,required:!0,type:"int",help:"Post number"},{name:"output",type:"str",default:"",help:"Directory to save attached photos"},{name:"comments",type:"bool",default:!0,help:"Include comments (default: true)"}],columns:["type","author","date","text"],func:async(n,r)=>{const f=Number(r.band_no),y=Number(r.post_no),{output:s,comments:b}=r;await n.goto(`https://www.band.us/band/${f}/post/${y}`);if(!(await n.getCookies({domain:"band.us"})).some((t)=>t.name==="band_session"))throw new g("band.us","Not logged in to Band");const o=await n.evaluate(`
2
2
  (async () => {
3
3
  const withComments = ${b};
4
4
  const sleep = ms => new Promise(r => setTimeout(r, ms));
@@ -1,4 +1,4 @@
1
- import{cli as v,Strategy as x}from"@jackwener/opencli/registry";import{CliError as g,CommandExecutionError as n,EXIT_CODES as P}from"@jackwener/opencli/errors";import{checkYtdlp as _,sanitizeFilename as C}from"@jackwener/opencli/download";import{downloadMedia as E}from"@jackwener/opencli/download/media-download";import{apiGet as d,resolveBvid as O,parsePageArg as A,selectVideoPart as q}from"./utils.js";const T={vip:"大会员专享/付费 OGV",ugc_pay:"UGC 单点付费",upower:"充电专属"};function l(e){return e&&typeof e==="object"&&!Array.isArray(e)}async function B(e,r){let i;try{const t=await d(e,"/x/web-interface/view",{params:{bvid:r}});if(!l(t)||!Object.hasOwn(t,"code"))throw new n("Bilibili view API returned a malformed payload during paid-content pre-check");if(t.code!==0)return;if(!l(t.data)||!l(t.data.rights))throw new n("Bilibili view API returned malformed paid-content metadata");i=t.data}catch(t){if(t instanceof n)throw t;return}const a=i.rights,o=a.pay?"vip":a.ugc_pay||a.arc_pay?"ugc_pay":i.is_upower_exclusive?"upower":"";if(!o)return;if(o==="vip")try{const t=await d(e,"/x/web-interface/nav");if(t.code===0&&Number(t.data?.vipStatus)===1)return}catch{}throw new g("PAID_CONTENT",`该视频为付费内容(${T[o]}),当前账号无观看权益,无法获取完整视频流`,"若已购买/已充电/已开通会员,加 --force 跳过本检查直接下载",P.NOPERM)}async function S(e,r,i){let a;try{a=await d(e,"/x/web-interface/view",{params:{bvid:r}})}catch(o){throw new n(`获取视频分P信息失败: ${o?.message||o}`)}if(!l(a)||a.code!==0)throw new n(`获取视频分P信息失败: ${a?.message??"unknown"} (${a?.code??"malformed"})`);return q(a.data,i)}v({site:"bilibili",name:"download",access:"read",description:"下载B站视频(需要 yt-dlp)",domain:"www.bilibili.com",strategy:x.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID (e.g., BV1xxx)"},{name:"output",default:"./bilibili-downloads",help:"Output directory"},{name:"quality",default:"best",help:"Video quality (best, 1080p, 720p, 480p)"},{name:"force",type:"boolean",default:!1,help:"跳过付费内容预检直接下载(已购买/已充电/已开通会员时用)"},{name:"page",required:!1,help:"分P 选集序号(从 1 开始)。多 P 视频下载该集;缺省下载默认 P1"}],columns:["bvid","title","status","size"],func:async(e,r)=>{const i=await O(r.bvid),a=r.output,o=r.quality,t=A(r.page),c=t!=null?await S(e,i,t):null,u=t!=null?`https://www.bilibili.com/video/${i}?p=${t}`:`https://www.bilibili.com/video/${i}`;if(!_())return[{bvid:i,title:"-",status:"failed",size:"yt-dlp not installed. Run: pip install yt-dlp"}];await e.goto(u);await e.wait(3);if(!r.force)await B(e,i);const w=await e.evaluate(`
1
+ import{cli as v,Strategy as x}from"@jackwener/opencli/registry";import{CliError as g,CommandExecutionError as n,EXIT_CODES as P}from"@jackwener/opencli/errors";import{checkYtdlp as _,sanitizeFilename as C}from"@jackwener/opencli/download";import{downloadMedia as E}from"@jackwener/opencli/download/media-download";import{apiGet as d,resolveBvid as O,parsePageArg as A,selectVideoPart as q}from"./utils.js";const T={vip:"大会员专享/付费 OGV",ugc_pay:"UGC 单点付费",upower:"充电专属"};function l(e){return e&&typeof e==="object"&&!Array.isArray(e)}async function B(e,r){let i;try{const t=await d(e,"/x/web-interface/view",{params:{bvid:r}});if(!l(t)||!Object.hasOwn(t,"code"))throw new n("Bilibili view API returned a malformed payload during paid-content pre-check");if(t.code!==0)return;if(!l(t.data)||!l(t.data.rights))throw new n("Bilibili view API returned malformed paid-content metadata");i=t.data}catch(t){if(t instanceof n)throw t;return}const a=i.rights,o=a.pay?"vip":a.ugc_pay||a.arc_pay?"ugc_pay":i.is_upower_exclusive?"upower":"";if(!o)return;if(o==="vip")try{const t=await d(e,"/x/web-interface/nav");if(t.code===0&&Number(t.data?.vipStatus)===1)return}catch{}throw new g("PAID_CONTENT",`该视频为付费内容(${T[o]}),当前账号无观看权益,无法获取完整视频流`,"若已购买/已充电/已开通会员,加 --force 跳过本检查直接下载",P.NOPERM)}async function S(e,r,i){let a;try{a=await d(e,"/x/web-interface/view",{params:{bvid:r}})}catch(o){throw new n(`获取视频分P信息失败: ${o?.message||o}`)}if(!l(a)||a.code!==0)throw new n(`获取视频分P信息失败: ${a?.message??"unknown"} (${a?.code??"malformed"})`);return q(a.data,i)}v({site:"bilibili",name:"download",access:"read",description:"下载B站视频(需要 yt-dlp)",domain:"www.bilibili.com",strategy:x.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID (e.g., BV1xxx)"},{name:"output",default:"./bilibili-downloads",help:"Output directory"},{name:"quality",default:"best",help:"Video quality (best, 1080p, 720p, 480p)"},{name:"force",type:"boolean",default:!1,help:"跳过付费内容预检直接下载(已购买/已充电/已开通会员时用)"},{name:"page",required:!1,help:"分P 选集序号(从 1 开始)。多 P 视频下载该集;缺省下载默认 P1"}],columns:["bvid","title","status","size"],func:async(e,r)=>{const i=await O(r.bvid),{output:a,quality:o}=r,t=A(r.page),c=t!=null?await S(e,i,t):null,u=t!=null?`https://www.bilibili.com/video/${i}?p=${t}`:`https://www.bilibili.com/video/${i}`;if(!_())return[{bvid:i,title:"-",status:"failed",size:"yt-dlp not installed. Run: pip install yt-dlp"}];await e.goto(u);await e.wait(3);if(!r.force)await B(e,i);const w=await e.evaluate(`
2
2
  (() => {
3
3
  const title = document.querySelector('h1.video-title, .video-title')?.textContent?.trim() || 'video';
4
4
  const author = document.querySelector('.up-name, .username')?.textContent?.trim() || 'unknown';
@@ -0,0 +1 @@
1
+ import{registerWatch as d,frameJson as p}from"../_shared/watch.js";function parseMsgContent(e){const s=String(e??"");try{const t=JSON.parse(s);if(t&&typeof t==="object"){if(typeof t.content==="string")return t.content;if(typeof t.title==="string"||typeof t.text==="string")return[t.title,t.text].filter(Boolean).join(":");return s}}catch{}return s}const f="session_type=1&group_fold=1&unfollow_fold=0&sort_rule=2&build=0&mobi_app=web",m=`https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions?${f}`;function sessionsToEvents(e,s){const t=e?.data?.session_list;if(!Array.isArray(t))return[];const i=String(s?.state?.selfUid??""),a=[];for(const o of t){const n=o?.last_msg;if(!n||!n.msg_key)continue;const c=String(n.sender_uid??"");if(!s?.includeOwn&&i&&c===i)continue;const r=String(o.talker_id??""),l=Number(n.timestamp??0);a.push({event:"dm",id:String(n.msg_key),ts:l?l*1000:Date.now(),conversation_id:r,sender:{id:c,name:""},text:parseMsgContent(n.content),url:r?`https://message.bilibili.com/#/whisper/mid${r}`:"",raw:{msg_type:n.msg_type,unread_count:o.unread_count,msg_seqno:n.msg_seqno}})}return a}d({site:"bilibili",name:"watch",domain:"www.bilibili.com",description:"B站私信监听:长驻旁听消息页自身的推送通道,收到新私信按 NDJSON 逐条输出(不对 B 站发轮询请求)",watchUrl:"https://message.bilibili.com/#/whisper",pattern:"session_svr/v1/session_svr/get_sessions",onAttach:async(e,s)=>{try{const i=(await e.fetchJson("https://api.bilibili.com/x/web-interface/nav"))?.data?.mid;if(i)s.state.selfUid=String(i)}catch{}},parse:(e,s)=>{const t=p(e);if(!t)return null;return sessionsToEvents(t,s)},reconcile:async(e,s)=>{const t=await e.fetchJson(m);return sessionsToEvents(t,s)}});export{sessionsToEvents,parseMsgContent};
@@ -0,0 +1,26 @@
1
+ import{registerWatch as a}from"../_shared/watch.js";const n="https://github.com/notifications",l=`
2
+ (function () {
3
+ var items = Array.prototype.slice.call(document.querySelectorAll('.notifications-list-item'));
4
+ return JSON.stringify(items.map(function (li) {
5
+ var link = li.querySelector('a[href*="/issues/"], a[href*="/pull/"], a[href*="/discussions/"], a[href*="/commit/"]');
6
+ var timeEl = li.querySelector('relative-time');
7
+ // 仓库名从通知指向的链接里切,别去猜 DOM 里哪个 a 是仓库链接——那个位置
8
+ // GitHub 改过好几次,切链接是稳的。
9
+ var titleEl = li.querySelector('.markdown-title') || li.querySelector('.notification-list-item-link .css-truncate-target');
10
+ var icon = li.querySelector('svg.octicon');
11
+ var unreadDot = li.querySelector('.notification-list-item-unread, [class*="unread"]');
12
+ return {
13
+ url: link ? link.getAttribute('href') : '',
14
+ title: titleEl ? (titleEl.textContent || '').replace(/\\s+/g, ' ').trim() : (li.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 200),
15
+ repo: (function () {
16
+ var h = link ? (link.getAttribute('href') || '') : '';
17
+ var m = h.replace(/^https?:\\/\\/github\\.com/, '').match(/^\\/([^/]+)\\/([^/]+)\\//);
18
+ return m ? m[1] + '/' + m[2] : '';
19
+ })(),
20
+ time: timeEl ? (timeEl.getAttribute('datetime') || '') : '',
21
+ kind: icon ? (icon.getAttribute('class') || '').replace('octicon ', '').trim() : '',
22
+ unread: !!unreadDot
23
+ };
24
+ }));
25
+ })()
26
+ `;function c(i){if(!Array.isArray(i))return[];const r=[];for(const t of i){if(!t||!t.url&&!t.title)continue;const e=String(t.url||""),o=t.time?Date.parse(t.time):NaN;r.push({event:"notification",id:`${e}|${t.time||""}`,ts:Number.isFinite(o)?o:Date.now(),conversation_id:e,sender:{id:"",name:String(t.repo||"")},text:String(t.title||""),url:e?e.startsWith("http")?e:`https://github.com${e}`:n,raw:{repo:t.repo||"",icon:t.kind||"",unread:!!t.unread}})}return r}a({site:"github",name:"watch",domain:"github.com",mode:"reconcile",description:"GitHub 通知监听:按间隔拉取通知页,有新的 issue/PR 活动按 NDJSON 逐条输出",watchUrl:n,pattern:"/notifications",parse:()=>null,reconcile:async(i)=>{await i.goto(n,{waitUntil:"load"});const r=await i.evaluate(l);try{return c(JSON.parse(typeof r==="string"?r:"[]"))}catch{return[]}}});
@@ -1 +1 @@
1
- import{adfToMarkdown as j,atlassianRequest as I,getJiraConfig as x,htmlToMarkdown as C,parseLimit as h,queryString as T,requireNonEmptyRows as b,requirePayloadArray as u,requirePayloadObject as a,requirePayloadString as c,requireString as N}from"../_atlassian/shared.js";import{ArgumentError as _}from"@jackwener/opencli/errors";const P=["summary","issuetype","status","priority","labels","description","comment","attachment","issuelinks","fixVersions","versions","components","project","reporter","assignee","created","updated"];function R(e){return`/rest/api/${e.deployment==="cloud"?"3":"2"}`}export function jiraApiPath(e,t,r){return`${R(e)}${t.startsWith("/")?t:`/${t}`}${r?T(r):""}`}export async function jiraRequest(e,t,r={}){return I(e,jiraApiPath(e,t,r.params),r)}function p(){return{acceptanceCriteria:process.env.ATLASSIAN_JIRA_ACCEPTANCE_FIELD?.trim()||"",sprint:process.env.ATLASSIAN_JIRA_SPRINT_FIELD?.trim()||"",storyPoints:process.env.ATLASSIAN_JIRA_STORY_POINTS_FIELD?.trim()||""}}function q(e=[]){const t=Object.values(p()).filter(Boolean);return[...new Set([...P,...t,...e.filter(Boolean)])].join(",")}export function parseJiraLimit(e,t=20,r=100){return h(e,t,r,"jira limit")}export function requireIssueKey(e){const t=N(e,"Jira issue key");if(!/^[A-Za-z][A-Za-z0-9_]+-\d+$/.test(t))throw new _(`Invalid Jira issue key: ${t}`,"Expected a key like PROJECT-123.");return t.toUpperCase()}function f(e){if(!e||typeof e!=="object")return"";return String(e.displayName??e.name??e.emailAddress??e.accountId??"")}function d(e){if(!e)return"";if(typeof e==="string")return e;if(typeof e==="object")return String(e.name??e.value??e.key??e.id??"");return String(e)}function l(e){return Array.isArray(e)?e.map(d).filter(Boolean):[]}export function jiraBodyToMarkdown(e,t){if(t)return C(t);if(e&&typeof e==="object")return j(e);if(typeof e==="string")return e.trim();return""}export function normalizeComment(e){const t=a(e,"jira comment");return{id:c(t.id,"comment id","jira comment"),author:f(t.author),created:t.created?String(t.created):"",updated:t.updated?String(t.updated):void 0,markdown:jiraBodyToMarkdown(t.body,t.renderedBody)}}export function normalizeAttachment(e){const t=a(e,"jira attachment");return{id:c(t.id,"attachment id","jira attachment"),filename:c(t.filename,"filename","jira attachment"),mimeType:t.mimeType?String(t.mimeType):void 0,size:t.size!=null?Number(t.size):void 0,url:c(t.content??t.self,"attachment url","jira attachment")}}export function normalizeIssueLink(e){const t=a(e,"jira issue link"),r=t.outwardIssue,i=t.inwardIssue;return{key:c((r??i??{}).key,"linked issue key","jira issue link"),type:String(t.type?.name??(r?t.type?.outward:t.type?.inward)??""),direction:r?"outward":"inward"}}function A(e){if(!e)return"";if(typeof e==="string")return e.trim();if(e&&typeof e==="object"&&e.type==="doc")return j(e);if(Array.isArray(e))return e.map(d).filter(Boolean).join(", ");return d(e)}function E(e,t,r){if(r.comments!==void 0)return u(r.comments,`jira issue ${t} comments`);if(r.requireNestedCollections===!1)return[];const i=a(e.comment,`jira issue ${t} comment field`);return u(i.comments,`jira issue ${t} comment field comments`)}export function normalizeJiraIssue(e,t,r={}){const i=a(e,"jira issue"),o=c(i.key,"issue key","jira issue"),n=a(i.fields,`jira issue ${o} fields`),S=i.renderedFields&&typeof i.renderedFields==="object"&&!Array.isArray(i.renderedFields)?i.renderedFields:{},s=p(),g=E(n,o,r),y=r.requireNestedCollections!==!1,w=y?u(n.attachment,`jira issue ${o} attachment field`):[],k=y?u(n.issuelinks,`jira issue ${o} issuelinks field`):[],m={key:o,id:i.id!=null?String(i.id):"",url:`${t.baseUrl}/browse/${o}`,summary:String(n.summary??""),issueType:d(n.issuetype)||void 0,status:d(n.status)||void 0,priority:d(n.priority)||void 0,project:d(n.project)||void 0,reporter:f(n.reporter)||void 0,assignee:f(n.assignee)||void 0,labels:Array.isArray(n.labels)?n.labels.map(String):[],description:{raw:n.description??null,markdown:jiraBodyToMarkdown(n.description,S.description)},comments:g.map(normalizeComment),attachments:w.map(normalizeAttachment),linkedIssues:k.map(normalizeIssueLink),fixVersions:l(n.fixVersions),affectedVersions:l(n.versions),components:l(n.components),created:n.created?String(n.created):void 0,updated:n.updated?String(n.updated):void 0};if(s.acceptanceCriteria&&n[s.acceptanceCriteria]!==void 0)m.acceptanceCriteria={raw:n[s.acceptanceCriteria],markdown:A(n[s.acceptanceCriteria])};if(s.sprint&&n[s.sprint]!==void 0)m.sprint=A(n[s.sprint]);if(s.storyPoints&&n[s.storyPoints]!==void 0)m.storyPoints=Number(n[s.storyPoints]);return m}export function issueSummaryRow(e,t){const r=normalizeJiraIssue(e,t,{comments:[],requireNestedCollections:!1});return{key:r.key,summary:r.summary,issueType:r.issueType,status:r.status,priority:r.priority,assignee:r.assignee,updated:r.updated,url:r.url}}export async function fetchIssue(e,t,r=[]){const i=await jiraRequest(e,`/issue/${encodeURIComponent(t)}`,{params:{fields:q(r),expand:"renderedFields"},label:`jira issue ${t}`});return a(i,`jira issue ${t}`)}export async function fetchComments(e,t,r=100){const i=parseJiraLimit(r,100,100),o=await jiraRequest(e,`/issue/${encodeURIComponent(t)}/comment`,{params:{startAt:0,maxResults:i,expand:"renderedBody"},label:`jira comments ${t}`}),n=a(o,`jira comments ${t}`);return u(n.comments,`jira comments ${t}`)}export function jiraRowsOrEmpty(e,t,r){return b(e,t,r)}export function jiraConfig(){return x()}export const __test__={configuredFieldNames:p,fetchIssue,issueSummaryRow,jiraApiPath,jiraBodyToMarkdown,normalizeAttachment,normalizeComment,normalizeIssueLink,normalizeJiraIssue,requireIssueKey};
1
+ import{adfToMarkdown as j,atlassianRequest as I,getJiraConfig as x,htmlToMarkdown as C,parseLimit as h,queryString as T,requireNonEmptyRows as b,requirePayloadArray as u,requirePayloadObject as a,requirePayloadString as c,requireString as N}from"../_atlassian/shared.js";import{ArgumentError as _}from"@jackwener/opencli/errors";const P=["summary","issuetype","status","priority","labels","description","comment","attachment","issuelinks","fixVersions","versions","components","project","reporter","assignee","created","updated"];function R(e){return`/rest/api/${e.deployment==="cloud"?"3":"2"}`}export function jiraApiPath(e,t,r){return`${R(e)}${t.startsWith("/")?t:`/${t}`}${r?T(r):""}`}export async function jiraRequest(e,t,r={}){return I(e,jiraApiPath(e,t,r.params),r)}function p(){return{acceptanceCriteria:process.env.ATLASSIAN_JIRA_ACCEPTANCE_FIELD?.trim()||"",sprint:process.env.ATLASSIAN_JIRA_SPRINT_FIELD?.trim()||"",storyPoints:process.env.ATLASSIAN_JIRA_STORY_POINTS_FIELD?.trim()||""}}function q(e=[]){const t=Object.values(p()).filter(Boolean);return[...new Set([...P,...t,...e.filter(Boolean)])].join(",")}export function parseJiraLimit(e,t=20,r=100){return h(e,t,r,"jira limit")}export function requireIssueKey(e){const t=N(e,"Jira issue key");if(!/^[A-Za-z][A-Za-z0-9_]+-\d+$/.test(t))throw new _(`Invalid Jira issue key: ${t}`,"Expected a key like PROJECT-123.");return t.toUpperCase()}function f(e){if(!e||typeof e!=="object")return"";return String(e.displayName??e.name??e.emailAddress??e.accountId??"")}function d(e){if(!e)return"";if(typeof e==="string")return e;if(typeof e==="object")return String(e.name??e.value??e.key??e.id??"");return String(e)}function l(e){return Array.isArray(e)?e.map(d).filter(Boolean):[]}export function jiraBodyToMarkdown(e,t){if(t)return C(t);if(e&&typeof e==="object")return j(e);if(typeof e==="string")return e.trim();return""}export function normalizeComment(e){const t=a(e,"jira comment");return{id:c(t.id,"comment id","jira comment"),author:f(t.author),created:t.created?String(t.created):"",updated:t.updated?String(t.updated):void 0,markdown:jiraBodyToMarkdown(t.body,t.renderedBody)}}export function normalizeAttachment(e){const t=a(e,"jira attachment");return{id:c(t.id,"attachment id","jira attachment"),filename:c(t.filename,"filename","jira attachment"),mimeType:t.mimeType?String(t.mimeType):void 0,size:t.size!=null?Number(t.size):void 0,url:c(t.content??t.self,"attachment url","jira attachment")}}export function normalizeIssueLink(e){const t=a(e,"jira issue link"),{outwardIssue:r,inwardIssue:i}=t;return{key:c((r??i??{}).key,"linked issue key","jira issue link"),type:String(t.type?.name??(r?t.type?.outward:t.type?.inward)??""),direction:r?"outward":"inward"}}function A(e){if(!e)return"";if(typeof e==="string")return e.trim();if(e&&typeof e==="object"&&e.type==="doc")return j(e);if(Array.isArray(e))return e.map(d).filter(Boolean).join(", ");return d(e)}function E(e,t,r){if(r.comments!==void 0)return u(r.comments,`jira issue ${t} comments`);if(r.requireNestedCollections===!1)return[];const i=a(e.comment,`jira issue ${t} comment field`);return u(i.comments,`jira issue ${t} comment field comments`)}export function normalizeJiraIssue(e,t,r={}){const i=a(e,"jira issue"),o=c(i.key,"issue key","jira issue"),n=a(i.fields,`jira issue ${o} fields`),S=i.renderedFields&&typeof i.renderedFields==="object"&&!Array.isArray(i.renderedFields)?i.renderedFields:{},s=p(),g=E(n,o,r),y=r.requireNestedCollections!==!1,w=y?u(n.attachment,`jira issue ${o} attachment field`):[],k=y?u(n.issuelinks,`jira issue ${o} issuelinks field`):[],m={key:o,id:i.id!=null?String(i.id):"",url:`${t.baseUrl}/browse/${o}`,summary:String(n.summary??""),issueType:d(n.issuetype)||void 0,status:d(n.status)||void 0,priority:d(n.priority)||void 0,project:d(n.project)||void 0,reporter:f(n.reporter)||void 0,assignee:f(n.assignee)||void 0,labels:Array.isArray(n.labels)?n.labels.map(String):[],description:{raw:n.description??null,markdown:jiraBodyToMarkdown(n.description,S.description)},comments:g.map(normalizeComment),attachments:w.map(normalizeAttachment),linkedIssues:k.map(normalizeIssueLink),fixVersions:l(n.fixVersions),affectedVersions:l(n.versions),components:l(n.components),created:n.created?String(n.created):void 0,updated:n.updated?String(n.updated):void 0};if(s.acceptanceCriteria&&n[s.acceptanceCriteria]!==void 0)m.acceptanceCriteria={raw:n[s.acceptanceCriteria],markdown:A(n[s.acceptanceCriteria])};if(s.sprint&&n[s.sprint]!==void 0)m.sprint=A(n[s.sprint]);if(s.storyPoints&&n[s.storyPoints]!==void 0)m.storyPoints=Number(n[s.storyPoints]);return m}export function issueSummaryRow(e,t){const r=normalizeJiraIssue(e,t,{comments:[],requireNestedCollections:!1});return{key:r.key,summary:r.summary,issueType:r.issueType,status:r.status,priority:r.priority,assignee:r.assignee,updated:r.updated,url:r.url}}export async function fetchIssue(e,t,r=[]){const i=await jiraRequest(e,`/issue/${encodeURIComponent(t)}`,{params:{fields:q(r),expand:"renderedFields"},label:`jira issue ${t}`});return a(i,`jira issue ${t}`)}export async function fetchComments(e,t,r=100){const i=parseJiraLimit(r,100,100),o=await jiraRequest(e,`/issue/${encodeURIComponent(t)}/comment`,{params:{startAt:0,maxResults:i,expand:"renderedBody"},label:`jira comments ${t}`}),n=a(o,`jira comments ${t}`);return u(n.comments,`jira comments ${t}`)}export function jiraRowsOrEmpty(e,t,r){return b(e,t,r)}export function jiraConfig(){return x()}export const __test__={configuredFieldNames:p,fetchIssue,issueSummaryRow,jiraApiPath,jiraBodyToMarkdown,normalizeAttachment,normalizeComment,normalizeIssueLink,normalizeJiraIssue,requireIssueKey};
@@ -0,0 +1 @@
1
+ import{registerWatch as d}from"../_shared/watch.js";import{WWW_BASE as w,getJson as m,getSelfUid as c,requireOk as g}from"./utils.js";function u(t,e){const r=t?.data?.conversations;if(!Array.isArray(r))return[];const i=[];for(const n of r){const a=Number(n?.unreadMessageCount??0);if(a<=0&&!e?.includeOwn)continue;const s=String(n?.conversationId??""),o=Number(n?.createDate??0);if(!s||!o)continue;i.push({event:"dm",id:`${s}:${o}`,ts:o,conversation_id:s,sender:{id:String(n?.targetUserId??""),name:String(n?.targetUserName??"")},text:String(n?.content??"").trim(),url:"https://www.nowcoder.com/message/private",raw:{unread_count:a,message_count:n?.messageCount??0}})}return i}d({site:"nowcoder",name:"watch",domain:"www.nowcoder.com",mode:"reconcile",description:"牛客私信监听:按间隔拉取会话列表,有未读私信按 NDJSON 逐条输出",watchUrl:"https://www.nowcoder.com/",pattern:"/sns/message/",parse:(t,e)=>{if(!String(t?.url??"").includes("get-conversation-list"))return null;try{return u(JSON.parse(String(t.responsePreview??"")),e)}catch{return null}},onAttach:async(t,e)=>{e.state.uid=await c(t)},reconcile:async(t,e)=>{if(!e.state.uid)e.state.uid=await c(t);const r=`${w}/sns/message/${e.state.uid}/get-conversation-list?page=1`;return u(g(await m(t,r),"私信会话列表"),e)}});
@@ -1 +1 @@
1
- import{ArgumentError as m,CommandExecutionError as w}from"@jackwener/opencli/errors";import{cli as k,Strategy as q}from"@jackwener/opencli/registry";import{SHARE_API as S,extractPwdId as I,apiPost as y,getToken as E,pollTask as A,findFolder as D}from"./utils.js";async function F(t,e,l,s,a,i){return(await y(t,`${S}/save?pr=ucpro&fr=pc`,{pwd_id:e,stoken:l,pdir_fid:"0",to_pdir_fid:a,fid_list:s,pdir_save_all:i,scene:"link"})).task_id}k({site:"quark",name:"save",access:"write",description:"Save shared files to your Quark Drive",domain:"pan.quark.cn",strategy:q.COOKIE,defaultFormat:"json",args:[{name:"url",required:!0,positional:!0,help:"Quark share URL or pwd_id"},{name:"to",default:"",help:"Destination folder path"},{name:"to-fid",default:"",help:"Destination folder ID (overrides --to)"},{name:"fids",default:"",help:"File IDs to save (comma-separated, from share-tree). Omit to save all."},{name:"stoken",default:"",help:"Share token (from share-tree output, required with --fids)"},{name:"passcode",default:"",help:"Share passcode (if required)"},{name:"timeout",type:"int",required:!1,default:120,help:"Max seconds for the overall command (default: 120)"}],func:async(t,e)=>{const{url:l,to:s}=e,a=e["to-fid"],i=e.fids,d=e.stoken,_=e.passcode;if(!s&&!a)throw new m("Either --to or --to-fid is required");if(s&&a)throw new m("Cannot use both --to and --to-fid");const p=I(l),f=!i;let u,n;if(f){u=d||await E(t,p,_);n=[]}else{if(!d)throw new m("--stoken is required when using --fids");u=d;n=[...new Set(i.split(",").map((r)=>r.trim()).filter(Boolean))]}const h=a||await D(t,s),c=await F(t,p,u,n,h,f),o={success:!1,task_id:c,saved_to:s||a,target_fid:h,...f?{}:{fids:n}};if(c){const r=await A(t,c,(v)=>{o.save_count=v.save_as?.save_as_sum_num});o.completed=r;o.success=r;if(!r)throw new w("quark: Save task timed out")}else o.success=!0;return o}});
1
+ import{ArgumentError as m,CommandExecutionError as w}from"@jackwener/opencli/errors";import{cli as k,Strategy as q}from"@jackwener/opencli/registry";import{SHARE_API as S,extractPwdId as I,apiPost as y,getToken as E,pollTask as A,findFolder as D}from"./utils.js";async function F(t,e,l,s,a,i){return(await y(t,`${S}/save?pr=ucpro&fr=pc`,{pwd_id:e,stoken:l,pdir_fid:"0",to_pdir_fid:a,fid_list:s,pdir_save_all:i,scene:"link"})).task_id}k({site:"quark",name:"save",access:"write",description:"Save shared files to your Quark Drive",domain:"pan.quark.cn",strategy:q.COOKIE,defaultFormat:"json",args:[{name:"url",required:!0,positional:!0,help:"Quark share URL or pwd_id"},{name:"to",default:"",help:"Destination folder path"},{name:"to-fid",default:"",help:"Destination folder ID (overrides --to)"},{name:"fids",default:"",help:"File IDs to save (comma-separated, from share-tree). Omit to save all."},{name:"stoken",default:"",help:"Share token (from share-tree output, required with --fids)"},{name:"passcode",default:"",help:"Share passcode (if required)"},{name:"timeout",type:"int",required:!1,default:120,help:"Max seconds for the overall command (default: 120)"}],func:async(t,e)=>{const{url:l,to:s}=e,a=e["to-fid"],{fids:i,stoken:d,passcode:_}=e;if(!s&&!a)throw new m("Either --to or --to-fid is required");if(s&&a)throw new m("Cannot use both --to and --to-fid");const p=I(l),f=!i;let u,n;if(f){u=d||await E(t,p,_);n=[]}else{if(!d)throw new m("--stoken is required when using --fids");u=d;n=[...new Set(i.split(",").map((r)=>r.trim()).filter(Boolean))]}const h=a||await D(t,s),c=await F(t,p,u,n,h,f),o={success:!1,task_id:c,saved_to:s||a,target_fid:h,...f?{}:{fids:n}};if(c){const r=await A(t,c,(v)=>{o.save_count=v.save_as?.save_as_sum_num});o.completed=r;o.success=r;if(!r)throw new w("quark: Save task timed out")}else o.success=!0;return o}});
@@ -0,0 +1 @@
1
+ import{registerWatch as c}from"../_shared/watch.js";const a="https://www.reddit.com/message/inbox.json?limit=25&raw_json=1";function d(e){const r=e?.data?.children;if(!Array.isArray(r))return[];const s=[];for(const n of r){const t=n?.data;if(!t)continue;const i=String(t.name||(n.kind&&t.id?`${n.kind}_${t.id}`:t.id||""));if(!i)continue;const o=n.kind==="t4";s.push({event:o?"dm":"reply",id:i,ts:t.created_utc?Number(t.created_utc)*1000:Date.now(),conversation_id:String(t.first_message_name||t.parent_id||i),sender:{id:String(t.author_fullname||""),name:String(t.author||"")},text:String(t.body||""),url:t.context?`https://www.reddit.com${t.context}`:"https://www.reddit.com/message/inbox/",raw:{subject:t.subject||"",subreddit:t.subreddit||"",link_title:t.link_title||"",unread:t.new===!0}})}return s}c({site:"reddit",name:"watch",domain:"www.reddit.com",mode:"reconcile",description:"Reddit 收件箱监听:按间隔拉取私信与评论回复,有新消息按 NDJSON 逐条输出",watchUrl:"https://www.reddit.com/message/inbox/",pattern:"/message/inbox",parse:(e)=>{if(!String(e?.url??"").includes("/message/inbox"))return null;try{return d(JSON.parse(String(e.responsePreview??"")))}catch{return null}},reconcile:async(e)=>d(await e.fetchJson(a))});
@@ -1 +1 @@
1
- import{cli as i,Strategy as c}from"@jackwener/opencli/registry";import{CliError as u}from"@jackwener/opencli/errors";import{readFileSync as _,writeFileSync as I,mkdirSync as R,existsSync as C}from"fs";import{createServer as v}from"http";import{homedir as m}from"os";import{join as y}from"path";import{execFile as d}from"child_process";import{assertSpotifyCredentialsConfigured as L,getFirstSpotifyTrack as x,mapSpotifyTrackResults as A,parseDotEnv as q,resolveSpotifyCredentials as B}from"./utils.js";const h=y(m(),".ppcli","spotify.env");function O(){if(!C(h))return{};return q(_(h,"utf-8"))}const N=O(),w=B(N),k=w.clientId,T=w.clientSecret,S="http://127.0.0.1:8888/callback",D=["user-read-playback-state","user-modify-playback-state","user-read-currently-playing","playlist-read-private"].join(" "),U=y(m(),".ppcli","spotify-tokens.json");function F(){try{return JSON.parse(_(U,"utf-8"))}catch{return null}}function P(t){R(y(m(),".ppcli"),{recursive:!0});I(U,JSON.stringify(t,null,2))}async function M(t){const e=await fetch("https://accounts.spotify.com/api/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:"Basic "+Buffer.from(`${k}:${T}`).toString("base64")},body:new URLSearchParams({grant_type:"refresh_token",refresh_token:t})});if(!e.ok){const r=await e.json().catch(()=>({}));throw new u("REFRESH_FAILED",r?.error_description||`Token refresh failed (${e.status})`)}const s=await e.json(),a={access_token:s.access_token,refresh_token:s.refresh_token||t,expires_at:Date.now()+s.expires_in*1000};P(a);return a.access_token}async function j(){const t=F();if(!t)throw new u("AUTH_REQUIRED","Not authenticated. Run: ppcli spotify auth");if(!t.access_token||!t.refresh_token||!(t.expires_at>0))throw new u("AUTH_CORRUPTED","Token file is corrupted. Run: ppcli spotify auth");if(Date.now()>t.expires_at-60000)return M(t.refresh_token);return t.access_token}async function o(t,e,s){const a=await j(),r=await fetch(`https://api.spotify.com/v1${e}`,{method:t,headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:s?JSON.stringify(s):void 0});if(r.status===204||r.status===202)return null;if(!r.ok){const n=await r.json().catch(()=>({}));throw new u("API_ERROR",n?.error?.message||`Spotify API error ${r.status}`)}return r.json()}async function b(t){const e=await o("GET",`/search?q=${encodeURIComponent(t)}&type=track&limit=1`),s=x(e);if(!s)throw new u("EMPTY_RESULT",`No track found for: ${t}`);return s}function H(t){if(process.platform==="win32")d("cmd",["/c","start","",t]);else if(process.platform==="darwin")d("open",[t]);else d("xdg-open",[t])}i({site:"spotify",name:"auth",access:"write",description:"Authenticate with Spotify (OAuth — run once)",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{L(w,h);return new Promise((t,e)=>{const s=v(async(r,n)=>{try{const p=new URL(r.url,"http://localhost:8888");if(p.pathname!=="/callback"){n.end();return}const g=p.searchParams.get("code");if(!g){n.end("Missing code");return}const l=await fetch("https://accounts.spotify.com/api/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:"Basic "+Buffer.from(`${k}:${T}`).toString("base64")},body:new URLSearchParams({grant_type:"authorization_code",code:g,redirect_uri:S})});if(!l.ok){const E=await l.json().catch(()=>({}));s.close();e(new u("AUTH_FAILED",E?.error_description||`Token exchange failed (${l.status})`));return}const f=await l.json();P({access_token:f.access_token,refresh_token:f.refresh_token,expires_at:Date.now()+f.expires_in*1000});n.writeHead(200,{"Content-Type":"text/html"});n.end("<h2>Spotify authenticated! You can close this tab.</h2>");s.close();t([{status:"Authenticated successfully"}])}catch(p){s.close();e(p)}});s.on("error",(r)=>{if(r.code==="EADDRINUSE")e(new u("PORT_IN_USE","Port 8888 is already in use. Stop the other process and retry."));else e(r)});const a=setTimeout(()=>{s.close();e(new u("AUTH_TIMEOUT","Authentication timed out after 5 minutes"))},300000);s.listen(8888,()=>{const r=`https://accounts.spotify.com/authorize?${new URLSearchParams({client_id:k,response_type:"code",redirect_uri:S,scope:D})}`;console.log("Opening browser for Spotify login...");console.log("If it does not open, visit:",r);H(r)});s.on("close",()=>clearTimeout(a))})}});i({site:"spotify",name:"status",access:"read",description:"Show current playback status",strategy:c.PUBLIC,browser:!1,args:[],columns:["track","artist","album","status","progress"],func:async()=>{const t=await o("GET","/me/player");if(!t||!t.item)return[{track:"Nothing playing",artist:"",album:"",status:"",progress:""}];const e=t.item;if(e.type!=="track")return[{track:e.name,artist:"",album:e.show?.name??"",status:t.is_playing?"playing":"paused",progress:""}];const s=(t.progress_ms??0)/1000|0,a=e.duration_ms/1000|0,r=(n)=>`${n/60|0}:${String(n%60).padStart(2,"0")}`;return[{track:e.name,artist:e.artists.map((n)=>n.name).join(", "),album:e.album.name,status:t.is_playing?"playing":"paused",progress:`${r(s)} / ${r(a)}`}]}});i({site:"spotify",name:"play",access:"write",description:"Resume playback or search and play a track/artist",strategy:c.PUBLIC,browser:!1,args:[{name:"query",type:"str",default:"",positional:!0,help:"Track or artist to play (optional)"}],columns:["track","artist","status"],func:async(t)=>{if(t.query){const{uri:e,name:s,artist:a}=await b(t.query);await o("PUT","/me/player/play",{uris:[e]});return[{track:s,artist:a,status:"playing"}]}await o("PUT","/me/player/play");return[{track:"",artist:"",status:"resumed"}]}});i({site:"spotify",name:"pause",access:"write",description:"Pause playback",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await o("PUT","/me/player/pause");return[{status:"paused"}]}});i({site:"spotify",name:"next",access:"write",description:"Skip to next track",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await o("POST","/me/player/next");return[{status:"skipped to next"}]}});i({site:"spotify",name:"prev",access:"write",description:"Skip to previous track",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await o("POST","/me/player/previous");return[{status:"skipped to previous"}]}});i({site:"spotify",name:"volume",access:"write",description:"Set playback volume (0-100)",strategy:c.PUBLIC,browser:!1,args:[{name:"level",type:"int",default:50,positional:!0,required:!0,help:"Volume 0–100"}],columns:["volume"],func:async(t)=>{const e=Math.round(t.level);if(e<0||e>100)throw new u("INVALID_ARGS","Volume must be between 0 and 100");await o("PUT",`/me/player/volume?volume_percent=${e}`);return[{volume:`${e}%`}]}});i({site:"spotify",name:"search",access:"read",description:"Search for tracks",strategy:c.PUBLIC,browser:!1,args:[{name:"query",type:"str",required:!0,positional:!0,help:"Search query"},{name:"limit",type:"int",default:10,help:"Number of results (default: 10)"}],columns:["track","artist","album","uri"],func:async(t)=>{const e=Math.min(50,Math.max(1,Math.round(t.limit))),s=await o("GET",`/search?q=${encodeURIComponent(t.query)}&type=track&limit=${e}`),a=A(s);if(!a.length)throw new u("EMPTY_RESULT",`No results found for: ${t.query}`);return a}});i({site:"spotify",name:"queue",access:"write",description:"Add a track to the playback queue",strategy:c.PUBLIC,browser:!1,args:[{name:"query",type:"str",required:!0,positional:!0,help:"Track to add to queue"}],columns:["track","artist","status"],func:async(t)=>{const{uri:e,name:s,artist:a}=await b(t.query);await o("POST",`/me/player/queue?uri=${encodeURIComponent(e)}`);return[{track:s,artist:a,status:"added to queue"}]}});i({site:"spotify",name:"shuffle",access:"write",description:"Toggle shuffle on/off",strategy:c.PUBLIC,browser:!1,args:[{name:"state",type:"str",default:"on",positional:!0,choices:["on","off"],help:"on or off"}],columns:["shuffle"],func:async(t)=>{await o("PUT",`/me/player/shuffle?state=${t.state==="on"}`);return[{shuffle:t.state}]}});i({site:"spotify",name:"repeat",access:"write",description:"Set repeat mode (off / track / context)",strategy:c.PUBLIC,browser:!1,args:[{name:"mode",type:"str",default:"context",positional:!0,choices:["off","track","context"],help:"off / track / context"}],columns:["repeat"],func:async(t)=>{await o("PUT",`/me/player/repeat?state=${t.mode}`);return[{repeat:t.mode}]}});
1
+ import{cli as i,Strategy as c}from"@jackwener/opencli/registry";import{CliError as u}from"@jackwener/opencli/errors";import{readFileSync as _,writeFileSync as I,mkdirSync as R,existsSync as C}from"fs";import{createServer as v}from"http";import{homedir as m}from"os";import{join as y}from"path";import{execFile as d}from"child_process";import{assertSpotifyCredentialsConfigured as L,getFirstSpotifyTrack as x,mapSpotifyTrackResults as A,parseDotEnv as q,resolveSpotifyCredentials as B}from"./utils.js";const h=y(m(),".ppcli","spotify.env");function O(){if(!C(h))return{};return q(_(h,"utf-8"))}const N=O(),w=B(N),{clientId:k,clientSecret:T}=w,S="http://127.0.0.1:8888/callback",D=["user-read-playback-state","user-modify-playback-state","user-read-currently-playing","playlist-read-private"].join(" "),U=y(m(),".ppcli","spotify-tokens.json");function F(){try{return JSON.parse(_(U,"utf-8"))}catch{return null}}function P(t){R(y(m(),".ppcli"),{recursive:!0});I(U,JSON.stringify(t,null,2))}async function M(t){const e=await fetch("https://accounts.spotify.com/api/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:"Basic "+Buffer.from(`${k}:${T}`).toString("base64")},body:new URLSearchParams({grant_type:"refresh_token",refresh_token:t})});if(!e.ok){const r=await e.json().catch(()=>({}));throw new u("REFRESH_FAILED",r?.error_description||`Token refresh failed (${e.status})`)}const s=await e.json(),a={access_token:s.access_token,refresh_token:s.refresh_token||t,expires_at:Date.now()+s.expires_in*1000};P(a);return a.access_token}async function j(){const t=F();if(!t)throw new u("AUTH_REQUIRED","Not authenticated. Run: ppcli spotify auth");if(!t.access_token||!t.refresh_token||!(t.expires_at>0))throw new u("AUTH_CORRUPTED","Token file is corrupted. Run: ppcli spotify auth");if(Date.now()>t.expires_at-60000)return M(t.refresh_token);return t.access_token}async function o(t,e,s){const a=await j(),r=await fetch(`https://api.spotify.com/v1${e}`,{method:t,headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:s?JSON.stringify(s):void 0});if(r.status===204||r.status===202)return null;if(!r.ok){const n=await r.json().catch(()=>({}));throw new u("API_ERROR",n?.error?.message||`Spotify API error ${r.status}`)}return r.json()}async function b(t){const e=await o("GET",`/search?q=${encodeURIComponent(t)}&type=track&limit=1`),s=x(e);if(!s)throw new u("EMPTY_RESULT",`No track found for: ${t}`);return s}function H(t){if(process.platform==="win32")d("cmd",["/c","start","",t]);else if(process.platform==="darwin")d("open",[t]);else d("xdg-open",[t])}i({site:"spotify",name:"auth",access:"write",description:"Authenticate with Spotify (OAuth — run once)",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{L(w,h);return new Promise((t,e)=>{const s=v(async(r,n)=>{try{const p=new URL(r.url,"http://localhost:8888");if(p.pathname!=="/callback"){n.end();return}const g=p.searchParams.get("code");if(!g){n.end("Missing code");return}const l=await fetch("https://accounts.spotify.com/api/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:"Basic "+Buffer.from(`${k}:${T}`).toString("base64")},body:new URLSearchParams({grant_type:"authorization_code",code:g,redirect_uri:S})});if(!l.ok){const E=await l.json().catch(()=>({}));s.close();e(new u("AUTH_FAILED",E?.error_description||`Token exchange failed (${l.status})`));return}const f=await l.json();P({access_token:f.access_token,refresh_token:f.refresh_token,expires_at:Date.now()+f.expires_in*1000});n.writeHead(200,{"Content-Type":"text/html"});n.end("<h2>Spotify authenticated! You can close this tab.</h2>");s.close();t([{status:"Authenticated successfully"}])}catch(p){s.close();e(p)}});s.on("error",(r)=>{if(r.code==="EADDRINUSE")e(new u("PORT_IN_USE","Port 8888 is already in use. Stop the other process and retry."));else e(r)});const a=setTimeout(()=>{s.close();e(new u("AUTH_TIMEOUT","Authentication timed out after 5 minutes"))},300000);s.listen(8888,()=>{const r=`https://accounts.spotify.com/authorize?${new URLSearchParams({client_id:k,response_type:"code",redirect_uri:S,scope:D})}`;console.log("Opening browser for Spotify login...");console.log("If it does not open, visit:",r);H(r)});s.on("close",()=>clearTimeout(a))})}});i({site:"spotify",name:"status",access:"read",description:"Show current playback status",strategy:c.PUBLIC,browser:!1,args:[],columns:["track","artist","album","status","progress"],func:async()=>{const t=await o("GET","/me/player");if(!t||!t.item)return[{track:"Nothing playing",artist:"",album:"",status:"",progress:""}];const e=t.item;if(e.type!=="track")return[{track:e.name,artist:"",album:e.show?.name??"",status:t.is_playing?"playing":"paused",progress:""}];const s=(t.progress_ms??0)/1000|0,a=e.duration_ms/1000|0,r=(n)=>`${n/60|0}:${String(n%60).padStart(2,"0")}`;return[{track:e.name,artist:e.artists.map((n)=>n.name).join(", "),album:e.album.name,status:t.is_playing?"playing":"paused",progress:`${r(s)} / ${r(a)}`}]}});i({site:"spotify",name:"play",access:"write",description:"Resume playback or search and play a track/artist",strategy:c.PUBLIC,browser:!1,args:[{name:"query",type:"str",default:"",positional:!0,help:"Track or artist to play (optional)"}],columns:["track","artist","status"],func:async(t)=>{if(t.query){const{uri:e,name:s,artist:a}=await b(t.query);await o("PUT","/me/player/play",{uris:[e]});return[{track:s,artist:a,status:"playing"}]}await o("PUT","/me/player/play");return[{track:"",artist:"",status:"resumed"}]}});i({site:"spotify",name:"pause",access:"write",description:"Pause playback",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await o("PUT","/me/player/pause");return[{status:"paused"}]}});i({site:"spotify",name:"next",access:"write",description:"Skip to next track",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await o("POST","/me/player/next");return[{status:"skipped to next"}]}});i({site:"spotify",name:"prev",access:"write",description:"Skip to previous track",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await o("POST","/me/player/previous");return[{status:"skipped to previous"}]}});i({site:"spotify",name:"volume",access:"write",description:"Set playback volume (0-100)",strategy:c.PUBLIC,browser:!1,args:[{name:"level",type:"int",default:50,positional:!0,required:!0,help:"Volume 0–100"}],columns:["volume"],func:async(t)=>{const e=Math.round(t.level);if(e<0||e>100)throw new u("INVALID_ARGS","Volume must be between 0 and 100");await o("PUT",`/me/player/volume?volume_percent=${e}`);return[{volume:`${e}%`}]}});i({site:"spotify",name:"search",access:"read",description:"Search for tracks",strategy:c.PUBLIC,browser:!1,args:[{name:"query",type:"str",required:!0,positional:!0,help:"Search query"},{name:"limit",type:"int",default:10,help:"Number of results (default: 10)"}],columns:["track","artist","album","uri"],func:async(t)=>{const e=Math.min(50,Math.max(1,Math.round(t.limit))),s=await o("GET",`/search?q=${encodeURIComponent(t.query)}&type=track&limit=${e}`),a=A(s);if(!a.length)throw new u("EMPTY_RESULT",`No results found for: ${t.query}`);return a}});i({site:"spotify",name:"queue",access:"write",description:"Add a track to the playback queue",strategy:c.PUBLIC,browser:!1,args:[{name:"query",type:"str",required:!0,positional:!0,help:"Track to add to queue"}],columns:["track","artist","status"],func:async(t)=>{const{uri:e,name:s,artist:a}=await b(t.query);await o("POST",`/me/player/queue?uri=${encodeURIComponent(e)}`);return[{track:s,artist:a,status:"added to queue"}]}});i({site:"spotify",name:"shuffle",access:"write",description:"Toggle shuffle on/off",strategy:c.PUBLIC,browser:!1,args:[{name:"state",type:"str",default:"on",positional:!0,choices:["on","off"],help:"on or off"}],columns:["shuffle"],func:async(t)=>{await o("PUT",`/me/player/shuffle?state=${t.state==="on"}`);return[{shuffle:t.state}]}});i({site:"spotify",name:"repeat",access:"write",description:"Set repeat mode (off / track / context)",strategy:c.PUBLIC,browser:!1,args:[{name:"mode",type:"str",default:"context",positional:!0,choices:["off","track","context"],help:"off / track / context"}],columns:["repeat"],func:async(t)=>{await o("PUT",`/me/player/repeat?state=${t.mode}`);return[{repeat:t.mode}]}});
@@ -0,0 +1 @@
1
+ import{registerWatch as o}from"../_shared/watch.js";import{DM_REQUEST_QUERY as d,buildQuery as c,getDmAuth as r,dmApiFetch as m,shapeDmMessages as u}from"./dm-shared.js";const l=`https://x.com/i/api/1.1/dm/inbox_initial_state.json?${c(d)}`;function a(n,t){if(!n||typeof n!=="object")return[];const i=String(t?.state?.ownId??""),s=[];for(const e of u(n.entries,n.users||{})){if(!e.id)continue;if(!t?.includeOwn&&i&&String(e.sender_id??"")===i)continue;s.push({event:"dm",id:String(e.id),ts:e.time?Date.parse(e.time):Date.now(),conversation_id:e.conversation_id??"",sender:{id:e.sender_id??"",name:e.sender??""},text:e.text??"",url:e.conversation_id?`https://x.com/messages/${e.conversation_id}`:"https://x.com/messages",raw:{media_url:e.media_url??null}})}return s}o({site:"twitter",name:"watch",domain:"x.com",mode:"reconcile",description:"X 私信监听:按间隔拉取收件箱,收到新私信按 NDJSON 逐条输出(X 新版私信没有可旁听的实时通道,见文件头说明)",watchUrl:"https://x.com/messages",pattern:"dm/inbox_initial_state|dm/user_updates|/live_pipeline/",parse:(n,t)=>{if(!String(n?.url??"").includes("inbox_initial_state"))return null;let i;try{i=JSON.parse(String(n.responsePreview??""))}catch{return null}return a(i?.inbox_initial_state,t)},onAttach:async(n,t)=>{const{ownId:i,headers:s}=await r(n);t.state.ownId=i;t.state.headers=s},reconcile:async(n,t)=>{if(!t.state.headers){const{ownId:s,headers:e}=await r(n);t.state.ownId=s;t.state.headers=e}const i=await m(n,l,t.state.headers);if(i?.error)return[];return a(i?.inbox_initial_state,t)}});
@@ -0,0 +1,22 @@
1
+ import{registerWatch as r}from"../_shared/watch.js";const i=`
2
+ (function () {
3
+ var cells = Array.prototype.slice.call(document.querySelectorAll('#Main .box .cell[id^="n_"]'));
4
+ return JSON.stringify(cells.map(function (cell) {
5
+ var payloadEl = cell.querySelector('.payload');
6
+ var payload = payloadEl ? (payloadEl.textContent || '').trim() : '';
7
+ var memberLink = cell.querySelector('a[href^="/member/"]');
8
+ var topicLink = cell.querySelector('a[href*="/t/"]');
9
+ var timeEl = cell.querySelector('.snow');
10
+ var full = (cell.textContent || '').replace(/\\s+/g, ' ').trim();
11
+ return {
12
+ id: cell.id || '',
13
+ sender: memberLink ? (memberLink.getAttribute('href') || '').replace('/member/', '') : '',
14
+ payload: payload,
15
+ summary: full,
16
+ url: topicLink ? topicLink.getAttribute('href') : '',
17
+ time: timeEl ? (timeEl.textContent || '').trim() : '',
18
+ kind: payload ? 'reply' : 'notification'
19
+ };
20
+ }));
21
+ })()
22
+ `;function l(t){if(!Array.isArray(t))return[];return t.filter((e)=>e&&e.id).map((e)=>({event:e.kind==="reply"?"reply":"notification",id:String(e.id),ts:Date.now(),conversation_id:String(e.url||""),sender:{id:String(e.sender||""),name:String(e.sender||"")},text:String(e.payload||e.summary||"").slice(0,2000),url:e.url?`https://www.v2ex.com${e.url}`:"https://www.v2ex.com/notifications",raw:{relative_time:e.time||""}}))}r({site:"v2ex",name:"watch",domain:"www.v2ex.com",mode:"reconcile",description:"V2EX 提醒监听:按间隔拉取提醒页,有新的回复/感谢/收藏/提及按 NDJSON 逐条输出",watchUrl:"https://www.v2ex.com/notifications",pattern:"/notifications",parse:()=>null,reconcile:async(t)=>{await t.goto("https://www.v2ex.com/notifications",{waitUntil:"load"});const e=await t.evaluate(i);try{return l(JSON.parse(typeof e==="string"?e:"[]"))}catch{return[]}}});
@@ -0,0 +1 @@
1
+ import{registerWatch as i}from"../_shared/watch.js";const o="https://weibo.com/ajax/statuses/mentions?page=1",m="https://weibo.com/ajax/comments/mentions?page=1";function n(e){return String(e??"").replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim()}function s(e){const r=e?.data?.statuses;if(!Array.isArray(r))return[];return r.filter((t)=>t?.idstr||t?.id).map((t)=>({event:"mention",id:`status:${t.idstr??t.id}`,ts:t.created_at?Date.parse(t.created_at)||Date.now():Date.now(),conversation_id:String(t.idstr??t.id??""),sender:{id:String(t.user?.idstr??""),name:String(t.user?.screen_name??"")},text:n(t.text_raw??t.text),url:t.user?.idstr&&t.mblogid?`https://weibo.com/${t.user.idstr}/${t.mblogid}`:"https://weibo.com/at/weibo",raw:{source:"mentions"}}))}function a(e){const r=e?.data?.comments;if(!Array.isArray(r))return[];return r.filter((t)=>t?.idstr||t?.id).map((t)=>({event:"comment",id:`comment:${t.idstr??t.id}`,ts:t.created_at?Date.parse(t.created_at)||Date.now():Date.now(),conversation_id:String(t.status?.idstr??t.rootidstr??""),sender:{id:String(t.user?.idstr??""),name:String(t.user?.screen_name??"")},text:n(t.text_raw??t.text),url:"https://weibo.com/at/comment",raw:{source:"comments",on_status:t.status?.text_raw?n(t.status.text_raw).slice(0,80):""}}))}i({site:"weibo",name:"watch",domain:"weibo.com",mode:"reconcile",description:"微博互动监听:按间隔拉取「@我的」和「评论我的」,有新互动按 NDJSON 逐条输出(不含私信)",watchUrl:"https://weibo.com/",pattern:"/ajax/statuses/mentions|/ajax/comments/mentions",parse:(e)=>{const r=String(e?.url??"");let t;try{t=JSON.parse(String(e.responsePreview??""))}catch{return null}if(r.includes("/ajax/statuses/mentions"))return s(t);if(r.includes("/ajax/comments/mentions"))return a(t);return null},reconcile:async(e)=>{const[r,t]=await Promise.all([e.fetchJson(o).catch(()=>null),e.fetchJson(m).catch(()=>null)]);return[...s(r),...a(t)]}});
@@ -0,0 +1 @@
1
+ import{registerWatch as s}from"../_shared/watch.js";import{buildExtractInboxEvaluate as u,buildInboxUrl as n,DEFAULT_INBOX_LIMIT as l,requireEvaluateObject as m}from"./im.js";function d(e){let r=5381;for(let i=0;i<e.length;i++)r=(r<<5)+r+e.charCodeAt(i)>>>0;return r.toString(36)}function w(e,r){if(!Array.isArray(e))return[];const i=[];for(const t of e){const o=Number(t?.unread_count??0)||(t?.unread?1:0);if(o<=0&&!r?.includeOwn)continue;const a=String(t?.peer_user_id||t?.peer_name||""),c=String(t?.last_message||"");i.push({event:"dm",id:d(`${a}|${t?.item_id||""}|${c}`),ts:Date.now(),conversation_id:String(t?.item_id||a),sender:{id:String(t?.peer_user_id||""),name:String(t?.peer_name||"")},text:c,url:String(t?.url||n()),raw:{unread_count:o,item_title:t?.item_title||"",price:t?.price||""}})}return i}s({site:"xianyu",name:"watch",domain:"www.goofish.com",mode:"reconcile",description:"闲鱼私信监听:按间隔读会话列表,有未读私信按 NDJSON 逐条输出",watchUrl:n(),pattern:"never-match-stream",parse:()=>null,reconcile:async(e,r)=>{const i=await(e.getCurrentUrl?e.getCurrentUrl().catch(()=>""):Promise.resolve(""));if(!/https:\/\/www\.goofish\.com\/im\b/.test(String(i||"")))await e.goto(n());await e.wait(4);const t=m(await e.evaluate(u(l)),"watch inbox");if(t?.requiresAuth||t?.blocked)return[];return w(t?.items,r)}});
@@ -0,0 +1,20 @@
1
+ import{registerWatch as u,frameJson as l}from"../_shared/watch.js";const a="https://www.xiaohongshu.com/notification",f=`
2
+ (function () {
3
+ var items = Array.prototype.slice.call(document.querySelectorAll('.tabs-content-container .container'));
4
+ return JSON.stringify(items.map(function (it) {
5
+ function txt(sel) { var e = it.querySelector(sel); return e ? (e.textContent || '').replace(/\\s+/g, ' ').trim() : ''; }
6
+ var avatar = it.querySelector('a.user-avatar');
7
+ var href = avatar ? (avatar.getAttribute('href') || '') : '';
8
+ var m = href.match(/\\/user\\/profile\\/([0-9a-zA-Z]+)/);
9
+ var noteLink = it.querySelector('a[href*="/explore/"], a[href*="/discovery/item/"]');
10
+ return {
11
+ senderId: m ? m[1] : '',
12
+ senderName: txt('.user-info a') || txt('.user-info'),
13
+ hint: txt('.interaction-hint'),
14
+ time: txt('.interaction-time'),
15
+ content: txt('.interaction-content'),
16
+ noteUrl: noteLink ? noteLink.getAttribute('href') : ''
17
+ };
18
+ }));
19
+ })()
20
+ `;function h(n){let e=5381;for(let t=0;t<n.length;t++)e=(e<<5)+e+n.charCodeAt(t)>>>0;return e.toString(36)}function d(n){if(!Array.isArray(n))return[];const e=[];for(const t of n){if(!t||!t.content&&!t.hint)continue;const i=`${t.senderId}|${t.time}|${String(t.content||t.hint).slice(0,40)}`;e.push({event:"notification",id:h(i),ts:Date.now(),conversation_id:String(t.noteUrl||""),sender:{id:String(t.senderId||""),name:String(t.senderName||"")},text:String(t.content||t.hint||""),url:t.noteUrl?`https://www.xiaohongshu.com${t.noteUrl}`:a,raw:{action:t.hint||"",relative_time:t.time||""}})}return e}async function s(n){await n.goto(a,{waitUntil:"load"});await n.wait({time:2});const e=await n.evaluate(f);try{return d(JSON.parse(typeof e==="string"?e:"[]"))}catch{return[]}}u({site:"xiaohongshu",name:"watch",domain:"www.xiaohongshu.com",description:"小红书互动监听:旁听页面自己的未读数轮询,一有新互动就读一次通知列表并按 NDJSON 输出",watchUrl:a,pattern:"api/sns/web/unread_count",parse:async(n,e)=>{if(!String(n?.url??"").includes("unread_count"))return null;const i=l(n)?.data??{};let r=0;for(const o of Object.values(i))if(typeof o==="number")r+=o;if(r===e.state.lastUnread)return null;const c=e.state.lastUnread;e.state.lastUnread=r;if(c===void 0||r<=0)return null;return s(e.page)},reconcile:async(n)=>s(n)});
@@ -0,0 +1,19 @@
1
+ import{registerWatch as n}from"../_shared/watch.js";const a="https://www.zhihu.com/notifications",o=`
2
+ (function () {
3
+ var items = Array.prototype.slice.call(document.querySelectorAll('.NotificationList-Item'));
4
+ return JSON.stringify(items.map(function (it) {
5
+ var header = it.querySelector('.NotificationList-Item-header');
6
+ var people = it.querySelector('a[href*="/people/"]');
7
+ var target = it.querySelector('a[href*="/question/"], a[href*="/answer/"], a[href*="/p/"], a[href*="/pin/"]');
8
+ var full = (it.textContent || '').replace(/\\s+/g, ' ').trim();
9
+ var head = header ? (header.textContent || '').replace(/\\s+/g, ' ').trim() : '';
10
+ return {
11
+ head: head,
12
+ body: head && full.indexOf(head) === 0 ? full.slice(head.length).trim() : full,
13
+ senderName: people ? (people.textContent || '').trim() : '',
14
+ senderUrl: people ? (people.getAttribute('href') || '') : '',
15
+ url: target ? (target.getAttribute('href') || '') : ''
16
+ };
17
+ }));
18
+ })()
19
+ `;function l(r){let t=5381;for(let e=0;e<r.length;e++)t=(t<<5)+t+r.charCodeAt(e)>>>0;return t.toString(36)}function h(r){if(!Array.isArray(r))return[];const t=[];for(const e of r){if(!e||!e.head&&!e.body)continue;const i=String(e.url||"");t.push({event:"notification",id:l(`${i}|${e.head||""}`),ts:Date.now(),conversation_id:i,sender:{id:String(e.senderUrl||"").replace("/people/",""),name:String(e.senderName||"")},text:String(e.body||e.head||"").slice(0,2000),url:i?i.startsWith("http")?i:`https://www.zhihu.com${i}`:a,raw:{header:e.head||""}})}return t}n({site:"zhihu",name:"watch",domain:"www.zhihu.com",mode:"reconcile",description:"知乎通知监听:按间隔拉取通知中心,有新的赞同/评论/关注按 NDJSON 逐条输出",watchUrl:a,pattern:"/api/v4/notifications",parse:()=>null,reconcile:async(r)=>{await r.goto(a,{waitUntil:"load"});await r.wait({time:2});const t=await r.evaluate(o);try{return h(JSON.parse(typeof t==="string"?t:"[]"))}catch{return[]}}});
@@ -16,6 +16,8 @@ export interface CDPTarget {
16
16
  webSocketDebuggerUrl?: string;
17
17
  }
18
18
  export declare const CDP_RESPONSE_BODY_CAPTURE_LIMIT: number;
19
+ export declare const CDP_STREAM_FRAME_CAPTURE_LIMIT: number;
20
+ export declare const STREAM_FRAME_LIMIT = 1000;
19
21
  export declare class CDPBridge implements IBrowserFactory {
20
22
  private _ws;
21
23
  private _idCounter;
@@ -1 +1 @@
1
- import{WebSocket as p}from"ws";import{request as _}from"node:http";import{request as y}from"node:https";import{buildEvaluateExpression as E}from"./utils.js";import{generateStealthJs as b}from"./stealth.js";import{waitForDomStableJs as C,keyEventMetadata as P}from"./dom-helpers.js";import{isRecord as h,saveBase64ToFile as v}from"../utils.js";import{getAllElectronApps as k}from"../electron-apps.js";import{BasePage as I}from"./base-page.js";const D=30000;export const CDP_RESPONSE_BODY_CAPTURE_LIMIT=8388608;export class CDPBridge{_ws=null;_idCounter=0;_pending=new Map;_eventListeners=new Map;async connect(e){if(this._ws)throw Error("CDPBridge is already connected. Call close() before reconnecting.");const n=e?.cdpEndpoint??process.env.OPENCLI_CDP_ENDPOINT;if(!n)throw Error("CDP endpoint not provided (pass cdpEndpoint or set OPENCLI_CDP_ENDPOINT)");let s=n;if(n.startsWith("http")){const t=await q(`${n.replace(/\/$/,"")}/json`),i=g(t);if(!i||!i.webSocketDebuggerUrl)throw Error("No inspectable targets found at CDP endpoint");s=i.webSocketDebuggerUrl}return new Promise((t,i)=>{const r=new p(s),o=(e?.timeout??10)*1000,d=setTimeout(()=>{this._ws=null;r.close();i(Error("CDP connect timeout"))},o);r.on("open",async()=>{clearTimeout(d);this._ws=r;try{await this.send("Page.enable");this.on("Page.javascriptDialogOpening",(c)=>{if(c?.type==="beforeunload")this.send("Page.handleJavaScriptDialog",{accept:!0}).catch(()=>{})});await this.send("Page.addScriptToEvaluateOnNewDocument",{source:b()});if(e?.initScript)await this.send("Page.addScriptToEvaluateOnNewDocument",{source:e.initScript});if(e?.windowMode==="background")await this.send("Emulation.setFocusEmulationEnabled",{enabled:!0}).catch(()=>{});else if(e?.windowMode==="foreground")await this.send("Page.bringToFront").catch(()=>{});if(e?.proxyAuth){this.on("Fetch.requestPaused",(c)=>{const u=c;if(u.requestId)this.send("Fetch.continueRequest",{requestId:u.requestId}).catch(()=>{})});this.on("Fetch.authRequired",(c)=>{const u=c;if(!u.requestId)return;this.send("Fetch.continueWithAuth",{requestId:u.requestId,authChallengeResponse:{response:"ProvideCredentials",username:e.proxyAuth.username,password:e.proxyAuth.password}}).catch(()=>{})});await this.send("Fetch.enable",{handleAuthRequests:!0})}}catch(c){r.close();i(c instanceof Error?c:Error(String(c)));return}const a=new m(this);if(e?.session)a.session=e.session;if(e?.contextId)a.contextId=e.contextId;t(a)});r.on("error",(a)=>{clearTimeout(d);i(a)});r.on("message",(a)=>{try{const c=JSON.parse(a.toString());if(c.id&&this._pending.has(c.id)){const u=this._pending.get(c.id);clearTimeout(u.timer);this._pending.delete(c.id);if(c.error)u.reject(Error(c.error.message));else u.resolve(c.result)}if(c.method){const u=this._eventListeners.get(c.method);if(u)for(const l of u)l(c.params)}}catch(c){if(process.env.OPENCLI_VERBOSE)console.error("[cdp] Failed to parse WebSocket message:",c instanceof Error?c.message:c)}})})}async close(){if(this._ws){this._ws.close();this._ws=null}for(const e of this._pending.values()){clearTimeout(e.timer);e.reject(Error("CDP connection closed"))}this._pending.clear();this._eventListeners.clear()}async send(e,n={},s=D){if(!this._ws||this._ws.readyState!==p.OPEN)throw Error("CDP connection is not open");const t=++this._idCounter;return new Promise((i,r)=>{const o=setTimeout(()=>{this._pending.delete(t);const d=e.startsWith("Page.")?"(页面可能被 JS 弹窗挡住:试 `ppcli browser dialog accept`;托管 profile 可 `ppcli profile stop <name>` 关闭后重跑命令自动重启会话)":"";r(Error(`CDP command '${e}' timed out after ${s/1000}s${d}`))},s);this._pending.set(t,{resolve:i,reject:r,timer:o});this._ws.send(JSON.stringify({id:t,method:e,params:n}))})}on(e,n){let s=this._eventListeners.get(e);if(!s){s=new Set;this._eventListeners.set(e,s)}s.add(n)}off(e,n){this._eventListeners.get(e)?.delete(n)}waitForEvent(e,n=15000){return new Promise((s,t)=>{const i=setTimeout(()=>{this.off(e,r);t(Error(`Timed out waiting for CDP event '${e}'`))},n),r=(o)=>{clearTimeout(i);this.off(e,r);s(o)};this.on(e,r)})}}class m extends I{bridge;session;contextId;_pageEnabled=!1;_networkCapturing=!1;_networkCapturePattern="";_networkEntries=[];_pendingRequests=new Map;_pendingBodyFetches=new Set;_consoleMessages=[];_consoleCapturing=!1;constructor(e){super();this.bridge=e}async goto(e,n){if(!this._pageEnabled){await this.bridge.send("Page.enable");this._pageEnabled=!0}const s=this.bridge.waitForEvent("Page.loadEventFired",30000).catch(()=>{});await this.bridge.send("Page.navigate",{url:e});await s;this._lastUrl=e;if(n?.waitUntil!=="none"){const t=n?.settleMs??1000;await this.evaluate(C(t,Math.min(500,t)))}}async evaluate(e,...n){const s=E(e,n),t=await this.bridge.send("Runtime.evaluate",{expression:s,returnByValue:!0,awaitPromise:!0});if(t.exceptionDetails)throw Error("Evaluate error: "+(t.exceptionDetails.exception?.description||"Unknown exception"));return t.result?.value}async getCookies(e={}){let n;try{const t=await this.bridge.send("Storage.getCookies");n=h(t)&&Array.isArray(t.cookies)?t.cookies:[]}catch{const t=await this.bridge.send("Network.getCookies",e.url?{urls:[e.url]}:{});n=h(t)&&Array.isArray(t.cookies)?t.cookies:[]}const s=e.domain??(e.url?N(e.url):void 0);return s?n.filter((t)=>f(t)&&S(t.domain,s)):n.filter(f)}async screenshot(e={}){const n=e.fullPage===!0,s=e.width&&e.width>0?Math.ceil(e.width):void 0,t=!n&&e.height&&e.height>0?Math.ceil(e.height):void 0,i=s!==void 0||t!==void 0;if(i){if(s!==void 0&&n)await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:s,height:0,deviceScaleFactor:1});let r=s??0,o=t??0;if(n){const d=await this.bridge.send("Page.getLayoutMetrics"),a=h(d)?d:{},c=h(a.cssContentSize)?a.cssContentSize:void 0,u=h(a.contentSize)?a.contentSize:void 0,l=c??u;if(l&&typeof l.width==="number"&&typeof l.height==="number"){if(r===0)r=Math.ceil(l.width);o=Math.ceil(l.height)}}await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:r,height:o,deviceScaleFactor:1})}try{const r=await this.bridge.send("Page.captureScreenshot",{format:e.format??"png",quality:e.format==="jpeg"?e.quality??80:void 0,captureBeyondViewport:!i&&n}),o=h(r)&&typeof r.data==="string"?r.data:"";if(e.path)await v(o,e.path);return o}finally{if(i)await this.bridge.send("Emulation.clearDeviceMetricsOverride").catch(()=>{})}}async startNetworkCapture(e=""){this._networkCapturePattern=e;if(!this._networkCapturing){this._networkEntries=[];this._pendingRequests.clear();this._pendingBodyFetches.clear();await this.bridge.send("Network.enable");this.bridge.on("Network.requestWillBeSent",(n)=>{const s=n;if(!this._networkCapturePattern||s.request.url.includes(this._networkCapturePattern)){const t=this._networkEntries.push({url:s.request.url,method:s.request.method,timestamp:Date.now()})-1;this._pendingRequests.set(s.requestId,t)}});this.bridge.on("Network.responseReceived",(n)=>{const s=n,t=this._pendingRequests.get(s.requestId);if(t!==void 0){this._networkEntries[t].responseStatus=s.response.status;this._networkEntries[t].responseContentType=s.response.mimeType||""}});this.bridge.on("Network.loadingFinished",(n)=>{const s=n,t=this._pendingRequests.get(s.requestId);if(t!==void 0){const i=this.bridge.send("Network.getResponseBody",{requestId:s.requestId}).then((r)=>{const o=r;if(typeof o?.body==="string"){const d=o.body.length,a=d>CDP_RESPONSE_BODY_CAPTURE_LIMIT,c=a?o.body.slice(0,CDP_RESPONSE_BODY_CAPTURE_LIMIT):o.body;this._networkEntries[t].responsePreview=o.base64Encoded?`base64:${c}`:c;this._networkEntries[t].responseBodyFullSize=d;this._networkEntries[t].responseBodyTruncated=a}}).catch((r)=>{if(process.env.OPENCLI_VERBOSE)console.error(`[cdp] getResponseBody failed for ${s.requestId}:`,r instanceof Error?r.message:r)}).finally(()=>{this._pendingBodyFetches.delete(i)});this._pendingBodyFetches.add(i);this._pendingRequests.delete(s.requestId)}});this._networkCapturing=!0}return!0}async readNetworkCapture(){if(this._pendingBodyFetches.size>0)await Promise.all([...this._pendingBodyFetches]);const e=[...this._networkEntries];this._networkEntries=[];return e}async consoleMessages(e="all"){if(!this._consoleCapturing){await this.bridge.send("Runtime.enable");this.bridge.on("Runtime.consoleAPICalled",(n)=>{const s=n,t=(s.args||[]).map((i)=>i.value!==void 0?String(i.value):i.description||"").join(" ");this._consoleMessages.push({type:s.type,text:t,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this.bridge.on("Runtime.exceptionThrown",(n)=>{const s=n,t=s.exceptionDetails?.exception?.description||s.exceptionDetails?.text||"Unknown exception";this._consoleMessages.push({type:"error",text:t,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this._consoleCapturing=!0}if(e==="all")return[...this._consoleMessages];if(e==="error")return this._consoleMessages.filter((n)=>n.type==="error"||n.type==="warning");return this._consoleMessages.filter((n)=>n.type===e)}async tabs(){return[]}async selectTab(e){}async cdp(e,n={}){return this.bridge.send(e,n)}async handleJavaScriptDialog(e,n){await this.cdp("Page.handleJavaScriptDialog",{accept:e,...n!==void 0&&{promptText:n}})}async nativeClick(e,n){await this.cdp("Input.dispatchMouseEvent",{type:"mouseMoved",x:e,y:n});await this.cdp("Input.dispatchMouseEvent",{type:"mousePressed",x:e,y:n,button:"left",clickCount:1});await this.cdp("Input.dispatchMouseEvent",{type:"mouseReleased",x:e,y:n,button:"left",clickCount:1})}async nativeType(e){await this.cdp("Input.insertText",{text:e})}async insertText(e){await this.nativeType(e)}async nativeKeyPress(e,n=[]){let s=0;for(const i of n){if(i==="Alt")s|=1;if(i==="Ctrl"||i==="Control")s|=2;if(i==="Meta")s|=4;if(i==="Shift")s|=8}const t=P(e,n);await this.cdp("Input.dispatchKeyEvent",{type:"keyDown",key:e,modifiers:s,...t});await this.cdp("Input.dispatchKeyEvent",{type:"keyUp",key:e,modifiers:s,...t.code?{code:t.code}:{},...t.windowsVirtualKeyCode?{windowsVirtualKeyCode:t.windowsVirtualKeyCode,nativeVirtualKeyCode:t.windowsVirtualKeyCode}:{}})}}function N(e){try{return new URL(e).hostname}catch{return}}function f(e){return h(e)&&typeof e.name==="string"&&typeof e.value==="string"&&typeof e.domain==="string"}function S(e,n){const s=e.replace(/^\./,"").toLowerCase(),t=n.replace(/^\./,"").toLowerCase();return t===s||t.endsWith(`.${s}`)}function g(e){const n=T(process.env.OPENCLI_CDP_TARGET);return e.map((t,i)=>({target:t,index:i,score:w(t,n)})).filter(({score:t})=>Number.isFinite(t)).sort((t,i)=>{if(i.score!==t.score)return i.score-t.score;return t.index-i.index})[0]?.target}function w(e,n){if(!e.webSocketDebuggerUrl)return Number.NEGATIVE_INFINITY;const s=(e.type??"").toLowerCase(),t=(e.url??"").toLowerCase(),i=(e.title??"").toLowerCase(),r=`${i} ${t}`;if(!r.trim()&&!s)return Number.NEGATIVE_INFINITY;if(r.includes("devtools"))return Number.NEGATIVE_INFINITY;if(s==="background_page"||s==="service_worker")return Number.NEGATIVE_INFINITY;let o=0;if(n&&n.test(r))o+=1000;if(s==="app")o+=120;else if(s==="webview")o+=100;else if(s==="page")o+=80;else if(s==="iframe")o+=20;if(t.startsWith("http://localhost")||t.startsWith("https://localhost"))o+=90;if(t.startsWith("file://"))o+=60;if(t.startsWith("http://127.0.0.1")||t.startsWith("https://127.0.0.1"))o+=50;if(t.startsWith("about:blank"))o-=120;if(t===""||t==="about:blank")o-=40;if(i&&i!=="devtools")o+=25;const d=Object.values(k()).map((a)=>(a.displayName??a.processName).toLowerCase());for(const a of d)if(i.includes(a)){o+=120;break}for(const a of d)if(t.includes(a)){o+=100;break}return o}function T(e){const n=e?.trim();if(!n)return;return new RegExp(M(n.toLowerCase()))}function M(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export const __test__={selectCDPTarget:g,scoreCDPTarget:w};function q(e){return new Promise((n,s)=>{const t=new URL(e),i=(t.protocol==="https:"?y:_)(t,(r)=>{const o=r.statusCode??0;if(o<200||o>=300){r.resume();s(Error(`Failed to fetch CDP targets: HTTP ${o}`));return}const d=[];r.on("data",(a)=>d.push(Buffer.isBuffer(a)?a:Buffer.from(a)));r.on("end",()=>{try{n(JSON.parse(Buffer.concat(d).toString("utf8")))}catch(a){s(a instanceof Error?a:Error(String(a)))}})});i.on("error",s);i.setTimeout(1e4,()=>i.destroy(Error("Timed out fetching CDP targets")));i.end()})}
1
+ import{WebSocket as p}from"ws";import{request as _}from"node:http";import{request as y}from"node:https";import{buildEvaluateExpression as b}from"./utils.js";import{generateStealthJs as E}from"./stealth.js";import{waitForDomStableJs as v,keyEventMetadata as C}from"./dom-helpers.js";import{isRecord as l,saveBase64ToFile as k}from"../utils.js";import{getAllElectronApps as P}from"../electron-apps.js";import{BasePage as S}from"./base-page.js";const I=30000;export const CDP_RESPONSE_BODY_CAPTURE_LIMIT=8388608,CDP_STREAM_FRAME_CAPTURE_LIMIT=262144,STREAM_FRAME_LIMIT=1000;export class CDPBridge{_ws=null;_idCounter=0;_pending=new Map;_eventListeners=new Map;async connect(e){if(this._ws)throw Error("CDPBridge is already connected. Call close() before reconnecting.");const s=e?.cdpEndpoint??process.env.OPENCLI_CDP_ENDPOINT;if(!s)throw Error("CDP endpoint not provided (pass cdpEndpoint or set OPENCLI_CDP_ENDPOINT)");let n=s;if(s.startsWith("http")){const t=await M(`${s.replace(/\/$/,"")}/json`),r=g(t);if(!r||!r.webSocketDebuggerUrl)throw Error("No inspectable targets found at CDP endpoint");n=r.webSocketDebuggerUrl}return new Promise((t,r)=>{const i=new p(n),o=(e?.timeout??10)*1000,d=setTimeout(()=>{this._ws=null;i.close();r(Error("CDP connect timeout"))},o);i.on("open",async()=>{clearTimeout(d);this._ws=i;try{await this.send("Page.enable");this.on("Page.javascriptDialogOpening",(c)=>{if(c?.type==="beforeunload")this.send("Page.handleJavaScriptDialog",{accept:!0}).catch(()=>{})});await this.send("Page.addScriptToEvaluateOnNewDocument",{source:E()});if(e?.initScript)await this.send("Page.addScriptToEvaluateOnNewDocument",{source:e.initScript});if(e?.windowMode==="background")await this.send("Emulation.setFocusEmulationEnabled",{enabled:!0}).catch(()=>{});else if(e?.windowMode==="foreground")await this.send("Page.bringToFront").catch(()=>{});if(e?.proxyAuth){this.on("Fetch.requestPaused",(c)=>{const u=c;if(u.requestId)this.send("Fetch.continueRequest",{requestId:u.requestId}).catch(()=>{})});this.on("Fetch.authRequired",(c)=>{const u=c;if(!u.requestId)return;this.send("Fetch.continueWithAuth",{requestId:u.requestId,authChallengeResponse:{response:"ProvideCredentials",username:e.proxyAuth.username,password:e.proxyAuth.password}}).catch(()=>{})});await this.send("Fetch.enable",{handleAuthRequests:!0})}}catch(c){i.close();r(c instanceof Error?c:Error(String(c)));return}const a=new m(this);if(e?.session)a.session=e.session;if(e?.contextId)a.contextId=e.contextId;t(a)});i.on("error",(a)=>{clearTimeout(d);r(a)});i.on("message",(a)=>{try{const c=JSON.parse(a.toString());if(c.id&&this._pending.has(c.id)){const u=this._pending.get(c.id);clearTimeout(u.timer);this._pending.delete(c.id);if(c.error)u.reject(Error(c.error.message));else u.resolve(c.result)}if(c.method){const u=this._eventListeners.get(c.method);if(u)for(const h of u)h(c.params)}}catch(c){if(process.env.OPENCLI_VERBOSE)console.error("[cdp] Failed to parse WebSocket message:",c instanceof Error?c.message:c)}})})}async close(){if(this._ws){this._ws.close();this._ws=null}for(const e of this._pending.values()){clearTimeout(e.timer);e.reject(Error("CDP connection closed"))}this._pending.clear();this._eventListeners.clear()}async send(e,s={},n=I){if(!this._ws||this._ws.readyState!==p.OPEN)throw Error("CDP connection is not open");const t=++this._idCounter;return new Promise((r,i)=>{const o=setTimeout(()=>{this._pending.delete(t);const d=e.startsWith("Page.")?"(页面可能被 JS 弹窗挡住:试 `ppcli browser dialog accept`;托管 profile 可 `ppcli profile stop <name>` 关闭后重跑命令自动重启会话)":"";i(Error(`CDP command '${e}' timed out after ${n/1000}s${d}`))},n);this._pending.set(t,{resolve:r,reject:i,timer:o});this._ws.send(JSON.stringify({id:t,method:e,params:s}))})}on(e,s){let n=this._eventListeners.get(e);if(!n){n=new Set;this._eventListeners.set(e,n)}n.add(s)}off(e,s){this._eventListeners.get(e)?.delete(s)}waitForEvent(e,s=15000){return new Promise((n,t)=>{const r=setTimeout(()=>{this.off(e,i);t(Error(`Timed out waiting for CDP event '${e}'`))},s),i=(o)=>{clearTimeout(r);this.off(e,i);n(o)};this.on(e,i)})}}class m extends S{bridge;session;contextId;_pageEnabled=!1;_networkCapturing=!1;_networkCapturePattern="";_networkEntries=[];_streamFrames=[];_streamFramesDropped=0;_streamUrls=new Map;_pendingRequests=new Map;_pendingBodyFetches=new Set;_consoleMessages=[];_consoleCapturing=!1;constructor(e){super();this.bridge=e}async goto(e,s){if(!this._pageEnabled){await this.bridge.send("Page.enable");this._pageEnabled=!0}const n=this.bridge.waitForEvent("Page.loadEventFired",30000).catch(()=>{});await this.bridge.send("Page.navigate",{url:e});await n;this._lastUrl=e;if(s?.waitUntil!=="none"){const t=s?.settleMs??1000;await this.evaluate(v(t,Math.min(500,t)))}}async evaluate(e,...s){const n=b(e,s),t=await this.bridge.send("Runtime.evaluate",{expression:n,returnByValue:!0,awaitPromise:!0});if(t.exceptionDetails)throw Error("Evaluate error: "+(t.exceptionDetails.exception?.description||"Unknown exception"));return t.result?.value}async getCookies(e={}){let s;try{const t=await this.bridge.send("Storage.getCookies");s=l(t)&&Array.isArray(t.cookies)?t.cookies:[]}catch{const t=await this.bridge.send("Network.getCookies",e.url?{urls:[e.url]}:{});s=l(t)&&Array.isArray(t.cookies)?t.cookies:[]}const n=e.domain??(e.url?N(e.url):void 0);return n?s.filter((t)=>f(t)&&D(t.domain,n)):s.filter(f)}async screenshot(e={}){const s=e.fullPage===!0,n=e.width&&e.width>0?Math.ceil(e.width):void 0,t=!s&&e.height&&e.height>0?Math.ceil(e.height):void 0,r=n!==void 0||t!==void 0;if(r){if(n!==void 0&&s)await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:n,height:0,deviceScaleFactor:1});let i=n??0,o=t??0;if(s){const d=await this.bridge.send("Page.getLayoutMetrics"),a=l(d)?d:{},c=l(a.cssContentSize)?a.cssContentSize:void 0,u=l(a.contentSize)?a.contentSize:void 0,h=c??u;if(h&&typeof h.width==="number"&&typeof h.height==="number"){if(i===0)i=Math.ceil(h.width);o=Math.ceil(h.height)}}await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:i,height:o,deviceScaleFactor:1})}try{const i=await this.bridge.send("Page.captureScreenshot",{format:e.format??"png",quality:e.format==="jpeg"?e.quality??80:void 0,captureBeyondViewport:!r&&s}),o=l(i)&&typeof i.data==="string"?i.data:"";if(e.path)await k(o,e.path);return o}finally{if(r)await this.bridge.send("Emulation.clearDeviceMetricsOverride").catch(()=>{})}}async startNetworkCapture(e=""){this._networkCapturePattern=e;if(!this._networkCapturing){this._networkEntries=[];this._streamFrames=[];this._streamFramesDropped=0;this._streamUrls.clear();this._pendingRequests.clear();this._pendingBodyFetches.clear();await this.bridge.send("Network.enable");this.bridge.on("Network.requestWillBeSent",(n)=>{const t=n;if(!this._networkCapturePattern||t.request.url.includes(this._networkCapturePattern)){const r=this._networkEntries.push({url:t.request.url,method:t.request.method,timestamp:Date.now()})-1;this._pendingRequests.set(t.requestId,r)}});this.bridge.on("Network.responseReceived",(n)=>{const t=n;if(t.requestId&&t.response?.mimeType==="text/event-stream")this._streamUrls.set(t.requestId,String(t.response.url||""));const r=this._pendingRequests.get(t.requestId);if(r!==void 0){this._networkEntries[r].responseStatus=t.response.status;this._networkEntries[r].responseContentType=t.response.mimeType||""}});this.bridge.on("Network.loadingFinished",(n)=>{const t=n,r=this._pendingRequests.get(t.requestId);if(r!==void 0){const i=this.bridge.send("Network.getResponseBody",{requestId:t.requestId}).then((o)=>{const d=o;if(typeof d?.body==="string"){const a=d.body.length,c=a>CDP_RESPONSE_BODY_CAPTURE_LIMIT,u=c?d.body.slice(0,CDP_RESPONSE_BODY_CAPTURE_LIMIT):d.body;this._networkEntries[r].responsePreview=d.base64Encoded?`base64:${u}`:u;this._networkEntries[r].responseBodyFullSize=a;this._networkEntries[r].responseBodyTruncated=c}}).catch((o)=>{if(process.env.OPENCLI_VERBOSE)console.error(`[cdp] getResponseBody failed for ${t.requestId}:`,o instanceof Error?o.message:o)}).finally(()=>{this._pendingBodyFetches.delete(i)});this._pendingBodyFetches.add(i);this._pendingRequests.delete(t.requestId)}});this.bridge.on("Network.webSocketCreated",(n)=>{const t=n;if(t?.requestId)this._streamUrls.set(t.requestId,String(t.url||""))});const s=(n)=>(t)=>{const r=t,i=Number(r?.response?.opcode??1);if(i!==1&&i!==2)return;this._pushStreamFrame(r.requestId,{channel:"websocket",direction:n,payload:String(r?.response?.payloadData||""),binary:i===2})};this.bridge.on("Network.webSocketFrameReceived",s("received"));this.bridge.on("Network.webSocketFrameSent",s("sent"));this.bridge.on("Network.webSocketClosed",(n)=>{const t=n;if(t?.requestId)this._streamUrls.delete(t.requestId)});this.bridge.on("Network.eventSourceMessageReceived",(n)=>{const t=n;this._pushStreamFrame(t.requestId,{channel:"eventsource",direction:"received",payload:String(t?.data||""),eventName:t?.eventName?String(t.eventName):void 0,eventId:t?.eventId?String(t.eventId):void 0})});this._networkCapturing=!0}return!0}_pushStreamFrame(e,s){const n=this._streamUrls.get(e)||`${s.channel==="websocket"?"ws":"sse"}://unknown/${e}`;if(this._networkCapturePattern&&!n.includes(this._networkCapturePattern))return;const t=String(s.payload??""),r=t.length,i=r>CDP_STREAM_FRAME_CAPTURE_LIMIT,o=i?t.slice(0,CDP_STREAM_FRAME_CAPTURE_LIMIT):t,d={url:n,method:s.channel==="websocket"?"WS":"SSE",channel:s.channel,direction:s.direction,responseStatus:200,responseContentType:s.binary?"application/octet-stream":"text/plain",responsePreview:s.binary?`base64:${o}`:o,responseBodyFullSize:r,responseBodyTruncated:i,timestamp:Date.now()};if(s.eventName)d.eventName=s.eventName;if(s.eventId)d.eventId=s.eventId;this._streamFrames.push(d);while(this._streamFrames.length>STREAM_FRAME_LIMIT){this._streamFrames.shift();this._streamFramesDropped++}}async readNetworkCapture(){if(this._pendingBodyFetches.size>0)await Promise.all([...this._pendingBodyFetches]);const e=[...this._networkEntries];this._networkEntries=[];const s=[...this._streamFrames];this._streamFrames=[];if(this._streamFramesDropped){if(process.env.OPENCLI_VERBOSE)console.error(`[cdp] dropped ${this._streamFramesDropped} stream frames over the ${STREAM_FRAME_LIMIT} buffer cap`);this._streamFramesDropped=0}return e.concat(s)}async consoleMessages(e="all"){if(!this._consoleCapturing){await this.bridge.send("Runtime.enable");this.bridge.on("Runtime.consoleAPICalled",(s)=>{const n=s,t=(n.args||[]).map((r)=>r.value!==void 0?String(r.value):r.description||"").join(" ");this._consoleMessages.push({type:n.type,text:t,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this.bridge.on("Runtime.exceptionThrown",(s)=>{const n=s,t=n.exceptionDetails?.exception?.description||n.exceptionDetails?.text||"Unknown exception";this._consoleMessages.push({type:"error",text:t,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this._consoleCapturing=!0}if(e==="all")return[...this._consoleMessages];if(e==="error")return this._consoleMessages.filter((s)=>s.type==="error"||s.type==="warning");return this._consoleMessages.filter((s)=>s.type===e)}async tabs(){return[]}async selectTab(e){}async cdp(e,s={}){return this.bridge.send(e,s)}async handleJavaScriptDialog(e,s){await this.cdp("Page.handleJavaScriptDialog",{accept:e,...s!==void 0&&{promptText:s}})}async nativeClick(e,s){await this.cdp("Input.dispatchMouseEvent",{type:"mouseMoved",x:e,y:s});await this.cdp("Input.dispatchMouseEvent",{type:"mousePressed",x:e,y:s,button:"left",clickCount:1});await this.cdp("Input.dispatchMouseEvent",{type:"mouseReleased",x:e,y:s,button:"left",clickCount:1})}async nativeType(e){await this.cdp("Input.insertText",{text:e})}async insertText(e){await this.nativeType(e)}async nativeKeyPress(e,s=[]){let n=0;for(const r of s){if(r==="Alt")n|=1;if(r==="Ctrl"||r==="Control")n|=2;if(r==="Meta")n|=4;if(r==="Shift")n|=8}const t=C(e,s);await this.cdp("Input.dispatchKeyEvent",{type:"keyDown",key:e,modifiers:n,...t});await this.cdp("Input.dispatchKeyEvent",{type:"keyUp",key:e,modifiers:n,...t.code?{code:t.code}:{},...t.windowsVirtualKeyCode?{windowsVirtualKeyCode:t.windowsVirtualKeyCode,nativeVirtualKeyCode:t.windowsVirtualKeyCode}:{}})}}function N(e){try{return new URL(e).hostname}catch{return}}function f(e){return l(e)&&typeof e.name==="string"&&typeof e.value==="string"&&typeof e.domain==="string"}function D(e,s){const n=e.replace(/^\./,"").toLowerCase(),t=s.replace(/^\./,"").toLowerCase();return t===n||t.endsWith(`.${n}`)}function g(e){const s=F(process.env.OPENCLI_CDP_TARGET);return e.map((t,r)=>({target:t,index:r,score:w(t,s)})).filter(({score:t})=>Number.isFinite(t)).sort((t,r)=>{if(r.score!==t.score)return r.score-t.score;return t.index-r.index})[0]?.target}function w(e,s){if(!e.webSocketDebuggerUrl)return Number.NEGATIVE_INFINITY;const n=(e.type??"").toLowerCase(),t=(e.url??"").toLowerCase(),r=(e.title??"").toLowerCase(),i=`${r} ${t}`;if(!i.trim()&&!n)return Number.NEGATIVE_INFINITY;if(i.includes("devtools"))return Number.NEGATIVE_INFINITY;if(n==="background_page"||n==="service_worker")return Number.NEGATIVE_INFINITY;let o=0;if(s&&s.test(i))o+=1000;if(n==="app")o+=120;else if(n==="webview")o+=100;else if(n==="page")o+=80;else if(n==="iframe")o+=20;if(t.startsWith("http://localhost")||t.startsWith("https://localhost"))o+=90;if(t.startsWith("file://"))o+=60;if(t.startsWith("http://127.0.0.1")||t.startsWith("https://127.0.0.1"))o+=50;if(t.startsWith("about:blank"))o-=120;if(t===""||t==="about:blank")o-=40;if(r&&r!=="devtools")o+=25;const d=Object.values(P()).map((a)=>(a.displayName??a.processName).toLowerCase());for(const a of d)if(r.includes(a)){o+=120;break}for(const a of d)if(t.includes(a)){o+=100;break}return o}function F(e){const s=e?.trim();if(!s)return;return new RegExp(T(s.toLowerCase()))}function T(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export const __test__={selectCDPTarget:g,scoreCDPTarget:w};function M(e){return new Promise((s,n)=>{const t=new URL(e),r=(t.protocol==="https:"?y:_)(t,(i)=>{const o=i.statusCode??0;if(o<200||o>=300){i.resume();n(Error(`Failed to fetch CDP targets: HTTP ${o}`));return}const d=[];i.on("data",(a)=>d.push(Buffer.isBuffer(a)?a:Buffer.from(a)));i.on("end",()=>{try{s(JSON.parse(Buffer.concat(d).toString("utf8")))}catch(a){n(a instanceof Error?a:Error(String(a)))}})});r.on("error",n);r.setTimeout(1e4,()=>r.destroy(Error("Timed out fetching CDP targets")));r.end()})}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import*as c from"node:fs";import*as a from"node:path";import{fileURLToPath as x,pathToFileURL as S}from"node:url";import{getErrorMessage as F}from"./errors.js";import{fullName as R,getRegistry as A}from"./registry.js";import{findPackageRoot as C,getCliManifestPath as j}from"./package-paths.js";import{isRecord as M}from"./utils.js";const y=C(x(import.meta.url)),w=a.join(y,"clis"),h=j(w),k=/\bcli\s*\(|\bregisterSiteAuthCommands\s*\(|\bregisterArticleLogin\s*\(|\bregisterTokenAuth\s*\(|\bmake[A-Z]\w*Command\s*\(/;export class ManifestImportError extends Error{filePath;cause;constructor(e,t){super(`failed to scan ${e}: ${F(t)}`);this.filePath=e;this.cause=t;this.name="ManifestImportError"}}function I(e){return e.map((t)=>({name:t.name,type:t.type??"str",default:t.default,required:!!t.required,valueRequired:!!t.valueRequired||void 0,positional:t.positional||void 0,help:t.help??"",choices:t.choices,fromFile:t.fromFile||void 0,requiredUnlessFile:t.requiredUnlessFile||void 0,generatedFileFor:t.generatedFileFor||void 0}))}function N(e,t){const r=a.basename(e,a.extname(e));return`${t}/${r}.js`}export function normalizeManifestPath(e){return e.replace(/\\/g,"/")}function O(e,t){return normalizeManifestPath(a.relative(t,e))}function T(e,t){return M(e)&&typeof e.site==="string"&&e.site===t&&typeof e.name==="string"&&(e.access==="read"||e.access==="write")&&Array.isArray(e.args)}function E(e,t,r){return{site:e.site,name:e.name,aliases:e.aliases,description:e.description??"",access:e.access,example:e.example,domain:e.domain,strategy:(e.strategy??"public").toString().toLowerCase(),browser:e.browser??!0,args:I(e.args),columns:e.columns,defaultFormat:e.defaultFormat,type:"js",modulePath:t,sourceFile:r,navigateBefore:e.navigateBefore,siteSession:e.siteSession,defaultWindowMode:e.defaultWindowMode}}export async function loadManifestEntries(e,t,r=(u)=>import(u),n=w){let u;try{u=c.readFileSync(e,"utf-8")}catch(s){throw new ManifestImportError(e,s)}if(!k.test(u))return[];try{const s=N(e,t),o=A(),i=new Map(o.entries()),d=await r(S(e).href),l=Object.values(M(d)?d:{}).filter((f)=>T(f,t)),p=l.length>0?l:[...o.entries()].filter(([f,m])=>{if(m.site!==t)return!1;const v=i.get(f);return!v||v!==m}).map(([,f])=>f),g=O(e,n),b=new Set;return p.filter((f)=>{const m=R(f);if(b.has(m))return!1;b.add(m);return!0}).sort((f,m)=>f.name.localeCompare(m.name)).map((f)=>E(f,s,g))}catch(s){throw new ManifestImportError(e,s)}}export async function scanClisDir(e,t=(r)=>import(r)){const r=new Map,n=[];if(!c.existsSync(e))return{entries:[],failures:n};for(const s of c.readdirSync(e)){const o=a.join(e,s);if(!c.statSync(o).isDirectory())continue;for(const i of c.readdirSync(o))if(i.endsWith(".js")&&!i.endsWith(".d.js")&&!i.endsWith(".test.js")&&i!=="index.js"){const d=a.join(o,i);try{const l=await loadManifestEntries(d,s,t,e);for(const p of l){const g=`${p.site}/${p.name}`;r.set(g,p)}}catch(l){if(l instanceof ManifestImportError){n.push(l);continue}throw l}}}return{entries:[...r.values()].sort((s,o)=>s.site.localeCompare(o.site)||s.name.localeCompare(o.name)),failures:n}}export async function buildManifest(){return scanClisDir(w)}export function serializeManifest(e){return`${JSON.stringify(e,null,2)}
2
+ import*as c from"node:fs";import*as a from"node:path";import{fileURLToPath as x,pathToFileURL as S}from"node:url";import{getErrorMessage as F}from"./errors.js";import{fullName as R,getRegistry as A}from"./registry.js";import{findPackageRoot as C,getCliManifestPath as j}from"./package-paths.js";import{isRecord as M}from"./utils.js";const y=C(x(import.meta.url)),w=a.join(y,"clis"),h=j(w),k=/\bcli\s*\(|\bregisterSiteAuthCommands\s*\(|\bregisterArticleLogin\s*\(|\bregisterTokenAuth\s*\(|\bregisterWatch\s*\(|\bmake[A-Z]\w*Command\s*\(/;export class ManifestImportError extends Error{filePath;cause;constructor(e,t){super(`failed to scan ${e}: ${F(t)}`);this.filePath=e;this.cause=t;this.name="ManifestImportError"}}function I(e){return e.map((t)=>({name:t.name,type:t.type??"str",default:t.default,required:!!t.required,valueRequired:!!t.valueRequired||void 0,positional:t.positional||void 0,help:t.help??"",choices:t.choices,fromFile:t.fromFile||void 0,requiredUnlessFile:t.requiredUnlessFile||void 0,generatedFileFor:t.generatedFileFor||void 0}))}function N(e,t){const r=a.basename(e,a.extname(e));return`${t}/${r}.js`}export function normalizeManifestPath(e){return e.replace(/\\/g,"/")}function O(e,t){return normalizeManifestPath(a.relative(t,e))}function T(e,t){return M(e)&&typeof e.site==="string"&&e.site===t&&typeof e.name==="string"&&(e.access==="read"||e.access==="write")&&Array.isArray(e.args)}function E(e,t,r){return{site:e.site,name:e.name,aliases:e.aliases,description:e.description??"",access:e.access,example:e.example,domain:e.domain,strategy:(e.strategy??"public").toString().toLowerCase(),browser:e.browser??!0,args:I(e.args),columns:e.columns,defaultFormat:e.defaultFormat,type:"js",modulePath:t,sourceFile:r,navigateBefore:e.navigateBefore,siteSession:e.siteSession,defaultWindowMode:e.defaultWindowMode}}export async function loadManifestEntries(e,t,r=(u)=>import(u),n=w){let u;try{u=c.readFileSync(e,"utf-8")}catch(s){throw new ManifestImportError(e,s)}if(!k.test(u))return[];try{const s=N(e,t),o=A(),i=new Map(o.entries()),d=await r(S(e).href),l=Object.values(M(d)?d:{}).filter((f)=>T(f,t)),p=l.length>0?l:[...o.entries()].filter(([f,m])=>{if(m.site!==t)return!1;const v=i.get(f);return!v||v!==m}).map(([,f])=>f),g=O(e,n),b=new Set;return p.filter((f)=>{const m=R(f);if(b.has(m))return!1;b.add(m);return!0}).sort((f,m)=>f.name.localeCompare(m.name)).map((f)=>E(f,s,g))}catch(s){throw new ManifestImportError(e,s)}}export async function scanClisDir(e,t=(r)=>import(r)){const r=new Map,n=[];if(!c.existsSync(e))return{entries:[],failures:n};for(const s of c.readdirSync(e)){const o=a.join(e,s);if(!c.statSync(o).isDirectory())continue;for(const i of c.readdirSync(o))if(i.endsWith(".js")&&!i.endsWith(".d.js")&&!i.endsWith(".test.js")&&i!=="index.js"){const d=a.join(o,i);try{const l=await loadManifestEntries(d,s,t,e);for(const p of l){const g=`${p.site}/${p.name}`;r.set(g,p)}}catch(l){if(l instanceof ManifestImportError){n.push(l);continue}throw l}}}return{entries:[...r.values()].sort((s,o)=>s.site.localeCompare(o.site)||s.name.localeCompare(o.name)),failures:n}}export async function buildManifest(){return scanClisDir(w)}export function serializeManifest(e){return`${JSON.stringify(e,null,2)}
3
3
  `}export function findManifestMetadataIssues(e){const t=[];for(const r of e){if(!Array.isArray(r.args))continue;for(const n of r.args){if(!n.positional)continue;if((typeof n.help==="string"?n.help.trim():"")==="")t.push({site:r.site,command:r.name,arg:n.name,sourceFile:r.sourceFile,reason:"positional arg missing non-empty `help` text"})}}return t}export function diffRemovedEntries(e,t){const r=new Set(t.map((n)=>`${n.site}/${n.name}`));return e.map((n)=>`${n.site}/${n.name}`).filter((n)=>!r.has(n)).sort()}export function parseBuildManifestArgs(e){let t=0;for(const r of e){if(r==="--allow-removals"){t=Number.POSITIVE_INFINITY;continue}const n=r.match(/^--allow-removals=(\d+)$/);if(n){t=Number.parseInt(n[1],10);continue}}return{allowRemovals:t}}function P(e){try{if(!c.existsSync(e))return null;const t=c.readFileSync(e,"utf-8"),r=JSON.parse(t);return Array.isArray(r)?r:null}catch{return null}}async function L(){if(x(import.meta.url).includes(`${a.sep}dist${a.sep}`)){process.stderr.write(`❌ Refusing to run build-manifest from dist/.
4
4
  `+" Stale compiled output silently drops adapters that import renamed/removed exports.\n Run `npm run build-manifest` (or `tsx src/build-manifest.ts`) from the source tree instead.\n");process.exit(1)}const e=parseBuildManifestArgs(process.argv.slice(2)),{entries:t,failures:r}=await buildManifest();if(r.length>0){process.stderr.write(`❌ ${r.length} adapter(s) failed to load:
5
5
  `);for(const s of r){const o=a.relative(y,s.filePath)||s.filePath;process.stderr.write(` - ${o}: ${F(s.cause)}