@aicloud360/mcp-server-disk 0.8.8 → 0.8.9
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 +1 -1
- package/build/index.js +1 -1
- package/package.json +2 -1
package/build/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as n}from"commander";import e from"fs";import t from"path";import{fileURLToPath as o}from"url";import{homedir as r}from"os";import s from"crypto";import i from"pino";import{promises as a}from"dns";import{spawn as c}from"child_process";import l from"fs/promises";function d(){const n=function(){try{const n=t.dirname(new URL(import.meta.url).pathname),o=[t.resolve(n,"../../package.json"),t.resolve(n,"../../../package.json")];for(const n of o)if(e.existsSync(n)){const t=JSON.parse(e.readFileSync(n,"utf8"));if(t.cli?.name)return t.cli.name}}catch{}return"360disk"}();return t.join(r(),`.${n}`)}function u(){return t.join(d(),"config.json")}function m(){try{const n=u();if(e.existsSync(n))return JSON.parse(e.readFileSync(n,"utf8"))}catch{}return{}}function f(n){!function(){const n=d();e.existsSync(n)||e.mkdirSync(n,{recursive:!0,mode:448})}();const t=u();e.writeFileSync(t,JSON.stringify(n,null,2),{mode:384})}function h(n){const e=m();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 p;!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"}(p||(p={}));class w extends Error{code;constructor(n,e=p.GENERAL){super(n),this.name="CLIError",this.code=e}}function g(n,e,t,o){if(o?.quiet)return void process.stdout.write(JSON.stringify(n)+"\n");const r={success:!0,result:n,meta:{duration_ms:Date.now()-t,command:e}};process.stdout.write(JSON.stringify(r,null,2)+"\n")}function _(n){process.stdout.write(n+"\n")}function $(n,e,t){const o=n instanceof Error?n.message:n;let r;r=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("网络")?p.NETWORK_ERROR:e.includes("api key")||e.includes("api_key")||e.includes("未配置")||e.includes("token")||e.includes("auth")||e.includes("登录")||e.includes("401")?p.AUTH_ERROR:e.includes("参数")||e.includes("argument")||e.includes("required")||e.includes("互斥")||e.includes("invalid")?p.INVALID_ARGS:e.includes("不存在")||e.includes("not found")||e.includes("404")||e.includes("no such")?p.NOT_FOUND:e.includes("权限")||e.includes("permission")||e.includes("forbidden")||e.includes("403")?p.PERMISSION_DENIED:e.includes("已存在")||e.includes("conflict")||e.includes("duplicate")||e.includes("409")?p.CONFLICT:e.includes("状态码: 5")||e.includes("500")||e.includes("502")||e.includes("503")||e.includes("服务端")?p.SERVER_ERROR:e.includes("空间不足")||e.includes("quota")||e.includes("limit")||e.includes("429")||e.includes("频率")?p.QUOTA_EXCEEDED:p.GENERAL}(n);const s={success:!1,error:o,code:r,meta:{duration_ms:t?Date.now()-t:0,command:e}};process.stderr.write(JSON.stringify(s,null,2)+"\n"),process.exit(r||1)}function y(n,e,t){if(n&&"number"==typeof n.errno&&0!==n.errno){const o=function(n){switch(n){case 1001:case 1002:case 1003:case 3001:case 3002:case 3003:return p.AUTH_ERROR;case 3008:return p.NOT_FOUND;case 3007:case 3013:case 3129:return p.CONFLICT;case 3005:case 3006:return p.PERMISSION_DENIED;case 3010:case 3011:return p.QUOTA_EXCEEDED;default:return n>=5e3?p.SERVER_ERROR:p.GENERAL}}(n.errno);$(new w(n.errmsg?`${n.errmsg} (errno: ${n.errno})`:`API 错误 (errno: ${n.errno})`,o),e,t)}}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 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 k(n,e){const t=e-E(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,E(e[t]||""))),0);return Math.max(E(n),o)})),o=[];o.push(n.map(((n,e)=>k(n,t[e]))).join(" ")),o.push(t.map((n=>"-".repeat(n))).join(" "));for(const n of e)o.push(n.map(((n,e)=>k(n||"",t[e]))).join(" "));return o.join("\n")}function O(n){const e=n?.data;if(!e)return{nodes:[],total:0};const t=e.list||e.data||e.node_list||[],o=Array.isArray(t)?t:[];return{nodes:o,total:Number(e.total_count??e.total??o.length)||o.length,page:void 0!==e.page_num?Number(e.page_num):void 0}}function R(n){return!!n&&(1===n.is_dir||"1"===n.is_dir||"dir"===n.type||(1===n.type||"1"===n.type))}function C(n){const{nodes:e,total:t,page:o}=O(n);if(0===e.length)return"(空目录)";const r=b(["类型","名称","大小","修改时间","NID"],e.map((n=>{const e=R(n);return[e?"d":"-",n.name||n.fname||"-",e?"-":P(parseInt(n.count_size||n.size||"0")),v(n.modify_time||n.mtime),n.nid||"-"]})));let s=`共 ${t} 项`;return void 0===o||Number.isNaN(o)||(s+=`,第 ${o} 页`),r+"\n"+s}function I(n){const{nodes:e,total:t}=O(n);if(0===e.length)return"无搜索结果";return b(["类型","名称","大小","路径","NID"],e.map((n=>{const e=R(n);return[e?"d":"-",n.name||n.fname||"-",e?"-":P(parseInt(n.count_size||n.size||"0")),n.path||"-",n.nid||"-"]})))+"\n"+`共找到 ${t} 项`}function S(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||"",r=n.fileSize||"",s=n.downloadPath||"";let i=e;return o&&(i+=`\n文件名: ${o}`),r&&(i+=`\n大小: ${r}`),s&&(i+=`\n保存到: ${s}`),t&&(i+=`\n链接: ${t}`),i}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 L(n){if(null==n||""===n)return;if("number"==typeof n)return Number.isFinite(n)&&n>=0?n:void 0;const e=parseInt(String(n),10);return Number.isFinite(e)&&e>=0?e:void 0}function z(n){const e=n?.data;if(!e)return"无法获取用户信息";const t=function(n){const e=String(n.nickname??n.nick??n.name??"").trim(),t=null!=n.qid?String(n.qid):"";let o,r;if(null!=n.space_used){const e=Number(n.space_used);o=Number.isFinite(e)&&e>=0?e:void 0}else o=L(n.used_size);if(null!=n.space_total){const e=Number(n.space_total);r=Number.isFinite(e)&&e>=0?e:void 0}else r=L(n.total_size);let s="";const i=n.vip_type;s=null!=i&&""!==String(i).trim()?String(i).trim():1===n.is_vip||"1"===n.is_vip||!0===n.is_vip?(null!=n.vip_desc?String(n.vip_desc).trim():"")||"VIP用户":"普通用户";return{nickname:e,qid:t,usedBytes:o,totalBytes:r,vipLabel:s}}(e),o=void 0!==t.usedBytes?P(t.usedBytes):"-",r=void 0!==t.totalBytes?P(t.totalBytes):"-";return[`昵称: ${t.nickname||"-"}`,`QID: ${t.qid||"-"}`,`空间: ${o} / ${r}`,`VIP: ${t.vipLabel||"-"}`].join("\n")}const M=Object.freeze({__proto__:null,checkApiResult:y,executeBatch:async function(n,e){const t=[];let o=0,r=0;for(let s=0;s<n.length;s++)try{const r=await e(n[s],s);t.push({index:s,input:n[s],success:!0,result:r}),o++}catch(e){t.push({index:s,input:n[s],success:!1,error:e.message}),r++}return{total:n.length,succeeded:o,failed:r,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:C,formatSearchResult:I,formatSimpleResult:S,formatTable:b,formatUserInfo:z,outputError:$,outputJson:g,outputText:_});function D(){const t=new n("auth").description("鉴权管理").enablePositionalOptions().passThroughOptions();return t.command("login").description("登录:保存 API Key 到本地配置").requiredOption("--api-key <api_key>","API 密钥").option("--env <env>","环境 (prod/test)","prod").option("--sub-channel <channel>","子渠道","open").action((n=>{const e=Date.now();try{const o=m();o.api_key=n.apiKey,n.env&&(o.ecs_env=n.env),n.subChannel&&(o.sub_channel=n.subChannel),f(o);const r=t.parent?.opts()||{};"text"===r.format?_(`登录成功!配置已保存到 ${u()}`):g({message:"登录成功",configPath:u()},"auth login",e,{quiet:r.quiet})}catch(n){$(n,"auth login",e)}})),t.command("whoami").description("查看当前鉴权状态").action((()=>{const n=Date.now();try{const e=m(),o=t.parent?.opts()||{},r={logged_in:!!e.api_key,api_key:e.api_key?e.api_key.substring(0,10)+"***":void 0,ecs_env:e.ecs_env||process.env.ECS_ENV||"prod",sub_channel:e.sub_channel||process.env.SUB_CHANNEL||"open",config_path:u()};"text"===o.format?_(r.logged_in?`已登录\nAPI Key: ${r.api_key}\n环境: ${r.ecs_env}\n渠道: ${r.sub_channel}`:"未登录。请使用 auth login --api-key <API_KEY> 登录"):g(r,"auth whoami",n,{quiet:o.quiet})}catch(e){$(e,"auth whoami",n)}})),t.command("logout").description("退出登录:清除本地配置").action((()=>{const n=Date.now();try{!function(){try{const n=u();e.existsSync(n)&&e.unlinkSync(n)}catch{}}();const o=t.parent?.opts()||{};"text"===o.format?_("已退出登录,本地配置已清除"):g({message:"已退出登录"},"auth logout",n,{quiet:o.quiet})}catch(e){$(e,"auth logout",n)}})),t}const x={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 A(n){const e=n||process.env.ECS_ENV||"prod";return{request_url:x[e]||x.prod,client_id:"e4757e933b6486c08ed206ecb6d5d9e684fcb4e2",client_secret:"test"===e?"b11b8fff1c75a5d227c8cc93aaeb0bb70c8eee47":"885fd3231f1c1e37c9f462261a09b8c38cde0c2b"}}function N(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,r=o,s.createHash("md5").update(r,"utf8").digest("hex");var r}const W="production"===process.env.NODE_ENV,Y="true"===process.env.LOG_TO_FILE,F=process.env.LOG_FILE_PATH||"/data/logs/ecs-mcp/app.log",U=(process.env.LOG_TIME_FORMAT||"epoch").toLowerCase();function T(){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 q;if(Y)q=i.destination({dest:F,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"),r=e?"true"===n.toLowerCase():o;q=i.destination({fd:r?2:1,minLength:256,sync:!0})}const B=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?T:i.stdTimeFunctions.isoTime},q);function j(){try{q?.flushSync?.()}catch{}try{q?.end?.()}catch{}}async function K(n,e){const t=function(n){let e="",t="",o="",r="";n&&(n.apiKey&&(e=n.apiKey),n.subChannel&&(t=n.subChannel),n.q&&(o=n.q),n.t&&(r=n.t)),e||(e=process.env.API_KEY||""),t||(t=process.env.SUB_CHANNEL||"open");const{client_id:s,client_secret:i}=A(n?.ecsEnv);return{apiKey:e,clientId:s,clientSecret:i,subChannel:t,q:o,t:r}}(e);if(!t.apiKey&&!t.q&&!t.t)throw new Error("未配置API_KEY环境变量");try{const{request_url:o}=A(e?.ecsEnv),r=t.subChannel,s=new URL(o);let i={};const a=n.extraParams;a&&"file-upload-stdio"===a.toolName?(i.method=a.method,i.client_id=a.clientId,i.client_secret=a.clientSecret,i.qid=a.qid,i.grant_type=a.grantType,i.sub_channel=r):(i.method="Oauth.getAccessTokenByApiKey",i.client_id=t.clientId,i.client_secret=t.clientSecret,i.grant_type="authorization_code",i.sub_channel=r,t.apiKey&&(i.api_key=t.apiKey)),Object.entries(i).forEach((([n,e])=>{s.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(s.toString(),{method:"GET",headers:c});if(!l.ok)throw new Error(`鉴权请求失败,状态码: ${l.status}`);const d=await l.json();if(0!==d.errno)throw new Error(`鉴权请求返回错误: ${d.errmsg}`);const{access_token:u,qid:m,token:f}=d.data;return{access_token:u,qid:m,token:f,sub_channel:r}}catch(n){throw B.error({err:n},"获取鉴权信息失败"),n}}async function V(n={},e){B.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 K(n,e);if(n.extraParams&&"file-upload-stdio"===n.extraParams.toolName)return t.qid=n.extraParams.qid,t;const o=N(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}process.on("uncaughtException",(n=>{B.error({err:n},"uncaught exception"),j(),process.exit(1)})),process.on("unhandledRejection",(n=>{B.error({err:n},"unhandled rejection"),j(),process.exit(1)})),process.on("SIGINT",(()=>{j(),process.exit(0)})),process.on("SIGTERM",(()=>{j(),process.exit(0)})),process.on("beforeExit",(()=>{j()}));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 V(n,e);return t.request_url=A(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(),r=new AbortController,s=setTimeout((()=>r.abort()),this.timeout),i={...o,signal:r.signal};try{const n=await fetch(t,i);if(clearTimeout(s),!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(s),e<this.retries&&this.isRetryable(t)){const o=this.retryDelay*Math.pow(2,e);return B.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 r=new URLSearchParams;return Object.entries(t).forEach((([n,e])=>{r.append(n,String(e))})),{url:o.toString(),init:{method:"POST",headers:{"Access-Token":n.access_token||"","Content-Type":"application/x-www-form-urlencoded"},body:r}}}))}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 B}}class G extends J{async info(){const n=await this.getAuth({}),e=this.buildBaseParams(n,"User.getUserDetail");return e.sign="",await this.apiGet(n,e)}}class H extends J{async list(n={}){const{path:e="/",page:t=0,page_size:o=50}=n;let r=e||"/";"/"!==r&&(r.startsWith("/")||(r="/"+r),r.endsWith("/")||(r+="/"));const s={path:r,page:t,page_size:o},i=await this.getAuth({method:"File.getList",extraParams:s}),a={...this.buildBaseParams(i,"File.getList")};for(const[n,e]of Object.entries(s))"access_token"!==n&&(a[n]=String(e));return await this.apiGet(i,a)}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 Q="未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量";async function X(){if(process.stdin.isTTY){if("win32"===process.platform)throw new Error("--stdin 模式需要管道输入。Windows 示例:cmd 使用 type report.md | 360disk file save --stdin,PowerShell 使用 Get-Content report.md -Raw | 360disk file save --stdin");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")}async function Z(){return function(n){let e=String(n||"").trim();e=e.replace(/^\uFEFF/,""),e.startsWith("-e ")&&(e=e.slice(3).trim(),(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))&&(e=e.slice(1,-1)));return e.replace(/\\r\\n/g,"\n").replace(/\\n/g,"\n")}(await X()).split(/\r?\n/).map((n=>n.trim())).filter(Boolean)}class nn extends J{async move(n){const{src_name:e,new_name:t}=n,o={src_name:e,new_name:t},r=await this.getAuth({method:"File.move",extraParams:o}),s=this.buildBaseParams(r,"File.move");return await this.apiPost(r,{},{...s,src_name:e,new_name:t})}async rename(n){const{src_name:e,new_name:t}=n,o={src_name:e,new_name:t},r=await this.getAuth({method:"File.rename",extraParams:o}),s=this.buildBaseParams(r,"File.rename");return await this.apiPost(r,{},{...s,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})}async transOrCopy(n){const{src_name:e,new_path:t,is_delete:o,is_replace:r=0,src_ks_id:s,new_ks_id:i}=n;if(0!==o&&1!==o)throw new Error("is_delete 仅支持 0 或 1");if(0!==r&&1!==r)throw new Error("is_replace 仅支持 0 或 1");const a={src_name:e,new_path:t,is_delete:o},c=await this.getAuth({method:"File.transOrCopy",extraParams:a}),l={...this.buildBaseParams(c,"File.transOrCopy"),src_name:e,new_path:t,is_delete:String(o),is_replace:String(r)};return s&&(l.src_ks_id=s),i&&(l.new_ks_id=i),await this.apiPost(c,{},l)}async countOriginSize(n){const{path:e}=n;if(!e||!e.startsWith("/"))throw new Error("path 必须以 / 开头");const t={path:e},o=await this.getAuth({method:"File.countFileOriginSize",extraParams:t}),r=this.buildBaseParams(o,"File.countFileOriginSize");return await this.apiPost(o,{},{...r,path:e})}async clearDir(n){const{fname:e}=n;if(!e)throw new Error("fname 不能为空");const t=await this.getAuth({method:"File.clearDir"}),o=this.buildBaseParams(t,"File.clearDir");return await this.apiPost(t,{},{...o,fname:e})}async config(n){const{path:e,command:t,type:o,key:r,value:s,content:i}=n;if(!e||!e.startsWith("/"))throw new Error("path 必须以 / 开头");const a={path:e,command:t,type:o},c=await this.getAuth({method:"MCP.config",extraParams:a}),l={...this.buildBaseParams(c,"MCP.config"),path:e,command:t,type:o};return void 0!==r&&(l.key=r),void 0!==s&&(l.value=s),void 0!==i&&(l.content=i),await this.apiPost(c,{},l)}}const en=["http:","https:"],tn=/[\\@]/,on=[/^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],rn=[22,23,25,53,135,139,445,1433,1521,3306,3389,5432,6379,9200,11211,27017];async function sn(n,e=5e3){const t=function(n){if(!n||"string"!=typeof n)return{isValid:!1,error:"URL不能为空"};try{const t=n.trim();if(tn.test(t))return{isValid:!1,error:"URL包含不安全的字符"};const o=new URL(t);if(!en.includes(o.protocol))return{isValid:!1,error:"不支持的协议,只支持 http/https"};const r=o.hostname.toLowerCase();if(!r)return{isValid:!1,error:"主机名不能为空"};for(const n of on)if(n.test(r))return{isValid:!1,error:"禁止访问受限地址"};if(an(r)&&!cn(r))return{isValid:!1,error:"禁止访问受限IP地址"};if(o.port){const n=parseInt(o.port,10);if(rn.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:r}}catch(n){return{isValid:!1,error:"URL格式无效"}}var e}(n);if(!t.isValid)return t;try{const o=new URL(n).hostname.toLowerCase();if(an(o))return{...t,resolvedIP:o,originalHostname:o,needsHostHeader:!1};let r=[];try{const n=a.lookup(o,{all:!0}),t=new Promise(((n,t)=>{setTimeout((()=>t(new Error("DNS解析超时"))),e)}));r=(await Promise.race([n,t])).map((n=>n.address))}catch(n){return{isValid:!1,error:"域名解析失败"}}for(const n of r){for(const e of on)if(e.test(n))return{isValid:!1,error:"域名解析到受限IP地址"};if(!cn(n))return{isValid:!1,error:"域名解析到受限IP地址"}}return{isValid:!0,normalizedUrl:t.normalizedUrl,resolvedIP:r[0],resolvedIPs:r,originalHostname:o,needsHostHeader:!1}}catch(n){return{isValid:!1,error:"URL验证过程中发生错误"}}}function an(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 cn(n){for(const e of on)if(e.test(n))return!1;return!0}class ln 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:r=20}=n;if(!e&&-1===t)throw new Error("必须提供搜索关键词(key)或指定文件类型(file_category)");const s={file_category:t,key:e,page:o,page_size:r},i=await this.getAuth({method:"File.searchList",extraParams:s}),a=this.buildBaseParams(i,"File.searchList"),c={};for(const[n,e]of Object.entries(s))"access_token"!==n&&(c[n]=String(e));return await this.apiPost(i,a,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 r=await this.getAuth({method:"MCP.getDownLoadUrl",extraParams:o}),s={...this.buildBaseParams(r,"MCP.getDownLoadUrl")};return void 0!==e?s.nid=String(e):t&&(s.fpath=t),await this.apiPost(r,{},s)}async getNodeInfoByNid(n){const{nid:e,ks_ext:t=0}=n;if(!e)throw new Error("nid 不能为空");if(0!==t&&1!==t)throw new Error("ks_ext 仅支持 0 或 1");const o={nid:e},r=await this.getAuth({method:"File.getNodeInfoByNid",extraParams:o}),s=this.buildBaseParams(r,"File.getNodeInfoByNid");return await this.apiGet(r,{...s,nid:e,ks_ext:String(t)})}async save(n){const{url:e,content:t,upload_path:o,file_name:r,is_rename:s=1}=n;if(!e&&!t||e&&t)throw new Error("url 与 content 互斥,必须且只能传一个");if(0!==s&&1!==s)throw new Error("is_rename 仅支持 0 或 1");if(e){const n=e.split("|").map((n=>n.trim())).filter(Boolean);if(0===n.length)throw new Error("url 不能为空");for(const e of n){const n=await sn(e,5e3);if(!n.isValid)throw new Error(`URL安全验证失败: ${n.error}`)}}const i=await this.getAuth({method:"MCP.saveFile"}),a=this.buildBaseParams(i,"MCP.saveFile"),c={};o&&(c.upload_path=o),e?c.url=e:t&&(c.content=t),r&&(c.file_name=r),c.is_rename=String(s);const l=await this.apiPost(i,a,c);if(l&&0===l.errno){const n=Array.isArray(l.data)?l.data:l.data?[l.data]:[],e=[],t=[];if(0===n.length)throw new Error("保存文件失败: 未获取到任何任务信息");for(const o of n){const n=o.task_id;if(n)try{const t=await this.pollTaskStatus(i,n);e.push(t)}catch(e){t.push(`任务 ${n} 失败: ${e.message}`)}else t.push(`URL ${o.url||"未知"} 未获取到任务ID`)}if(0===e.length&&t.length>0)throw new Error(`文件保存失败: ${t.join("; ")}`);return 1===e.length?e[0]:{errno:0,errmsg:t.length>0?`部分文件保存失败: ${t.join("; ")}`:"",data:{tasks:e,is_multi:!0}}}throw new Error(l?.errmsg||"API请求失败")}async appendContent(n){const{path:e,content:t}=n;if(!e||!e.startsWith("/"))throw new Error("path 必须以 / 开头");if("string"!=typeof t||0===t.length)throw new Error("content 不能为空");const o={path:e,content:t},r=await this.getAuth({method:"File.appendContent",extraParams:o}),s=this.buildBaseParams(r,"File.appendContent");return await this.apiPost(r,{},{...s,path:e,content:t})}async detectFileExists(n){const{path:e,files:t}=n;if(!e||!e.startsWith("/"))throw new Error("path 必须以 / 开头");if(!Array.isArray(t)||0===t.length)throw new Error("files 不能为空");for(const n of t){if(!n?.fname||"string"!=typeof n.fname)throw new Error("files[].fname 必须为非空字符串");if(!Number.isFinite(n.fsize)||n.fsize<0)throw new Error("files[].fsize 必须为大于等于 0 的数字")}const o=JSON.stringify(t),r={path:e,data:o},s=await this.getAuth({method:"Sync.detectFileExists",extraParams:r}),i=this.buildBaseParams(s,"Sync.detectFileExists");return await this.apiGet(s,{...i,path:e,data:o})}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 r,s=0;for(;s<o;){if(s++,r=await this.queryTaskStatus(n,e),0!==r.errno)throw new Error(r.errmsg||"查询任务状态失败");const o=r.data?.status;if(2===o)return r;if(3===o)throw new Error(r.data?.error||"文件保存失败");await new Promise((n=>setTimeout(n,t)))}return r}async upload(n){const{filePaths:t,uploadPath:o="/"}=n;if(!t||0===t.length)throw new Error("filePaths 为必填参数且不能为空");for(const n of t)if(!e.existsSync(n))throw new Error(`文件不存在: ${n}`);const r=await this.getAuth({});let s;try{const n=await import("@aicloud360/sec-sdk-node");s=n.UploadNode}catch{throw new Error("请先安装 @aicloud360/sec-sdk-node: npm install @aicloud360/sec-sdk-node")}const i={qid:r.qid||"0",token:r.token||"",access_token:r.access_token||"",env:this.ecsEnv,path:o};return new Promise(((n,e)=>{const r=Date.now(),a=[],c=[],l=[];new s(i,{success:n=>{a.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===a.length?e(new Error(`所有文件上传失败: ${c.map((n=>`${n.fileName}: ${n.error}`)).join("; ")}`)):n({uploadResults:a,uploadErrors:c,duplicateFiles:l,totalTime:((Date.now()-r)/1e3).toFixed(2),fileCount:a.length,totalFileCount:t.length})}}).addWaitFile(t)}))}async download(n){const{nid:e,auto:o=!0,downloadDir:s}=n,i=s||t.join(r(),".mcp-downloads"),a=await this.getAuth({method:"Sync.getVerifiedDownLoadUrl",extraParams:{nid:e}}),c={...this.buildBaseParams(a,"Sync.getVerifiedDownLoadUrl"),nid:String(e)},d=await this.apiGet(a,c);if(!d||0!==d.errno)throw new Error(d?.errmsg||"API请求失败");const u=d.data?.downloadUrl||"";if(!u)throw new Error("未能获取到文件下载链接");const{filename:m,sizeMB:f}=this.extractFileInfoFromUrl(u),h=f>0?`${f.toFixed(2)} MB`:"未知大小";if(!o)return{downloadUrl:u,filename:m,fileSize:h,downloadPath:""};await l.mkdir(i,{recursive:!0});const p=t.join(i,m);return f>10?(this.spawnBackgroundDownload(u,p),{downloadUrl:u,filename:m,fileSize:h,downloadPath:p,background:!0}):(await this.downloadWithCurl(u,p),{downloadUrl:u,filename:m,fileSize:h,downloadPath:p})}extractFileInfoFromUrl(n){try{const e=new URL(n);let o="downloaded_file";const r=e.searchParams.get("fname");if(r?o=t.basename(decodeURIComponent(r)):e.pathname&&e.pathname.length>1&&(o=t.basename(e.pathname)),o=o.replace(/[\x00/]/g,"_"),o&&"."!==o||(o="downloaded_file"),o.length>200){const n=t.extname(o);o=t.basename(o,n).substring(0,200-n.length)+n}const s=e.searchParams.get("fsize");return{filename:o,sizeMB:s?parseInt(s,10)/1048576:0}}catch{return{filename:"downloaded_file",sizeMB:0}}}downloadWithCurl(n,e,t=3e5){return new Promise(((o,r)=>{const s=c("curl",["-L","-s","-A","yunpan_mcp_server",n,"-o",e]),i=setTimeout((()=>{s.kill(),r(new Error("下载超时"))}),t);s.on("close",(n=>{clearTimeout(i),0===n?o():r(new Error(`下载失败,curl 退出码: ${n}`))})),s.on("error",(n=>{clearTimeout(i),r(n)}))}))}spawnBackgroundDownload(n,e){c("curl",["-L","-s","-A","yunpan_mcp_server",n,"-o",e],{detached:!0,stdio:"ignore"}).unref()}}const dn="未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量";function un(n,e,t,o,r){"text"===o.format&&r?_(r(n)):g(n,e,t,{quiet:o.quiet})}async function mn(n,e){const t=await n.delete(e);if(3008===t?.errno&&!e.endsWith("/")){const t=await n.delete(e+"/");if(0===t?.errno)return t}return t}function fn(n){return"string"!=typeof n?n:n.replace(/\\r\\n/g,"\r\n").replace(/\\n/g,"\n")}function hn(){const e=new n("file").description("文件操作");return e.command("mv").description("移动文件或文件夹").argument("<src>","源路径,多个用 | 分隔").argument("<dest>","目标文件夹路径").action((async(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file mv",o);const s=new nn(e),i=await async function(n,e,t){const o=await n.move({src_name:e,new_name:t}),r=!e.includes("|");if(3008===o?.errno&&r&&!e.endsWith("/")){const o=await n.move({src_name:e+"/",new_name:t});if(0===o?.errno)return o}return o}(s,n,t);y(i,"file mv",o),un(i,"file mv",o,r,(e=>S(e,`已移动: ${n} -> ${t}`)))}catch(n){$(n,"file mv",o)}})),e.command("trans-copy").description("转移或复制文件到目标目录").argument("<src>","源路径,多个用 | 分隔").argument("<dest>","目标目录路径").option("--delete <0|1>","是否删除源文件:1=转移,0=复制","1").option("--replace <0|1>","同名文件处理:0=重命名,1=覆盖","0").option("--src-ks-id <id>","源文件所在群组 ks_id(可选)").option("--new-ks-id <id>","目标目录所在群组 ks_id(可选)").action((async(n,t,o)=>{const r=Date.now(),s=e.parent?.opts()||{};try{const e=h(s);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file trans-copy",r);const i=parseInt(o.delete,10),a=parseInt(o.replace,10);if(![0,1].includes(i))return void $(new w("--delete 仅支持 0 或 1",p.INVALID_ARGS),"file trans-copy",r);if(![0,1].includes(a))return void $(new w("--replace 仅支持 0 或 1",p.INVALID_ARGS),"file trans-copy",r);if(!t.startsWith("/"))return void $(new w("dest 必须以 / 开头",p.INVALID_ARGS),"file trans-copy",r);const c=new nn(e),l=await c.transOrCopy({src_name:n,new_path:t,is_delete:i,is_replace:a,src_ks_id:o.srcKsId,new_ks_id:o.newKsId});y(l,"file trans-copy",r),un(l,"file trans-copy",r,s,(e=>S(e,`${1===i?"转移":"复制"}完成: ${n} -> ${t}`)))}catch(n){$(n,"file trans-copy",r)}})),e.command("rename").description("重命名文件或文件夹").argument("<path>","原文件完整路径").argument("<new_name>","新名称").action((async(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file rename",o);const s=function(n,e){const t=String(n||"").trim();let o=String(e||"").trim();const r=t.endsWith("/");if(!t.startsWith("/"))throw new w("path 必须以 / 开头",p.INVALID_ARGS);if(!o)throw new w("new_name 不能为空",p.INVALID_ARGS);if(r){o=o.replace(/^\/+/,"");const n=o.replace(/\/+$/,"").split("/").filter(Boolean);if(0===n.length)throw new w("new_name 不能为空(目录场景需传新目录名,如 bbb_renamed/)",p.INVALID_ARGS);return o=n[n.length-1]+"/",{src_name:t,new_name:o,isDir:!0}}if(o.includes("/"))throw new w("new_name 仅支持文件名(不含路径)。若要改路径请使用 file mv",p.INVALID_ARGS);return{src_name:t,new_name:o,isDir:!1}}(n,t),i=new nn(e),a=await i.rename({src_name:s.src_name,new_name:s.new_name});y(a,"file rename",o),un(a,"file rename",o,r,(n=>S(n,`已重命名: ${s.src_name} -> ${s.new_name}`)))}catch(n){$(n,"file rename",o)}})),e.command("rm").description("删除文件或文件夹").argument("<path>","文件路径,多个用 | 分隔").option("--batch","批量模式:从 stdin 读取路径列表(每行一个)").action((async(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file rm",o);const s=new nn(e);if(t.batch){const e=await Z(),t=[n,...e].filter(Boolean),{executeBatch:i,formatBatchResult:a}=await Promise.resolve().then((function(){return M}));un(await i(t,(async n=>{const e=await mn(s,n);if(e&&"number"==typeof e.errno&&0!==e.errno){throw new Error(e.errmsg?`${e.errmsg} (errno: ${e.errno})`:`API 错误 (errno: ${e.errno})`)}return e})),"file rm --batch",o,r,(n=>a(n)))}else{const e=await mn(s,n);y(e,"file rm",o),un(e,"file rm",o,r,(e=>S(e,`已删除: ${n}`)))}}catch(n){$(n,"file rm",o)}})),e.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(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file search",o);const s=new ln(e),i=await s.search({key:n,file_category:parseInt(t.type),page:parseInt(t.page),page_size:parseInt(t.size)});y(i,"file search",o),un(i,"file search",o,r,I)}catch(n){$(n,"file search",o)}})),e.command("share").description("生成文件分享链接").argument("<paths>","文件路径,多个用 | 分隔").action((async n=>{const t=Date.now(),o=e.parent?.opts()||{};try{const e=h(o);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file share",t);const r=new ln(e),s=await r.share(n);y(s,"file share",t),un(s,"file share",t,o,(n=>S(n,"分享链接已生成")))}catch(n){$(n,"file share",t)}})),e.command("url").description("获取文件下载链接").argument("<path>","文件路径").option("--nid <nid>","文件 NID(与路径二选一)").action((async(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file url",o);const s=new ln(e),i=await s.getDownloadUrl({nid:t.nid,fpath:t.nid?void 0:n});y(i,"file url",o),un(i,"file url",o,r,(n=>S(n,"下载链接已获取")))}catch(n){$(n,"file url",o)}})),e.command("node-info").description("根据 nid 获取节点信息(可选返回 ks_info)").argument("<nid>","节点 nid(仅支持文件夹/知识库)").option("--ks-ext <0|1>","是否返回 ks_info:0=不返回,1=返回","0").action((async(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file node-info",o);const s=parseInt(t.ksExt,10);if(![0,1].includes(s))return void $(new w("--ks-ext 仅支持 0 或 1",p.INVALID_ARGS),"file node-info",o);const i=new ln(e),a=await i.getNodeInfoByNid({nid:n,ks_ext:s});y(a,"file node-info",o),un(a,"file node-info",o,r,(e=>S(e,`节点信息获取成功: ${n}`)))}catch(n){$(n,"file node-info",o)}})),e.command("origin-size").description("统计目录下文件和文件夹(递归)原始大小").argument("<path>","目录完整路径").action((async(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file origin-size",o);if(!n.startsWith("/"))return void $(new w("path 必须以 / 开头",p.INVALID_ARGS),"file origin-size",o);const t=new nn(e),s=await t.countOriginSize({path:n});y(s,"file origin-size",o),un(s,"file origin-size",o,r,(e=>S(e,`目录大小统计完成: ${n}`)))}catch(n){$(n,"file origin-size",o)}})),e.command("clear-dir").description("清空目录下文件,保留目录本身").argument("<paths>","目录路径,多个用 | 分隔").action((async(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file clear-dir",o);const t=new nn(e),s=await t.clearDir({fname:n});y(s,"file clear-dir",o),un(s,"file clear-dir",o,r,(e=>S(e,`目录清空完成: ${n}`)))}catch(n){$(n,"file clear-dir",o)}})),e.command("config").description("读取/写入/列出配置文件(INI/JSON/YAML)").requiredOption("--path <path>","配置文件路径,必须以 / 开头").requiredOption("--command <cmd>","操作命令:config:get|config:set|config:delete|config:list|config:read|config:write").requiredOption("--type <type>","配置类型:ini|json|yaml|yml").option("--key <key>","键路径,如 a.b.c").option("--value <value>","写入值(set 场景)").option("--content <text>","写入完整内容(write 场景,与 --stdin 互斥)").option("--stdin","从标准输入读取 content(write 场景,与 --content 互斥)").action((async n=>{const t=Date.now(),o=e.parent?.opts()||{};try{const e=h(o);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file config",t);if(!n.path.startsWith("/"))return void $(new w("--path 必须以 / 开头",p.INVALID_ARGS),"file config",t);if(!new Set(["config:get","config:set","config:delete","config:list","config:read","config:write"]).has(n.command))return void $(new w("--command 非法",p.INVALID_ARGS),"file config",t);if(!new Set(["ini","json","yaml","yml"]).has(n.type))return void $(new w("--type 非法",p.INVALID_ARGS),"file config",t);if("config:write"===n.command){if(1!==[n.content,n.stdin].filter(Boolean).length)return void $(new w("config:write 场景下 --content 与 --stdin 互斥,必须且只能传一个",p.INVALID_ARGS),"file config",t)}else if(n.content||n.stdin)return void $(new w("--content/--stdin 仅在 config:write 场景使用",p.INVALID_ARGS),"file config",t);let r=fn(n.content);if(n.stdin&&(r=await X(),!r.trim()))return void $(new w("stdin 输入内容为空",p.INVALID_ARGS),"file config",t);const s=new nn(e),i=await s.config({path:n.path,command:n.command,type:n.type,key:n.key,value:n.value,content:r});y(i,"file config",t),un(i,"file config",t,o,(e=>S(e,`配置操作完成: ${n.command}`)))}catch(n){$(n,"file config",t)}})),e.command("save").description("通过 URL 或文本内容保存文件到云盘").option("--url <url>","文件下载地址,支持多个URL以英文竖线(|)分隔(与 --content/--stdin 互斥)").option("--content <text>","文件内容(与 --url/--stdin 互斥)").option("--stdin","从标准输入读取内容(与 --url/--content 互斥)").option("--dest <path>","云盘存储路径,必须以 / 开头和结尾").option("--filename <name>","保存的文件名").option("--rename <0|1>","同名文件处理策略:0=直接替换,1=自动重命名","1").action((async n=>{const t=Date.now(),o=e.parent?.opts()||{};try{const e=h(o);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file save",t);if(1!==[n.url,n.content,n.stdin].filter(Boolean).length)return void $(new w("--url、--content、--stdin 三者互斥,必须且只能传一个",p.INVALID_ARGS),"file save",t);const r=parseInt(n.rename,10);if(![0,1].includes(r))return void $(new w("--rename 仅支持 0 或 1",p.INVALID_ARGS),"file save",t);let s=fn(n.content);if(n.stdin&&(s=await X(),!s.trim()))return void $(new w("stdin 输入内容为空",p.INVALID_ARGS),"file save",t);const i=new ln(e),a=await i.save({url:n.url,content:s,upload_path:n.dest,file_name:n.filename,is_rename:r});y(a,"file save",t),un(a,"file save",t,o,(n=>S(n,"文件保存成功")))}catch(n){$(n,"file save",t)}})),e.command("append").description("向云盘文本文件末尾追加内容").argument("<path>","文件完整路径,必须以 / 开头").option("--content <text>","追加的文本内容(与 --stdin 互斥)").option("--stdin","从标准输入读取追加内容(与 --content 互斥)").action((async(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file append",o);if(!n.startsWith("/"))return void $(new w("path 必须以 / 开头",p.INVALID_ARGS),"file append",o);if(1!==[t.content,t.stdin].filter(Boolean).length)return void $(new w("--content 与 --stdin 互斥,必须且只能传一个",p.INVALID_ARGS),"file append",o);let s=fn(t.content);if(t.stdin&&(s=await X(),!s||!s.trim()))return void $(new w("stdin 输入内容为空",p.INVALID_ARGS),"file append",o);const i=new ln(e),a=await i.appendContent({path:n,content:s});y(a,"file append",o),un(a,"file append",o,r,(e=>S(e,`已追加内容到: ${n}`)))}catch(n){$(n,"file append",o)}})),e.command("exists").description("检测目录下是否存在同名文件").option("--path <path>","目标目录,必须以 / 开头").option("--files <json>",'待检测文件数组 JSON,例如 [{"fname":"a.txt","fsize":123}](与 --stdin 互斥)').option("--stdin","从标准输入读取 files JSON(与 --files 互斥)").action((async n=>{const t=Date.now(),o=e.parent?.opts()||{};try{const e=h(o);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file exists",t);if(!n.path||!n.path.startsWith("/"))return void $(new w("--path 必须以 / 开头",p.INVALID_ARGS),"file exists",t);if(1!==[n.files,n.stdin].filter(Boolean).length)return void $(new w("--files 与 --stdin 互斥,必须且只能传一个",p.INVALID_ARGS),"file exists",t);const r=function(n){let e;try{e=JSON.parse(n)}catch{throw new w('files 必须是合法 JSON(示例: [{"fname":"a.txt","fsize":123}])',p.INVALID_ARGS)}if(!Array.isArray(e)||0===e.length)throw new w("files 必须是非空数组",p.INVALID_ARGS);for(const n of e){if(!n||"string"!=typeof n.fname||""===n.fname.trim())throw new w("files[].fname 必须为非空字符串",p.INVALID_ARGS);if(!Number.isFinite(n.fsize)||n.fsize<0)throw new w("files[].fsize 必须为大于等于 0 的数字",p.INVALID_ARGS)}return e}(n.stdin?await X():n.files),s=new ln(e),i=await s.detectFileExists({path:n.path,files:r});y(i,"file exists",t),un(i,"file exists",t,o,(e=>S(e,`检测完成: ${n.path}`)))}catch(n){$(n,"file exists",t)}})),e.command("upload").description("上传本地文件到云盘").argument("<files>","本地文件路径,多个用逗号分隔").option("--dest <path>","云盘目标路径","/").action((async(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file upload",o);const s=n.split(",").map((n=>n.trim())),i=new ln(e);un(await i.upload({filePaths:s,uploadPath:t.dest}),"file upload",o,r,(n=>S(n,"上传完成")))}catch(n){$(n,"file upload",o)}})),e.command("download").description("下载云盘文件到本地").argument("<nid>","文件 NID(可通过 file search / dir ls 获取)").option("--dir <path>","本地下载目录").option("--no-auto","仅获取下载链接,不自动下载").action((async(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(dn,p.AUTH_ERROR),"file download",o);const s=new ln(e);un(await s.download({nid:n,auto:t.auto,downloadDir:t.dir}),"file download",o,r,(n=>S(n,"下载完成")))}catch(n){$(n,"file download",o)}})),e}function pn(){return t.join(r(),".360disk")}function wn(){const n=process.env.SHELL||"";return n.includes("zsh")?"zsh":n.includes("bash")?"bash":"unknown"}function gn(n){const e=r();return t.join(e,"zsh"===n?".zshrc":".bashrc")}(async function(){const r=function(){try{const n=t.dirname(o(import.meta.url)),r=[t.resolve(n,"../package.json"),t.resolve(n,"../../package.json"),t.resolve(n,"../../../package.json"),t.resolve(process.cwd(),"package.json")];for(const n of r)if(e.existsSync(n)){const t=JSON.parse(e.readFileSync(n,"utf8"));return{name:t.cli?.name||"360disk",version:t.version||"0.0.0",description:t.cli?.description||"360 AI 云盘 CLI"}}}catch{}return{name:"360disk",version:"0.0.0",description:"360 AI 云盘 CLI"}}(),s=new n;s.name(r.name).description(r.description).version(r.version).enablePositionalOptions().passThroughOptions().option("--api-key <key>","API 密钥").option("--env <env>","环境 (prod/test)").option("--sub-channel <channel>","子渠道").option("--format <type>","输出格式 (json|text)","json").option("--quiet","仅输出结果数据").option("--timeout <ms>","请求超时时间(毫秒)","30000").option("--retries <n>","失败重试次数","0"),s.addCommand(D()),s.addCommand(function(){const e=new n("user").description("用户信息");return e.command("info").description("获取用户详细信息").action((async()=>{const n=Date.now(),t=e.parent?.opts()||{};try{const e=h(t);if(!e.apiKey)return void $(new w("未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量",p.AUTH_ERROR),"user info",n);const o=new G(e),r=await o.info();y(r,"user info",n),"text"===t.format?_(z(r)):g(r,"user info",n,{quiet:t.quiet})}catch(e){$(e,"user info",n)}})),e}()),s.addCommand(function(){const e=new n("dir").description("目录操作");return e.command("ls").description("列出云盘指定目录下的文件和文件夹").argument("[path]","目录路径","/").option("--page <n>","页码","0").option("--size <n>","每页数量","50").action((async(n,t)=>{const o=Date.now(),r=e.parent?.opts()||{};try{const e=h(r);if(!e.apiKey)return void $(new w(Q,p.AUTH_ERROR),"dir ls",o);const s=new H(e),i=await s.list({path:n,page:parseInt(t.page),page_size:parseInt(t.size)});y(i,"dir ls",o),"text"===r.format?_(C(i)):g(i,"dir ls",o,{quiet:r.quiet})}catch(n){$(n,"dir ls",o)}})),e.command("mkdir").description("创建新文件夹").argument("<path>","文件夹路径").action((async n=>{const t=Date.now(),o=e.parent?.opts()||{};try{const e=h(o);if(!e.apiKey)return void $(new w(Q,p.AUTH_ERROR),"dir mkdir",t);const r=new H(e),s=await r.mkdir(n);y(s,"dir mkdir",t),"text"===o.format?_(S(s,`已创建目录: ${n}`)):g(s,"dir mkdir",t,{quiet:o.quiet})}catch(n){$(n,"dir mkdir",t)}})),e}()),s.addCommand(hn()),s.addCommand(function(){const o=new n("completion").description("Shell 自动补全");return o.command("install").description("安装补全脚本到当前 shell").option("--bash","安装 bash 补全").option("--zsh","安装 zsh 补全").action((n=>{try{const o=n.bash?"bash":n.zsh?"zsh":wn();if("unknown"===o)return void $("无法检测 shell 类型,请使用 --bash 或 --zsh 指定","completion install");const r="zsh"===o?"#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',s="zsh"===o?"zsh":"bash",i=pn(),a=t.join(i,`completion.${s}`);e.existsSync(i)||e.mkdirSync(i,{recursive:!0,mode:448}),e.writeFileSync(a,r,{mode:420});const c=gn(o),l=function(n){return`source ~/.360disk/completion.${"zsh"===n?"zsh":"bash"} # 360disk auto-completion`}(o);let d="";e.existsSync(c)&&(d=e.readFileSync(c,"utf8")),d.includes("360disk auto-completion")?_(`补全脚本已更新: ${a}\n配置已存在于 ${c},无需重复添加`):(e.appendFileSync(c,"\n"+l+"\n"),_(`补全脚本已安装:\n 脚本: ${a}\n 配置: ${c}\n\n请执行 source ${c} 或重新打开终端生效`))}catch(n){$(n,"completion install")}})),o.command("uninstall").description("卸载补全脚本").option("--bash","卸载 bash 补全").option("--zsh","卸载 zsh 补全").action((n=>{try{const o=n.bash?"bash":n.zsh?"zsh":wn();if("unknown"===o)return void $("无法检测 shell 类型,请使用 --bash 或 --zsh 指定","completion uninstall");const r="zsh"===o?"zsh":"bash",s=t.join(pn(),`completion.${r}`);e.existsSync(s)&&e.unlinkSync(s);const i=gn(o);if(e.existsSync(i)){const n=e.readFileSync(i,"utf8").split("\n").filter((n=>!n.includes("360disk auto-completion")));e.writeFileSync(i,n.join("\n"))}_(`补全脚本已卸载\n请执行 source ${gn(o)} 或重新打开终端生效`)}catch(n){$(n,"completion uninstall")}})),o.command("script").description("输出补全脚本到 stdout(手动安装用)").option("--bash","输出 bash 补全脚本").option("--zsh","输出 zsh 补全脚本").action((n=>{try{const e=n.bash?"bash":n.zsh?"zsh":wn();if("unknown"===e)return void $("无法检测 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){$(n,"completion script")}})),o}()),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{Command as n}from"commander";import e from"fs";import t from"path";import{fileURLToPath as r}from"url";import{homedir as o}from"os";import i from"crypto";import s from"pino";import a from"iconv-lite";import{promises as c}from"dns";import{spawn as l}from"child_process";import u from"fs/promises";function d(){const n=function(){try{const n=t.dirname(new URL(import.meta.url).pathname),r=[t.resolve(n,"../../package.json"),t.resolve(n,"../../../package.json")];for(const n of r)if(e.existsSync(n)){const t=JSON.parse(e.readFileSync(n,"utf8"));if(t.cli?.name)return t.cli.name}}catch{}return"360disk"}();return t.join(o(),`.${n}`)}function m(){return t.join(d(),"config.json")}function f(){try{const n=m();if(e.existsSync(n))return JSON.parse(e.readFileSync(n,"utf8"))}catch{}return{}}function h(n){!function(){const n=d();e.existsSync(n)||e.mkdirSync(n,{recursive:!0,mode:448})}();const t=m();e.writeFileSync(t,JSON.stringify(n,null,2),{mode:384})}function p(n){const e=f();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 g extends Error{code;constructor(n,e=w.GENERAL){super(n),this.name="CLIError",this.code=e}}function _(n,e,t,r){if(r?.quiet)return void process.stdout.write(JSON.stringify(n)+"\n");const o={success:!0,result:n,meta:{duration_ms:Date.now()-t,command:e}};process.stdout.write(JSON.stringify(o,null,2)+"\n")}function $(n){process.stdout.write(n+"\n")}function y(n,e,t){const r=n instanceof Error?n.message:n;let o;o=n instanceof g?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 i={success:!1,error:r,code:o,meta:{duration_ms:t?Date.now()-t:0,command:e}};process.stderr.write(JSON.stringify(i,null,2)+"\n"),process.exit(o||1)}function P(n,e,t){if(n&&"number"==typeof n.errno&&0!==n.errno){const r=function(n){switch(n){case 1001:case 1002:case 1003:case 3001:case 3002:case 3003:return w.AUTH_ERROR;case 3008:return w.NOT_FOUND;case 3007:case 3013:case 3129:return w.CONFLICT;case 3005:case 3006:return w.PERMISSION_DENIED;case 3010:case 3011:return w.QUOTA_EXCEEDED;default:return n>=5e3?w.SERVER_ERROR:w.GENERAL}}(n.errno);y(new g(n.errmsg?`${n.errmsg} (errno: ${n.errno})`:`API 错误 (errno: ${n.errno})`,r),e,t)}}function v(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 E(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),r=n=>String(n).padStart(2,"0");return`${t.getFullYear()}-${r(t.getMonth()+1)}-${r(t.getDate())} ${r(t.getHours())}:${r(t.getMinutes())}`}function b(n){let e=0;for(const t of n)e+=t.charCodeAt(0)>127?2:1;return e}function k(n,e){const t=e-b(n);return t>0?n+" ".repeat(t):n}function O(n,e){const t=n.map(((n,t)=>{const r=e.reduce(((n,e)=>Math.max(n,b(e[t]||""))),0);return Math.max(b(n),r)})),r=[];r.push(n.map(((n,e)=>k(n,t[e]))).join(" ")),r.push(t.map((n=>"-".repeat(n))).join(" "));for(const n of e)r.push(n.map(((n,e)=>k(n||"",t[e]))).join(" "));return r.join("\n")}function R(n){const e=n?.data;if(!e)return{nodes:[],total:0};const t=e.list||e.data||e.node_list||[],r=Array.isArray(t)?t:[];return{nodes:r,total:Number(e.total_count??e.total??r.length)||r.length,page:void 0!==e.page_num?Number(e.page_num):void 0}}function C(n){return!!n&&(1===n.is_dir||"1"===n.is_dir||"dir"===n.type||(1===n.type||"1"===n.type))}function S(n){const{nodes:e,total:t,page:r}=R(n);if(0===e.length)return"(空目录)";const o=O(["类型","名称","大小","修改时间","NID"],e.map((n=>{const e=C(n);return[e?"d":"-",n.name||n.fname||"-",e?"-":v(parseInt(n.count_size||n.size||"0")),E(n.modify_time||n.mtime),n.nid||"-"]})));let i=`共 ${t} 项`;return void 0===r||Number.isNaN(r)||(i+=`,第 ${r} 页`),o+"\n"+i}function I(n){const{nodes:e,total:t}=R(n);if(0===e.length)return"无搜索结果";return O(["类型","名称","大小","路径","NID"],e.map((n=>{const e=C(n);return[e?"d":"-",n.name||n.fname||"-",e?"-":v(parseInt(n.count_size||n.size||"0")),n.path||"-",n.nid||"-"]})))+"\n"+`共找到 ${t} 项`}function L(n,e,t){if(!n||"number"!=typeof n.errno||0!==n.errno)return z(n,t);const r=n.data;if(!r||"object"!=typeof r)return z(n,t);const o=n=>null==n?String(n):"object"==typeof n?JSON.stringify(n,null,2):String(n);if("config:read"===e&&"config"in r)return`${t}\n\n${o(r.config)}`;if("config:get"===e&&"value"in r)return`${t}\n\n${o(r.value)}`;if("config:list"===e&&"keys"in r){const n=r.keys;return`${t}\n\n${Array.isArray(n)?n.join("\n"):o(n)}`}return z(n,t)}function z(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,r=n.filename||"",o=n.fileSize||"",i=n.downloadPath||"";let s=e;return r&&(s+=`\n文件名: ${r}`),o&&(s+=`\n大小: ${o}`),i&&(s+=`\n保存到: ${i}`),t&&(s+=`\n链接: ${t}`),s}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大小: ${v(n.data.file_size)}`),t}return e}function M(n){if(null==n||""===n)return;if("number"==typeof n)return Number.isFinite(n)&&n>=0?n:void 0;const e=parseInt(String(n),10);return Number.isFinite(e)&&e>=0?e:void 0}function D(n){const e=n?.data;if(!e)return"无法获取用户信息";const t=function(n){const e=String(n.nickname??n.nick??n.name??"").trim(),t=null!=n.qid?String(n.qid):"";let r,o;if(null!=n.space_used){const e=Number(n.space_used);r=Number.isFinite(e)&&e>=0?e:void 0}else r=M(n.used_size);if(null!=n.space_total){const e=Number(n.space_total);o=Number.isFinite(e)&&e>=0?e:void 0}else o=M(n.total_size);let i="";const s=n.vip_type;i=null!=s&&""!==String(s).trim()?String(s).trim():1===n.is_vip||"1"===n.is_vip||!0===n.is_vip?(null!=n.vip_desc?String(n.vip_desc).trim():"")||"VIP用户":"普通用户";return{nickname:e,qid:t,usedBytes:r,totalBytes:o,vipLabel:i}}(e),r=void 0!==t.usedBytes?v(t.usedBytes):"-",o=void 0!==t.totalBytes?v(t.totalBytes):"-";return[`昵称: ${t.nickname||"-"}`,`QID: ${t.qid||"-"}`,`空间: ${r} / ${o}`,`VIP: ${t.vipLabel||"-"}`].join("\n")}const x=Object.freeze({__proto__:null,checkApiResult:P,executeBatch:async function(n,e){const t=[];let r=0,o=0;for(let i=0;i<n.length;i++)try{const o=await e(n[i],i);t.push({index:i,input:n[i],success:!0,result:o}),r++}catch(e){t.push({index:i,input:n[i],success:!1,error:e.message}),o++}return{total:n.length,succeeded:r,failed:o,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:v,formatFileList:S,formatMcpConfigTextResult:L,formatSearchResult:I,formatSimpleResult:z,formatTable:O,formatUserInfo:D,outputError:y,outputJson:_,outputText:$});function N(){const t=new n("auth").description("鉴权管理").enablePositionalOptions().passThroughOptions();return t.command("login").description("登录:保存 API Key 到本地配置").requiredOption("--api-key <api_key>","API 密钥").option("--env <env>","环境 (prod/test)","prod").option("--sub-channel <channel>","子渠道","open").action((n=>{const e=Date.now();try{const r=f();r.api_key=n.apiKey,n.env&&(r.ecs_env=n.env),n.subChannel&&(r.sub_channel=n.subChannel),h(r);const o=t.parent?.opts()||{};"text"===o.format?$(`登录成功!配置已保存到 ${m()}`):_({message:"登录成功",configPath:m()},"auth login",e,{quiet:o.quiet})}catch(n){y(n,"auth login",e)}})),t.command("whoami").description("查看当前鉴权状态").action((()=>{const n=Date.now();try{const e=f(),r=t.parent?.opts()||{},o={logged_in:!!e.api_key,api_key:e.api_key?e.api_key.substring(0,10)+"***":void 0,ecs_env:e.ecs_env||process.env.ECS_ENV||"prod",sub_channel:e.sub_channel||process.env.SUB_CHANNEL||"open",config_path:m()};"text"===r.format?$(o.logged_in?`已登录\nAPI Key: ${o.api_key}\n环境: ${o.ecs_env}\n渠道: ${o.sub_channel}`:"未登录。请使用 auth login --api-key <API_KEY> 登录"):_(o,"auth whoami",n,{quiet:r.quiet})}catch(e){y(e,"auth whoami",n)}})),t.command("logout").description("退出登录:清除本地配置").action((()=>{const n=Date.now();try{!function(){try{const n=m();e.existsSync(n)&&e.unlinkSync(n)}catch{}}();const r=t.parent?.opts()||{};"text"===r.format?$("已退出登录,本地配置已清除"):_({message:"已退出登录"},"auth logout",n,{quiet:r.quiet})}catch(e){y(e,"auth logout",n)}})),t}const A={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 W(n){const e=n||process.env.ECS_ENV||"prod";return{request_url:A[e]||A.prod,client_id:"e4757e933b6486c08ed206ecb6d5d9e684fcb4e2",client_secret:"test"===e?"b11b8fff1c75a5d227c8cc93aaeb0bb70c8eee47":"885fd3231f1c1e37c9f462261a09b8c38cde0c2b"}}function F(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 r=t.join("&");return r+=e,o=r,i.createHash("md5").update(o,"utf8").digest("hex");var o}const Y="production"===process.env.NODE_ENV,U="true"===process.env.LOG_TO_FILE,T=process.env.LOG_FILE_PATH||"/data/logs/ecs-mcp/app.log",q=(process.env.LOG_TIME_FORMAT||"epoch").toLowerCase();function B(){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 j;if(U)j=s.destination({dest:T,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):[],r=t.includes("--stdio")||t.includes("--all"),o=e?"true"===n.toLowerCase():r;j=s.destination({fd:o?2:1,minLength:256,sync:!0})}const K=s({level:process.env.LOG_LEVEL||(Y?"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"===q?s.stdTimeFunctions.isoTime:"epoch"===q?s.stdTimeFunctions.epochTime:"beijing"===q||"cst"===q||"asia/shanghai"===q?B:s.stdTimeFunctions.isoTime},j);function J(){try{j?.flushSync?.()}catch{}try{j?.end?.()}catch{}}async function V(n,e){const t=function(n){let e="",t="",r="",o="";n&&(n.apiKey&&(e=n.apiKey),n.subChannel&&(t=n.subChannel),n.q&&(r=n.q),n.t&&(o=n.t)),e||(e=process.env.API_KEY||""),t||(t=process.env.SUB_CHANNEL||"open");const{client_id:i,client_secret:s}=W(n?.ecsEnv);return{apiKey:e,clientId:i,clientSecret:s,subChannel:t,q:r,t:o}}(e);if(!t.apiKey&&!t.q&&!t.t)throw new Error("未配置API_KEY环境变量");try{const{request_url:r}=W(e?.ecsEnv),o=t.subChannel,i=new URL(r);let s={};const a=n.extraParams;a&&"file-upload-stdio"===a.toolName?(s.method=a.method,s.client_id=a.clientId,s.client_secret=a.clientSecret,s.qid=a.qid,s.grant_type=a.grantType,s.sub_channel=o):(s.method="Oauth.getAccessTokenByApiKey",s.client_id=t.clientId,s.client_secret=t.clientSecret,s.grant_type="authorization_code",s.sub_channel=o,t.apiKey&&(s.api_key=t.apiKey)),Object.entries(s).forEach((([n,e])=>{i.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(i.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:d,qid:m,token:f}=u.data;return{access_token:d,qid:m,token:f,sub_channel:o}}catch(n){throw K.error({err:n},"获取鉴权信息失败"),n}}async function G(n={},e){K.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 V(n,e);if(n.extraParams&&"file-upload-stdio"===n.extraParams.toolName)return t.qid=n.extraParams.qid,t;const r=F(function(n,e,t={}){const r={};if(n.access_token&&(r.access_token=String(n.access_token)),e&&(r.method=String(e)),n.qid&&(r.qid=String(n.qid)),t)for(const[n,e]of Object.entries(t))null!=e&&(r[String(n)]=String(e));return{...r}}({access_token:t.access_token,qid:t.qid},n.method||"",n.extraParams));return t.sign=r,t}process.on("uncaughtException",(n=>{K.error({err:n},"uncaught exception"),J(),process.exit(1)})),process.on("unhandledRejection",(n=>{K.error({err:n},"unhandled rejection"),J(),process.exit(1)})),process.on("SIGINT",(()=>{J(),process.exit(0)})),process.on("SIGTERM",(()=>{J(),process.exit(0)})),process.on("beforeExit",(()=>{J()}));class H{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 G(n,e);return t.request_url=W(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:r}=n(),o=new AbortController,i=setTimeout((()=>o.abort()),this.timeout),s={...r,signal:o.signal};try{const n=await fetch(t,s);if(clearTimeout(i),!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(i),e<this.retries&&this.isRetryable(t)){const r=this.retryDelay*Math.pow(2,e);return K.debug({attempt:e+1,delay:r,error:t.message},"请求失败,准备重试"),await new Promise((n=>setTimeout(n,r))),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 r=new URL(n.request_url||"");Object.entries(e).forEach((([n,e])=>{r.searchParams.append(n,String(e))}));const o=new URLSearchParams;return Object.entries(t).forEach((([n,e])=>{o.append(n,String(e))})),{url:r.toString(),init:{method:"POST",headers:{"Access-Token":n.access_token||"","Content-Type":"application/x-www-form-urlencoded"},body:o}}}))}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 K}}class Q extends H{async info(){const n=await this.getAuth({}),e=this.buildBaseParams(n,"User.getUserDetail");return e.sign="",await this.apiGet(n,e)}}class X extends H{async list(n={}){const{path:e="/",page:t=0,page_size:r=50}=n;let o=e||"/";"/"!==o&&(o.startsWith("/")||(o="/"+o),o.endsWith("/")||(o+="/"));const i={path:o,page:t,page_size:r},s=await this.getAuth({method:"File.getList",extraParams:i}),a={...this.buildBaseParams(s,"File.getList")};for(const[n,e]of Object.entries(i))"access_token"!==n&&(a[n]=String(e));return await this.apiGet(s,a)}async mkdir(n){const e={fname:n},t=await this.getAuth({method:"File.mkdir",extraParams:e}),r=this.buildBaseParams(t,"File.mkdir");return await this.apiPost(t,{},{...r,fname:n})}}const Z="未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量";function nn(n){let e=String(n||"").trim();return e=e.replace(/^\uFEFF/,""),e.startsWith("-e ")&&(e=e.slice(3).trim(),(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))&&(e=e.slice(1,-1))),e.replace(/\\r\\n/g,"\r\n").replace(/\\n/g,"\n")}async function en(){if(process.stdin.isTTY){if("win32"===process.platform)throw new Error("--stdin 模式需要管道输入。Windows cmd 勿用 echo -e(会把 -e 写入内容且编码易乱码),请用 --content、或 chcp 65001 后 echo、或 type/PowerShell Get-Content 读 UTF-8 文件管道;追加/保存示例:type x.md | 360disk file save --stdin ...");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 function(n){if(0===n.length)return"";const e=n=>n.replace(/^\uFEFF/,"");if("win32"!==process.platform)return e(n.toString("utf-8"));const t=e(n.toString("utf-8"));if(!/\uFFFD/.test(t))return t;try{const t=e(a.decode(n,"gbk"));if(!/\uFFFD/.test(t))return t}catch{}return t}(Buffer.concat(n))}async function tn(){const n=await en();return nn(n).split(/\r?\n/).map((n=>n.trim())).filter(Boolean)}class rn extends H{async move(n){const{src_name:e,new_name:t}=n,r={src_name:e,new_name:t},o=await this.getAuth({method:"File.move",extraParams:r}),i=this.buildBaseParams(o,"File.move");return await this.apiPost(o,{},{...i,src_name:e,new_name:t})}async rename(n){const{src_name:e,new_name:t}=n,r={src_name:e,new_name:t},o=await this.getAuth({method:"File.rename",extraParams:r}),i=this.buildBaseParams(o,"File.rename");return await this.apiPost(o,{},{...i,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})}async transOrCopy(n){const{src_name:e,new_path:t,is_delete:r,is_replace:o=0,src_ks_id:i,new_ks_id:s}=n;if(0!==r&&1!==r)throw new Error("is_delete 仅支持 0 或 1");if(0!==o&&1!==o)throw new Error("is_replace 仅支持 0 或 1");const a={src_name:e,new_path:t,is_delete:r},c=await this.getAuth({method:"File.transOrCopy",extraParams:a}),l={...this.buildBaseParams(c,"File.transOrCopy"),src_name:e,new_path:t,is_delete:String(r),is_replace:String(o)};return i&&(l.src_ks_id=i),s&&(l.new_ks_id=s),await this.apiPost(c,{},l)}async countOriginSize(n){const{path:e}=n;if(!e||!e.startsWith("/"))throw new Error("path 必须以 / 开头");const t={path:e},r=await this.getAuth({method:"File.countFileOriginSize",extraParams:t}),o=this.buildBaseParams(r,"File.countFileOriginSize");return await this.apiPost(r,{},{...o,path:e})}async clearDir(n){const{fname:e}=n;if(!e)throw new Error("fname 不能为空");if(e.includes("|"))throw new Error("File.clearDir 仅支持单个目录路径,勿用英文竖线(|)拼接;竖线会被服务端视为路径非法字符(errno:3022)。请对每个目录分别调用");const t=await this.getAuth({method:"File.clearDir"}),r=this.buildBaseParams(t,"File.clearDir");return await this.apiPost(t,{},{...r,fname:e})}async config(n){const{path:e,command:t,type:r,key:o,value:i,content:s}=n;if(!e||!e.startsWith("/"))throw new Error("path 必须以 / 开头");const a="yml"===r?"yaml":r,c={path:e,command:t,type:a},l=await this.getAuth({method:"MCP.config",extraParams:c}),u={...this.buildBaseParams(l,"MCP.config"),path:e,command:t,type:a};return void 0!==o&&(u.key=o),void 0!==i&&(u.value=i),void 0!==s&&(u.content=s),await this.apiPost(l,{},u)}}const on=["http:","https:"],sn=/[\\@]/,an=[/^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],cn=[22,23,25,53,135,139,445,1433,1521,3306,3389,5432,6379,9200,11211,27017];async function ln(n,e=5e3){const t=function(n){if(!n||"string"!=typeof n)return{isValid:!1,error:"URL不能为空"};try{const t=n.trim();if(sn.test(t))return{isValid:!1,error:"URL包含不安全的字符"};const r=new URL(t);if(!on.includes(r.protocol))return{isValid:!1,error:"不支持的协议,只支持 http/https"};const o=r.hostname.toLowerCase();if(!o)return{isValid:!1,error:"主机名不能为空"};for(const n of an)if(n.test(o))return{isValid:!1,error:"禁止访问受限地址"};if(un(o)&&!dn(o))return{isValid:!1,error:"禁止访问受限IP地址"};if(r.port){const n=parseInt(r.port,10);if(cn.includes(n))return{isValid:!1,error:"禁止访问受限端口"}}return e=r.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:r.toString(),originalHostname:o}}catch(n){return{isValid:!1,error:"URL格式无效"}}var e}(n);if(!t.isValid)return t;try{const r=new URL(n).hostname.toLowerCase();if(un(r))return{...t,resolvedIP:r,originalHostname:r,needsHostHeader:!1};let o=[];try{const n=c.lookup(r,{all:!0}),t=new Promise(((n,t)=>{setTimeout((()=>t(new Error("DNS解析超时"))),e)}));o=(await Promise.race([n,t])).map((n=>n.address))}catch(n){return{isValid:!1,error:"域名解析失败"}}for(const n of o){for(const e of an)if(e.test(n))return{isValid:!1,error:"域名解析到受限IP地址"};if(!dn(n))return{isValid:!1,error:"域名解析到受限IP地址"}}return{isValid:!0,normalizedUrl:t.normalizedUrl,resolvedIP:o[0],resolvedIPs:o,originalHostname:r,needsHostHeader:!1}}catch(n){return{isValid:!1,error:"URL验证过程中发生错误"}}}function un(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 dn(n){for(const e of an)if(e.test(n))return!1;return!0}class mn extends H{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:r=1,page_size:o=20}=n;if(!e&&-1===t)throw new Error("必须提供搜索关键词(key)或指定文件类型(file_category)");const i={file_category:t,key:e,page:r,page_size:o},s=await this.getAuth({method:"File.searchList",extraParams:i}),a=this.buildBaseParams(s,"File.searchList"),c={};for(const[n,e]of Object.entries(i))"access_token"!==n&&(c[n]=String(e));return await this.apiPost(s,a,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 r={};void 0!==e?r.nid=e:t&&(r.fpath=t);const o=await this.getAuth({method:"MCP.getDownLoadUrl",extraParams:r}),i={...this.buildBaseParams(o,"MCP.getDownLoadUrl")};return void 0!==e?i.nid=String(e):t&&(i.fpath=t),await this.apiPost(o,{},i)}async getNodeInfoByNid(n){const{nid:e,ks_ext:t=0}=n;if(!e)throw new Error("nid 不能为空");if(0!==t&&1!==t)throw new Error("ks_ext 仅支持 0 或 1");const r={nid:e},o=await this.getAuth({method:"File.getNodeInfoByNid",extraParams:r}),i=this.buildBaseParams(o,"File.getNodeInfoByNid");return await this.apiGet(o,{...i,nid:e,ks_ext:String(t)})}async save(n){const{url:e,content:t,upload_path:r,file_name:o,is_rename:i=1}=n;if(!e&&!t||e&&t)throw new Error("url 与 content 互斥,必须且只能传一个");if(0!==i&&1!==i)throw new Error("is_rename 仅支持 0 或 1");if(e){if(e.includes("|"))throw new Error("MCP.saveFile 仅支持单个下载 URL,请勿使用英文竖线(|)连接多个地址;请对每个 URL 分别执行保存");const n=await ln(e.trim(),5e3);if(!n.isValid)throw new Error(`URL安全验证失败: ${n.error}`)}const s=await this.getAuth({method:"MCP.saveFile"}),a=this.buildBaseParams(s,"MCP.saveFile"),c={};r&&(c.upload_path=r),e?c.url=e:t&&(c.content=t),o&&(c.file_name=o),c.is_rename=String(i);const l=await this.apiPost(s,a,c);if(l&&0===l.errno){const n=Array.isArray(l.data)?l.data:l.data?[l.data]:[];if(0===n.length)throw new Error("保存文件失败: 未获取到任何任务信息");const e=[],t=[];for(const r of n){const n=r.task_id;if(n)try{e.push(await this.pollTaskStatus(s,n))}catch(e){t.push(`任务 ${n} 失败: ${e.message}`)}else t.push(`URL ${r.url||"未知"} 未获取到任务ID`)}if(0===e.length&&t.length>0)throw new Error(`文件保存失败: ${t.join("; ")}`);return 1===e.length?e[0]:{errno:0,errmsg:t.length>0?`部分任务失败: ${t.join("; ")}`:"",data:{tasks:e,is_multi:!0}}}throw new Error(l?.errmsg||"API请求失败")}async appendContent(n){const{path:e,content:t}=n;if(!e||!e.startsWith("/"))throw new Error("path 必须以 / 开头");if("string"!=typeof t||0===t.length)throw new Error("content 不能为空");const r={path:e,content:t},o=await this.getAuth({method:"File.appendContent",extraParams:r}),i=this.buildBaseParams(o,"File.appendContent");return await this.apiPost(o,{},{...i,path:e,content:t})}async detectFileExists(n){const{path:e,files:t}=n;if(!e||!e.startsWith("/"))throw new Error("path 必须以 / 开头");if(!Array.isArray(t)||0===t.length)throw new Error("files 不能为空");for(const n of t){if(!n?.fname||"string"!=typeof n.fname)throw new Error("files[].fname 必须为非空字符串");if(!Number.isFinite(n.fsize)||n.fsize<0)throw new Error("files[].fsize 必须为大于等于 0 的数字")}const r=JSON.stringify(t),o={path:e,data:r},i=await this.getAuth({method:"Sync.detectFileExists",extraParams:o}),s=this.buildBaseParams(i,"Sync.detectFileExists");return await this.apiGet(i,{...s,path:e,data:r})}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,r=120){let o,i=0;for(;i<r;){if(i++,o=await this.queryTaskStatus(n,e),0!==o.errno)throw new Error(o.errmsg||"查询任务状态失败");const r=o.data?.status;if(2===r)return o;if(3===r)throw new Error(o.data?.error||"文件保存失败");await new Promise((n=>setTimeout(n,t)))}return o}async upload(n){const{filePaths:t,uploadPath:r="/"}=n;if(!t||0===t.length)throw new Error("filePaths 为必填参数且不能为空");for(const n of t)if(!e.existsSync(n))throw new Error(`文件不存在: ${n}`);const o=await this.getAuth({});let i;try{const n=await import("@aicloud360/sec-sdk-node");i=n.UploadNode}catch{throw new Error("请先安装 @aicloud360/sec-sdk-node: npm install @aicloud360/sec-sdk-node")}const s={qid:o.qid||"0",token:o.token||"",access_token:o.access_token||"",env:this.ecsEnv,path:r};return new Promise(((n,e)=>{const o=Date.now(),a=[],c=[],l=[];new i(s,{success:n=>{a.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||r}))))},complete:()=>{c.length>0&&0===a.length?e(new Error(`所有文件上传失败: ${c.map((n=>`${n.fileName}: ${n.error}`)).join("; ")}`)):n({uploadResults:a,uploadErrors:c,duplicateFiles:l,totalTime:((Date.now()-o)/1e3).toFixed(2),fileCount:a.length,totalFileCount:t.length})}}).addWaitFile(t)}))}async download(n){const{nid:e,auto:r=!0,downloadDir:i}=n,s=i||t.join(o(),".mcp-downloads"),a=await this.getAuth({method:"Sync.getVerifiedDownLoadUrl",extraParams:{nid:e}}),c={...this.buildBaseParams(a,"Sync.getVerifiedDownLoadUrl"),nid:String(e)},l=await this.apiGet(a,c);if(!l||0!==l.errno)throw new Error(l?.errmsg||"API请求失败");const d=l.data?.downloadUrl||"";if(!d)throw new Error("未能获取到文件下载链接");const{filename:m,sizeMB:f}=this.extractFileInfoFromUrl(d),h=f>0?`${f.toFixed(2)} MB`:"未知大小";if(!r)return{downloadUrl:d,filename:m,fileSize:h,downloadPath:""};await u.mkdir(s,{recursive:!0});const p=t.join(s,m);return f>10?(this.spawnBackgroundDownload(d,p),{downloadUrl:d,filename:m,fileSize:h,downloadPath:p,background:!0}):(await this.downloadWithCurl(d,p),{downloadUrl:d,filename:m,fileSize:h,downloadPath:p})}extractFileInfoFromUrl(n){try{const e=new URL(n);let r="downloaded_file";const o=e.searchParams.get("fname");if(o?r=t.basename(decodeURIComponent(o)):e.pathname&&e.pathname.length>1&&(r=t.basename(e.pathname)),r=r.replace(/[\x00/]/g,"_"),r&&"."!==r||(r="downloaded_file"),r.length>200){const n=t.extname(r);r=t.basename(r,n).substring(0,200-n.length)+n}const i=e.searchParams.get("fsize");return{filename:r,sizeMB:i?parseInt(i,10)/1048576:0}}catch{return{filename:"downloaded_file",sizeMB:0}}}downloadWithCurl(n,e,t=3e5){return new Promise(((r,o)=>{const i=l("curl",["-L","-s","-A","yunpan_mcp_server",n,"-o",e]),s=setTimeout((()=>{i.kill(),o(new Error("下载超时"))}),t);i.on("close",(n=>{clearTimeout(s),0===n?r():o(new Error(`下载失败,curl 退出码: ${n}`))})),i.on("error",(n=>{clearTimeout(s),o(n)}))}))}spawnBackgroundDownload(n,e){l("curl",["-L","-s","-A","yunpan_mcp_server",n,"-o",e],{detached:!0,stdio:"ignore"}).unref()}}const fn="未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量";function hn(n,e,t,r,o){"text"===r.format&&o?$(o(n)):_(n,e,t,{quiet:r.quiet})}async function pn(n,e){const t=await n.delete(e);if(3008===t?.errno&&!e.endsWith("/")){const t=await n.delete(e+"/");if(0===t?.errno)return t}return t}function wn(n){return"string"!=typeof n?n:n.replace(/\\r\\n/g,"\r\n").replace(/\\n/g,"\n")}function gn(n){let e;for(const t of function(n){const e=[],t=new Set,r=n=>{const r=String(n).trim();r&&!t.has(r)&&(t.add(r),e.push(r))},o=String(n??"").trim().replace(/^\uFEFF/,"");if(r(o),o.length>=2&&o.startsWith("'")&&o.endsWith("'")){const n=o.slice(1,-1).trim();r(n),r(n.replace(/\\"/g,'"'))}return r(o.replace(/\\"/g,'"')),e}(n))try{e=JSON.parse(t);break}catch{}if(void 0===e)throw new g('files 必须是合法 JSON(示例: [{"fname":"a.txt","fsize":123}])。Windows cmd 不支持单引号包裹参数,请用双引号并对内部 " 转义,或改用 --stdin。',w.INVALID_ARGS);if("string"==typeof e)try{e=JSON.parse(e)}catch{throw new g('files 必须是合法 JSON(示例: [{"fname":"a.txt","fsize":123}])',w.INVALID_ARGS)}if(!Array.isArray(e)||0===e.length)throw new g("files 必须是非空数组",w.INVALID_ARGS);for(const n of e){if(!n||"string"!=typeof n.fname||""===n.fname.trim())throw new g("files[].fname 必须为非空字符串",w.INVALID_ARGS);if(!Number.isFinite(n.fsize)||n.fsize<0)throw new g("files[].fsize 必须为大于等于 0 的数字",w.INVALID_ARGS)}return e}function _n(){const e=new n("file").description("文件操作");return e.command("mv").description("移动文件或文件夹").argument("<src>","源路径,多个用 | 分隔").argument("<dest>","目标文件夹路径").action((async(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file mv",r);const i=new rn(e),s=await async function(n,e,t){const r=await n.move({src_name:e,new_name:t}),o=!e.includes("|");if(3008===r?.errno&&o&&!e.endsWith("/")){const r=await n.move({src_name:e+"/",new_name:t});if(0===r?.errno)return r}return r}(i,n,t);P(s,"file mv",r),hn(s,"file mv",r,o,(e=>z(e,`已移动: ${n} -> ${t}`)))}catch(n){y(n,"file mv",r)}})),e.command("trans-copy").description("转移或复制文件到目标目录").argument("<src>","源路径,多个用 | 分隔").argument("<dest>","目标目录路径").option("--delete <0|1>","是否删除源文件:1=转移,0=复制","1").option("--replace <0|1>","同名文件处理:0=重命名,1=覆盖","0").option("--src-ks-id <id>","源文件所在群组 ks_id(可选)").option("--new-ks-id <id>","目标目录所在群组 ks_id(可选)").action((async(n,t,r)=>{const o=Date.now(),i=e.parent?.opts()||{};try{const e=p(i);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file trans-copy",o);const s=parseInt(r.delete,10),a=parseInt(r.replace,10);if(![0,1].includes(s))return void y(new g("--delete 仅支持 0 或 1",w.INVALID_ARGS),"file trans-copy",o);if(![0,1].includes(a))return void y(new g("--replace 仅支持 0 或 1",w.INVALID_ARGS),"file trans-copy",o);if(!t.startsWith("/"))return void y(new g("dest 必须以 / 开头",w.INVALID_ARGS),"file trans-copy",o);const c=new rn(e),l=await c.transOrCopy({src_name:n,new_path:t,is_delete:s,is_replace:a,src_ks_id:r.srcKsId,new_ks_id:r.newKsId});P(l,"file trans-copy",o),hn(l,"file trans-copy",o,i,(e=>z(e,`${1===s?"转移":"复制"}完成: ${n} -> ${t}`)))}catch(n){y(n,"file trans-copy",o)}})),e.command("rename").description("重命名文件或文件夹").argument("<path>","原文件完整路径").argument("<new_name>","新名称").action((async(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file rename",r);const i=function(n,e){const t=String(n||"").trim();let r=String(e||"").trim();const o=t.endsWith("/");if(!t.startsWith("/"))throw new g("path 必须以 / 开头",w.INVALID_ARGS);if(!r)throw new g("new_name 不能为空",w.INVALID_ARGS);if(o){r=r.replace(/^\/+/,"");const n=r.replace(/\/+$/,"").split("/").filter(Boolean);if(0===n.length)throw new g("new_name 不能为空(目录场景需传新目录名,如 bbb_renamed/)",w.INVALID_ARGS);return r=n[n.length-1]+"/",{src_name:t,new_name:r,isDir:!0}}if(r.includes("/"))throw new g("new_name 仅支持文件名(不含路径)。若要改路径请使用 file mv",w.INVALID_ARGS);return{src_name:t,new_name:r,isDir:!1}}(n,t),s=new rn(e),a=await s.rename({src_name:i.src_name,new_name:i.new_name});P(a,"file rename",r),hn(a,"file rename",r,o,(n=>z(n,`已重命名: ${i.src_name} -> ${i.new_name}`)))}catch(n){y(n,"file rename",r)}})),e.command("rm").description("删除文件或文件夹").argument("<path>","文件路径,多个用 | 分隔").option("--batch","批量模式:从 stdin 读取路径列表(每行一个)").action((async(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file rm",r);const i=new rn(e);if(t.batch){const e=await tn(),t=[n,...e].filter(Boolean),{executeBatch:s,formatBatchResult:a}=await Promise.resolve().then((function(){return x}));hn(await s(t,(async n=>{const e=await pn(i,n);if(e&&"number"==typeof e.errno&&0!==e.errno){throw new Error(e.errmsg?`${e.errmsg} (errno: ${e.errno})`:`API 错误 (errno: ${e.errno})`)}return e})),"file rm --batch",r,o,(n=>a(n)))}else{const e=await pn(i,n);P(e,"file rm",r),hn(e,"file rm",r,o,(e=>z(e,`已删除: ${n}`)))}}catch(n){y(n,"file rm",r)}})),e.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(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file search",r);const i=new mn(e),s=await i.search({key:n,file_category:parseInt(t.type),page:parseInt(t.page),page_size:parseInt(t.size)});P(s,"file search",r),hn(s,"file search",r,o,I)}catch(n){y(n,"file search",r)}})),e.command("share").description("生成文件分享链接").argument("<paths>","文件路径,多个用 | 分隔").action((async n=>{const t=Date.now(),r=e.parent?.opts()||{};try{const e=p(r);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file share",t);const o=new mn(e),i=await o.share(n);P(i,"file share",t),hn(i,"file share",t,r,(n=>z(n,"分享链接已生成")))}catch(n){y(n,"file share",t)}})),e.command("url").description("获取文件下载链接").argument("<path>","文件路径").option("--nid <nid>","文件 NID(与路径二选一)").action((async(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file url",r);const i=new mn(e),s=await i.getDownloadUrl({nid:t.nid,fpath:t.nid?void 0:n});P(s,"file url",r),hn(s,"file url",r,o,(n=>z(n,"下载链接已获取")))}catch(n){y(n,"file url",r)}})),e.command("node-info").description("根据 nid 获取节点信息(可选返回 ks_info)").argument("<nid>","节点 nid(仅支持文件夹/知识库)").option("--ks-ext <0|1>","是否返回 ks_info:0=不返回,1=返回","0").action((async(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file node-info",r);const i=parseInt(t.ksExt,10);if(![0,1].includes(i))return void y(new g("--ks-ext 仅支持 0 或 1",w.INVALID_ARGS),"file node-info",r);const s=new mn(e),a=await s.getNodeInfoByNid({nid:n,ks_ext:i});P(a,"file node-info",r),hn(a,"file node-info",r,o,(e=>z(e,`节点信息获取成功: ${n}`)))}catch(n){y(n,"file node-info",r)}})),e.command("origin-size").description("统计目录下文件和文件夹(递归)原始大小").argument("<path>","目录完整路径").action((async(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file origin-size",r);if(!n.startsWith("/"))return void y(new g("path 必须以 / 开头",w.INVALID_ARGS),"file origin-size",r);const t=new rn(e),i=await t.countOriginSize({path:n});P(i,"file origin-size",r),hn(i,"file origin-size",r,o,(e=>z(e,`目录大小统计完成: ${n}`)))}catch(n){y(n,"file origin-size",r)}})),e.command("clear-dir").description("清空目录下文件,保留目录本身(每次仅一个目录)").argument("<path>","单个目录路径,必须以 / 开头").action((async(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file clear-dir",r);if(!n.startsWith("/"))return void y(new g("目录 path 必须以 / 开头",w.INVALID_ARGS),"file clear-dir",r);if(n.includes("|"))return void y(new g("每次仅支持清空一个目录,不支持用英文竖线(|)连接多个路径(会触发服务端非法字符错误);请分多次执行 file clear-dir",w.INVALID_ARGS),"file clear-dir",r);const t=new rn(e),i=await t.clearDir({fname:n});P(i,"file clear-dir",r),hn(i,"file clear-dir",r,o,(e=>z(e,`目录清空完成: ${n}`)))}catch(n){y(n,"file clear-dir",r)}})),e.command("config").description("读取/写入/列出配置文件(INI/JSON/YAML)").requiredOption("--path <path>","配置文件路径,必须以 / 开头").requiredOption("--command <cmd>","操作命令:config:get|config:set|config:delete|config:list|config:read|config:write").requiredOption("--type <type>","配置类型:ini|json|yaml|yml").option("--key <key>","键路径,如 a.b.c").option("--value <value>","写入值(set 场景)").option("--content <text>","写入完整内容(write 场景,与 --stdin 互斥)").option("--stdin","从标准输入读取 content(write 场景,与 --content 互斥)").action((async n=>{const t=Date.now(),r=e.parent?.opts()||{};try{const e=p(r);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file config",t);if(!n.path.startsWith("/"))return void y(new g("--path 必须以 / 开头",w.INVALID_ARGS),"file config",t);if(!new Set(["config:get","config:set","config:delete","config:list","config:read","config:write"]).has(n.command))return void y(new g("--command 非法",w.INVALID_ARGS),"file config",t);if(!new Set(["ini","json","yaml","yml"]).has(n.type))return void y(new g("--type 非法",w.INVALID_ARGS),"file config",t);if("config:write"===n.command){if(1!==[n.content,n.stdin].filter(Boolean).length)return void y(new g("config:write 场景下 --content 与 --stdin 互斥,必须且只能传一个",w.INVALID_ARGS),"file config",t)}else if(n.content||n.stdin)return void y(new g("--content/--stdin 仅在 config:write 场景使用",w.INVALID_ARGS),"file config",t);let o=wn(n.content);if(n.stdin&&(o=await en(),!o.trim()))return void y(new g("stdin 输入内容为空",w.INVALID_ARGS),"file config",t);const i=new rn(e),s=await i.config({path:n.path,command:n.command,type:n.type,key:n.key,value:n.value,content:o});P(s,"file config",t),hn(s,"file config",t,r,(e=>L(e,n.command,`配置操作完成: ${n.command}`)))}catch(n){y(n,"file config",t)}})),e.command("save").description("通过 URL 或文本内容保存文件到云盘").option("--url <url>","单个文件下载地址(与 --content/--stdin 互斥;勿用 | 连接多个 URL)").option("--content <text>","文件内容(与 --url/--stdin 互斥)").option("--stdin","从标准输入读取内容(与 --url/--content 互斥)").option("--dest <path>","云盘存储路径,必须以 / 开头和结尾").option("--filename <name>","保存的文件名").option("--rename <0|1>","同名文件处理策略:0=直接替换,1=自动重命名","1").action((async n=>{const t=Date.now(),r=e.parent?.opts()||{};try{const e=p(r);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file save",t);if(1!==[n.url,n.content,n.stdin].filter(Boolean).length)return void y(new g("--url、--content、--stdin 三者互斥,必须且只能传一个",w.INVALID_ARGS),"file save",t);const o=parseInt(n.rename,10);if(![0,1].includes(o))return void y(new g("--rename 仅支持 0 或 1",w.INVALID_ARGS),"file save",t);if(n.url&&String(n.url).includes("|"))return void y(new g("`--url` 仅支持单个下载地址,不支持用英文竖线(|)连接多个 URL;请对每个地址分别执行 file save",w.INVALID_ARGS),"file save",t);let i=wn(n.content);if(n.stdin&&(i=nn(await en()),!i.trim()))return void y(new g("stdin 输入内容为空",w.INVALID_ARGS),"file save",t);const s=new mn(e),a=await s.save({url:n.url,content:i,upload_path:n.dest,file_name:n.filename,is_rename:o});P(a,"file save",t),hn(a,"file save",t,r,(n=>z(n,"文件保存成功")))}catch(n){y(n,"file save",t)}})),e.command("append").description("向云盘文本文件末尾追加内容").argument("<path>","文件完整路径,必须以 / 开头").option("--content <text>","追加的文本内容(与 --stdin 互斥)").option("--stdin","从标准输入读取追加内容(与 --content 互斥;Windows cmd 用 type 文件 | ... 勿用 cat)").action((async(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file append",r);if(!n.startsWith("/"))return void y(new g("path 必须以 / 开头",w.INVALID_ARGS),"file append",r);if(1!==[t.content,t.stdin].filter(Boolean).length)return void y(new g("--content 与 --stdin 互斥,必须且只能传一个",w.INVALID_ARGS),"file append",r);let i=wn(t.content);if(t.stdin&&(i=nn(await en()),!i||!i.trim()))return void y(new g("stdin 输入内容为空",w.INVALID_ARGS),"file append",r);const s=new mn(e),a=await s.appendContent({path:n,content:i});P(a,"file append",r),hn(a,"file append",r,o,(e=>z(e,`已追加内容到: ${n}`)))}catch(n){y(n,"file append",r)}})),e.command("exists").description("检测目录下是否存在同名文件").option("--path <path>","目标目录,必须以 / 开头").option("--files <json>",'待检测文件数组 JSON,例如 [{"fname":"a.txt","fsize":123}](与 --stdin 互斥;Windows cmd 勿用单引号,见文档)').option("--stdin","从标准输入读取 files JSON(与 --files 互斥)").action((async n=>{const t=Date.now(),r=e.parent?.opts()||{};try{const e=p(r);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file exists",t);if(!n.path||!n.path.startsWith("/"))return void y(new g("--path 必须以 / 开头",w.INVALID_ARGS),"file exists",t);if(1!==[n.files,n.stdin].filter(Boolean).length)return void y(new g("--files 与 --stdin 互斥,必须且只能传一个",w.INVALID_ARGS),"file exists",t);const o=gn(n.stdin?await en():n.files),i=new mn(e),s=await i.detectFileExists({path:n.path,files:o});P(s,"file exists",t),hn(s,"file exists",t,r,(e=>z(e,`检测完成: ${n.path}`)))}catch(n){y(n,"file exists",t)}})),e.command("upload").description("上传本地文件到云盘").argument("<files>","本地文件路径,多个用逗号分隔").option("--dest <path>","云盘目标路径","/").action((async(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file upload",r);const i=n.split(",").map((n=>n.trim())),s=new mn(e);hn(await s.upload({filePaths:i,uploadPath:t.dest}),"file upload",r,o,(n=>z(n,"上传完成")))}catch(n){y(n,"file upload",r)}})),e.command("download").description("下载云盘文件到本地").argument("<nid>","文件 NID(可通过 file search / dir ls 获取)").option("--dir <path>","本地下载目录").option("--no-auto","仅获取下载链接,不自动下载").action((async(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(fn,w.AUTH_ERROR),"file download",r);const i=new mn(e);hn(await i.download({nid:n,auto:t.auto,downloadDir:t.dir}),"file download",r,o,(n=>z(n,"下载完成")))}catch(n){y(n,"file download",r)}})),e}function $n(){return t.join(o(),".360disk")}function yn(){const n=process.env.SHELL||"";return n.includes("zsh")?"zsh":n.includes("bash")?"bash":"unknown"}function Pn(n){const e=o();return t.join(e,"zsh"===n?".zshrc":".bashrc")}(async function(){const o=function(){try{const n=t.dirname(r(import.meta.url)),o=[t.resolve(n,"../package.json"),t.resolve(n,"../../package.json"),t.resolve(n,"../../../package.json"),t.resolve(process.cwd(),"package.json")];for(const n of o)if(e.existsSync(n)){const t=JSON.parse(e.readFileSync(n,"utf8"));return{name:t.cli?.name||"360disk",version:t.version||"0.0.0",description:t.cli?.description||"360 AI 云盘 CLI"}}}catch{}return{name:"360disk",version:"0.0.0",description:"360 AI 云盘 CLI"}}(),i=new n;i.name(o.name).description(o.description).version(o.version).enablePositionalOptions().passThroughOptions().option("--api-key <key>","API 密钥").option("--env <env>","环境 (prod/test)").option("--sub-channel <channel>","子渠道").option("--format <type>","输出格式 (json|text)","json").option("--quiet","仅输出结果数据").option("--timeout <ms>","请求超时时间(毫秒)","30000").option("--retries <n>","失败重试次数","0"),i.addCommand(N()),i.addCommand(function(){const e=new n("user").description("用户信息");return e.command("info").description("获取用户详细信息").action((async()=>{const n=Date.now(),t=e.parent?.opts()||{};try{const e=p(t);if(!e.apiKey)return void y(new g("未配置 API Key,请先执行 auth login --api-key <API_KEY> 或设置 API_KEY 环境变量",w.AUTH_ERROR),"user info",n);const r=new Q(e),o=await r.info();P(o,"user info",n),"text"===t.format?$(D(o)):_(o,"user info",n,{quiet:t.quiet})}catch(e){y(e,"user info",n)}})),e}()),i.addCommand(function(){const e=new n("dir").description("目录操作");return e.command("ls").description("列出云盘指定目录下的文件和文件夹").argument("[path]","目录路径","/").option("--page <n>","页码","0").option("--size <n>","每页数量","50").action((async(n,t)=>{const r=Date.now(),o=e.parent?.opts()||{};try{const e=p(o);if(!e.apiKey)return void y(new g(Z,w.AUTH_ERROR),"dir ls",r);const i=new X(e),s=await i.list({path:n,page:parseInt(t.page),page_size:parseInt(t.size)});P(s,"dir ls",r),"text"===o.format?$(S(s)):_(s,"dir ls",r,{quiet:o.quiet})}catch(n){y(n,"dir ls",r)}})),e.command("mkdir").description("创建新文件夹").argument("<path>","文件夹路径").action((async n=>{const t=Date.now(),r=e.parent?.opts()||{};try{const e=p(r);if(!e.apiKey)return void y(new g(Z,w.AUTH_ERROR),"dir mkdir",t);const o=new X(e),i=await o.mkdir(n);P(i,"dir mkdir",t),"text"===r.format?$(z(i,`已创建目录: ${n}`)):_(i,"dir mkdir",t,{quiet:r.quiet})}catch(n){y(n,"dir mkdir",t)}})),e}()),i.addCommand(_n()),i.addCommand(function(){const r=new n("completion").description("Shell 自动补全");return r.command("install").description("安装补全脚本到当前 shell").option("--bash","安装 bash 补全").option("--zsh","安装 zsh 补全").action((n=>{try{const r=n.bash?"bash":n.zsh?"zsh":yn();if("unknown"===r)return void y("无法检测 shell 类型,请使用 --bash 或 --zsh 指定","completion install");const o="zsh"===r?"#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',i="zsh"===r?"zsh":"bash",s=$n(),a=t.join(s,`completion.${i}`);e.existsSync(s)||e.mkdirSync(s,{recursive:!0,mode:448}),e.writeFileSync(a,o,{mode:420});const c=Pn(r),l=function(n){return`source ~/.360disk/completion.${"zsh"===n?"zsh":"bash"} # 360disk auto-completion`}(r);let u="";e.existsSync(c)&&(u=e.readFileSync(c,"utf8")),u.includes("360disk auto-completion")?$(`补全脚本已更新: ${a}\n配置已存在于 ${c},无需重复添加`):(e.appendFileSync(c,"\n"+l+"\n"),$(`补全脚本已安装:\n 脚本: ${a}\n 配置: ${c}\n\n请执行 source ${c} 或重新打开终端生效`))}catch(n){y(n,"completion install")}})),r.command("uninstall").description("卸载补全脚本").option("--bash","卸载 bash 补全").option("--zsh","卸载 zsh 补全").action((n=>{try{const r=n.bash?"bash":n.zsh?"zsh":yn();if("unknown"===r)return void y("无法检测 shell 类型,请使用 --bash 或 --zsh 指定","completion uninstall");const o="zsh"===r?"zsh":"bash",i=t.join($n(),`completion.${o}`);e.existsSync(i)&&e.unlinkSync(i);const s=Pn(r);if(e.existsSync(s)){const n=e.readFileSync(s,"utf8").split("\n").filter((n=>!n.includes("360disk auto-completion")));e.writeFileSync(s,n.join("\n"))}$(`补全脚本已卸载\n请执行 source ${Pn(r)} 或重新打开终端生效`)}catch(n){y(n,"completion uninstall")}})),r.command("script").description("输出补全脚本到 stdout(手动安装用)").option("--bash","输出 bash 补全脚本").option("--zsh","输出 zsh 补全脚本").action((n=>{try{const e=n.bash?"bash":n.zsh?"zsh":yn();if("unknown"===e)return void y("无法检测 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){y(n,"completion script")}})),r}()),await i.parseAsync()})().then((()=>{process.exit(0)})).catch((n=>{process.stderr.write(JSON.stringify({success:!1,error:n.message||"CLI 启动失败"},null,2)+"\n"),process.exit(1)}));
|