@aicloud360/mcp-server-disk 0.8.0 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import n from"dotenv";import{Command as e}from"commander";import t from"fs";import o from"path";import{homedir as s}from"os";import r from"crypto";import a from"pino";import{promises as i}from"dns";import{spawn as c}from"child_process";import l from"fs/promises";function u(){const n=function(){try{const n=o.dirname(new URL(import.meta.url).pathname),e=[o.resolve(n,"../../package.json"),o.resolve(n,"../../../package.json")];for(const n of e)if(t.existsSync(n)){const e=JSON.parse(t.readFileSync(n,"utf8"));if(e.cli?.name)return e.cli.name}}catch{}return"360disk"}();return o.join(s(),`.${n}`)}function m(){return o.join(u(),"config.json")}function d(){try{const n=m();if(t.existsSync(n))return JSON.parse(t.readFileSync(n,"utf8"))}catch{}return{}}function h(n){!function(){const n=u();t.existsSync(n)||t.mkdirSync(n,{recursive:!0,mode:448})}();const e=m();t.writeFileSync(e,JSON.stringify(n,null,2),{mode:384})}function p(n){const e=d();return{apiKey:n.apiKey||process.env.API_KEY||e.api_key||"",ecsEnv:n.env||process.env.ECS_ENV||e.ecs_env||"prod",subChannel:n.subChannel||process.env.SUB_CHANNEL||e.sub_channel||"open",timeout:n.timeout?parseInt(n.timeout):void 0,retries:n.retries?parseInt(n.retries):void 0}}var f;!function(n){n[n.SUCCESS=0]="SUCCESS",n[n.GENERAL=1]="GENERAL",n[n.INVALID_ARGS=2]="INVALID_ARGS",n[n.AUTH_ERROR=3]="AUTH_ERROR",n[n.NOT_FOUND=4]="NOT_FOUND",n[n.PERMISSION_DENIED=5]="PERMISSION_DENIED",n[n.NETWORK_ERROR=6]="NETWORK_ERROR",n[n.CONFLICT=7]="CONFLICT",n[n.SERVER_ERROR=8]="SERVER_ERROR",n[n.QUOTA_EXCEEDED=10]="QUOTA_EXCEEDED"}(f||(f={}));class w extends Error{code;constructor(n,e=f.GENERAL){super(n),this.name="CLIError",this.code=e}}function $(n,e,t,o){if(o?.quiet)return void process.stdout.write(JSON.stringify(n)+"\n");const s={success:!0,result:n,meta:{duration_ms:Date.now()-t,command:e}};process.stdout.write(JSON.stringify(s,null,2)+"\n")}function _(n){process.stdout.write(n+"\n")}function g(n,e,t){const o=n instanceof Error?n.message:n;let s;s=n instanceof w?n.code:function(n){const e=(n?.message||n?.toString()||"").toLowerCase();return e.includes("超时")||e.includes("timeout")||e.includes("aborted")||"AbortError"===n?.name||e.includes("econnreset")||e.includes("etimedout")||e.includes("econnrefused")||e.includes("fetch failed")||e.includes("网络")?f.NETWORK_ERROR:e.includes("api key")||e.includes("api_key")||e.includes("未配置")||e.includes("token")||e.includes("auth")||e.includes("登录")||e.includes("401")?f.AUTH_ERROR:e.includes("参数")||e.includes("argument")||e.includes("required")||e.includes("互斥")||e.includes("invalid")?f.INVALID_ARGS:e.includes("不存在")||e.includes("not found")||e.includes("404")||e.includes("no such")?f.NOT_FOUND:e.includes("权限")||e.includes("permission")||e.includes("forbidden")||e.includes("403")?f.PERMISSION_DENIED:e.includes("已存在")||e.includes("conflict")||e.includes("duplicate")||e.includes("409")?f.CONFLICT:e.includes("状态码: 5")||e.includes("500")||e.includes("502")||e.includes("503")||e.includes("服务端")?f.SERVER_ERROR:e.includes("空间不足")||e.includes("quota")||e.includes("limit")||e.includes("429")||e.includes("频率")?f.QUOTA_EXCEEDED:f.GENERAL}(n);const r={success:!1,error:o,code:s,meta:{duration_ms:t?Date.now()-t:0,command:e}};process.stderr.write(JSON.stringify(r,null,2)+"\n"),process.exit(s||1)}function P(n,e=2){if(!n||n<=0)return"-";const t=Math.floor(Math.log(n)/Math.log(1024));return parseFloat((n/Math.pow(1024,t)).toFixed(e))+" "+["B","KB","MB","GB","TB"][t]}function y(n){if(!n)return"-";const e="string"==typeof n?parseInt(n):n;if(isNaN(e)||e<=0)return"-";const t=new Date(e<1e12?1e3*e:e),o=n=>String(n).padStart(2,"0");return`${t.getFullYear()}-${o(t.getMonth()+1)}-${o(t.getDate())} ${o(t.getHours())}:${o(t.getMinutes())}`}function v(n){let e=0;for(const t of n)e+=t.charCodeAt(0)>127?2:1;return e}function E(n,e){const t=e-v(n);return t>0?n+" ".repeat(t):n}function b(n,e){const t=n.map(((n,t)=>{const o=e.reduce(((n,e)=>Math.max(n,v(e[t]||""))),0);return Math.max(v(n),o)})),o=[];o.push(n.map(((n,e)=>E(n,t[e]))).join(" ")),o.push(t.map((n=>"-".repeat(n))).join(" "));for(const n of e)o.push(n.map(((n,e)=>E(n||"",t[e]))).join(" "));return o.join("\n")}function k(n){const e=n?.data;if(!e)return"(空目录)";const t=e.list||e.data||[];if(!Array.isArray(t)||0===t.length)return"(空目录)";const o=b(["类型","名称","大小","修改时间","NID"],t.map((n=>{const e=1===n.is_dir||"1"===n.is_dir||"dir"===n.type;return[e?"d":"-",n.name||n.fname||"-",e?"-":P(parseInt(n.count_size||n.size||"0")),y(n.modify_time||n.mtime),n.nid||"-"]}))),s=void 0!==e.page_num?e.page_num:"";let r=`共 ${e.total_count||e.total||t.length} 项`;return""!==s&&(r+=`,第 ${s} 页`),o+"\n"+r}function R(n){const e=n?.data;if(!e)return"无搜索结果";const t=e.list||e.data||[];if(!Array.isArray(t)||0===t.length)return"无搜索结果";return b(["类型","名称","大小","路径","NID"],t.map((n=>{const e=1===n.is_dir||"1"===n.is_dir;return[e?"d":"-",n.name||n.fname||"-",e?"-":P(parseInt(n.count_size||n.size||"0")),n.path||"-",n.nid||"-"]})))+"\n"+`共找到 ${e.total_count||e.total||t.length} 项`}function O(n,e){if(!n)return e;if(n.data?.share_url)return`${e}\n分享链接: ${n.data.share_url}`;if(n.data?.downloadUrl||n.downloadUrl){const t=n.data?.downloadUrl||n.downloadUrl,o=n.filename||"",s=n.fileSize||"",r=n.downloadPath||"";let a=e;return o&&(a+=`\n文件名: ${o}`),s&&(a+=`\n大小: ${s}`),r&&(a+=`\n保存到: ${r}`),t&&(a+=`\n链接: ${t}`),a}if(void 0!==n.fileCount){let t=`${e}\n上传: ${n.fileCount}/${n.totalFileCount} 文件,耗时 ${n.totalTime}秒`;if(n.uploadResults?.length)for(const e of n.uploadResults)t+=`\n - ${e.name||e.uploadRes?.name||"未知"}`;return n.duplicateFiles?.length&&(t+=`\n重名文件: ${n.duplicateFiles.map((n=>n.name)).join(", ")}`),t}if(n.data?.task_id||n.data?.file_path){let t=e;return n.data?.file_path&&(t+=`\n路径: ${n.data.file_path}`),n.data?.file_size&&(t+=`\n大小: ${P(n.data.file_size)}`),t}return e}function C(n){const e=n?.data;if(!e)return"无法获取用户信息";const t=[];return(e.nickname||e.nick)&&t.push(`昵称: ${e.nickname||e.nick}`),e.qid&&t.push(`QID: ${e.qid}`),void 0!==e.space_used&&void 0!==e.space_total&&t.push(`空间: ${P(e.space_used)} / ${P(e.space_total)}`),e.vip_type&&t.push(`VIP: ${e.vip_type}`),t.length>0?t.join("\n"):JSON.stringify(e,null,2)}const L=Object.freeze({__proto__:null,executeBatch:async function(n,e){const t=[];let o=0,s=0;for(let r=0;r<n.length;r++)try{const s=await e(n[r],r);t.push({index:r,input:n[r],success:!0,result:s}),o++}catch(e){t.push({index:r,input:n[r],success:!1,error:e.message}),s++}return{total:n.length,succeeded:o,failed:s,items:t}},formatBatchResult:function(n){const e=[];e.push(`批量操作完成: 成功 ${n.succeeded}/${n.total},失败 ${n.failed}/${n.total}`);for(const t of n.items){const n=t.success?"":` (${t.error})`;e.push(` ${t.success?"✓":"✗"} [${t.index+1}] ${JSON.stringify(t.input)}${n}`)}return e.join("\n")},formatBytes:P,formatFileList:k,formatSearchResult:R,formatSimpleResult:O,formatTable:b,formatUserInfo:C,outputError:g,outputJson:$,outputText:_});function I(){const n=new e("auth").description("鉴权管理").enablePositionalOptions().passThroughOptions();return n.command("login").description("登录:保存 API Key 到本地配置").requiredOption("--api-key <api_key>","API 密钥").option("--env <env>","环境 (prod/test)","prod").option("--sub-channel <channel>","子渠道","open").action((e=>{const t=Date.now();try{const o=d();o.api_key=e.apiKey,e.env&&(o.ecs_env=e.env),e.subChannel&&(o.sub_channel=e.subChannel),h(o);const s=n.parent?.opts()||{};"text"===s.format?_(`登录成功!配置已保存到 ${m()}`):$({message:"登录成功",configPath:m()},"auth login",t,{quiet:s.quiet})}catch(n){g(n,"auth login",t)}})),n.command("whoami").description("查看当前鉴权状态").action((()=>{const e=Date.now();try{const t=d(),o=n.parent?.opts()||{},s={logged_in:!!t.api_key,api_key:t.api_key?t.api_key.substring(0,10)+"***":void 0,ecs_env:t.ecs_env||process.env.ECS_ENV||"prod",sub_channel:t.sub_channel||process.env.SUB_CHANNEL||"open",config_path:m()};"text"===o.format?_(s.logged_in?`已登录\nAPI Key: ${s.api_key}\n环境: ${s.ecs_env}\n渠道: ${s.sub_channel}`:"未登录。请使用 auth login --api-key <API_KEY> 登录"):$(s,"auth whoami",e,{quiet:o.quiet})}catch(n){g(n,"auth whoami",e)}})),n.command("logout").description("退出登录:清除本地配置").action((()=>{const e=Date.now();try{!function(){try{const n=m();t.existsSync(n)&&t.unlinkSync(n)}catch{}}();const o=n.parent?.opts()||{};"text"===o.format?_("已退出登录,本地配置已清除"):$({message:"已退出登录"},"auth logout",e,{quiet:o.quiet})}catch(n){g(n,"auth logout",e)}})),n}const M={test:"https://qaopen.eyun.360.cn/intf.php",hgtest:"https://hg-openapi.eyun.360.cn/intf.php",prod:"https://openapi.eyun.360.cn/intf.php"};function z(n){const e=n||process.env.ECS_ENV||"prod";return{request_url:M[e]||M.prod,client_id:"e4757e933b6486c08ed206ecb6d5d9e684fcb4e2",client_secret:"test"===e?"b11b8fff1c75a5d227c8cc93aaeb0bb70c8eee47":"885fd3231f1c1e37c9f462261a09b8c38cde0c2b"}}function S(n,e="e7b24b112a44fdd9ee93bdf998c6ca0e"){const t=Object.keys(n).sort().map((e=>{const t=function(n){return encodeURIComponent(n).replace(/%20/g,"+").replace(/[!'()*~]/g,(n=>`%${n.charCodeAt(0).toString(16).toUpperCase()}`))}(n[e]);return`${e}=${t}`}));let o=t.join("&");return o+=e,s=o,r.createHash("md5").update(s,"utf8").digest("hex");var s}n.config();const D="production"===process.env.NODE_ENV,W="true"===process.env.LOG_TO_FILE,A=process.env.LOG_FILE_PATH||"/data/logs/ecs-mcp/app.log",Y=(process.env.LOG_TIME_FORMAT||"epoch").toLowerCase();function U(){const n=new Date,e=new Intl.DateTimeFormat("en-CA",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}).formatToParts(n).reduce(((n,e)=>("literal"!==e.type&&(n[e.type]=e.value),n)),{}),t=String(n.getMilliseconds()).padStart(3,"0");return`,"time":"${`${e.year}-${e.month}-${e.day}T${e.hour}:${e.minute}:${e.second}.${t}+08:00`}"`}let N;if(W)N=a.destination({dest:A,minLength:512,sync:!1});else{const n=process.env.LOG_STDERR,e="string"==typeof n&&n.length>0,t=Array.isArray(process.argv)?process.argv.slice(2):[],o=t.includes("--stdio")||t.includes("--all"),s=e?"true"===n.toLowerCase():o;N=a.destination({fd:s?2:1,minLength:256,sync:!0})}const x=a({level:process.env.LOG_LEVEL||(D?"info":"debug"),base:{service:process.env.APP_NAME||"ecs-mcp",version:process.env.APP_VERSION||"0.0.0",log_type:"app"},formatters:{level:(n,e)=>({level:n,level_number:e})},redact:{paths:["req.headers.authorization","authInfo.token","authInfo.access_token","req.body.params.arguments.access_token","req.body.params.arguments.token"],censor:"***"},timestamp:"iso"===Y?a.stdTimeFunctions.isoTime:"epoch"===Y?a.stdTimeFunctions.epochTime:"beijing"===Y||"cst"===Y||"asia/shanghai"===Y?U:a.stdTimeFunctions.isoTime},N),T=n=>{n&&x.error({err:n},"fatal error");try{N?.flushSync?.()}catch{}try{N?.end?.()}catch{}process.exit(1)};async function q(n,e){const t=function(n){let e="",t="",o="",s="";n&&(n.apiKey&&(e=n.apiKey),n.subChannel&&(t=n.subChannel),n.q&&(o=n.q),n.t&&(s=n.t)),e||(e=process.env.API_KEY||""),t||(t=process.env.SUB_CHANNEL||"open");const{client_id:r,client_secret:a}=z(n?.ecsEnv);return{apiKey:e,clientId:r,clientSecret:a,subChannel:t,q:o,t:s}}(e);if(!t.apiKey&&!t.q&&!t.t)throw new Error("未配置YUNPAN_API_KEY环境变量");try{const{request_url:o}=z(e?.ecsEnv),s=t.subChannel,r=new URL(o);let a={};const i=n.extraParams;i&&"file-upload-stdio"===i.toolName?(a.method=i.method,a.client_id=i.clientId,a.client_secret=i.clientSecret,a.qid=i.qid,a.grant_type=i.grantType,a.sub_channel=s):(a.method="Oauth.getAccessTokenByApiKey",a.client_id=t.clientId,a.client_secret=t.clientSecret,a.grant_type="authorization_code",a.sub_channel=s,t.apiKey&&(a.api_key=t.apiKey)),Object.entries(a).forEach((([n,e])=>{r.searchParams.append(n,e)}));const c={Accept:"application/json"};t.apiKey&&(c.api_key=t.apiKey),t.q&&(c.q=t.q),t.t&&(c.t=t.t);const l=await fetch(r.toString(),{method:"GET",headers:c});if(!l.ok)throw new Error(`鉴权请求失败,状态码: ${l.status}`);const u=await l.json();if(0!==u.errno)throw new Error(`鉴权请求返回错误: ${u.errmsg}`);const{access_token:m,qid:d,token:h}=u.data;return{access_token:m,qid:d,token:h,sub_channel:s}}catch(n){throw x.error({err:n},"获取鉴权信息失败"),n}}async function F(n={},e){x.debug({transportAuthInfo:e?{hasApiKey:!!e.apiKey,apiKey:e.apiKey||"",subChannel:e.subChannel,ecsEnv:e.ecsEnv,q:e.q||"",t:e.t||""}:void 0},"getAuthInfo");const t=await q(n,e);if(n.extraParams&&"file-upload-stdio"===n.extraParams.toolName)return t.qid=n.extraParams.qid,t;const o=S(function(n,e,t={}){const o={};if(n.access_token&&(o.access_token=String(n.access_token)),e&&(o.method=String(e)),n.qid&&(o.qid=String(n.qid)),t)for(const[n,e]of Object.entries(t))null!=e&&(o[String(n)]=String(e));return{...o}}({access_token:t.access_token,qid:t.qid},n.method||"",n.extraParams));return t.sign=o,t}["SIGINT","SIGTERM","beforeExit","uncaughtException"].forEach((n=>{process.on(n,T)})),process.on("unhandledRejection",(n=>T(n))),n.config();class K{apiKey;ecsEnv;subChannel;timeout;retries;retryDelay;constructor(n){this.apiKey=n.apiKey,this.ecsEnv=n.ecsEnv||"prod",this.subChannel=n.subChannel||"open",this.timeout=n.timeout||3e4,this.retries=n.retries||0,this.retryDelay=n.retryDelay||1e3}getTransportAuthInfo(){return{apiKey:this.apiKey,ecsEnv:this.ecsEnv,subChannel:this.subChannel}}async getAuth(n={}){const e=this.getTransportAuthInfo(),t=await F(n,e);return t.request_url=z(this.ecsEnv).request_url,t}isRetryable(n){return"AbortError"===n.name||("ECONNRESET"===n.code||"ETIMEDOUT"===n.code||"ECONNREFUSED"===n.code||!!n.message?.includes("状态码: 5"))}async fetchWithRetry(n,e=0){const{url:t,init:o}=n(),s=new AbortController,r=setTimeout((()=>s.abort()),this.timeout),a={...o,signal:s.signal};try{const n=await fetch(t,a);if(clearTimeout(r),!n.ok)throw new Error(`API 请求失败,状态码: ${n.status}`);const e=await n.text();try{return JSON.parse(e)}catch{throw new Error(`无法解析API响应: ${e.substring(0,100)}...`)}}catch(t){if(clearTimeout(r),e<this.retries&&this.isRetryable(t)){const o=this.retryDelay*Math.pow(2,e);return x.debug({attempt:e+1,delay:o,error:t.message},"请求失败,准备重试"),await new Promise((n=>setTimeout(n,o))),this.fetchWithRetry(n,e+1)}if("AbortError"===t.name)throw new Error(`请求超时 (${this.timeout}ms)`);throw t}}async apiGet(n,e){return this.fetchWithRetry((()=>{const t=new URL(n.request_url||"");return Object.entries(e).forEach((([n,e])=>{t.searchParams.append(n,String(e))})),{url:t.toString(),init:{method:"GET",headers:{"Access-Token":n.access_token||""}}}}))}async apiPost(n,e,t={}){return this.fetchWithRetry((()=>{const o=new URL(n.request_url||"");Object.entries(e).forEach((([n,e])=>{o.searchParams.append(n,String(e))}));const s=new URLSearchParams;return Object.entries(t).forEach((([n,e])=>{s.append(n,String(e))})),{url:o.toString(),init:{method:"POST",headers:{"Access-Token":n.access_token||"","Content-Type":"application/x-www-form-urlencoded"},body:s}}}))}buildBaseParams(n,e){return{method:e,access_token:n.access_token||"",qid:n.qid||"",sign:n.sign||"",sub_channel:n.sub_channel}}get logger(){return x}}class j extends K{async info(){const n=await this.getAuth({}),e=this.buildBaseParams(n,"User.getUserDetail");return e.sign="",await this.apiGet(n,e)}}class B extends K{async list(n={}){const{path:e="/",page:t=0,page_size:o=50}=n;let s=e||"/";"/"!==s&&(s.startsWith("/")||(s="/"+s),s.endsWith("/")||(s+="/"));const r={path:s,page:t,page_size:o},a=await this.getAuth({method:"File.getList",extraParams:r}),i={...this.buildBaseParams(a,"File.getList")};for(const[n,e]of Object.entries(r))"access_token"!==n&&(i[n]=String(e));return await this.apiGet(a,i)}async mkdir(n){const e={fname:n},t=await this.getAuth({method:"File.mkdir",extraParams:e}),o=this.buildBaseParams(t,"File.mkdir");return await this.apiPost(t,{},{...o,fname:n})}}const V="未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量";async function J(){if(process.stdin.isTTY)throw new Error('--stdin 模式需要管道输入,例如: echo "content" | 360disk file save --stdin');const n=[];for await(const e of process.stdin)n.push(Buffer.from(e));return Buffer.concat(n).toString("utf-8")}class G extends K{async move(n){const{src_name:e,new_name:t}=n,o={src_name:e,new_name:t},s=await this.getAuth({method:"File.move",extraParams:o}),r=this.buildBaseParams(s,"File.move");return await this.apiPost(s,{},{...r,src_name:e,new_name:t})}async rename(n){const{src_name:e,new_name:t}=n,o={src_name:e,new_name:t},s=await this.getAuth({method:"File.rename",extraParams:o}),r=this.buildBaseParams(s,"File.rename");return await this.apiPost(s,{},{...r,src_name:e,new_name:t})}async delete(n){const e=await this.getAuth({method:"File.delete"}),t=this.buildBaseParams(e,"File.delete");return await this.apiPost(e,{},{...t,fname:n})}}const H=["http:","https:"],Q=/[\\@]/,X=[/^127\./,/^10\./,/^11\./,/^172\.(1[6-9]|2\d|3[01])\./,/^192\.168\./,/^169\.254\./,/^224\./,/^0\./,/^255\./,/^::1$/,/^fe80::/i,/^fc00::/i,/^fd00::/i,/^localhost$/i,/\.local$/i,/\.lan$/i,/\.internal$/i,/\.intranet$/i,/\.corp$/i,/\.home$/i],Z=[22,23,25,53,135,139,445,1433,1521,3306,3389,5432,6379,9200,11211,27017];async function nn(n,e=5e3){const t=function(n){if(!n||"string"!=typeof n)return{isValid:!1,error:"URL不能为空"};try{const t=n.trim();if(Q.test(t))return{isValid:!1,error:"URL包含不安全的字符"};const o=new URL(t);if(!H.includes(o.protocol))return{isValid:!1,error:"不支持的协议,只支持 http/https"};const s=o.hostname.toLowerCase();if(!s)return{isValid:!1,error:"主机名不能为空"};for(const n of X)if(n.test(s))return{isValid:!1,error:"禁止访问受限地址"};if(en(s)&&!tn(s))return{isValid:!1,error:"禁止访问受限IP地址"};if(o.port){const n=parseInt(o.port,10);if(Z.includes(n))return{isValid:!1,error:"禁止访问受限端口"}}return e=o.pathname,[/\.\./,/%2e%2e/i,/%252e%252e/i,/\/etc\//i,/\/proc\//i,/\/sys\//i,/\/dev\//i].some((n=>n.test(e)))?{isValid:!1,error:"路径包含不安全的内容"}:{isValid:!0,normalizedUrl:o.toString(),originalHostname:s}}catch(n){return{isValid:!1,error:"URL格式无效"}}var e}(n);if(!t.isValid)return t;try{const o=new URL(n).hostname.toLowerCase();if(en(o))return{...t,resolvedIP:o,originalHostname:o,needsHostHeader:!1};let s=[];try{const n=i.lookup(o,{all:!0}),t=new Promise(((n,t)=>{setTimeout((()=>t(new Error("DNS解析超时"))),e)}));s=(await Promise.race([n,t])).map((n=>n.address))}catch(n){return{isValid:!1,error:"域名解析失败"}}for(const n of s){for(const e of X)if(e.test(n))return{isValid:!1,error:"域名解析到受限IP地址"};if(!tn(n))return{isValid:!1,error:"域名解析到受限IP地址"}}return{isValid:!0,normalizedUrl:t.normalizedUrl,resolvedIP:s[0],resolvedIPs:s,originalHostname:o,needsHostHeader:!1}}catch(n){return{isValid:!1,error:"URL验证过程中发生错误"}}}function en(n){if(/^(\d{1,3}\.){3}\d{1,3}$/.test(n)){return n.split(".").every((n=>{const e=parseInt(n,10);return e>=0&&e<=255}))}return/^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}$/i.test(n)}function tn(n){for(const e of X)if(e.test(n))return!1;return!0}class on extends K{normalizeUploadResult(n){return{id:n?.id,name:n?.name||n?.uploadRes?.name,path:n?.uploadRes?.name,fid:n?.fid,size:n?.size||n?.uploadRes?.count_size,uploadRes:n?.uploadRes?{nid:n.uploadRes.nid,qid:n.uploadRes.qid,name:n.uploadRes.name,count_size:n.uploadRes.count_size,file_hash:n.uploadRes.file_hash,create_time:n.uploadRes.create_time,modify_time:n.uploadRes.modify_time}:void 0}}async search(n){const{key:e="",file_category:t=-1,page:o=1,page_size:s=20}=n;if(!e&&-1===t)throw new Error("必须提供搜索关键词(key)或指定文件类型(file_category)");const r={file_category:t,key:e,page:o,page_size:s},a=await this.getAuth({method:"File.searchList",extraParams:r}),i=this.buildBaseParams(a,"File.searchList"),c={};for(const[n,e]of Object.entries(r))"access_token"!==n&&(c[n]=String(e));return await this.apiPost(a,i,c)}async share(n){const e=await this.getAuth({method:"Share.preShare"}),t=this.buildBaseParams(e,"Share.preShare");return await this.apiPost(e,{},{...t,paths:n})}async getDownloadUrl(n){const{nid:e,fpath:t}=n;if(void 0===e&&!t)throw new Error("必须提供nid或fpath中的一个参数");const o={};void 0!==e?o.nid=e:t&&(o.fpath=t);const s=await this.getAuth({method:"MCP.getDownLoadUrl",extraParams:o}),r={...this.buildBaseParams(s,"MCP.getDownLoadUrl")};return void 0!==e?r.nid=String(e):t&&(r.fpath=t),await this.apiPost(s,{},r)}async save(n){const{url:e,content:t,upload_path:o,file_name:s}=n;if(!e&&!t||e&&t)throw new Error("url 与 content 互斥,必须且只能传一个");if(e){const n=await nn(e,5e3);if(!n.isValid)throw new Error(`URL安全验证失败: ${n.error}`)}const r=await this.getAuth({method:"MCP.saveFile"}),a=this.buildBaseParams(r,"MCP.saveFile"),i={};o&&(i.upload_path=o),e?i.url=e:t&&(i.content=t),s&&(i.file_name=s);const c=await this.apiPost(r,a,i);if(c&&0===c.errno){const n=c.data?.task_id;if(!n)throw new Error("保存文件失败: 未获取到任务ID");return await this.pollTaskStatus(r,n)}throw new Error(c?.errmsg||"API请求失败")}async queryTaskStatus(n,e){const t={method:"MCP.query",qid:n.qid||"",access_token:n.access_token||"",sign:n.sign||""};return await this.apiPost(n,t,{task_id:e})}async pollTaskStatus(n,e,t=1e3,o=120){let s,r=0;for(;r<o;){if(r++,s=await this.queryTaskStatus(n,e),0!==s.errno)throw new Error(s.errmsg||"查询任务状态失败");const o=s.data?.status;if(2===o)return s;if(3===o)throw new Error(s.data?.error||"文件保存失败");await new Promise((n=>setTimeout(n,t)))}return s}async upload(n){const{filePaths:e,uploadPath:o="/"}=n;if(!e||0===e.length)throw new Error("filePaths 为必填参数且不能为空");for(const n of e)if(!t.existsSync(n))throw new Error(`文件不存在: ${n}`);const s=await this.getAuth({});let r;try{const n=await import("@aicloud360/sec-sdk-node");r=n.UploadNode}catch{throw new Error("请先安装 @aicloud360/sec-sdk-node: npm install @aicloud360/sec-sdk-node")}const a={qid:s.qid||"0",token:s.token||"",access_token:s.access_token||"",env:this.ecsEnv,path:o};return new Promise(((n,t)=>{const s=Date.now(),i=[],c=[],l=[];new r(a,{success:n=>{i.push(this.normalizeUploadResult(n))},progress:()=>{},error:(n,e)=>{const t=e?.errmsg||e?.message||JSON.stringify(e);c.push({fileName:n?.name||"未知文件",error:t})},duplicateList:n=>{l.push(...n.map((n=>({name:n.name,nid:n.nid,size:n.count_size,path:n.path||o}))))},complete:()=>{c.length>0&&0===i.length?t(new Error(`所有文件上传失败: ${c.map((n=>`${n.fileName}: ${n.error}`)).join("; ")}`)):n({uploadResults:i,uploadErrors:c,duplicateFiles:l,totalTime:((Date.now()-s)/1e3).toFixed(2),fileCount:i.length,totalFileCount:e.length})}}).addWaitFile(e)}))}async download(n){const{nid:e,auto:t=!0,downloadDir:r}=n,a=r||o.join(s(),".mcp-downloads"),i=await this.getAuth({method:"Sync.getVerifiedDownLoadUrl",extraParams:{nid:e}}),c={...this.buildBaseParams(i,"Sync.getVerifiedDownLoadUrl"),nid:String(e)},u=await this.apiGet(i,c);if(!u||0!==u.errno)throw new Error(u?.errmsg||"API请求失败");const m=u.data?.downloadUrl||"";if(!m)throw new Error("未能获取到文件下载链接");const{filename:d,sizeMB:h}=this.extractFileInfoFromUrl(m),p=h>0?`${h.toFixed(2)} MB`:"未知大小";if(!t)return{downloadUrl:m,filename:d,fileSize:p,downloadPath:""};await l.mkdir(a,{recursive:!0});const f=o.join(a,d);return h>10?(this.spawnBackgroundDownload(m,f),{downloadUrl:m,filename:d,fileSize:p,downloadPath:f,background:!0}):(await this.downloadWithCurl(m,f),{downloadUrl:m,filename:d,fileSize:p,downloadPath:f})}extractFileInfoFromUrl(n){try{const e=new URL(n);let t="downloaded_file";const s=e.searchParams.get("fname");if(s?t=o.basename(decodeURIComponent(s)):e.pathname&&e.pathname.length>1&&(t=o.basename(e.pathname)),t=t.replace(/[\x00/]/g,"_"),t&&"."!==t||(t="downloaded_file"),t.length>200){const n=o.extname(t);t=o.basename(t,n).substring(0,200-n.length)+n}const r=e.searchParams.get("fsize");return{filename:t,sizeMB:r?parseInt(r,10)/1048576:0}}catch{return{filename:"downloaded_file",sizeMB:0}}}downloadWithCurl(n,e,t=3e5){return new Promise(((o,s)=>{const r=c("curl",["-L","-s","-A","yunpan_mcp_server",n,"-o",e]),a=setTimeout((()=>{r.kill(),s(new Error("下载超时"))}),t);r.on("close",(n=>{clearTimeout(a),0===n?o():s(new Error(`下载失败,curl 退出码: ${n}`))})),r.on("error",(n=>{clearTimeout(a),s(n)}))}))}spawnBackgroundDownload(n,e){c("curl",["-L","-s","-A","yunpan_mcp_server",n,"-o",e],{detached:!0,stdio:"ignore"}).unref()}}const sn="未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量";function rn(n,e,t,o,s){"text"===o.format&&s?_(s(n)):$(n,e,t,{quiet:o.quiet})}function an(){const n=new e("file").description("文件操作");return n.command("mv").description("移动文件或文件夹").argument("<src>","源路径,多个用 | 分隔").argument("<dest>","目标文件夹路径").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=p(s);if(!n.apiKey)return void g(new w(sn,f.AUTH_ERROR),"file mv",o);const r=new G(n);rn(await r.move({src_name:e,new_name:t}),"file mv",o,s,(n=>O(n,`已移动: ${e} -> ${t}`)))}catch(n){g(n,"file mv",o)}})),n.command("rename").description("重命名文件或文件夹").argument("<path>","原文件完整路径").argument("<new_name>","新名称").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=p(s);if(!n.apiKey)return void g(new w(sn,f.AUTH_ERROR),"file rename",o);const r=new G(n);rn(await r.rename({src_name:e,new_name:t}),"file rename",o,s,(n=>O(n,`已重命名: ${e} -> ${t}`)))}catch(n){g(n,"file rename",o)}})),n.command("rm").description("删除文件或文件夹").argument("<path>","文件路径,多个用 | 分隔").option("--batch","批量模式:从 stdin 读取路径列表(每行一个)").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=p(s);if(!n.apiKey)return void g(new w(sn,f.AUTH_ERROR),"file rm",o);const r=new G(n);if(t.batch){const n=await async function(){return(await J()).split("\n").map((n=>n.trim())).filter(Boolean)}(),t=[e,...n].filter(Boolean),{executeBatch:a,formatBatchResult:i}=await Promise.resolve().then((function(){return L}));rn(await a(t,(async n=>await r.delete(n))),"file rm --batch",o,s,(n=>i(n)))}else{rn(await r.delete(e),"file rm",o,s,(n=>O(n,`已删除: ${e}`)))}}catch(n){g(n,"file rm",o)}})),n.command("search").description("按关键词搜索文件").argument("<keyword>","搜索关键词").option("--type <type>","文件类型: -1(全部) 0(其他) 1(图片) 2(文档) 3(音乐) 4(视频)","-1").option("--page <n>","页码","1").option("--size <n>","每页数量","20").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=p(s);if(!n.apiKey)return void g(new w(sn,f.AUTH_ERROR),"file search",o);const r=new on(n);rn(await r.search({key:e,file_category:parseInt(t.type),page:parseInt(t.page),page_size:parseInt(t.size)}),"file search",o,s,R)}catch(n){g(n,"file search",o)}})),n.command("share").description("生成文件分享链接").argument("<paths>","文件路径,多个用 | 分隔").action((async e=>{const t=Date.now(),o=n.parent?.opts()||{};try{const n=p(o);if(!n.apiKey)return void g(new w(sn,f.AUTH_ERROR),"file share",t);const s=new on(n);rn(await s.share(e),"file share",t,o,(n=>O(n,"分享链接已生成")))}catch(n){g(n,"file share",t)}})),n.command("url").description("获取文件下载链接").argument("<path>","文件路径").option("--nid <nid>","文件 NID(与路径二选一)").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=p(s);if(!n.apiKey)return void g(new w(sn,f.AUTH_ERROR),"file url",o);const r=new on(n);rn(await r.getDownloadUrl({nid:t.nid,fpath:t.nid?void 0:e}),"file url",o,s,(n=>O(n,"下载链接已获取")))}catch(n){g(n,"file url",o)}})),n.command("save").description("通过 URL 或文本内容保存文件到云盘").option("--url <url>","文件下载地址(与 --content/--stdin 互斥)").option("--content <text>","文件内容(与 --url/--stdin 互斥)").option("--stdin","从标准输入读取内容(与 --url/--content 互斥)").option("--dest <path>","云盘存储路径,必须以 / 开头和结尾").option("--filename <name>","保存的文件名").action((async e=>{const t=Date.now(),o=n.parent?.opts()||{};try{const n=p(o);if(!n.apiKey)return void g(new w(sn,f.AUTH_ERROR),"file save",t);if(1!==[e.url,e.content,e.stdin].filter(Boolean).length)return void g(new w("--url、--content、--stdin 三者互斥,必须且只能传一个",f.INVALID_ARGS),"file save",t);let s=e.content;if(e.stdin&&(s=await J(),!s.trim()))return void g(new w("stdin 输入内容为空",f.INVALID_ARGS),"file save",t);const r=new on(n);rn(await r.save({url:e.url,content:s,upload_path:e.dest,file_name:e.filename}),"file save",t,o,(n=>O(n,"文件保存成功")))}catch(n){g(n,"file save",t)}})),n.command("upload").description("上传本地文件到云盘").argument("<files>","本地文件路径,多个用逗号分隔").option("--dest <path>","云盘目标路径","/").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=p(s);if(!n.apiKey)return void g(new w(sn,f.AUTH_ERROR),"file upload",o);const r=e.split(",").map((n=>n.trim())),a=new on(n);rn(await a.upload({filePaths:r,uploadPath:t.dest}),"file upload",o,s,(n=>O(n,"上传完成")))}catch(n){g(n,"file upload",o)}})),n.command("download").description("下载云盘文件到本地").argument("<nid>","文件 NID(可通过 file search / dir ls 获取)").option("--dir <path>","本地下载目录").option("--no-auto","仅获取下载链接,不自动下载").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=p(s);if(!n.apiKey)return void g(new w(sn,f.AUTH_ERROR),"file download",o);const r=new on(n);rn(await r.download({nid:e,auto:t.auto,downloadDir:t.dir}),"file download",o,s,(n=>O(n,"下载完成")))}catch(n){g(n,"file download",o)}})),n}function cn(){return o.join(s(),".360disk")}function ln(){const n=process.env.SHELL||"";return n.includes("zsh")?"zsh":n.includes("bash")?"bash":"unknown"}function un(n){const e=s();return o.join(e,"zsh"===n?".zshrc":".bashrc")}n.config(),async function(){const n=function(){try{const n=[o.resolve(process.cwd(),"package.json"),o.resolve(new URL(".",import.meta.url).pathname,"../../package.json"),o.resolve(new URL(".",import.meta.url).pathname,"../../../package.json")];for(const e of n)if(t.existsSync(e)){const n=JSON.parse(t.readFileSync(e,"utf8"));return{name:n.cli?.name||"360disk",version:n.version||"0.0.0",description:n.cli?.description||"360 AI 云盘 CLI"}}}catch{}return{name:"360disk",version:"0.0.0",description:"360 AI 云盘 CLI"}}(),s=new e;s.name(n.name).description(n.description).version(n.version).enablePositionalOptions().passThroughOptions().option("--api-key <key>","API 密钥").option("--env <env>","环境 (prod/test)","prod").option("--sub-channel <channel>","子渠道","open").option("--format <type>","输出格式 (json|text)","json").option("--quiet","仅输出结果数据").option("--timeout <ms>","请求超时时间(毫秒)","30000").option("--retries <n>","失败重试次数","0"),s.addCommand(I()),s.addCommand(function(){const n=new e("user").description("用户信息");return n.command("info").description("获取用户详细信息").action((async()=>{const e=Date.now(),t=n.parent?.opts()||{};try{const n=p(t);if(!n.apiKey)return void g(new w("未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量",f.AUTH_ERROR),"user info",e);const o=new j(n),s=await o.info();"text"===t.format?_(C(s)):$(s,"user info",e,{quiet:t.quiet})}catch(n){g(n,"user info",e)}})),n}()),s.addCommand(function(){const n=new e("dir").description("目录操作");return n.command("ls").description("列出云盘指定目录下的文件和文件夹").argument("[path]","目录路径","/").option("--page <n>","页码","0").option("--size <n>","每页数量","50").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=p(s);if(!n.apiKey)return void g(new w(V,f.AUTH_ERROR),"dir ls",o);const r=new B(n),a=await r.list({path:e,page:parseInt(t.page),page_size:parseInt(t.size)});"text"===s.format?_(k(a)):$(a,"dir ls",o,{quiet:s.quiet})}catch(n){g(n,"dir ls",o)}})),n.command("mkdir").description("创建新文件夹").argument("<path>","文件夹路径").action((async e=>{const t=Date.now(),o=n.parent?.opts()||{};try{const n=p(o);if(!n.apiKey)return void g(new w(V,f.AUTH_ERROR),"dir mkdir",t);const s=new B(n),r=await s.mkdir(e);"text"===o.format?_(O(r,`已创建目录: ${e}`)):$(r,"dir mkdir",t,{quiet:o.quiet})}catch(n){g(n,"dir mkdir",t)}})),n}()),s.addCommand(an()),s.addCommand(function(){const n=new e("completion").description("Shell 自动补全");return n.command("install").description("安装补全脚本到当前 shell").option("--bash","安装 bash 补全").option("--zsh","安装 zsh 补全").action((n=>{try{const e=n.bash?"bash":n.zsh?"zsh":ln();if("unknown"===e)return void g("无法检测 shell 类型,请使用 --bash 或 --zsh 指定","completion install");const s="zsh"===e?"#compdef 360disk\n# 360disk zsh completion\n# 安装: source ~/.360disk/completion.zsh\n# 或添加到 ~/.zshrc: source ~/.360disk/completion.zsh\n\n_360disk() {\n local -a top_commands\n top_commands=(\n 'auth:鉴权管理'\n 'user:用户信息'\n 'dir:目录操作'\n 'file:文件操作'\n 'completion:Shell 自动补全'\n 'help:显示帮助'\n )\n\n local -a auth_commands\n auth_commands=(\n 'login:登录(保存 API Key)'\n 'whoami:查看鉴权状态'\n 'logout:退出登录'\n )\n\n local -a user_commands\n user_commands=(\n 'info:获取用户详细信息'\n )\n\n local -a dir_commands\n dir_commands=(\n 'ls:列出目录内容'\n 'mkdir:创建文件夹'\n )\n\n local -a file_commands\n file_commands=(\n 'mv:移动文件或文件夹'\n 'rename:重命名文件或文件夹'\n 'rm:删除文件或文件夹'\n 'search:搜索文件'\n 'share:生成分享链接'\n 'url:获取下载链接'\n 'save:保存文件到云盘'\n 'upload:上传文件'\n 'download:下载文件'\n )\n\n local -a completion_commands\n completion_commands=(\n 'install:安装补全脚本'\n 'uninstall:卸载补全脚本'\n 'script:输出补全脚本'\n )\n\n _arguments -C \\\n '--api-key[API 密钥]:key:' \\\n '--env[环境]:env:(prod test)' \\\n '--sub-channel[子渠道]:channel:' \\\n '--format[输出格式]:format:(json text)' \\\n '--quiet[仅输出结果]' \\\n '--timeout[超时时间(ms)]:ms:' \\\n '--retries[重试次数]:n:' \\\n '--help[显示帮助]' \\\n '--version[显示版本]' \\\n '1:command:->command' \\\n '2:subcommand:->subcommand' \\\n '*::arg:->args'\n\n case \"$state\" in\n command)\n _describe 'command' top_commands\n ;;\n subcommand)\n case \"$words[2]\" in\n auth) _describe 'auth command' auth_commands ;;\n user) _describe 'user command' user_commands ;;\n dir) _describe 'dir command' dir_commands ;;\n file) _describe 'file command' file_commands ;;\n completion) _describe 'completion command' completion_commands ;;\n esac\n ;;\n args)\n case \"$words[2]\" in\n file)\n case \"$words[3]\" in\n search) _arguments '--type[文件类型]:type:(-1 0 1 2 3 4)' '--page[页码]:n:' '--size[每页数量]:n:' ;;\n save) _arguments '--url[文件URL]:url:' '--content[文件内容]:text:' '--stdin[从stdin读取]' '--dest[目标路径]:path:' '--filename[文件名]:name:' ;;\n upload) _arguments '--dest[云盘路径]:path:' ;;\n download) _arguments '--dir[下载目录]:path:_files -/' '--no-auto[不自动下载]' ;;\n url) _arguments '--nid[文件NID]:nid:' ;;\n rm) _arguments '--batch[批量模式]' ;;\n esac\n ;;\n dir)\n case \"$words[3]\" in\n ls) _arguments '--page[页码]:n:' '--size[每页数量]:n:' ;;\n esac\n ;;\n auth)\n case \"$words[3]\" in\n login) _arguments '--api-key[API密钥]:key:' '--env[环境]:env:(prod test)' '--sub-channel[渠道]:channel:' ;;\n esac\n ;;\n esac\n ;;\n esac\n}\n\n_360disk \"$@\"\n":'# 360disk bash completion\n# 安装: source ~/.360disk/completion.bash\n# 或添加到 ~/.bashrc: source ~/.360disk/completion.bash\n\n_360disk_completions() {\n local cur prev commands\n COMPREPLY=()\n cur="${COMP_WORDS[COMP_CWORD]}"\n prev="${COMP_WORDS[COMP_CWORD-1]}"\n\n # 顶级命令\n local top_commands="auth user dir file completion help"\n\n # 子命令映射\n local auth_commands="login whoami logout"\n local user_commands="info"\n local dir_commands="ls mkdir"\n local file_commands="mv rename rm search share url save upload download"\n local completion_commands="install uninstall script"\n\n # 全局选项\n local global_options="--api-key --env --sub-channel --format --quiet --timeout --retries --help --version"\n\n case "${COMP_WORDS[1]}" in\n auth)\n case "$prev" in\n login) COMPREPLY=( $(compgen -W "--api-key --env --sub-channel --help" -- "$cur") ) ;;\n whoami|logout) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$auth_commands" -- "$cur") ) ;;\n esac\n ;;\n user)\n case "$prev" in\n info) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$user_commands" -- "$cur") ) ;;\n esac\n ;;\n dir)\n case "$prev" in\n ls) COMPREPLY=( $(compgen -W "--page --size --help" -- "$cur") ) ;;\n mkdir) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$dir_commands" -- "$cur") ) ;;\n esac\n ;;\n file)\n case "$prev" in\n search) COMPREPLY=( $(compgen -W "--type --page --size --help" -- "$cur") ) ;;\n url) COMPREPLY=( $(compgen -W "--nid --help" -- "$cur") ) ;;\n save) COMPREPLY=( $(compgen -W "--url --content --stdin --dest --filename --help" -- "$cur") ) ;;\n upload) COMPREPLY=( $(compgen -W "--dest --help" -- "$cur") ) ;;\n download) COMPREPLY=( $(compgen -W "--dir --no-auto --help" -- "$cur") ) ;;\n rm) COMPREPLY=( $(compgen -W "--batch --help" -- "$cur") ) ;;\n mv|rename|share) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$file_commands" -- "$cur") ) ;;\n esac\n ;;\n completion)\n case "$prev" in\n install|uninstall) COMPREPLY=( $(compgen -W "--bash --zsh --help" -- "$cur") ) ;;\n script) COMPREPLY=( $(compgen -W "--bash --zsh --help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$completion_commands" -- "$cur") ) ;;\n esac\n ;;\n *)\n if [[ "$cur" == -* ]]; then\n COMPREPLY=( $(compgen -W "$global_options" -- "$cur") )\n else\n COMPREPLY=( $(compgen -W "$top_commands" -- "$cur") )\n fi\n ;;\n esac\n}\n\ncomplete -F _360disk_completions 360disk\n',r="zsh"===e?"zsh":"bash",a=cn(),i=o.join(a,`completion.${r}`);t.existsSync(a)||t.mkdirSync(a,{recursive:!0,mode:448}),t.writeFileSync(i,s,{mode:420});const c=un(e),l=function(n){return`source ~/.360disk/completion.${"zsh"===n?"zsh":"bash"} # 360disk auto-completion`}(e);let u="";t.existsSync(c)&&(u=t.readFileSync(c,"utf8")),u.includes("360disk auto-completion")?_(`补全脚本已更新: ${i}\n配置已存在于 ${c},无需重复添加`):(t.appendFileSync(c,"\n"+l+"\n"),_(`补全脚本已安装:\n 脚本: ${i}\n 配置: ${c}\n\n请执行 source ${c} 或重新打开终端生效`))}catch(n){g(n,"completion install")}})),n.command("uninstall").description("卸载补全脚本").option("--bash","卸载 bash 补全").option("--zsh","卸载 zsh 补全").action((n=>{try{const e=n.bash?"bash":n.zsh?"zsh":ln();if("unknown"===e)return void g("无法检测 shell 类型,请使用 --bash 或 --zsh 指定","completion uninstall");const s="zsh"===e?"zsh":"bash",r=o.join(cn(),`completion.${s}`);t.existsSync(r)&&t.unlinkSync(r);const a=un(e);if(t.existsSync(a)){const n=t.readFileSync(a,"utf8").split("\n").filter((n=>!n.includes("360disk auto-completion")));t.writeFileSync(a,n.join("\n"))}_(`补全脚本已卸载\n请执行 source ${un(e)} 或重新打开终端生效`)}catch(n){g(n,"completion uninstall")}})),n.command("script").description("输出补全脚本到 stdout(手动安装用)").option("--bash","输出 bash 补全脚本").option("--zsh","输出 zsh 补全脚本").action((n=>{try{const e=n.bash?"bash":n.zsh?"zsh":ln();if("unknown"===e)return void g("无法检测 shell 类型,请使用 --bash 或 --zsh 指定","completion script");process.stdout.write("zsh"===e?"#compdef 360disk\n# 360disk zsh completion\n# 安装: source ~/.360disk/completion.zsh\n# 或添加到 ~/.zshrc: source ~/.360disk/completion.zsh\n\n_360disk() {\n local -a top_commands\n top_commands=(\n 'auth:鉴权管理'\n 'user:用户信息'\n 'dir:目录操作'\n 'file:文件操作'\n 'completion:Shell 自动补全'\n 'help:显示帮助'\n )\n\n local -a auth_commands\n auth_commands=(\n 'login:登录(保存 API Key)'\n 'whoami:查看鉴权状态'\n 'logout:退出登录'\n )\n\n local -a user_commands\n user_commands=(\n 'info:获取用户详细信息'\n )\n\n local -a dir_commands\n dir_commands=(\n 'ls:列出目录内容'\n 'mkdir:创建文件夹'\n )\n\n local -a file_commands\n file_commands=(\n 'mv:移动文件或文件夹'\n 'rename:重命名文件或文件夹'\n 'rm:删除文件或文件夹'\n 'search:搜索文件'\n 'share:生成分享链接'\n 'url:获取下载链接'\n 'save:保存文件到云盘'\n 'upload:上传文件'\n 'download:下载文件'\n )\n\n local -a completion_commands\n completion_commands=(\n 'install:安装补全脚本'\n 'uninstall:卸载补全脚本'\n 'script:输出补全脚本'\n )\n\n _arguments -C \\\n '--api-key[API 密钥]:key:' \\\n '--env[环境]:env:(prod test)' \\\n '--sub-channel[子渠道]:channel:' \\\n '--format[输出格式]:format:(json text)' \\\n '--quiet[仅输出结果]' \\\n '--timeout[超时时间(ms)]:ms:' \\\n '--retries[重试次数]:n:' \\\n '--help[显示帮助]' \\\n '--version[显示版本]' \\\n '1:command:->command' \\\n '2:subcommand:->subcommand' \\\n '*::arg:->args'\n\n case \"$state\" in\n command)\n _describe 'command' top_commands\n ;;\n subcommand)\n case \"$words[2]\" in\n auth) _describe 'auth command' auth_commands ;;\n user) _describe 'user command' user_commands ;;\n dir) _describe 'dir command' dir_commands ;;\n file) _describe 'file command' file_commands ;;\n completion) _describe 'completion command' completion_commands ;;\n esac\n ;;\n args)\n case \"$words[2]\" in\n file)\n case \"$words[3]\" in\n search) _arguments '--type[文件类型]:type:(-1 0 1 2 3 4)' '--page[页码]:n:' '--size[每页数量]:n:' ;;\n save) _arguments '--url[文件URL]:url:' '--content[文件内容]:text:' '--stdin[从stdin读取]' '--dest[目标路径]:path:' '--filename[文件名]:name:' ;;\n upload) _arguments '--dest[云盘路径]:path:' ;;\n download) _arguments '--dir[下载目录]:path:_files -/' '--no-auto[不自动下载]' ;;\n url) _arguments '--nid[文件NID]:nid:' ;;\n rm) _arguments '--batch[批量模式]' ;;\n esac\n ;;\n dir)\n case \"$words[3]\" in\n ls) _arguments '--page[页码]:n:' '--size[每页数量]:n:' ;;\n esac\n ;;\n auth)\n case \"$words[3]\" in\n login) _arguments '--api-key[API密钥]:key:' '--env[环境]:env:(prod test)' '--sub-channel[渠道]:channel:' ;;\n esac\n ;;\n esac\n ;;\n esac\n}\n\n_360disk \"$@\"\n":'# 360disk bash completion\n# 安装: source ~/.360disk/completion.bash\n# 或添加到 ~/.bashrc: source ~/.360disk/completion.bash\n\n_360disk_completions() {\n local cur prev commands\n COMPREPLY=()\n cur="${COMP_WORDS[COMP_CWORD]}"\n prev="${COMP_WORDS[COMP_CWORD-1]}"\n\n # 顶级命令\n local top_commands="auth user dir file completion help"\n\n # 子命令映射\n local auth_commands="login whoami logout"\n local user_commands="info"\n local dir_commands="ls mkdir"\n local file_commands="mv rename rm search share url save upload download"\n local completion_commands="install uninstall script"\n\n # 全局选项\n local global_options="--api-key --env --sub-channel --format --quiet --timeout --retries --help --version"\n\n case "${COMP_WORDS[1]}" in\n auth)\n case "$prev" in\n login) COMPREPLY=( $(compgen -W "--api-key --env --sub-channel --help" -- "$cur") ) ;;\n whoami|logout) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$auth_commands" -- "$cur") ) ;;\n esac\n ;;\n user)\n case "$prev" in\n info) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$user_commands" -- "$cur") ) ;;\n esac\n ;;\n dir)\n case "$prev" in\n ls) COMPREPLY=( $(compgen -W "--page --size --help" -- "$cur") ) ;;\n mkdir) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$dir_commands" -- "$cur") ) ;;\n esac\n ;;\n file)\n case "$prev" in\n search) COMPREPLY=( $(compgen -W "--type --page --size --help" -- "$cur") ) ;;\n url) COMPREPLY=( $(compgen -W "--nid --help" -- "$cur") ) ;;\n save) COMPREPLY=( $(compgen -W "--url --content --stdin --dest --filename --help" -- "$cur") ) ;;\n upload) COMPREPLY=( $(compgen -W "--dest --help" -- "$cur") ) ;;\n download) COMPREPLY=( $(compgen -W "--dir --no-auto --help" -- "$cur") ) ;;\n rm) COMPREPLY=( $(compgen -W "--batch --help" -- "$cur") ) ;;\n mv|rename|share) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$file_commands" -- "$cur") ) ;;\n esac\n ;;\n completion)\n case "$prev" in\n install|uninstall) COMPREPLY=( $(compgen -W "--bash --zsh --help" -- "$cur") ) ;;\n script) COMPREPLY=( $(compgen -W "--bash --zsh --help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$completion_commands" -- "$cur") ) ;;\n esac\n ;;\n *)\n if [[ "$cur" == -* ]]; then\n COMPREPLY=( $(compgen -W "$global_options" -- "$cur") )\n else\n COMPREPLY=( $(compgen -W "$top_commands" -- "$cur") )\n fi\n ;;\n esac\n}\n\ncomplete -F _360disk_completions 360disk\n')}catch(n){g(n,"completion script")}})),n}()),await s.parseAsync()}().then((()=>{process.exit(0)})).catch((n=>{process.stderr.write(JSON.stringify({success:!1,error:n.message||"CLI 启动失败"},null,2)+"\n"),process.exit(1)}));
2
+ import n from"dotenv";import{Command as e}from"commander";import t from"fs";import o from"path";import{fileURLToPath as s}from"url";import{homedir as r}from"os";import a from"crypto";import i from"pino";import{promises as c}from"dns";import{spawn as l}from"child_process";import u from"fs/promises";function m(){const n=function(){try{const n=o.dirname(new URL(import.meta.url).pathname),e=[o.resolve(n,"../../package.json"),o.resolve(n,"../../../package.json")];for(const n of e)if(t.existsSync(n)){const e=JSON.parse(t.readFileSync(n,"utf8"));if(e.cli?.name)return e.cli.name}}catch{}return"360disk"}();return o.join(r(),`.${n}`)}function d(){return o.join(m(),"config.json")}function h(){try{const n=d();if(t.existsSync(n))return JSON.parse(t.readFileSync(n,"utf8"))}catch{}return{}}function p(n){!function(){const n=m();t.existsSync(n)||t.mkdirSync(n,{recursive:!0,mode:448})}();const e=d();t.writeFileSync(e,JSON.stringify(n,null,2),{mode:384})}function f(n){const e=h();return{apiKey:n.apiKey||process.env.API_KEY||e.api_key||"",ecsEnv:n.env||process.env.ECS_ENV||e.ecs_env||"prod",subChannel:n.subChannel||process.env.SUB_CHANNEL||e.sub_channel||"open",timeout:n.timeout?parseInt(n.timeout):void 0,retries:n.retries?parseInt(n.retries):void 0}}var w;!function(n){n[n.SUCCESS=0]="SUCCESS",n[n.GENERAL=1]="GENERAL",n[n.INVALID_ARGS=2]="INVALID_ARGS",n[n.AUTH_ERROR=3]="AUTH_ERROR",n[n.NOT_FOUND=4]="NOT_FOUND",n[n.PERMISSION_DENIED=5]="PERMISSION_DENIED",n[n.NETWORK_ERROR=6]="NETWORK_ERROR",n[n.CONFLICT=7]="CONFLICT",n[n.SERVER_ERROR=8]="SERVER_ERROR",n[n.QUOTA_EXCEEDED=10]="QUOTA_EXCEEDED"}(w||(w={}));class $ extends Error{code;constructor(n,e=w.GENERAL){super(n),this.name="CLIError",this.code=e}}function _(n,e,t,o){if(o?.quiet)return void process.stdout.write(JSON.stringify(n)+"\n");const s={success:!0,result:n,meta:{duration_ms:Date.now()-t,command:e}};process.stdout.write(JSON.stringify(s,null,2)+"\n")}function g(n){process.stdout.write(n+"\n")}function P(n,e,t){const o=n instanceof Error?n.message:n;let s;s=n instanceof $?n.code:function(n){const e=(n?.message||n?.toString()||"").toLowerCase();return e.includes("超时")||e.includes("timeout")||e.includes("aborted")||"AbortError"===n?.name||e.includes("econnreset")||e.includes("etimedout")||e.includes("econnrefused")||e.includes("fetch failed")||e.includes("网络")?w.NETWORK_ERROR:e.includes("api key")||e.includes("api_key")||e.includes("未配置")||e.includes("token")||e.includes("auth")||e.includes("登录")||e.includes("401")?w.AUTH_ERROR:e.includes("参数")||e.includes("argument")||e.includes("required")||e.includes("互斥")||e.includes("invalid")?w.INVALID_ARGS:e.includes("不存在")||e.includes("not found")||e.includes("404")||e.includes("no such")?w.NOT_FOUND:e.includes("权限")||e.includes("permission")||e.includes("forbidden")||e.includes("403")?w.PERMISSION_DENIED:e.includes("已存在")||e.includes("conflict")||e.includes("duplicate")||e.includes("409")?w.CONFLICT:e.includes("状态码: 5")||e.includes("500")||e.includes("502")||e.includes("503")||e.includes("服务端")?w.SERVER_ERROR:e.includes("空间不足")||e.includes("quota")||e.includes("limit")||e.includes("429")||e.includes("频率")?w.QUOTA_EXCEEDED:w.GENERAL}(n);const r={success:!1,error:o,code:s,meta:{duration_ms:t?Date.now()-t:0,command:e}};process.stderr.write(JSON.stringify(r,null,2)+"\n"),process.exit(s||1)}function y(n,e=2){if(!n||n<=0)return"-";const t=Math.floor(Math.log(n)/Math.log(1024));return parseFloat((n/Math.pow(1024,t)).toFixed(e))+" "+["B","KB","MB","GB","TB"][t]}function v(n){if(!n)return"-";const e="string"==typeof n?parseInt(n):n;if(isNaN(e)||e<=0)return"-";const t=new Date(e<1e12?1e3*e:e),o=n=>String(n).padStart(2,"0");return`${t.getFullYear()}-${o(t.getMonth()+1)}-${o(t.getDate())} ${o(t.getHours())}:${o(t.getMinutes())}`}function E(n){let e=0;for(const t of n)e+=t.charCodeAt(0)>127?2:1;return e}function b(n,e){const t=e-E(n);return t>0?n+" ".repeat(t):n}function k(n,e){const t=n.map(((n,t)=>{const o=e.reduce(((n,e)=>Math.max(n,E(e[t]||""))),0);return Math.max(E(n),o)})),o=[];o.push(n.map(((n,e)=>b(n,t[e]))).join(" ")),o.push(t.map((n=>"-".repeat(n))).join(" "));for(const n of e)o.push(n.map(((n,e)=>b(n||"",t[e]))).join(" "));return o.join("\n")}function R(n){const e=n?.data;if(!e)return"(空目录)";const t=e.list||e.data||[];if(!Array.isArray(t)||0===t.length)return"(空目录)";const o=k(["类型","名称","大小","修改时间","NID"],t.map((n=>{const e=1===n.is_dir||"1"===n.is_dir||"dir"===n.type;return[e?"d":"-",n.name||n.fname||"-",e?"-":y(parseInt(n.count_size||n.size||"0")),v(n.modify_time||n.mtime),n.nid||"-"]}))),s=void 0!==e.page_num?e.page_num:"";let r=`共 ${e.total_count||e.total||t.length} 项`;return""!==s&&(r+=`,第 ${s} 页`),o+"\n"+r}function O(n){const e=n?.data;if(!e)return"无搜索结果";const t=e.list||e.data||[];if(!Array.isArray(t)||0===t.length)return"无搜索结果";return k(["类型","名称","大小","路径","NID"],t.map((n=>{const e=1===n.is_dir||"1"===n.is_dir;return[e?"d":"-",n.name||n.fname||"-",e?"-":y(parseInt(n.count_size||n.size||"0")),n.path||"-",n.nid||"-"]})))+"\n"+`共找到 ${e.total_count||e.total||t.length} 项`}function C(n,e){if(!n)return e;if(n.data?.share_url)return`${e}\n分享链接: ${n.data.share_url}`;if(n.data?.downloadUrl||n.downloadUrl){const t=n.data?.downloadUrl||n.downloadUrl,o=n.filename||"",s=n.fileSize||"",r=n.downloadPath||"";let a=e;return o&&(a+=`\n文件名: ${o}`),s&&(a+=`\n大小: ${s}`),r&&(a+=`\n保存到: ${r}`),t&&(a+=`\n链接: ${t}`),a}if(void 0!==n.fileCount){let t=`${e}\n上传: ${n.fileCount}/${n.totalFileCount} 文件,耗时 ${n.totalTime}秒`;if(n.uploadResults?.length)for(const e of n.uploadResults)t+=`\n - ${e.name||e.uploadRes?.name||"未知"}`;return n.duplicateFiles?.length&&(t+=`\n重名文件: ${n.duplicateFiles.map((n=>n.name)).join(", ")}`),t}if(n.data?.task_id||n.data?.file_path){let t=e;return n.data?.file_path&&(t+=`\n路径: ${n.data.file_path}`),n.data?.file_size&&(t+=`\n大小: ${y(n.data.file_size)}`),t}return e}function L(n){const e=n?.data;if(!e)return"无法获取用户信息";const t=[];return(e.nickname||e.nick)&&t.push(`昵称: ${e.nickname||e.nick}`),e.qid&&t.push(`QID: ${e.qid}`),void 0!==e.space_used&&void 0!==e.space_total&&t.push(`空间: ${y(e.space_used)} / ${y(e.space_total)}`),e.vip_type&&t.push(`VIP: ${e.vip_type}`),t.length>0?t.join("\n"):JSON.stringify(e,null,2)}const I=Object.freeze({__proto__:null,executeBatch:async function(n,e){const t=[];let o=0,s=0;for(let r=0;r<n.length;r++)try{const s=await e(n[r],r);t.push({index:r,input:n[r],success:!0,result:s}),o++}catch(e){t.push({index:r,input:n[r],success:!1,error:e.message}),s++}return{total:n.length,succeeded:o,failed:s,items:t}},formatBatchResult:function(n){const e=[];e.push(`批量操作完成: 成功 ${n.succeeded}/${n.total},失败 ${n.failed}/${n.total}`);for(const t of n.items){const n=t.success?"":` (${t.error})`;e.push(` ${t.success?"✓":"✗"} [${t.index+1}] ${JSON.stringify(t.input)}${n}`)}return e.join("\n")},formatBytes:y,formatFileList:R,formatSearchResult:O,formatSimpleResult:C,formatTable:k,formatUserInfo:L,outputError:P,outputJson:_,outputText:g});function M(){const n=new e("auth").description("鉴权管理").enablePositionalOptions().passThroughOptions();return n.command("login").description("登录:保存 API Key 到本地配置").requiredOption("--api-key <api_key>","API 密钥").option("--env <env>","环境 (prod/test)","prod").option("--sub-channel <channel>","子渠道","open").action((e=>{const t=Date.now();try{const o=h();o.api_key=e.apiKey,e.env&&(o.ecs_env=e.env),e.subChannel&&(o.sub_channel=e.subChannel),p(o);const s=n.parent?.opts()||{};"text"===s.format?g(`登录成功!配置已保存到 ${d()}`):_({message:"登录成功",configPath:d()},"auth login",t,{quiet:s.quiet})}catch(n){P(n,"auth login",t)}})),n.command("whoami").description("查看当前鉴权状态").action((()=>{const e=Date.now();try{const t=h(),o=n.parent?.opts()||{},s={logged_in:!!t.api_key,api_key:t.api_key?t.api_key.substring(0,10)+"***":void 0,ecs_env:t.ecs_env||process.env.ECS_ENV||"prod",sub_channel:t.sub_channel||process.env.SUB_CHANNEL||"open",config_path:d()};"text"===o.format?g(s.logged_in?`已登录\nAPI Key: ${s.api_key}\n环境: ${s.ecs_env}\n渠道: ${s.sub_channel}`:"未登录。请使用 auth login --api-key <API_KEY> 登录"):_(s,"auth whoami",e,{quiet:o.quiet})}catch(n){P(n,"auth whoami",e)}})),n.command("logout").description("退出登录:清除本地配置").action((()=>{const e=Date.now();try{!function(){try{const n=d();t.existsSync(n)&&t.unlinkSync(n)}catch{}}();const o=n.parent?.opts()||{};"text"===o.format?g("已退出登录,本地配置已清除"):_({message:"已退出登录"},"auth logout",e,{quiet:o.quiet})}catch(n){P(n,"auth logout",e)}})),n}const z={test:"https://qaopen.eyun.360.cn/intf.php",hgtest:"https://hg-openapi.eyun.360.cn/intf.php",prod:"https://openapi.eyun.360.cn/intf.php"};function S(n){const e=n||process.env.ECS_ENV||"prod";return{request_url:z[e]||z.prod,client_id:"e4757e933b6486c08ed206ecb6d5d9e684fcb4e2",client_secret:"test"===e?"b11b8fff1c75a5d227c8cc93aaeb0bb70c8eee47":"885fd3231f1c1e37c9f462261a09b8c38cde0c2b"}}function D(n,e="e7b24b112a44fdd9ee93bdf998c6ca0e"){const t=Object.keys(n).sort().map((e=>{const t=function(n){return encodeURIComponent(n).replace(/%20/g,"+").replace(/[!'()*~]/g,(n=>`%${n.charCodeAt(0).toString(16).toUpperCase()}`))}(n[e]);return`${e}=${t}`}));let o=t.join("&");return o+=e,s=o,a.createHash("md5").update(s,"utf8").digest("hex");var s}n.config();const W="production"===process.env.NODE_ENV,A="true"===process.env.LOG_TO_FILE,Y=process.env.LOG_FILE_PATH||"/data/logs/ecs-mcp/app.log",U=(process.env.LOG_TIME_FORMAT||"epoch").toLowerCase();function x(){const n=new Date,e=new Intl.DateTimeFormat("en-CA",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}).formatToParts(n).reduce(((n,e)=>("literal"!==e.type&&(n[e.type]=e.value),n)),{}),t=String(n.getMilliseconds()).padStart(3,"0");return`,"time":"${`${e.year}-${e.month}-${e.day}T${e.hour}:${e.minute}:${e.second}.${t}+08:00`}"`}let N;if(A)N=i.destination({dest:Y,minLength:512,sync:!1});else{const n=process.env.LOG_STDERR,e="string"==typeof n&&n.length>0,t=Array.isArray(process.argv)?process.argv.slice(2):[],o=t.includes("--stdio")||t.includes("--all"),s=e?"true"===n.toLowerCase():o;N=i.destination({fd:s?2:1,minLength:256,sync:!0})}const T=i({level:process.env.LOG_LEVEL||(W?"info":"debug"),base:{service:process.env.APP_NAME||"ecs-mcp",version:process.env.APP_VERSION||"0.0.0",log_type:"app"},formatters:{level:(n,e)=>({level:n,level_number:e})},redact:{paths:["req.headers.authorization","authInfo.token","authInfo.access_token","req.body.params.arguments.access_token","req.body.params.arguments.token"],censor:"***"},timestamp:"iso"===U?i.stdTimeFunctions.isoTime:"epoch"===U?i.stdTimeFunctions.epochTime:"beijing"===U||"cst"===U||"asia/shanghai"===U?x:i.stdTimeFunctions.isoTime},N),q=n=>{n&&T.error({err:n},"fatal error");try{N?.flushSync?.()}catch{}try{N?.end?.()}catch{}process.exit(1)};async function F(n,e){const t=function(n){let e="",t="",o="",s="";n&&(n.apiKey&&(e=n.apiKey),n.subChannel&&(t=n.subChannel),n.q&&(o=n.q),n.t&&(s=n.t)),e||(e=process.env.API_KEY||""),t||(t=process.env.SUB_CHANNEL||"open");const{client_id:r,client_secret:a}=S(n?.ecsEnv);return{apiKey:e,clientId:r,clientSecret:a,subChannel:t,q:o,t:s}}(e);if(!t.apiKey&&!t.q&&!t.t)throw new Error("未配置API_KEY环境变量");try{const{request_url:o}=S(e?.ecsEnv),s=t.subChannel,r=new URL(o);let a={};const i=n.extraParams;i&&"file-upload-stdio"===i.toolName?(a.method=i.method,a.client_id=i.clientId,a.client_secret=i.clientSecret,a.qid=i.qid,a.grant_type=i.grantType,a.sub_channel=s):(a.method="Oauth.getAccessTokenByApiKey",a.client_id=t.clientId,a.client_secret=t.clientSecret,a.grant_type="authorization_code",a.sub_channel=s,t.apiKey&&(a.api_key=t.apiKey)),Object.entries(a).forEach((([n,e])=>{r.searchParams.append(n,e)}));const c={Accept:"application/json"};t.apiKey&&(c.api_key=t.apiKey),t.q&&(c.q=t.q),t.t&&(c.t=t.t);const l=await fetch(r.toString(),{method:"GET",headers:c});if(!l.ok)throw new Error(`鉴权请求失败,状态码: ${l.status}`);const u=await l.json();if(0!==u.errno)throw new Error(`鉴权请求返回错误: ${u.errmsg}`);const{access_token:m,qid:d,token:h}=u.data;return{access_token:m,qid:d,token:h,sub_channel:s}}catch(n){throw T.error({err:n},"获取鉴权信息失败"),n}}async function K(n={},e){T.debug({transportAuthInfo:e?{hasApiKey:!!e.apiKey,apiKey:e.apiKey||"",subChannel:e.subChannel,ecsEnv:e.ecsEnv,q:e.q||"",t:e.t||""}:void 0},"getAuthInfo");const t=await F(n,e);if(n.extraParams&&"file-upload-stdio"===n.extraParams.toolName)return t.qid=n.extraParams.qid,t;const o=D(function(n,e,t={}){const o={};if(n.access_token&&(o.access_token=String(n.access_token)),e&&(o.method=String(e)),n.qid&&(o.qid=String(n.qid)),t)for(const[n,e]of Object.entries(t))null!=e&&(o[String(n)]=String(e));return{...o}}({access_token:t.access_token,qid:t.qid},n.method||"",n.extraParams));return t.sign=o,t}["SIGINT","SIGTERM","beforeExit","uncaughtException"].forEach((n=>{process.on(n,q)})),process.on("unhandledRejection",(n=>q(n))),n.config();class j{apiKey;ecsEnv;subChannel;timeout;retries;retryDelay;constructor(n){this.apiKey=n.apiKey,this.ecsEnv=n.ecsEnv||"prod",this.subChannel=n.subChannel||"open",this.timeout=n.timeout||3e4,this.retries=n.retries||0,this.retryDelay=n.retryDelay||1e3}getTransportAuthInfo(){return{apiKey:this.apiKey,ecsEnv:this.ecsEnv,subChannel:this.subChannel}}async getAuth(n={}){const e=this.getTransportAuthInfo(),t=await K(n,e);return t.request_url=S(this.ecsEnv).request_url,t}isRetryable(n){return"AbortError"===n.name||("ECONNRESET"===n.code||"ETIMEDOUT"===n.code||"ECONNREFUSED"===n.code||!!n.message?.includes("状态码: 5"))}async fetchWithRetry(n,e=0){const{url:t,init:o}=n(),s=new AbortController,r=setTimeout((()=>s.abort()),this.timeout),a={...o,signal:s.signal};try{const n=await fetch(t,a);if(clearTimeout(r),!n.ok)throw new Error(`API 请求失败,状态码: ${n.status}`);const e=await n.text();try{return JSON.parse(e)}catch{throw new Error(`无法解析API响应: ${e.substring(0,100)}...`)}}catch(t){if(clearTimeout(r),e<this.retries&&this.isRetryable(t)){const o=this.retryDelay*Math.pow(2,e);return T.debug({attempt:e+1,delay:o,error:t.message},"请求失败,准备重试"),await new Promise((n=>setTimeout(n,o))),this.fetchWithRetry(n,e+1)}if("AbortError"===t.name)throw new Error(`请求超时 (${this.timeout}ms)`);throw t}}async apiGet(n,e){return this.fetchWithRetry((()=>{const t=new URL(n.request_url||"");return Object.entries(e).forEach((([n,e])=>{t.searchParams.append(n,String(e))})),{url:t.toString(),init:{method:"GET",headers:{"Access-Token":n.access_token||""}}}}))}async apiPost(n,e,t={}){return this.fetchWithRetry((()=>{const o=new URL(n.request_url||"");Object.entries(e).forEach((([n,e])=>{o.searchParams.append(n,String(e))}));const s=new URLSearchParams;return Object.entries(t).forEach((([n,e])=>{s.append(n,String(e))})),{url:o.toString(),init:{method:"POST",headers:{"Access-Token":n.access_token||"","Content-Type":"application/x-www-form-urlencoded"},body:s}}}))}buildBaseParams(n,e){return{method:e,access_token:n.access_token||"",qid:n.qid||"",sign:n.sign||"",sub_channel:n.sub_channel}}get logger(){return T}}class B extends j{async info(){const n=await this.getAuth({}),e=this.buildBaseParams(n,"User.getUserDetail");return e.sign="",await this.apiGet(n,e)}}class V extends j{async list(n={}){const{path:e="/",page:t=0,page_size:o=50}=n;let s=e||"/";"/"!==s&&(s.startsWith("/")||(s="/"+s),s.endsWith("/")||(s+="/"));const r={path:s,page:t,page_size:o},a=await this.getAuth({method:"File.getList",extraParams:r}),i={...this.buildBaseParams(a,"File.getList")};for(const[n,e]of Object.entries(r))"access_token"!==n&&(i[n]=String(e));return await this.apiGet(a,i)}async mkdir(n){const e={fname:n},t=await this.getAuth({method:"File.mkdir",extraParams:e}),o=this.buildBaseParams(t,"File.mkdir");return await this.apiPost(t,{},{...o,fname:n})}}const J="未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量";async function G(){if(process.stdin.isTTY)throw new Error('--stdin 模式需要管道输入,例如: echo "content" | 360disk file save --stdin');const n=[];for await(const e of process.stdin)n.push(Buffer.from(e));return Buffer.concat(n).toString("utf-8")}class H extends j{async move(n){const{src_name:e,new_name:t}=n,o={src_name:e,new_name:t},s=await this.getAuth({method:"File.move",extraParams:o}),r=this.buildBaseParams(s,"File.move");return await this.apiPost(s,{},{...r,src_name:e,new_name:t})}async rename(n){const{src_name:e,new_name:t}=n,o={src_name:e,new_name:t},s=await this.getAuth({method:"File.rename",extraParams:o}),r=this.buildBaseParams(s,"File.rename");return await this.apiPost(s,{},{...r,src_name:e,new_name:t})}async delete(n){const e=await this.getAuth({method:"File.delete"}),t=this.buildBaseParams(e,"File.delete");return await this.apiPost(e,{},{...t,fname:n})}}const Q=["http:","https:"],X=/[\\@]/,Z=[/^127\./,/^10\./,/^11\./,/^172\.(1[6-9]|2\d|3[01])\./,/^192\.168\./,/^169\.254\./,/^224\./,/^0\./,/^255\./,/^::1$/,/^fe80::/i,/^fc00::/i,/^fd00::/i,/^localhost$/i,/\.local$/i,/\.lan$/i,/\.internal$/i,/\.intranet$/i,/\.corp$/i,/\.home$/i],nn=[22,23,25,53,135,139,445,1433,1521,3306,3389,5432,6379,9200,11211,27017];async function en(n,e=5e3){const t=function(n){if(!n||"string"!=typeof n)return{isValid:!1,error:"URL不能为空"};try{const t=n.trim();if(X.test(t))return{isValid:!1,error:"URL包含不安全的字符"};const o=new URL(t);if(!Q.includes(o.protocol))return{isValid:!1,error:"不支持的协议,只支持 http/https"};const s=o.hostname.toLowerCase();if(!s)return{isValid:!1,error:"主机名不能为空"};for(const n of Z)if(n.test(s))return{isValid:!1,error:"禁止访问受限地址"};if(tn(s)&&!on(s))return{isValid:!1,error:"禁止访问受限IP地址"};if(o.port){const n=parseInt(o.port,10);if(nn.includes(n))return{isValid:!1,error:"禁止访问受限端口"}}return e=o.pathname,[/\.\./,/%2e%2e/i,/%252e%252e/i,/\/etc\//i,/\/proc\//i,/\/sys\//i,/\/dev\//i].some((n=>n.test(e)))?{isValid:!1,error:"路径包含不安全的内容"}:{isValid:!0,normalizedUrl:o.toString(),originalHostname:s}}catch(n){return{isValid:!1,error:"URL格式无效"}}var e}(n);if(!t.isValid)return t;try{const o=new URL(n).hostname.toLowerCase();if(tn(o))return{...t,resolvedIP:o,originalHostname:o,needsHostHeader:!1};let s=[];try{const n=c.lookup(o,{all:!0}),t=new Promise(((n,t)=>{setTimeout((()=>t(new Error("DNS解析超时"))),e)}));s=(await Promise.race([n,t])).map((n=>n.address))}catch(n){return{isValid:!1,error:"域名解析失败"}}for(const n of s){for(const e of Z)if(e.test(n))return{isValid:!1,error:"域名解析到受限IP地址"};if(!on(n))return{isValid:!1,error:"域名解析到受限IP地址"}}return{isValid:!0,normalizedUrl:t.normalizedUrl,resolvedIP:s[0],resolvedIPs:s,originalHostname:o,needsHostHeader:!1}}catch(n){return{isValid:!1,error:"URL验证过程中发生错误"}}}function tn(n){if(/^(\d{1,3}\.){3}\d{1,3}$/.test(n)){return n.split(".").every((n=>{const e=parseInt(n,10);return e>=0&&e<=255}))}return/^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}$/i.test(n)}function on(n){for(const e of Z)if(e.test(n))return!1;return!0}class sn extends j{normalizeUploadResult(n){return{id:n?.id,name:n?.name||n?.uploadRes?.name,path:n?.uploadRes?.name,fid:n?.fid,size:n?.size||n?.uploadRes?.count_size,uploadRes:n?.uploadRes?{nid:n.uploadRes.nid,qid:n.uploadRes.qid,name:n.uploadRes.name,count_size:n.uploadRes.count_size,file_hash:n.uploadRes.file_hash,create_time:n.uploadRes.create_time,modify_time:n.uploadRes.modify_time}:void 0}}async search(n){const{key:e="",file_category:t=-1,page:o=1,page_size:s=20}=n;if(!e&&-1===t)throw new Error("必须提供搜索关键词(key)或指定文件类型(file_category)");const r={file_category:t,key:e,page:o,page_size:s},a=await this.getAuth({method:"File.searchList",extraParams:r}),i=this.buildBaseParams(a,"File.searchList"),c={};for(const[n,e]of Object.entries(r))"access_token"!==n&&(c[n]=String(e));return await this.apiPost(a,i,c)}async share(n){const e=await this.getAuth({method:"Share.preShare"}),t=this.buildBaseParams(e,"Share.preShare");return await this.apiPost(e,{},{...t,paths:n})}async getDownloadUrl(n){const{nid:e,fpath:t}=n;if(void 0===e&&!t)throw new Error("必须提供nid或fpath中的一个参数");const o={};void 0!==e?o.nid=e:t&&(o.fpath=t);const s=await this.getAuth({method:"MCP.getDownLoadUrl",extraParams:o}),r={...this.buildBaseParams(s,"MCP.getDownLoadUrl")};return void 0!==e?r.nid=String(e):t&&(r.fpath=t),await this.apiPost(s,{},r)}async save(n){const{url:e,content:t,upload_path:o,file_name:s}=n;if(!e&&!t||e&&t)throw new Error("url 与 content 互斥,必须且只能传一个");if(e){const n=await en(e,5e3);if(!n.isValid)throw new Error(`URL安全验证失败: ${n.error}`)}const r=await this.getAuth({method:"MCP.saveFile"}),a=this.buildBaseParams(r,"MCP.saveFile"),i={};o&&(i.upload_path=o),e?i.url=e:t&&(i.content=t),s&&(i.file_name=s);const c=await this.apiPost(r,a,i);if(c&&0===c.errno){const n=c.data?.task_id;if(!n)throw new Error("保存文件失败: 未获取到任务ID");return await this.pollTaskStatus(r,n)}throw new Error(c?.errmsg||"API请求失败")}async queryTaskStatus(n,e){const t={method:"MCP.query",qid:n.qid||"",access_token:n.access_token||"",sign:n.sign||""};return await this.apiPost(n,t,{task_id:e})}async pollTaskStatus(n,e,t=1e3,o=120){let s,r=0;for(;r<o;){if(r++,s=await this.queryTaskStatus(n,e),0!==s.errno)throw new Error(s.errmsg||"查询任务状态失败");const o=s.data?.status;if(2===o)return s;if(3===o)throw new Error(s.data?.error||"文件保存失败");await new Promise((n=>setTimeout(n,t)))}return s}async upload(n){const{filePaths:e,uploadPath:o="/"}=n;if(!e||0===e.length)throw new Error("filePaths 为必填参数且不能为空");for(const n of e)if(!t.existsSync(n))throw new Error(`文件不存在: ${n}`);const s=await this.getAuth({});let r;try{const n=await import("@aicloud360/sec-sdk-node");r=n.UploadNode}catch{throw new Error("请先安装 @aicloud360/sec-sdk-node: npm install @aicloud360/sec-sdk-node")}const a={qid:s.qid||"0",token:s.token||"",access_token:s.access_token||"",env:this.ecsEnv,path:o};return new Promise(((n,t)=>{const s=Date.now(),i=[],c=[],l=[];new r(a,{success:n=>{i.push(this.normalizeUploadResult(n))},progress:()=>{},error:(n,e)=>{const t=e?.errmsg||e?.message||JSON.stringify(e);c.push({fileName:n?.name||"未知文件",error:t})},duplicateList:n=>{l.push(...n.map((n=>({name:n.name,nid:n.nid,size:n.count_size,path:n.path||o}))))},complete:()=>{c.length>0&&0===i.length?t(new Error(`所有文件上传失败: ${c.map((n=>`${n.fileName}: ${n.error}`)).join("; ")}`)):n({uploadResults:i,uploadErrors:c,duplicateFiles:l,totalTime:((Date.now()-s)/1e3).toFixed(2),fileCount:i.length,totalFileCount:e.length})}}).addWaitFile(e)}))}async download(n){const{nid:e,auto:t=!0,downloadDir:s}=n,a=s||o.join(r(),".mcp-downloads"),i=await this.getAuth({method:"Sync.getVerifiedDownLoadUrl",extraParams:{nid:e}}),c={...this.buildBaseParams(i,"Sync.getVerifiedDownLoadUrl"),nid:String(e)},l=await this.apiGet(i,c);if(!l||0!==l.errno)throw new Error(l?.errmsg||"API请求失败");const m=l.data?.downloadUrl||"";if(!m)throw new Error("未能获取到文件下载链接");const{filename:d,sizeMB:h}=this.extractFileInfoFromUrl(m),p=h>0?`${h.toFixed(2)} MB`:"未知大小";if(!t)return{downloadUrl:m,filename:d,fileSize:p,downloadPath:""};await u.mkdir(a,{recursive:!0});const f=o.join(a,d);return h>10?(this.spawnBackgroundDownload(m,f),{downloadUrl:m,filename:d,fileSize:p,downloadPath:f,background:!0}):(await this.downloadWithCurl(m,f),{downloadUrl:m,filename:d,fileSize:p,downloadPath:f})}extractFileInfoFromUrl(n){try{const e=new URL(n);let t="downloaded_file";const s=e.searchParams.get("fname");if(s?t=o.basename(decodeURIComponent(s)):e.pathname&&e.pathname.length>1&&(t=o.basename(e.pathname)),t=t.replace(/[\x00/]/g,"_"),t&&"."!==t||(t="downloaded_file"),t.length>200){const n=o.extname(t);t=o.basename(t,n).substring(0,200-n.length)+n}const r=e.searchParams.get("fsize");return{filename:t,sizeMB:r?parseInt(r,10)/1048576:0}}catch{return{filename:"downloaded_file",sizeMB:0}}}downloadWithCurl(n,e,t=3e5){return new Promise(((o,s)=>{const r=l("curl",["-L","-s","-A","yunpan_mcp_server",n,"-o",e]),a=setTimeout((()=>{r.kill(),s(new Error("下载超时"))}),t);r.on("close",(n=>{clearTimeout(a),0===n?o():s(new Error(`下载失败,curl 退出码: ${n}`))})),r.on("error",(n=>{clearTimeout(a),s(n)}))}))}spawnBackgroundDownload(n,e){l("curl",["-L","-s","-A","yunpan_mcp_server",n,"-o",e],{detached:!0,stdio:"ignore"}).unref()}}const rn="未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量";function an(n,e,t,o,s){"text"===o.format&&s?g(s(n)):_(n,e,t,{quiet:o.quiet})}function cn(){const n=new e("file").description("文件操作");return n.command("mv").description("移动文件或文件夹").argument("<src>","源路径,多个用 | 分隔").argument("<dest>","目标文件夹路径").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=f(s);if(!n.apiKey)return void P(new $(rn,w.AUTH_ERROR),"file mv",o);const r=new H(n);an(await r.move({src_name:e,new_name:t}),"file mv",o,s,(n=>C(n,`已移动: ${e} -> ${t}`)))}catch(n){P(n,"file mv",o)}})),n.command("rename").description("重命名文件或文件夹").argument("<path>","原文件完整路径").argument("<new_name>","新名称").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=f(s);if(!n.apiKey)return void P(new $(rn,w.AUTH_ERROR),"file rename",o);const r=new H(n);an(await r.rename({src_name:e,new_name:t}),"file rename",o,s,(n=>C(n,`已重命名: ${e} -> ${t}`)))}catch(n){P(n,"file rename",o)}})),n.command("rm").description("删除文件或文件夹").argument("<path>","文件路径,多个用 | 分隔").option("--batch","批量模式:从 stdin 读取路径列表(每行一个)").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=f(s);if(!n.apiKey)return void P(new $(rn,w.AUTH_ERROR),"file rm",o);const r=new H(n);if(t.batch){const n=await async function(){return(await G()).split("\n").map((n=>n.trim())).filter(Boolean)}(),t=[e,...n].filter(Boolean),{executeBatch:a,formatBatchResult:i}=await Promise.resolve().then((function(){return I}));an(await a(t,(async n=>await r.delete(n))),"file rm --batch",o,s,(n=>i(n)))}else{an(await r.delete(e),"file rm",o,s,(n=>C(n,`已删除: ${e}`)))}}catch(n){P(n,"file rm",o)}})),n.command("search").description("按关键词搜索文件").argument("<keyword>","搜索关键词").option("--type <type>","文件类型: -1(全部) 0(其他) 1(图片) 2(文档) 3(音乐) 4(视频)","-1").option("--page <n>","页码","1").option("--size <n>","每页数量","20").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=f(s);if(!n.apiKey)return void P(new $(rn,w.AUTH_ERROR),"file search",o);const r=new sn(n);an(await r.search({key:e,file_category:parseInt(t.type),page:parseInt(t.page),page_size:parseInt(t.size)}),"file search",o,s,O)}catch(n){P(n,"file search",o)}})),n.command("share").description("生成文件分享链接").argument("<paths>","文件路径,多个用 | 分隔").action((async e=>{const t=Date.now(),o=n.parent?.opts()||{};try{const n=f(o);if(!n.apiKey)return void P(new $(rn,w.AUTH_ERROR),"file share",t);const s=new sn(n);an(await s.share(e),"file share",t,o,(n=>C(n,"分享链接已生成")))}catch(n){P(n,"file share",t)}})),n.command("url").description("获取文件下载链接").argument("<path>","文件路径").option("--nid <nid>","文件 NID(与路径二选一)").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=f(s);if(!n.apiKey)return void P(new $(rn,w.AUTH_ERROR),"file url",o);const r=new sn(n);an(await r.getDownloadUrl({nid:t.nid,fpath:t.nid?void 0:e}),"file url",o,s,(n=>C(n,"下载链接已获取")))}catch(n){P(n,"file url",o)}})),n.command("save").description("通过 URL 或文本内容保存文件到云盘").option("--url <url>","文件下载地址(与 --content/--stdin 互斥)").option("--content <text>","文件内容(与 --url/--stdin 互斥)").option("--stdin","从标准输入读取内容(与 --url/--content 互斥)").option("--dest <path>","云盘存储路径,必须以 / 开头和结尾").option("--filename <name>","保存的文件名").action((async e=>{const t=Date.now(),o=n.parent?.opts()||{};try{const n=f(o);if(!n.apiKey)return void P(new $(rn,w.AUTH_ERROR),"file save",t);if(1!==[e.url,e.content,e.stdin].filter(Boolean).length)return void P(new $("--url、--content、--stdin 三者互斥,必须且只能传一个",w.INVALID_ARGS),"file save",t);let s=e.content;if(e.stdin&&(s=await G(),!s.trim()))return void P(new $("stdin 输入内容为空",w.INVALID_ARGS),"file save",t);const r=new sn(n);an(await r.save({url:e.url,content:s,upload_path:e.dest,file_name:e.filename}),"file save",t,o,(n=>C(n,"文件保存成功")))}catch(n){P(n,"file save",t)}})),n.command("upload").description("上传本地文件到云盘").argument("<files>","本地文件路径,多个用逗号分隔").option("--dest <path>","云盘目标路径","/").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=f(s);if(!n.apiKey)return void P(new $(rn,w.AUTH_ERROR),"file upload",o);const r=e.split(",").map((n=>n.trim())),a=new sn(n);an(await a.upload({filePaths:r,uploadPath:t.dest}),"file upload",o,s,(n=>C(n,"上传完成")))}catch(n){P(n,"file upload",o)}})),n.command("download").description("下载云盘文件到本地").argument("<nid>","文件 NID(可通过 file search / dir ls 获取)").option("--dir <path>","本地下载目录").option("--no-auto","仅获取下载链接,不自动下载").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=f(s);if(!n.apiKey)return void P(new $(rn,w.AUTH_ERROR),"file download",o);const r=new sn(n);an(await r.download({nid:e,auto:t.auto,downloadDir:t.dir}),"file download",o,s,(n=>C(n,"下载完成")))}catch(n){P(n,"file download",o)}})),n}function ln(){return o.join(r(),".360disk")}function un(){const n=process.env.SHELL||"";return n.includes("zsh")?"zsh":n.includes("bash")?"bash":"unknown"}function mn(n){const e=r();return o.join(e,"zsh"===n?".zshrc":".bashrc")}n.config(),async function(){const n=function(){try{const n=o.dirname(s(import.meta.url)),e=[o.resolve(n,"../package.json"),o.resolve(n,"../../package.json"),o.resolve(n,"../../../package.json"),o.resolve(process.cwd(),"package.json")];for(const n of e)if(t.existsSync(n)){const e=JSON.parse(t.readFileSync(n,"utf8"));return{name:e.cli?.name||"360disk",version:e.version||"0.0.0",description:e.cli?.description||"360 AI 云盘 CLI"}}}catch{}return{name:"360disk",version:"0.0.0",description:"360 AI 云盘 CLI"}}(),r=new e;r.name(n.name).description(n.description).version(n.version).enablePositionalOptions().passThroughOptions().option("--api-key <key>","API 密钥").option("--env <env>","环境 (prod/test)","prod").option("--sub-channel <channel>","子渠道","open").option("--format <type>","输出格式 (json|text)","json").option("--quiet","仅输出结果数据").option("--timeout <ms>","请求超时时间(毫秒)","30000").option("--retries <n>","失败重试次数","0"),r.addCommand(M()),r.addCommand(function(){const n=new e("user").description("用户信息");return n.command("info").description("获取用户详细信息").action((async()=>{const e=Date.now(),t=n.parent?.opts()||{};try{const n=f(t);if(!n.apiKey)return void P(new $("未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量",w.AUTH_ERROR),"user info",e);const o=new B(n),s=await o.info();"text"===t.format?g(L(s)):_(s,"user info",e,{quiet:t.quiet})}catch(n){P(n,"user info",e)}})),n}()),r.addCommand(function(){const n=new e("dir").description("目录操作");return n.command("ls").description("列出云盘指定目录下的文件和文件夹").argument("[path]","目录路径","/").option("--page <n>","页码","0").option("--size <n>","每页数量","50").action((async(e,t)=>{const o=Date.now(),s=n.parent?.opts()||{};try{const n=f(s);if(!n.apiKey)return void P(new $(J,w.AUTH_ERROR),"dir ls",o);const r=new V(n),a=await r.list({path:e,page:parseInt(t.page),page_size:parseInt(t.size)});"text"===s.format?g(R(a)):_(a,"dir ls",o,{quiet:s.quiet})}catch(n){P(n,"dir ls",o)}})),n.command("mkdir").description("创建新文件夹").argument("<path>","文件夹路径").action((async e=>{const t=Date.now(),o=n.parent?.opts()||{};try{const n=f(o);if(!n.apiKey)return void P(new $(J,w.AUTH_ERROR),"dir mkdir",t);const s=new V(n),r=await s.mkdir(e);"text"===o.format?g(C(r,`已创建目录: ${e}`)):_(r,"dir mkdir",t,{quiet:o.quiet})}catch(n){P(n,"dir mkdir",t)}})),n}()),r.addCommand(cn()),r.addCommand(function(){const n=new e("completion").description("Shell 自动补全");return n.command("install").description("安装补全脚本到当前 shell").option("--bash","安装 bash 补全").option("--zsh","安装 zsh 补全").action((n=>{try{const e=n.bash?"bash":n.zsh?"zsh":un();if("unknown"===e)return void P("无法检测 shell 类型,请使用 --bash 或 --zsh 指定","completion install");const s="zsh"===e?"#compdef 360disk\n# 360disk zsh completion\n# 安装: source ~/.360disk/completion.zsh\n# 或添加到 ~/.zshrc: source ~/.360disk/completion.zsh\n\n_360disk() {\n local -a top_commands\n top_commands=(\n 'auth:鉴权管理'\n 'user:用户信息'\n 'dir:目录操作'\n 'file:文件操作'\n 'completion:Shell 自动补全'\n 'help:显示帮助'\n )\n\n local -a auth_commands\n auth_commands=(\n 'login:登录(保存 API Key)'\n 'whoami:查看鉴权状态'\n 'logout:退出登录'\n )\n\n local -a user_commands\n user_commands=(\n 'info:获取用户详细信息'\n )\n\n local -a dir_commands\n dir_commands=(\n 'ls:列出目录内容'\n 'mkdir:创建文件夹'\n )\n\n local -a file_commands\n file_commands=(\n 'mv:移动文件或文件夹'\n 'rename:重命名文件或文件夹'\n 'rm:删除文件或文件夹'\n 'search:搜索文件'\n 'share:生成分享链接'\n 'url:获取下载链接'\n 'save:保存文件到云盘'\n 'upload:上传文件'\n 'download:下载文件'\n )\n\n local -a completion_commands\n completion_commands=(\n 'install:安装补全脚本'\n 'uninstall:卸载补全脚本'\n 'script:输出补全脚本'\n )\n\n _arguments -C \\\n '--api-key[API 密钥]:key:' \\\n '--env[环境]:env:(prod test)' \\\n '--sub-channel[子渠道]:channel:' \\\n '--format[输出格式]:format:(json text)' \\\n '--quiet[仅输出结果]' \\\n '--timeout[超时时间(ms)]:ms:' \\\n '--retries[重试次数]:n:' \\\n '--help[显示帮助]' \\\n '--version[显示版本]' \\\n '1:command:->command' \\\n '2:subcommand:->subcommand' \\\n '*::arg:->args'\n\n case \"$state\" in\n command)\n _describe 'command' top_commands\n ;;\n subcommand)\n case \"$words[2]\" in\n auth) _describe 'auth command' auth_commands ;;\n user) _describe 'user command' user_commands ;;\n dir) _describe 'dir command' dir_commands ;;\n file) _describe 'file command' file_commands ;;\n completion) _describe 'completion command' completion_commands ;;\n esac\n ;;\n args)\n case \"$words[2]\" in\n file)\n case \"$words[3]\" in\n search) _arguments '--type[文件类型]:type:(-1 0 1 2 3 4)' '--page[页码]:n:' '--size[每页数量]:n:' ;;\n save) _arguments '--url[文件URL]:url:' '--content[文件内容]:text:' '--stdin[从stdin读取]' '--dest[目标路径]:path:' '--filename[文件名]:name:' ;;\n upload) _arguments '--dest[云盘路径]:path:' ;;\n download) _arguments '--dir[下载目录]:path:_files -/' '--no-auto[不自动下载]' ;;\n url) _arguments '--nid[文件NID]:nid:' ;;\n rm) _arguments '--batch[批量模式]' ;;\n esac\n ;;\n dir)\n case \"$words[3]\" in\n ls) _arguments '--page[页码]:n:' '--size[每页数量]:n:' ;;\n esac\n ;;\n auth)\n case \"$words[3]\" in\n login) _arguments '--api-key[API密钥]:key:' '--env[环境]:env:(prod test)' '--sub-channel[渠道]:channel:' ;;\n esac\n ;;\n esac\n ;;\n esac\n}\n\n_360disk \"$@\"\n":'# 360disk bash completion\n# 安装: source ~/.360disk/completion.bash\n# 或添加到 ~/.bashrc: source ~/.360disk/completion.bash\n\n_360disk_completions() {\n local cur prev commands\n COMPREPLY=()\n cur="${COMP_WORDS[COMP_CWORD]}"\n prev="${COMP_WORDS[COMP_CWORD-1]}"\n\n # 顶级命令\n local top_commands="auth user dir file completion help"\n\n # 子命令映射\n local auth_commands="login whoami logout"\n local user_commands="info"\n local dir_commands="ls mkdir"\n local file_commands="mv rename rm search share url save upload download"\n local completion_commands="install uninstall script"\n\n # 全局选项\n local global_options="--api-key --env --sub-channel --format --quiet --timeout --retries --help --version"\n\n case "${COMP_WORDS[1]}" in\n auth)\n case "$prev" in\n login) COMPREPLY=( $(compgen -W "--api-key --env --sub-channel --help" -- "$cur") ) ;;\n whoami|logout) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$auth_commands" -- "$cur") ) ;;\n esac\n ;;\n user)\n case "$prev" in\n info) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$user_commands" -- "$cur") ) ;;\n esac\n ;;\n dir)\n case "$prev" in\n ls) COMPREPLY=( $(compgen -W "--page --size --help" -- "$cur") ) ;;\n mkdir) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$dir_commands" -- "$cur") ) ;;\n esac\n ;;\n file)\n case "$prev" in\n search) COMPREPLY=( $(compgen -W "--type --page --size --help" -- "$cur") ) ;;\n url) COMPREPLY=( $(compgen -W "--nid --help" -- "$cur") ) ;;\n save) COMPREPLY=( $(compgen -W "--url --content --stdin --dest --filename --help" -- "$cur") ) ;;\n upload) COMPREPLY=( $(compgen -W "--dest --help" -- "$cur") ) ;;\n download) COMPREPLY=( $(compgen -W "--dir --no-auto --help" -- "$cur") ) ;;\n rm) COMPREPLY=( $(compgen -W "--batch --help" -- "$cur") ) ;;\n mv|rename|share) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$file_commands" -- "$cur") ) ;;\n esac\n ;;\n completion)\n case "$prev" in\n install|uninstall) COMPREPLY=( $(compgen -W "--bash --zsh --help" -- "$cur") ) ;;\n script) COMPREPLY=( $(compgen -W "--bash --zsh --help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$completion_commands" -- "$cur") ) ;;\n esac\n ;;\n *)\n if [[ "$cur" == -* ]]; then\n COMPREPLY=( $(compgen -W "$global_options" -- "$cur") )\n else\n COMPREPLY=( $(compgen -W "$top_commands" -- "$cur") )\n fi\n ;;\n esac\n}\n\ncomplete -F _360disk_completions 360disk\n',r="zsh"===e?"zsh":"bash",a=ln(),i=o.join(a,`completion.${r}`);t.existsSync(a)||t.mkdirSync(a,{recursive:!0,mode:448}),t.writeFileSync(i,s,{mode:420});const c=mn(e),l=function(n){return`source ~/.360disk/completion.${"zsh"===n?"zsh":"bash"} # 360disk auto-completion`}(e);let u="";t.existsSync(c)&&(u=t.readFileSync(c,"utf8")),u.includes("360disk auto-completion")?g(`补全脚本已更新: ${i}\n配置已存在于 ${c},无需重复添加`):(t.appendFileSync(c,"\n"+l+"\n"),g(`补全脚本已安装:\n 脚本: ${i}\n 配置: ${c}\n\n请执行 source ${c} 或重新打开终端生效`))}catch(n){P(n,"completion install")}})),n.command("uninstall").description("卸载补全脚本").option("--bash","卸载 bash 补全").option("--zsh","卸载 zsh 补全").action((n=>{try{const e=n.bash?"bash":n.zsh?"zsh":un();if("unknown"===e)return void P("无法检测 shell 类型,请使用 --bash 或 --zsh 指定","completion uninstall");const s="zsh"===e?"zsh":"bash",r=o.join(ln(),`completion.${s}`);t.existsSync(r)&&t.unlinkSync(r);const a=mn(e);if(t.existsSync(a)){const n=t.readFileSync(a,"utf8").split("\n").filter((n=>!n.includes("360disk auto-completion")));t.writeFileSync(a,n.join("\n"))}g(`补全脚本已卸载\n请执行 source ${mn(e)} 或重新打开终端生效`)}catch(n){P(n,"completion uninstall")}})),n.command("script").description("输出补全脚本到 stdout(手动安装用)").option("--bash","输出 bash 补全脚本").option("--zsh","输出 zsh 补全脚本").action((n=>{try{const e=n.bash?"bash":n.zsh?"zsh":un();if("unknown"===e)return void P("无法检测 shell 类型,请使用 --bash 或 --zsh 指定","completion script");process.stdout.write("zsh"===e?"#compdef 360disk\n# 360disk zsh completion\n# 安装: source ~/.360disk/completion.zsh\n# 或添加到 ~/.zshrc: source ~/.360disk/completion.zsh\n\n_360disk() {\n local -a top_commands\n top_commands=(\n 'auth:鉴权管理'\n 'user:用户信息'\n 'dir:目录操作'\n 'file:文件操作'\n 'completion:Shell 自动补全'\n 'help:显示帮助'\n )\n\n local -a auth_commands\n auth_commands=(\n 'login:登录(保存 API Key)'\n 'whoami:查看鉴权状态'\n 'logout:退出登录'\n )\n\n local -a user_commands\n user_commands=(\n 'info:获取用户详细信息'\n )\n\n local -a dir_commands\n dir_commands=(\n 'ls:列出目录内容'\n 'mkdir:创建文件夹'\n )\n\n local -a file_commands\n file_commands=(\n 'mv:移动文件或文件夹'\n 'rename:重命名文件或文件夹'\n 'rm:删除文件或文件夹'\n 'search:搜索文件'\n 'share:生成分享链接'\n 'url:获取下载链接'\n 'save:保存文件到云盘'\n 'upload:上传文件'\n 'download:下载文件'\n )\n\n local -a completion_commands\n completion_commands=(\n 'install:安装补全脚本'\n 'uninstall:卸载补全脚本'\n 'script:输出补全脚本'\n )\n\n _arguments -C \\\n '--api-key[API 密钥]:key:' \\\n '--env[环境]:env:(prod test)' \\\n '--sub-channel[子渠道]:channel:' \\\n '--format[输出格式]:format:(json text)' \\\n '--quiet[仅输出结果]' \\\n '--timeout[超时时间(ms)]:ms:' \\\n '--retries[重试次数]:n:' \\\n '--help[显示帮助]' \\\n '--version[显示版本]' \\\n '1:command:->command' \\\n '2:subcommand:->subcommand' \\\n '*::arg:->args'\n\n case \"$state\" in\n command)\n _describe 'command' top_commands\n ;;\n subcommand)\n case \"$words[2]\" in\n auth) _describe 'auth command' auth_commands ;;\n user) _describe 'user command' user_commands ;;\n dir) _describe 'dir command' dir_commands ;;\n file) _describe 'file command' file_commands ;;\n completion) _describe 'completion command' completion_commands ;;\n esac\n ;;\n args)\n case \"$words[2]\" in\n file)\n case \"$words[3]\" in\n search) _arguments '--type[文件类型]:type:(-1 0 1 2 3 4)' '--page[页码]:n:' '--size[每页数量]:n:' ;;\n save) _arguments '--url[文件URL]:url:' '--content[文件内容]:text:' '--stdin[从stdin读取]' '--dest[目标路径]:path:' '--filename[文件名]:name:' ;;\n upload) _arguments '--dest[云盘路径]:path:' ;;\n download) _arguments '--dir[下载目录]:path:_files -/' '--no-auto[不自动下载]' ;;\n url) _arguments '--nid[文件NID]:nid:' ;;\n rm) _arguments '--batch[批量模式]' ;;\n esac\n ;;\n dir)\n case \"$words[3]\" in\n ls) _arguments '--page[页码]:n:' '--size[每页数量]:n:' ;;\n esac\n ;;\n auth)\n case \"$words[3]\" in\n login) _arguments '--api-key[API密钥]:key:' '--env[环境]:env:(prod test)' '--sub-channel[渠道]:channel:' ;;\n esac\n ;;\n esac\n ;;\n esac\n}\n\n_360disk \"$@\"\n":'# 360disk bash completion\n# 安装: source ~/.360disk/completion.bash\n# 或添加到 ~/.bashrc: source ~/.360disk/completion.bash\n\n_360disk_completions() {\n local cur prev commands\n COMPREPLY=()\n cur="${COMP_WORDS[COMP_CWORD]}"\n prev="${COMP_WORDS[COMP_CWORD-1]}"\n\n # 顶级命令\n local top_commands="auth user dir file completion help"\n\n # 子命令映射\n local auth_commands="login whoami logout"\n local user_commands="info"\n local dir_commands="ls mkdir"\n local file_commands="mv rename rm search share url save upload download"\n local completion_commands="install uninstall script"\n\n # 全局选项\n local global_options="--api-key --env --sub-channel --format --quiet --timeout --retries --help --version"\n\n case "${COMP_WORDS[1]}" in\n auth)\n case "$prev" in\n login) COMPREPLY=( $(compgen -W "--api-key --env --sub-channel --help" -- "$cur") ) ;;\n whoami|logout) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$auth_commands" -- "$cur") ) ;;\n esac\n ;;\n user)\n case "$prev" in\n info) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$user_commands" -- "$cur") ) ;;\n esac\n ;;\n dir)\n case "$prev" in\n ls) COMPREPLY=( $(compgen -W "--page --size --help" -- "$cur") ) ;;\n mkdir) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$dir_commands" -- "$cur") ) ;;\n esac\n ;;\n file)\n case "$prev" in\n search) COMPREPLY=( $(compgen -W "--type --page --size --help" -- "$cur") ) ;;\n url) COMPREPLY=( $(compgen -W "--nid --help" -- "$cur") ) ;;\n save) COMPREPLY=( $(compgen -W "--url --content --stdin --dest --filename --help" -- "$cur") ) ;;\n upload) COMPREPLY=( $(compgen -W "--dest --help" -- "$cur") ) ;;\n download) COMPREPLY=( $(compgen -W "--dir --no-auto --help" -- "$cur") ) ;;\n rm) COMPREPLY=( $(compgen -W "--batch --help" -- "$cur") ) ;;\n mv|rename|share) COMPREPLY=( $(compgen -W "--help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$file_commands" -- "$cur") ) ;;\n esac\n ;;\n completion)\n case "$prev" in\n install|uninstall) COMPREPLY=( $(compgen -W "--bash --zsh --help" -- "$cur") ) ;;\n script) COMPREPLY=( $(compgen -W "--bash --zsh --help" -- "$cur") ) ;;\n *) COMPREPLY=( $(compgen -W "$completion_commands" -- "$cur") ) ;;\n esac\n ;;\n *)\n if [[ "$cur" == -* ]]; then\n COMPREPLY=( $(compgen -W "$global_options" -- "$cur") )\n else\n COMPREPLY=( $(compgen -W "$top_commands" -- "$cur") )\n fi\n ;;\n esac\n}\n\ncomplete -F _360disk_completions 360disk\n')}catch(n){P(n,"completion script")}})),n}()),await r.parseAsync()}().then((()=>{process.exit(0)})).catch((n=>{process.stderr.write(JSON.stringify({success:!1,error:n.message||"CLI 启动失败"},null,2)+"\n"),process.exit(1)}));