@litingwei621/test-deveco-cli-common 0.0.0-stage → 1.3.7

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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1902 -3
  3. package/THIRD-PARTY-LICENSES +5 -0
  4. package/dist/cli.js +1423 -0
  5. package/dist/internal/telemetry-upload-background.js +9 -0
  6. package/package.json +69 -6
  7. package/skills/deveco-cli/SKILL.md +327 -0
  8. package/src/resources/aclPermission/aclPermissionsInfo.json +311 -0
  9. package/src/resources/arkts-check.cjs +3642 -0
  10. package/src/resources/uitest/uitest_agent_v1.1.9.so +0 -0
  11. package/src/resources/uitest/uitest_agent_v1.1.9.x86_64.so +0 -0
  12. package/src/resources/uitest/uitest_agent_v1.2.2.so +0 -0
  13. package/templates/application/AppScope/app.json5 +10 -0
  14. package/templates/application/AppScope/resources/base/element/string.json +8 -0
  15. package/templates/application/AppScope/resources/base/media/layered_image.json +7 -0
  16. package/templates/application/build-profile.json5 +42 -0
  17. package/templates/application/code-linter.json5 +32 -0
  18. package/templates/application/entry/build-profile.json5 +33 -0
  19. package/templates/application/entry/gitignore.txt +6 -0
  20. package/templates/application/entry/hvigorfile.ts +7 -0
  21. package/templates/application/entry/obfuscation-rules.txt +20 -0
  22. package/templates/application/entry/oh-package.json5 +10 -0
  23. package/templates/application/entry/src/main/ets/entryability/EntryAbility.ets +63 -0
  24. package/templates/application/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +31 -0
  25. package/templates/application/entry/src/main/ets/pages/Index.ets +38 -0
  26. package/templates/application/entry/src/main/module.json5 +50 -0
  27. package/templates/application/entry/src/main/resources/base/element/color.json +8 -0
  28. package/templates/application/entry/src/main/resources/base/element/float.json +8 -0
  29. package/templates/application/entry/src/main/resources/base/element/string.json +16 -0
  30. package/templates/application/entry/src/main/resources/base/media/layered_image.json +7 -0
  31. package/templates/application/entry/src/main/resources/base/profile/backup_config.json +3 -0
  32. package/templates/application/entry/src/main/resources/base/profile/main_pages.json +5 -0
  33. package/templates/application/entry/src/main/resources/dark/element/color.json +8 -0
  34. package/templates/application/gitignore.txt +13 -0
  35. package/templates/application/hvigor/hvigor-config.json5 +23 -0
  36. package/templates/application/hvigorfile.ts +7 -0
  37. package/templates/application/oh-package.json5 +10 -0
@@ -0,0 +1,9 @@
1
+ import Vt from"path";import*as S from"fs";import*as Y from"path";import{homedir as bt}from"os";import*as H from"fs";import*as g from"path";function W(n){let t=g.resolve(n),e=[],r=t;for(;;)try{let i=H.realpathSync(r);return e.length===0?i:g.join(i,...e.reverse())}catch(i){if(i.code!=="ENOENT")throw i;let o=g.dirname(r);if(o===r)return t;e.push(g.basename(r)),r=o}}function N(n){let t=n.trim();return(t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'"))&&(t=t.slice(1,-1).trim()),t}function K(n){let t=N(n);if(!t)throw new Error("Path must not be empty.");return W(t)}var Et="deveco-cli",J,$=class extends Error{constructor(t){super(t),this.name="CliDataDirError"}};function wt(){let n=process.env.DEVECO_CLI_DATA_DIR,e=(n===void 0||n===""?"":N(n))||Y.join(bt(),".local","share",Et);try{return K(e)}catch(r){throw new $(r instanceof Error?r.message:String(r))}}function X(){if(J!==void 0)return J;let n=wt();try{if(S.existsSync(n))return S.realpathSync(n)}catch{}return n}import vt from"crypto";import u from"fs";import I from"os";import m from"path";import Tt from"https";var G={devecoApiTraceUpload:"https://cn.devecostudio.huawei.com/codeGenie/cli/trace/upload"};var D=class n{static ENDPOINT=G.devecoApiTraceUpload;static SENDER="deveco-cli";async upload(t,e){let r={"Content-Type":"application/json",sender:n.SENDER,deviceid:e.replace(/-/g,""),"Content-Length":String(Buffer.byteLength(t,"utf8"))};try{let{body:i}=await this.httpPost(n.ENDPOINT,r,Buffer.from(t,"utf8"),6e4);return n.isSuccess(i)}catch{return!1}}static isSuccess(t){try{let e=JSON.parse(t),r=e?.code??e?.errorCode;return String(r)==="200"}catch{return!1}}async httpPost(t,e,r,i){let o=new URL(t);return new Promise((s,l)=>{let p=Tt.request({protocol:o.protocol,hostname:o.hostname,port:o.port||(o.protocol==="https:"?443:80),path:o.pathname+o.search,method:"POST",headers:e,timeout:i},h=>{let T=[];h.on("data",St=>T.push(St)),h.on("end",()=>{s({code:h.statusCode??0,body:Buffer.concat(T).toString("utf8")})})});p.on("timeout",()=>{p.destroy(new Error("request timeout"))}),p.on("error",l),p.write(r),p.end()})}};var q={identity:n=>n,camelCase:n=>n,snakeCase:n=>n.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").toLowerCase()},M=class{constructor(t={}){this.config=t}serialize(t){let e=this.transform(t);return this.config.pretty?JSON.stringify(e,null,2):JSON.stringify(e)}toObject(t){return this.transform(t)}transform(t){if(t==null)return t;if(Array.isArray(t))return t.map(o=>this.transform(o));if(typeof t!="object")return t;let e=this.config.namingStrategy??q.identity,r=this.config.fieldNames??{},i={};for(let[o,s]of Object.entries(t)){let l=r[o]??e(o);i[l]=this.transform(s)}return i}},Z=new M({namingStrategy:q.snakeCase,fieldNames:{}});import b from"crypto";import C from"fs";import Dt from"path";var _="aes-256-gcm",Ct=12,_t="deveco-cli-trace-file",Lt="trace-file-secret",tt=32;function Q(n){try{let t=C.readFileSync(n,"utf8").trim();return t&&Buffer.from(t,"base64").length===tt?t:null}catch{return null}}function et(n){let t=Dt.join(n,Lt),e=Q(t);if(e)return B(e);let r=b.randomBytes(tt).toString("base64");try{C.mkdirSync(n,{recursive:!0});try{C.writeFileSync(t,r,{flag:"wx"})}catch{let i=Q(t);if(i)return B(i);C.writeFileSync(t,r)}}catch{}return B(r)}function B(n){return b.createHash("sha256").update(_t).update(n).digest()}function rt(n,t){let e=b.randomBytes(Ct),r=b.createCipheriv(_,t,e),i=Buffer.concat([r.update(n,"utf8"),r.final()]),o={version:1,algorithm:_,ciphertext:i.toString("base64"),iv:e.toString("base64"),authTag:r.getAuthTag().toString("base64")};return JSON.stringify(o)}function nt(n,t){let e;try{e=JSON.parse(n)}catch{return null}if(!Ft(e))return n;try{let r=b.createDecipheriv(_,t,Buffer.from(e.iv,"base64"));return r.setAuthTag(Buffer.from(e.authTag,"base64")),Buffer.concat([r.update(Buffer.from(e.ciphertext,"base64")),r.final()]).toString("utf8")}catch{return null}}function Ft(n){if(!n||typeof n!="object")return!1;let t=n;return t.version===1&&t.algorithm===_&&typeof t.ciphertext=="string"&&typeof t.iv=="string"&&typeof t.authTag=="string"}import*as a from"fs";import*as A from"path";import*as L from"os";import*as y from"path";import ge from"json5";var ve=64*1024;function it(){if(process.platform==="win32"){let n=process.env.LOCALAPPDATA??y.join(L.homedir(),"AppData","Local");return y.join(n,"devecocli-mcp-server","logs")}return process.platform==="darwin"?y.join(L.homedir(),"Library","Logs","devecocli-mcp-server"):y.join(L.homedir(),".local","share","devecocli-mcp-server","logs")}var ot="mcp-server.log",At="mcp-server",kt={maxSize:10*1024*1024,maxFiles:4},j=class n{fd=null;logDir=null;currentLogFile=null;mode;rotationOptions;currentFileSize=0;currentDate="";isRotating=!1;minLevel;static LEVEL_ORDER={debug:0,info:1,warn:2,error:3};constructor(t,e){this.rotationOptions={...kt,...e},this.minLevel=t?"debug":"info",t?(this.mode="console",this.logDir=null,this.currentLogFile=null):(this.mode="file",this.logDir=it(),this.currentLogFile=A.join(this.logDir,ot),a.existsSync(this.logDir)||a.mkdirSync(this.logDir,{recursive:!0}),this.cleanupOrphanLogFiles(),this.openLogFile())}getCurrentDateString(){let t=new Date,e=t.getFullYear(),r=String(t.getMonth()+1).padStart(2,"0"),i=String(t.getDate()).padStart(2,"0");return`${e}-${r}-${i}`}getRotatedFileName(t,e){return A.join(this.logDir,`${At}-${t}.log.${e}`)}fileExists(t){try{return a.accessSync(t,a.constants.F_OK),!0}catch{return!1}}cleanupOrphanLogFiles(){if(this.logDir)try{let t=a.readdirSync(this.logDir),e=[];for(let i of t)if(i===ot||/^mcp-server-\d{4}-\d{2}-\d{2}\.log\.\d+$/.test(i)){let o=A.join(this.logDir,i),s=a.statSync(o);e.push({name:i,mtime:s.mtime,path:o})}e.sort((i,o)=>o.mtime.getTime()-i.mtime.getTime());let r=1+this.rotationOptions.maxFiles;for(let i=r;i<e.length;i++)try{a.unlinkSync(e[i].path)}catch{}}catch{}}rotateLog(){if(!(this.isRotating||!this.logDir||!this.currentLogFile)){this.isRotating=!0;try{if(this.closeLogFile(),!this.fileExists(this.currentLogFile)){this.isRotating=!1,this.openLogFile();return}let t=this.getCurrentDateString(),e=this.getRotatedFileName(t,this.rotationOptions.maxFiles);this.fileExists(e)&&a.unlinkSync(e);for(let i=this.rotationOptions.maxFiles-1;i>=1;i--){let o=this.getRotatedFileName(t,i),s=this.getRotatedFileName(t,i+1);this.fileExists(o)&&a.renameSync(o,s)}let r=this.getRotatedFileName(t,1);a.renameSync(this.currentLogFile,r),this.cleanupOrphanLogFiles(),this.openLogFile()}catch{this.openLogFile()}finally{this.isRotating=!1}}}openLogFile(){if(this.currentLogFile){this.currentDate=this.getCurrentDateString();try{if(this.fileExists(this.currentLogFile)){let t=a.statSync(this.currentLogFile);this.currentFileSize=t.size}else this.currentFileSize=0}catch{this.currentFileSize=0}try{this.fd=a.openSync(this.currentLogFile,"a")}catch{this.fd=null}}}closeLogFile(){if(this.fd!==null){try{a.closeSync(this.fd)}catch{}this.fd=null}}checkRotation(t){let e=this.getCurrentDateString();this.currentDate&&this.currentDate!==e&&(this.rotateLog(),this.currentDate=e),this.currentFileSize+=t,this.currentFileSize>=this.rotationOptions.maxSize&&this.rotateLog()}write(t,e,...r){if(this.mode==="silent"||n.LEVEL_ORDER[t]<n.LEVEL_ORDER[this.minLevel])return;let i=r.map(p=>p instanceof Error?`${p.name}: ${p.message}`:typeof p=="object"?JSON.stringify(p,null,2):String(p)).join(" "),o=i?`${e} ${i}`:e,l=`[${new Date().toLocaleString("sv-SE",{timeZone:"Asia/Shanghai",hour12:!1})+"."+String(new Date().getMilliseconds()).padStart(3,"0")}] [MCP/${t.toUpperCase()}] [pid=${process.pid}] ${o}
2
+ `;if(this.mode==="file"&&this.currentLogFile){if(this.fd===null&&this.openLogFile(),this.fd!==null)try{let p=Buffer.from(l);a.writeSync(this.fd,p),this.checkRotation(p.byteLength)}catch{this.closeLogFile(),this.openLogFile()}}else this.mode==="console"&&process.stderr.write(l)}debug(t,...e){this.write("debug",t,...e)}info(t,...e){this.write("info",t,...e)}warn(t,...e){this.write("warn",t,...e)}error(t,...e){this.write("error",t,...e)}dispose(){this.closeLogFile()}flush(){if(!(this.mode!=="file"||this.fd===null))try{a.fsyncSync(this.fd)}catch{}}getLogFilePath(){return this.currentLogFile}getLogDirectory(){return this.logDir}},E=null;function Ot(n=!1){E&&E.dispose(),E=new j(n)}function F(){return E||Ot(!1),E}var c={debug:(n,...t)=>F().debug(n,...t),info:(n,...t)=>F().info(n,...t),warn:(n,...t)=>F().warn(n,...t),error:(n,...t)=>F().error(n,...t)};import at from"fs";import Pt from"path";var k=300*1e3,O=3600*1e3,lt=10080*60*1e3,v=8*1024*1024,ct=3600*1e3,st=".pending",Rt=/\.reclaim-\d+-\d+$/;function P(){let n=process.env.DEVECO_CLI_DISABLE_TELEMETRY;return n==="1"||n==="true"}var It="upload-state.json";function ut(n){return Pt.join(n,It)}function Nt(){return{firstEventAt:null,lastUploadAt:null,lastRetryAt:null}}function U(n){try{let t=at.readFileSync(ut(n),"utf8"),e=JSON.parse(t);return{firstEventAt:typeof e.firstEventAt=="number"?e.firstEventAt:null,lastUploadAt:typeof e.lastUploadAt=="number"?e.lastUploadAt:null,lastRetryAt:typeof e.lastRetryAt=="number"?e.lastRetryAt:null}}catch{return Nt()}}function V(n,t){try{at.writeFileSync(ut(n),JSON.stringify(t),"utf8")}catch{}}function pt(n){let t=U(n);t.firstEventAt===null&&(t.firstEventAt=Date.now(),V(n,t))}function ft(n){let t=U(n);t.lastUploadAt=Date.now(),t.firstEventAt=null,V(n,t)}function dt(n){let t=U(n);t.lastRetryAt=Date.now(),V(n,t)}function mt(n){let t=n.match(/^telemetry-(\d{4})-(\d{2})-(\d{2})\.txt$/);if(!t)return null;let e=Date.parse(`${t[1]}-${t[2]}-${t[3]}T00:00:00`);return Number.isFinite(e)?e:null}function z(n){if(!n.startsWith("telemetry-"))return null;let t=Rt.exec(n),e=t?n.slice(0,t.index):n;if(!e.endsWith(st))return null;let r=e.slice(0,-st.length);return r.endsWith(".txt")?r:null}function gt(n){return z(n)!==null}import $t from"crypto";import R from"fs";import Mt from"path";var Bt="install-id";function jt(n){return Mt.join(n,Bt)}function ht(n){try{let t=R.readFileSync(n,"utf8").trim();return Ut(t)?t:null}catch{return null}}function Ut(n){return/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(n)}function yt(n){let t=jt(n),e=ht(t);if(e)return e;let r=$t.randomUUID();try{R.mkdirSync(n,{recursive:!0});try{R.writeFileSync(t,r,{flag:"wx"})}catch{let i=ht(t);if(i)return i;R.writeFileSync(t,r)}}catch{}return r}var w=class n{static MAX_BATCH=200;static MAX_PAYLOAD_BYTES=Math.floor(1.7*1024*1024);static TRACE_OS_VERSION="1.0";static COUNTRY_CODE="CN";static EVENT_PREFIX="devecocli_";uploader=new D;storageDir="";failedDir="";installId="";disabled=P();traceFileKey=Buffer.alloc(0);sessionId=vt.randomUUID();cliVersion="";devecoStudioVersion=null;sourceType="";cltVersion=null;nodeVersion="";initialTimer=null;intervalTimer=null;retryInitialTimer=null;retryTimer=null;flushChain=Promise.resolve();constructor(){c.debug(`[telemetry] instance created, sessionId=${this.sessionId}`)}init(t){this.storageDir=t,this.failedDir=m.join(t,"failed"),this.cliVersion="1.3.7",this.nodeVersion=process.version,this.installId=yt(t).replace(/-/g,""),this.traceFileKey=et(t),u.mkdirSync(t,{recursive:!0}),u.mkdirSync(this.failedDir,{recursive:!0}),c.debug(`[telemetry] init: dir=${t}, failedDir=${this.failedDir}, cliVersion=${this.cliVersion}, os=${this.resolveOsName()}, arch=${this.resolveOsArch()}, node=${this.nodeVersion}`)}setStudioVersion(t){this.devecoStudioVersion=t,c.debug(`[telemetry] studio version set: ${t}`)}setSourceType(t){this.sourceType=t,c.debug(`[telemetry] sourceType set: ${t}`)}setCltVersion(t){this.cltVersion=t,c.debug(`[telemetry] CLT version set: ${t}`)}async track(t,e){if(this.disabled)return typeof e=="function"?e():void 0;if(!this.storageDir)throw new Error("Telemetry not initialized. Call init() first.");if(!e){await this.record(t,0,!0,null);return}if(typeof e!="function"){let o=e;await this.record(t,o.duration_ms,o.success,o.error_code);return}let r=e,i=Date.now();try{let o=await r(),s=Date.now()-i;return await this.record(t,s,!0,null),o}catch(o){let s=Date.now()-i,l=this.toErrorCode(o);throw await this.record(t,s,!1,l),o}}async record(t,e,r,i){let o=this.buildTraceEvent(t,e,r,i);try{let s=rt(JSON.stringify(o),this.traceFileKey);await u.promises.appendFile(this.currentFile(),s+`
3
+ `,"utf8"),pt(this.storageDir)}catch{}}buildTraceEvent(t,e,r,i){let o=`${n.EVENT_PREFIX}${t.event}`,s=Z.toObject(t);return s&&typeof s=="object"&&delete s.event,{countryCode:n.COUNTRY_CODE,event:o,eventtime:String(Date.now()),properties:{uid:this.installId,trace_uuid:vt.randomUUID(),trace_os_version:n.TRACE_OS_VERSION,os_arch:this.resolveOsArch(),action:o,trace_os_name:this.resolveOsName(),version:`${this.cliVersion}_${this.resolveOsSuffix()}`,deveco_studio_version:this.formatVersion(this.devecoStudioVersion),command_line_version:this.formatVersion(this.cltVersion),source_type:this.sourceType,session_id:this.sessionId,node_version:this.nodeVersion,ram:this.resolveTotalRam(),duration_ms:e,success:r,error_code:i,event_detail:s}}}toErrorCode(t){return t instanceof Error?t.code??t.name:"UnknownError"}resolveOsArch(){let t=I.arch();return t==="x64"?"amd64":t}resolveOsName(){switch(process.platform){case"win32":{let t=I.release().split(".");return(parseInt(t[t.length-1],10)||0)>=22e3?"Windows 11":"Windows 10"}case"darwin":return"macOS";case"linux":return"Linux";default:return process.platform}}resolveOsSuffix(){switch(process.platform){case"win32":return"windows";case"darwin":return I.arch()==="arm64"?"mac_arm":"mac";default:return"linux"}}resolveTotalRam(){return`${Math.round(I.totalmem()/1024)}KB`}formatVersion(t){return t?`${t}_${this.resolveOsSuffix()}`:""}dateString(){let t=new Date,e=String(t.getMonth()+1).padStart(2,"0"),r=String(t.getDate()).padStart(2,"0");return`${t.getFullYear()}-${e}-${r}`}currentFile(){return m.join(this.storageDir,`telemetry-${this.dateString()}.txt`)}startScheduler(){if(this.stopScheduler(),this.disabled){c.debug("[telemetry] scheduler not started (telemetry disabled)");return}c.debug(`[telemetry] scheduler started (flush every ${k/1e3}s, retry failed every ${O/1e3}s)`),this.initialTimer=setTimeout(()=>{this.flush().catch(()=>{}),this.intervalTimer=setInterval(()=>{this.flush().catch(()=>{})},k)},k),this.retryInitialTimer=setTimeout(()=>{this.retryFailed().catch(()=>{}),this.retryTimer=setInterval(()=>{this.retryFailed().catch(()=>{})},O)},O)}stopScheduler(){this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.intervalTimer&&(clearInterval(this.intervalTimer),this.intervalTimer=null),this.retryInitialTimer&&(clearTimeout(this.retryInitialTimer),this.retryInitialTimer=null),this.retryTimer&&(clearInterval(this.retryTimer),this.retryTimer=null),c.debug("[telemetry] scheduler stopped")}async flush(){return this.disabled?!0:this.runExclusive(()=>this.performFlush())}async performFlush(){if(!this.storageDir)throw new Error("Telemetry not initialized. Call init() first.");let t=await this.listPendingFiles(),e=!0;for(let r of t)await this.flushFile(r)||(e=!1);return ft(this.storageDir),e}async retryFailed(){return this.disabled?!0:this.runExclusive(()=>this.performRetryFailed())}async performRetryFailed(){if(!this.storageDir)throw new Error("Telemetry not initialized. Call init() first.");await this.reclaimStalePending();let t=Date.now(),e;try{e=await u.promises.readdir(this.failedDir)}catch{return!0}let r=e.filter(o=>o.startsWith("telemetry-")&&o.endsWith(".txt")).map(o=>m.join(this.failedDir,o)),i=!0;for(let o of r){let s=m.basename(o),l=mt(s);if(l!==null){if(t-l>lt){try{await u.promises.unlink(o)}catch{}continue}await this.flushFile(o)||(i=!1)}}return i||c.warn("[telemetry] retry failed"),dt(this.storageDir),i}async reclaimStalePending(){let t=Date.now();for(let e of[this.storageDir,this.failedDir]){let r;try{r=await u.promises.readdir(e)}catch{continue}for(let i of r)gt(i)&&await this.reclaimOrphan(m.join(e,i),t)}}async reclaimOrphan(t,e){let r;try{let h=await u.promises.stat(t);if(e-h.mtimeMs<ct)return;r=h.size}catch{return}let i=m.dirname(t),o=z(m.basename(t));if(o===null)return;let s=await this.claimOrphan(t,i,o);if(s===null)return;let l=m.join(i,o),p=v-await this.fileSize(l);if(r>p){try{await u.promises.unlink(s)}catch{}c.warn(`[telemetry] dropped stale orphan pending: ${s} (${r} bytes > budget ${p})`);return}if(await this.mergeOrphanInto(s,l)){c.warn(`[telemetry] reclaimed orphan pending: ${s} (${r} bytes)`);return}c.warn(`[telemetry] merge failed, kept orphan pending for retry: ${s} (${r} bytes)`)}async claimOrphan(t,e,r){let i=m.join(e,`${r}.pending.reclaim-${process.pid}-${Date.now()}`);try{await u.promises.rename(t,i)}catch{return null}return await this.touch(i),i}async mergeOrphanInto(t,e){try{await u.promises.appendFile(e,await u.promises.readFile(t))}catch(r){return c.warn(`[telemetry] merge orphan pending failed: ${t} -> ${e}: ${r instanceof Error?r.message:String(r)}`),!1}try{await u.promises.unlink(t)}catch(r){c.warn(`[telemetry] merged but failed to remove orphan pending: ${t}: ${r instanceof Error?r.message:String(r)}`)}return!0}async fileSize(t){try{return(await u.promises.stat(t)).size}catch{return 0}}async touch(t){let e=new Date;try{await u.promises.utimes(t,e,e)}catch{}}runExclusive(t){let e=()=>t(),r=this.flushChain.then(e,e);return this.flushChain=r.then(()=>{},()=>{}),r}async listPendingFiles(){let t;try{t=await u.promises.readdir(this.storageDir)}catch{return[]}return t.filter(e=>e.startsWith("telemetry-")&&e.endsWith(".txt")).map(e=>m.join(this.storageDir,e)).sort()}async flushFile(t){let e=await this.fileSize(t);if(e>v){c.warn(`[telemetry] ${m.basename(t)} is ${e} bytes (> ${v}), dropped without uploading`);try{await u.promises.unlink(t)}catch{}return!0}let r=t+".pending";try{await u.promises.rename(t,r)}catch{return!1}await this.touch(r);let i;try{i=await u.promises.readFile(r,"utf8")}catch{return!1}let o=i.split(`
4
+ `).map(s=>s.trim()).filter(Boolean);if(o.length===0){try{await u.promises.unlink(r)}catch{}return!0}return this.uploadInBatches(r,o)}collectDecryptBatch(t,e){let r=[],i=16,o=e;for(;o<t.length&&r.length<n.MAX_BATCH;){let s=nt(t[o],this.traceFileKey);if(s===null){o++;continue}let l=Buffer.byteLength(s,"utf8")+1;if(r.length>0&&i+l>n.MAX_PAYLOAD_BYTES)break;r.push(s),i+=l,o++}return{batch:r,next:o}}async uploadInBatches(t,e){let r=0;for(;r<e.length;){let i=r,{batch:o,next:s}=this.collectDecryptBatch(e,r);r=s;let l=this.buildPayloadItems(o);if(l.length===0)continue;let p=JSON.stringify(l),h=!1;try{h=await this.uploader.upload(p,this.installId)}catch{}if(!h){c.warn("[telemetry] upload failed");let T=e.slice(i).join(`
5
+ `)+`
6
+ `;return await this.moveToFailed(t,T),!1}}try{await u.promises.unlink(t)}catch{}return!0}buildPayloadItems(t){let e=[];for(let r of t)try{let i=JSON.parse(r);e.push({action:i.event,detail:JSON.stringify(i.properties),timestamp:Number(i.eventtime)||Date.now()})}catch{}return e}async moveToFailed(t,e){let r=m.basename(t,".pending"),i=m.join(this.failedDir,r),o=e.split(`
7
+ `).filter(Boolean),s=v-await this.fileSize(i),l=this.takeNewestWithinBudget(o,s);l.length<o.length&&c.warn(`[telemetry] failed file capped at ${v} bytes, dropped ${o.length-l.length} older events`);try{l.length>0&&await u.promises.appendFile(i,l.join(`
8
+ `)+`
9
+ `,"utf8")}catch{return}try{await u.promises.unlink(t)}catch{}}takeNewestWithinBudget(t,e){if(e<=0)return[];let r=[],i=0;for(let o=t.length-1;o>=0;o--){let s=Buffer.byteLength(t[o],"utf8")+1;if(i+s>e)break;i+=s,r.push(t[o])}return r.reverse(),r}};var x=new w;import{spawn as Ge}from"child_process";import{existsSync as Ze}from"fs";import{dirname as tr,join as er}from"path";import{fileURLToPath as nr}from"url";var zt=Vt.join(X(),"TraceLogData");x.init(zt);(async()=>{try{await x.flush(),await x.retryFailed()}catch(n){c.warn("[telemetry] background upload error:",n),process.exit(1)}})();
package/package.json CHANGED
@@ -1,6 +1,69 @@
1
- {
2
- "name": "@litingwei621/test-deveco-cli-common",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "@litingwei621/test-deveco-cli-common",
3
+ "version": "1.3.7",
4
+ "description": "HarmonyOS application development command line tool",
5
+ "supportedDocsApi": 1,
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "files": [
9
+ "dist",
10
+ "templates",
11
+ "src/resources",
12
+ "THIRD-PARTY-LICENSES",
13
+ "skills"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsup",
17
+ "dev": "tsx watch src/cli.ts",
18
+ "start": "tsx src/cli.ts",
19
+ "test": "vitest run",
20
+ "lint": "eslint \"src/**/*.ts\"",
21
+ "lint:fix": "eslint src --fix",
22
+ "format": "prettier --write src",
23
+ "prepublishOnly": "npm run lint && npm run build"
24
+ },
25
+ "engines": { "node": ">=22" },
26
+ "os": [
27
+ "darwin",
28
+ "linux",
29
+ "win32"
30
+ ],
31
+ "cpu": [
32
+ "x64",
33
+ "arm64"
34
+ ],
35
+ "keywords": ["harmonyos", "deveco", "cli"],
36
+ "dependencies": {
37
+ "@deveco/deveco-cli-knowledgebase": "1.3.7",
38
+ "@modelcontextprotocol/sdk": "^1.29.0",
39
+ "@xmldom/xmldom": "^0.8.15",
40
+ "adm-zip": "0.6.1",
41
+ "axios": "1.20.0",
42
+ "colorette": "^2.0.20",
43
+ "commander": "^13.1.0",
44
+ "execa": "^9.6.1",
45
+ "fs-extra": "^11.3.4",
46
+ "global-agent": "^4.1.3",
47
+ "json5": "^2.2.3",
48
+ "mp4box": "^2.4.1",
49
+ "node-forge": "^1.4.0",
50
+ "ora": "^9.4.0",
51
+ "p-limit": "^5.0.0",
52
+ "proper-lockfile": "^4.1.2",
53
+ "regedit": "^5.1.4",
54
+ "semver": "^7.8.5",
55
+ "smol-toml": "^1.6.1",
56
+ "socket.io-client": "^4.7.5",
57
+ "ui-verification-mcp": "0.0.18",
58
+ "xpath": "^0.0.35",
59
+ "zod": "^4.4.3"
60
+ },
61
+ "devDependencies": {
62
+ "@types/adm-zip": "^0.5.8",
63
+ "@types/fs-extra": "^11.0.4",
64
+ "@types/global-agent": "^3.0.0",
65
+ "@types/node-forge": "^1.3.14",
66
+ "@types/proper-lockfile": "^4.1.4",
67
+ "@types/semver": "^7.7.1"
68
+ }
69
+ }
@@ -0,0 +1,327 @@
1
+ ---
2
+ name: deveco-cli
3
+ description: >-
4
+ Use 'devecocli' for HarmonyOS apps (scaffold, build, run, debug, devices, emulators, logs, docs, UI inspection, 鸿蒙开发文档, skills). Use it if workspace has build-profile.json5 or oh-package.json5, or user mentions HarmonyOS, 鸿蒙, DevEco, ArkTS, ArkUI, docs, 鸿蒙文档, 知识, 搜索, skills, UI, UI测试, 组件调试. If user explicitly specifies another tool, skill, or workflow, follow that choice; otherwise use 'devecocli'.
5
+ ---
6
+
7
+ # DevEco CLI
8
+
9
+ `devecocli` wraps DevEco Studio's `hvigor`, `ohpm`, `hdc`, emulator toolchain, and bundled skills installer.
10
+
11
+ Available commands: `build`, `check`, `run`, `update`, `device`, `emulator`, `ui`, `skills`, `log`, `verify`, `create`, `init`, `serve`, `docs`, `signature`, `auth`.
12
+
13
+ **Sandbox Rule**: Commands tagged `[Outside sandbox]` must be run outside the sandbox.
14
+
15
+ ## 1. Code → Build → Deploy → Run → Debug
16
+
17
+ ### `devecocli create`
18
+ Scaffold a new HarmonyOS project.
19
+ - `--app-name <name>` (Req): 1–200 chars, `^[a-zA-Z][a-zA-Z0-9_]*$`
20
+ - `--project-path <path>`: Auto-created if omitted (`./<app-name>`). Must be empty if exists.
21
+ - `--bundle-name <bundle>`: Default `com.example.<appname-lowercase>`. 7–128 chars, ≥3 segments.
22
+ - `--api-level <level>`: int ≥17 (default: auto or 23).
23
+ - `--device-types <types...>`: One or more device types written to `module.json5` (default: `phone`). Supported: `phone`, `tablet`, `2in1`, `car`, `wearable`, `tv`. Unknown values fail with the supported list.
24
+ *Ex*: `devecocli create --app-name MyApp --project-path ./CustomDir --api-level 23`
25
+ *Ex*: `devecocli create --app-name MyApp --device-types tablet`
26
+
27
+ ### `devecocli build` `[Outside sandbox]`
28
+ Compile and package project/modules. (Defaults: `--product default`, `--build-mode debug`)
29
+ | Goal | Command |
30
+ |---|---|
31
+ | Single-module / single-`entry` | `devecocli build` |
32
+ | Specific modules | `devecocli build --modules <m1> <m2>@<target>` |
33
+ | Whole product bundle (.app) | `devecocli build --product <name>` |
34
+ | Specific assemble type | `devecocli build --assemble-type <assembleHap\|assembleApp\|assembleHsp\|assembleHar>` |
35
+ | Override signing config | `devecocli build --signing-config <name>` |
36
+ | Clean build outputs | `devecocli build clean` |
37
+ | Clean specific modules | `devecocli build clean --modules <m1> <m2>` |
38
+ - **Build efficiency**: if build/clean is slow, consult https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-build-optimize-practices for build optimization practices.
39
+ - **Clean mode**: `devecocli build clean [--mode <mode>] [--modules <m1> <m2>]` runs in module mode by default and reads build options (analyze/parallel/incremental/daemon) from `hvigor-config.json5` `execution`, matching DevEco Studio.
40
+ - **Build artifacts**: on success, prints absolute paths of all produced `.hap`/`.hsp`/`.har`/`.app` files for direct use.
41
+ - **Assemble type**: `--assemble-type` lets you explicitly choose the hvigor task (`assembleHap`, `assembleApp`, `assembleHsp`, `assembleHar`) instead of auto-detecting by module type. Use `assembleApp` for whole-project builds; `assembleHap`/`assembleHsp`/`assembleHar` for module-level builds.
42
+ - **Signing config**: `--signing-config <name>` overrides the product's default signing config from `build-profile.json5`, enabling release builds with a release signing config.
43
+
44
+ ### `devecocli check lint`
45
+ Run DevEco Code Linter checks for TS/ArkTS code.
46
+ - **Studio requirement**: Default lint checks and `--fix`, `--incremental`, `--product`, `--config-path`, and `--limit` support DevEco Studio `>= 6.0.0`; explicitly using `--format` or `--output-path` requires DevEco Studio `>= 6.1.0`. Command Line Tools mode follows the CLT `>= 26.0.0` baseline.
47
+ - `[path]`: File or directory to lint. Defaults to the project root from `build-profile.json5`, otherwise the current directory.
48
+ - Options: `--config-path <file>`, `--fix`, `--incremental`, `--product <name>`, `--format <default|json>`, `--output-path <path>`, `--limit <number>`.
49
+ - Set `DEVECO_CLI_CLT_PATH` to the Command Line Tools root when DevEco Studio is not installed; CLT is not discovered automatically from PATH or default installation directories.
50
+
51
+ ### `devecocli check arkts`
52
+ Run this command once after a round of edits, right before the build. Catches most ArkTS errors faster than a full build. Do not run it per edit.
53
+ - `[files...]`: Specific `.ets` files to check. Defaults to all `.ets` files in the project.
54
+ - `--fix`: Auto-fix high-confidence errors (e.g., missing `export` keywords) before reporting.
55
+ - `--project <path>`: Project root directory. Defaults to auto-detection from cwd via `build-profile.json5`.
56
+
57
+ *Ex*: `devecocli check arkts --fix entry/src/main/ets/pages/Index.ets`
58
+
59
+ ### `devecocli emulator`
60
+ Manage local emulator instances and system images.
61
+ - **Studio requirement**: DevEco Studio `>= 6.1.0`.
62
+ - `license view`: Read agreement text without changes.
63
+ - `license accept`: Accept all emulator agreements without displaying the text or prompting. For agents, use this when agreement acceptance is authorized, then resume the blocked operation after success. Ask only if authorization is missing. The bare `license` command requires a TTY and y/N input; do not use it in an Agent terminal.
64
+ - `list`: Show instances (status, serial, device type). Opt: `--format <table|json>` (default: `table`); `--details` (mutually exclusive with `--format`) dumps the raw JSON of `Emulator -list -details` verbatim — no field trimming/renaming.
65
+ - `start <names...>`: Start instances. Quote names with spaces. Opts: `--hdcport <10000-16555>` (Emulator `-hdcport`), `--boot-mode <coldboot|snapshot|reset>` (Emulator `-bootMode`; `coldboot` keeps user data, `snapshot` restores the last snapshot and needs Quick Boot, `reset` wipes data then cold boots; overrides the mode chosen when the instance was created), `--no-window` (Emulator `-noWindow`, headless start; required on Linux hosts without a GUI). (See Troubleshooting if blocked).
66
+ - `stop <names...>`: Stop by name or serial (`127.0.0.1:<port>`).
67
+ - Scene control commands require Emulator 7.0 or later. Use `DEVECO_CLI_DEBUG=1` to inspect the underlying `Emulator` command mapping.
68
+ - `shake` / `power` / `rotate <left|right>` / `volume <up|down>` (Req: `--target <nameOrSerial>`): Basic emulator controls.
69
+ - `fold <state>` (Req: `--target <nameOrSerial>`): Set foldable display state, matched against the target emulator's reported `deviceType`. `foldable` uses `open|half-open|close`; `2in1_foldable` uses `open|vertical-open|half-open|close`; `triplefold` uses `single|double|triple` or one of its six left/right folded-state combinations. Other device types and cross-device states are rejected before execution.
70
+ - `battery` (Req: `--target`; `--level <0-100>` and/or `--status <charging|discharging>`): Set battery state. `--level` and `--status` may be set together in one call. `--level` checks the current emulator charging state automatically (`0-100` while charging, `1-100` otherwise); `--level 0` combined with `--status discharging` is rejected as invalid.
71
+ - `geolocation` (Req: `--target`; `--longitude`+`--latitude` recommended together, missing one warns only; Opt: `--altitude`, `--direction`): Inject GPS data (multiple options in one call).
72
+ - `scene <outdoorRunning|outdoorCycling|drivingNavigation>` (Req: `--target`): Start motion simulation.
73
+ - `sensor` (Req: `--target`; `--light-intensity`/`--humidity`/`--temperature`/`--steps`/`--heartrate`, multiple may be set in one call): Inject sensor data.
74
+ - Multi-option `geolocation`/`battery`/`sensor` commands dispatch actions sequentially and are non-atomic: on action N failure, actions 1..N-1 are already applied with no rollback; re-run the whole command to converge.
75
+ - `create <name>` (Req: `--device-type`, `--os-version`): Create instance. Opts: `--instance-path <path>` (alias `--path`), `--image-root <path>`, `--screen-profile <model>`, `--screen <config...>`, `--storage <2-1023>`, `--memory <2-32>`, `--hot-boot <true|false>`, `--force`. Device type is case-insensitive. `--screen` accepts one quoted configuration or two for a foldable device in the format `width(px) height(px) DPI screen-diagonal-length(inch)`; width/height: 720-3500 px, DPI: 240-640, screen diagonal length: 3.5-9 inch. When combined with `--screen-profile`, `--screen` takes precedence. API 26+ uses hot boot by default unless explicitly disabled.
76
+ - `delete <name>`: Delete instance. Opt: `--instance-path <path>` (alias `--path`) — required to delete an instance created with a custom path.
77
+ - `image list`: List downloaded images. Opts: `--device-type <type>`, `--all`, `--format <table|json>`.
78
+ - `image download` / `image remove` (Req: `--device-type`, `--os-version`): Download/remove image. (Takes 30+ min, set long timeout).
79
+ *Device types*: `phone`, `foldable`, `widefold`, `triplefold`, `tablet`, `2in1`, `2in1 foldable`, `wearable`, `tv`.
80
+
81
+ ### `devecocli ui`
82
+ Inspect UI on a connected physical device or running emulator.
83
+ - `screenshot`: Capture a screenshot from a physical device or running emulator. `--device <name|serial>` is optional when exactly one device is connected, and required when multiple devices are connected.
84
+ - Optional: `--display <displayId>`. Required: `--path <path>` (existing directory or PNG file path, including paths relative to the current directory; writable destination; no overwrite).
85
+ - Implementation uses `hdc shell snapshot_display` and `hdc file recv`; set `DEVECO_CLI_DEBUG=1` to inspect the actual `hdc` commands.
86
+ *Ex*: `devecocli ui screenshot --device Phone --path ./screenshots/phone.png`
87
+
88
+ `screenrecord` records the screen of a physical device to an MP4 file (emulators are rejected). No subcommand enters interactive mode (Ctrl+C stops and saves); `start` returns immediately and the device keeps recording until `stop`. `--device <name|serial>` follows the same multi-device rule; interactive and `start` require `--path <dir|file.mp4>`. `status` reports `recording` / `busy` / `idle`; with `--format json`, stdout carries the JSON object. `stop --force` stops a recording not started by this CLI without downloading it (view it in the device gallery). A recording already stopped on the device can be recovered by `stop` on the same `DEVECO_CLI_DATA_DIR`. Starting again (interactive or `start`) deletes the interrupted registration without downloading the old video; the original remains in the device gallery.
89
+ *Ex*: `devecocli ui screenrecord start --device Phone --path ./recordings/` → `devecocli ui screenrecord stop --device Phone`
90
+
91
+ ### `devecocli docs`
92
+ Search, read, catalog, and update local HarmonyOS docs.
93
+ - `search <keywords...>`: Match any keyword. Opts: `--catalog <name>`, `--format <default|json>`, `--limit <n>`.
94
+ - `read <documentId>`: Read full content by ID (e.g. `devecocli docs read 开发指南/冷启动_Launch分析/Launch模板基本操作/ide-insight-session-launch`).
95
+ - `catalog`: List available catalogs.
96
+ - `update`: Update docs to the latest compatible version — downloads, sha512-verifies, and installs the docs-data package with atomic version switching; falls back to the bundled baseline if missing/corrupted. Opts: `--force`, `--check`.
97
+
98
+ ### `devecocli device`
99
+ - `list`: Show active real devices and running emulators. Opt: `--format <table|json>` (default: `table`).
100
+ - `view`: Detailed info. Req `-t <name|serial>` on multi-device hosts. Opt: `--format <table|json>` (default: `table`).
101
+ - `file`: Transfer files between the host and a device. Opt: `--device <name|serial>` (optional when exactly one device is connected, required when multiple). `send <local> <remote>` pushes a local file onto the device; `recv <remote> <local>` pulls a file back. Maps to `hdc -t <serial> file send/recv <src> <dst>`; the operation only succeeds when hdc reports `FileTransfer finish`.
102
+ - `sqlite3`: Run `sqlite3` on a device. Opt: `--device <name|serial>` (optional when exactly one device is connected, required when multiple). `db-path` is the on-device SQLite file; all further args are forwarded to the device `sqlite3` as-is (e.g. `-json`, `-readonly`, SQL strings, dot-commands). Maps to `hdc -t <serial> shell sqlite3 <db-path> [args...]`.
103
+
104
+ ### `devecocli run` `[Outside sandbox]`
105
+ Build, install, and launch. After a successful ability launch (and after a successful `--apply`), runs a **default smoke check**. Success prints `Smoke: PASS`; failures print `Smoke: FAIL_CRASH` or `Smoke: FAIL_BLANK` (crash log path via `crash_log:`, blank screenshot via `screenshot:`) and exit non-zero. Smoke is skipped when there is no ability to launch, and for `--hotreload` / `--hotreload-apply`.
106
+ - `--module <m1> [m2...]`: Target module(s) (auto-selected if only one runnable).
107
+ - `--device <name|serial>`: Target device (Req if multiple connected).
108
+ - `--product <product>` / `--build-mode <mode>`: Defaults: `default` / `debug`.
109
+ - `--ability <ability>`: Default from `module.json5`.
110
+ - `--uninstall`: Uninstall existing app first (Fixes signing key issues).
111
+ - `--skip-build`: Deploy existing artifacts.
112
+ - `--apply <fileName>`: **Fast incremental deploy** — rebuilds only changed files into a signed hqf, installs via `bm quickfix -a -f -o`, then restarts the app. Much faster than a full `devecocli run` for iterating on code changes. Modules are auto-detected from the file paths in `<fileName>` (no `--module` needed).
113
+ - `<fileName>`: a plain file name (no path separators) under the project's `.hvigor/` directory; the caller writes the changed-file list there. File name is sanitized to prevent path traversal. Content: list of **source file paths changed this round** (one per line, relative to project root or absolute; `#` comments and blank lines ignored; typically `.ets`/`.ts`/`.cpp`/resource files). The changeFileList is **incrementally merged** — only list files changed since the last apply; previously listed files are retained automatically.
114
+ - **Prereq**: DevEco Studio ≥6.1.1 (hvigor `assembleDevHqf` support; below is rejected with an upgrade hint); run `devecocli run` once first (full build + deploy — `--apply` reuses its build intermediates and generates its own short-lived `buildConfig.json` per apply).
115
+ - **Do not mix with an active `--hotreload` watch session**: both drive patch builds over the same per-module `buildConfig.json`; stop the session (`--hotreload stop`) first.
116
+ - **If changes don't take effect**: on any apply failure, fall back to a full `devecocli run`.
117
+ *Ex*: `devecocli run` → edit code → write `.hvigor/changes.txt` → `devecocli run --apply changes.txt`
118
+ - `--hotreload [action]`: **Hot-reload watch session** — must be started as a **background process** (the process holds a persistent socket to the hvigor daemon). It builds the hap in hot-reload mode (`hotReload=true debuggable=true`), deploys+launches the app, then the process itself connects a socket to the daemon and sends `CommonBuild assembleHap --hot-reload-build --watch`, **staying connected** so the daemon's watch worker (rollup watch) stays alive for `--hotreload-apply` to reuse. The process stays alive (onBuildOutput streams to its stdout). **Must be stopped (kill the background process) when the hot-reload debug session ends** — otherwise the watch worker/daemon leaks. `--hotreload stop` additionally stops the hvigor daemon.
119
+ - **Usage**: start `devecocli run --module <m> --hotreload` as a background process; wait for the `.hotreload-mode` marker (or "watch session active" on its stdout) = ready; then run `devecocli run --module <m> --hotreload-apply <file>` (foreground). When done: kill the background process + `devecocli run --hotreload stop`.
120
+ - `--hotreload-apply <fileName>`: **Hot reload without app restart** — reads the changed-file list from `.hvigor/<fileName>` (same format/merge rules as `--apply`), opens a **short socket** to the daemon (kept alive by the background `--hotreload` process) and sends `CommonBuild assembleDevHqf --hot-compile` → the watch worker incrementally hot-compiles changed `.ets`/`.ts` into abc (HotReloadArkTS; output/errors streamed live via WatchLog), then **manually** packs + signs a hqf from that abc (`app_packing_tool.jar` + `hap-sign-tool.jar`, DevEco material password decrypted via `DecipherUtil`), and installs it via `bm quickfix -a -f -o`. The app is **not restarted** — changes take effect immediately.
121
+ - **Prereq**: the background `devecocli run --hotreload` process must be running (holds the watch session). If `--hotreload-apply` times out, the watch session isn't alive — (re)start the background `--hotreload`.
122
+ - **Session exclusivity**: do not run `--apply` or a normal `run`/`build` against the project while the watch session is live — they clear the `buildConfig.json` the session's hot compile depends on. Stop the session (`--hotreload stop`) first, restart it after.
123
+ - **Scope (important)**: hot reload targets a **single module** (`--module` required; the target can be entry/hsp/feature; `har` deps are folded in and fine). **`har` dependency changes are auto-routed** to the dependent main module's (entry/feature/hsp) `hotReload/changedFileList.json` — so editing a har's `.ets` and listing it hot-reloads via the target module. Changes in **`feature`/`hsp` dependency modules** (not the target) are NOT hot-reloadable — they're detected and warned (run a full `devecocli run` to redeploy those). `@State`/state-decorator changes are **not** hot-reloadable (state isn't re-initialized); hot-reloadable changes are non-state code: UI property values (`.fontColor`/`.fontSize`), `Text`/`Button` literal labels, method bodies, expressions. Resources/native changes need a full run.
124
+ - **Compile errors**: ArkTS compile errors from the hot compile are routed (via WatchLog) to the **background `--hotreload` process**, which writes them to **`.hvigor/hotreload-watch.log`** (and its stdout). On failure, `--hotreload-apply` reads that file and prints the errors inline (fast-fail, ~15s timeout if the watch session is dead).
125
+ - **Artifacts under `.hvigor/`** (shared with `--apply`): `.hvigor/<fileName>` = the changed-file list you write; `.hvigor/hotreload-watch.log` = the watch session's onBuildOutput/compile-errors (written by the background `--hotreload` process; safe to read/delete). The hot-reload marker is `<module>/build/<product>/.hotreload-mode`.
126
+ *Ex*: background `devecocli run --module entry --hotreload` → (ready) → edit `.fontColor` in `entry/src/main/ets/pages/Index.ets` → write path to `.hvigor/changes.txt` → `devecocli run --module entry --hotreload-apply changes.txt` (UI updates without restart). When done: kill the background process + `devecocli run --hotreload stop`.
127
+
128
+ ### `devecocli signature generate` `[Outside sandbox]`
129
+ Auto-generate HarmonyOS signing materials (local p12/csr + cloud cert + profile) and write signing config to `build-profile.json5`. Supports `debug` and `release` signing types.
130
+ - **Prereq**: Authenticate with `devecocli auth login --no-prompt` if needed and authorized (see Setup); run from a project directory (with `build-profile.json5`); a connected device or emulator is required for device registration (debug only; release signing skips device registration).
131
+ - `--product <name>`: Product name for local p12/csr file naming (default: `default`).
132
+ - `--team-id <id>`: Specify the team-id (default: current user's id).
133
+ - `--type <type>`: Signing type: `debug` or `release` (default: `debug`). Release uses real provision endpoint and release certificate type; debug uses test provision and debug certificate.
134
+ - `--force`: Force regenerate even if existing materials are valid.
135
+ - Generates under `~/.ohos/config/`: `.p12` keystore, `.csr`, downloaded `.cer` certificate, `.p7b` profile. Debug and release materials use separate file paths (release paths include `_release` segment).
136
+ - Writes `signingConfigs` + `products` entries to `build-profile.json5` with encrypted key/store passwords (AES-128-GCM). Release configs use `<product>_release` as the config name.
137
+ - Cloud cert name: `auto_debug_<teamId>.cer` (debug) or `auto_release_<teamId>.cer` (release). Key alias: `debugKey` (debug) or `releaseKey` (release).
138
+ - Error handling (aligned with DevEco Studio JAR): 401→re-login, 403→no AGC permission, `205389872`→cert limit, `205389904`→not Harmony user, `205389938`→provision limit, invalid `.cer`→retry.
139
+ *Ex*: `devecocli signature generate --product default` / `devecocli signature generate --type release`
140
+
141
+
142
+ ### `devecocli log`
143
+ Fetch hilog or crash logs. Req `--device <name|serial>` on multi-device hosts.
144
+ - `--crash`: Dump crash logs.
145
+ - `--level D|I|W|E|F`: Filter by level.
146
+ - `--bundle-name` / `--keyword`: Filter output.
147
+ - `--from <start>` / `--to <end>`: Relative offsets (`30s`, `5m`).
148
+ - `--tail <num>` / `--follow`: Keep last N lines / stream real-time (no `--to`).
149
+ *Ex*: `devecocli log --crash --bundle-name com.example.app`, `devecocli log --level E --from 5m --tail 200`
150
+
151
+ ### `devecocli verify`
152
+ Run UI verification on a connected device. Based on natural-language functional steps, the tool launches the app, uses screenshots to judge the current UI state, performs UI operations, and verifies whether the functionality completes correctly. This is the standard tool for UI testing and issue reproduction.
153
+
154
+ Supported operations include tap, double-tap, swipe, long press, key input, mouse, and keyboard actions. Supported navigation includes direct navigation to a specified page via a URI in a test step. When using URI navigation, the test plan must provide the complete, explicit, directly executable URI; do not only describe the target page or omit the URI content.
155
+
156
+ - `--test-plan <plan>` (**required**) — Natural-language test plan describing each step and its expected result. The test plan must not exceed 3000 characters. **Must be wrapped in single quotes** (`--test-plan '...'`) to prevent shell interpretation. Write a clear, specific plan based on the user's description. Example:
157
+ ```
158
+ 1. 启动应用,预期:进入应用主界面,底部显示「书架」「发现」「订阅源」三个Tab
159
+ 2. 点击「发现」Tab,预期:进入发现页面
160
+ 3. 点击「订阅源」Tab,预期:进入订阅源页面
161
+ 4. 点击「书架」Tab,预期:返回书架页面
162
+ ```
163
+ - `--bundle-name <name>` — Bundle name of the app to be tested; auto-detected from `AppScope/app.json5` when omitted.
164
+ - `--no-fresh-start` — Skip relaunching the app before verification; omit to relaunch by default.
165
+ - `--device <name|serial>` — device name (substring) or serial; auto-selected on single-device hosts, **required on multi-device hosts**. Quote names with spaces (e.g. `--device "Mate 70 Pro"`).
166
+
167
+ Output JSON contains `success`, `reason` (failure cause), `successPart`, `failPart`, and `id` (unique task ID for log and screenshot retrieval).
168
+
169
+ Each UI verification run is limited to 10 minutes.
170
+
171
+ Usage guidance:
172
+ - Write a test plan based on the user's description and call `devecocli verify --test-plan '...'`
173
+ - If log analysis is needed, call `devecocli verify log --id <id>` to retrieve the execution log
174
+ - If screenshots are needed, call `devecocli verify screenshot --id <id> --save-path <path>` to save step-by-step screenshots
175
+
176
+ Examples:
177
+ - `devecocli verify --test-plan '1. 启动应用,预期:进入应用主界面'`
178
+ - `devecocli verify --test-plan '1. 启动应用,deeplink跳转到"aigomall://test/order-confirm",预期:显示订单提交页面'`
179
+ - `devecocli verify --test-plan '1. 启动应用,预期:进入应用主界面' --bundle-name com.example.app`
180
+ - `devecocli verify --test-plan '1. 启动应用,预期:进入应用主界面' --no-fresh-start`
181
+ - `devecocli verify --test-plan '1. 启动应用,预期:进入应用主界面' --device 127.0.0.1:5555`
182
+
183
+ ### `devecocli verify log`
184
+ Retrieve the execution log of a verification task by ID.
185
+
186
+ - `--id <id>` (**required**) — Task ID from the `id` field in `verify` output.
187
+ - `--search-keywords <keywords>` — Filter to lines containing the keyword; **wrap value in single quotes**.
188
+ - `--max-log-size <n>` — Max characters to show (default `5000`; `-1` for unlimited).
189
+
190
+ Examples:
191
+ - `devecocli verify log --id <id>`
192
+ - `devecocli verify log --id <id> --search-keywords 'error' --max-log-size -1`
193
+
194
+ ### `devecocli verify screenshot`
195
+ Save step-by-step screenshots from a verification task to a specified directory; returns a list of saved file paths.
196
+
197
+ - `--id <id>` (**required**) — Task ID from the `id` field in `verify` output.
198
+ - `--save-path <path>` (**required**) — Absolute path to the target directory (created automatically if it does not exist).
199
+
200
+ Examples:
201
+ - `devecocli verify screenshot --id <id> --save-path 'C:\Users\user\Desktop\screenshots'`
202
+
203
+ ### `devecocli ui`
204
+ Inspect UI on a connected device. All subcommands accept `--device <name|serial>` (Req on multi-device hosts).
205
+
206
+ | Subcommand | Description | Key Options |
207
+ |---|---|---|
208
+ | `layout` | Dump ArkUI accessibility layout tree — **visible area only** (on-screen nodes). Requires device API 20+ | `--id <id>`, `--window <windowId>`, `--all-windows`, `--depth <n>` (0=unlimited, 1=root only, 2=root+children), `--format default\|json`, `--mode full\|simplified` |
209
+ | `window list` | List active windows | `--format default\|json`, `--all` (include system windows) |
210
+ | `screenshot` | Capture a screenshot of the device screen | `--display <displayId>`, required `--path <path>` (existing directory or PNG file path; relative paths supported; writable destination; no overwrite) |
211
+ | `screenrecord` | Record the screen to an MP4 file (physical devices only; emulators are rejected) | interactive by default, or `start` / `stop` / `status`; required `--path <dir|file.mp4>` for interactive and `start`; `stop --force` stops a recording not started by this CLI; `status --format table\|json` |
212
+ | `click [x] [y]` | Tap at the specified coordinates or node | `--id <id>` (auto-resolves to center), `--window <windowId>` (used with `--id`) |
213
+ | `doubleclick [x] [y]` | Double-tap at the specified coordinates or node | `--id <id>`, `--window <windowId>` |
214
+ | `longclick [x] [y]` | Long-press at the specified coordinates or node | `--id <id>`, `--window <windowId>` |
215
+ | `swipe <x1> <y1> <x2> <y2>` | Swipe from one point to another (precise coordinates, custom speed) | `--speed <n>` (200–40000, px/s) |
216
+ | `fling <x1> <y1> <x2> <y2>` | Fling from one point to another | `--speed <n>` (200–40000, px/s) |
217
+ | `drag <x1> <y1> <x2> <y2>` | Drag from one point to another | `--speed <n>` (200–40000, px/s) |
218
+ | `dircfling <direction>` | Quick directional fling (system default speed, ideal for scrolling) | `direction`: `up`, `down`, `left`, `right` |
219
+ | `text <text> [x] [y]` | Input text at a target location or the currently focused field | `--id <id>` (auto-resolves to center), `--window <windowId>` (used with `--id`) |
220
+
221
+ - **Coordinates vs `--id`**: Mutually exclusive. Provide either `x y` or `--id <id>`. For `text`, if neither is given, text goes to the currently focused field.
222
+ - **`--window`**: May only be used together with `--id`. Default is focused window. Secondary display operations via `--id` + `--window` are not supported.
223
+ - **`swipe` vs `dircfling`**: `swipe` requires exact start/end coordinates and supports `--speed`; `dircfling` only needs a direction (`up/down/left/right`) and uses system default speed (ideal for page/list scrolling).
224
+ - **Text encoding**: Special characters in `text` are Base64-encoded internally to safely pass through device shell.
225
+ - `--format json` pairs well with `jq`.
226
+ - `--mode full`: full layout tree, no filtering.
227
+ - `--mode simplified` (default): folds meaningless wrapper containers (non-root, no `id`, no text, not interactive) by lifting their surviving children up. `--depth` truncates after folding.
228
+
229
+
230
+ ## 2. Setup
231
+
232
+ ### Agent Plugins
233
+
234
+ When this skill is provided by an agent plugin, follow its injected Runtime guidance for CLI selection, installation and updates. Plugin commands are available only in that environment; standalone CLI users follow the setup commands below.
235
+
236
+ ### `devecocli init`
237
+ MUTUALLY EXCLUSIVE modes for setup:
238
+ 1. `--skill` (Default): Install `deveco-cli` skill to AI agents.
239
+ 2. `--mcp`: Configure `deveco-mcp` server (ArkTS/C++ syntax checking).
240
+ *Options*:
241
+ - `--agent <agents>`: Comma-separated (e.g. `opencode,pi,cursor`). Defaults to detected agents.
242
+ - `--project <path>`: Project-level config (Abs path for MCP).
243
+ - `--path <path>`: Direct skill install path; cannot combine with `--agent`, `--project` or `--mcp`.
244
+ *MCP Rules*: Configured MCP agents support global (no `--project`) and project-level setup; `qoder` is unsupported.
245
+ *Skill agents*: See README.md, “Skill 支持的智能体”. All supported agents allow global and project installation. If an agent is not detected, specify `--agent`.
246
+
247
+ **Existing Skill paths and MCP entries are skipped by default. To update them or convert copied Skill installations to symlinks, rerun `devecocli init -f` (or `--force`); old content and local edits will be deleted.** This flag also allows missing target directories to be created. Correct links are kept and follow CLI updates; failed linking falls back to copying. `skills remove` keeps the npm bundled Skill.
248
+
249
+ ### `devecocli auth login`
250
+ Sign in to your Huawei Developer account. Opens a browser for OAuth authentication. Required before `signature generate`. On an interactive terminal it waits for Enter before opening the browser; pass `--no-prompt` to open it immediately (also auto-skipped when stdin is not a TTY).
251
+ For login and CLI/runtime installation, use existing user authorization from the conversation and ask only if it is missing. Once login is authorized, run `devecocli auth login --no-prompt` directly instead of asking the user to run it in a terminal. The user still completes authentication in the browser; `--no-prompt` only skips the terminal Enter confirmation.
252
+ *Ex*: `devecocli auth login --no-prompt`
253
+
254
+ ### `devecocli auth logout`
255
+ Sign out and clear locally stored credentials.
256
+
257
+ ### `devecocli auth status`
258
+ Show the current logged-in user.
259
+
260
+ ### `devecocli auth team list`
261
+ List team accounts the current user has joined.
262
+
263
+ ### `devecocli skills`
264
+ Manage HarmonyOS skills in AI agents/projects.
265
+ - `list [-l|--long]` / `find <keyword>`: List or search skills.
266
+ - `add (--all | --skill <name>) [--agent <a,b…>] [--project <path>] [--path <path>] [-f]`: Install.
267
+ - `remove --skill <name> [...]`: Uninstall.
268
+ - Ordinary project paths such as `skills/` and `data/` require an explicit `--agent` for installation or removal; automatic selection ignores them.
269
+
270
+ **Existing Skill paths are skipped by default. Use `-f` / `--force` to replace them, deleting old content and local edits. To convert copied agent installations to symlinks, rerun `devecocli init -f` for `deveco-cli` or `devecocli skills add --skill <name> -f` for other Skills.** This flag also allows missing target directories to be created. Correct links are kept; failed linking falls back to copying. Batch installation continues after failures, prints a summary and exits nonzero if any Skill fails.
271
+
272
+ Extracted skills are stored in <CLI data directory>/skills/store/<name> globally or <project>/.deveco-cli/skills/store/<name> per project (consider ignoring .deveco-cli/skills/ in version control). Agent skill entries like .agents/skills/<name> link directly to this store, reusing existing files for new Agents. Note that -f updates affect every linked Agent in that scope.
273
+
274
+ *Ex*: `devecocli skills add --skill <name> --agent opencode --project ./MyApp`
275
+
276
+ ### `devecocli check compat` `[Outside sandbox]`
277
+ Scan source code for breaking API changes between two SDK versions. Built on top of DevEco Studio's `arkanalyzer-apiscan` plugin.
278
+ - `versions`: List available target SDK versions. Opts: `--format <default|json>` (default: `default`).
279
+ - Default (no args): project-level scan.
280
+ - `--modules <m1> [m2...]`: Module-level scan.
281
+ - `<file1> [file2...]`: File-level scan (`.ets`/`.c`/`.cpp` only).
282
+ - `--source-version <v>` (Req) / `--target-version <v>` (Req): SDK version pair. Run `devecocli check compat versions` first; on zsh, **quote the value** (e.g. `"<source_version>"`, `"<target_version>"`).
283
+ - `--format <default|csv|json>` (default: `default`): Console output accepts `default` (text) or `json`; file output (via `--output-path`) accepts `default` (csv), `csv`, or `json`. `csv` requires `--output-path`.
284
+ - `--output-path <path>`: Directory (writes `apiChange-*.csv`/`apiChange-*.json`) or explicit file (extension must match `--format`).
285
+ - `--limit <n>` (default `100`): Max records shown on console when no `--output-path`.
286
+
287
+ Validation order: `files` + `--modules` mutually exclusive → `--source-version`/`--target-version` required → `csv` requires `--output-path` → project dir valid → modules exist → files exist + extension valid → versions in catalog + `target > source` → format/extension match → output target writable.
288
+
289
+ *Ex*: `devecocli check compat --source-version "<source_version>" --target-version "<target_version>" --output-path ./report`
290
+
291
+ ## 3. Maintenance
292
+
293
+ - **`devecocli update`** `[Outside sandbox]`: Update CLI to latest version.
294
+ - **`devecocli serve mcp`**: Host stdio MCP server (`check` tool for `.ets`/C/C++). Used via `init --mcp`. (Env: `PROJECT_PATH`, `DEVECO_PATH`, `NODE_MAX_OLD_SPACE_SIZE`, `DEBUG=1`).
295
+ - **`devecocli serve lsp`**: Host a bundled stdio **LSP** language server — `--arkts` (ArkTS, ace-server) or `--cpp` (clangd). Standard LSP protocol: completion, go-to-definition, hover, references, diagnostics. Opts: `--project-path <path>` (used as-is), `--auto-detect` (search cwd and subdirs for project root; ignored when `--project-path` given).
296
+ - Agents/editors with a built-in LSP client drive it directly — e.g. **opencode** (`lsp` config in `opencode.json`), **Zed**, **Neovim**, **VS Code**, **Emacs**:
297
+ `"lsp": { "ArkTS": { "command": ["devecocli", "serve", "lsp", "--arkts"], "extensions": [".ets"] } }`
298
+ - MCP-only agents (no LSP client) can bridge via [agent-lsp](https://github.com/blackwell-systems/agent-lsp), an MCP server that wraps any stdio LSP. MCP command: `["agent-lsp", "ets:devecocli,serve,lsp,--arkts"]`.
299
+ - **`serve lsp` is a long-running stdio server, not a shell command — never execute it directly.** Look up how the target agent registers a custom LSP server in its own documentation, then plug in the commands above.
300
+ - **Never wire `serve lsp` and `serve mcp` into the same agent.** Each spawns its own ace-server/clangd, so running both doubles memory. Pick one: direct LSP or MCP — **prefer MCP** (simpler to integrate, initialization built in).
301
+
302
+ ## Recipes
303
+
304
+ - **Fresh checkout to emulator**:
305
+ `devecocli build` -> `devecocli emulator list` -> `devecocli emulator start "Name"` -> `devecocli run`
306
+ - **Diagnose crash**:
307
+ `devecocli log --crash --bundle-name <bundle>`
308
+ - **Release build**:
309
+ `devecocli build --product oversea --build-mode release`
310
+ - **First-time signing setup** (login authorized; user completes browser authentication):
311
+ `devecocli auth login --no-prompt` -> `devecocli signature generate --product default` -> `devecocli build` -> `devecocli run`
312
+
313
+ ## Troubleshooting
314
+
315
+ - **"Product / Build mode `<x>` not found"**: Check `build-profile.json5`.
316
+ - **"Multiple entry modules" / "No entry module"**: Pass `--modules` (build) or `--module` (run).
317
+ - **"No active devices" / "Multiple devices connected"**: Connect/start emulator. Pass `-t <serial>` (device view) or `--device <name|serial>` (run/log).
318
+ - **`error:install sign info inconsistent`**: Signing key changed. Run `devecocli run --uninstall` or `devecocli signature generate --force`.
319
+ - **`Not logged in. Run devecocli auth login first`**: Run `devecocli auth login --no-prompt` with login authorization (see Setup).
320
+ - **`Provision number exceeds limit`**: Test provision quota is full. Delete old test provisions in DevEco Studio (Signing Configs) or AGC console, then retry `devecocli signature generate`.
321
+ - **`Invalid AccessToken. Sign in and try again`**: Token expired. Run `devecocli auth login --no-prompt` with login authorization (see Setup).
322
+ - **Invalid Skill agent**: Use an ID from the README supported-agent list.
323
+ - **`emulator start` / `image download` blocked on agreement**: Follow `emulator license accept` above: use existing agreement acceptance authorization, ask only if missing, and run `devecocli emulator license accept`. Resume the blocked operation only after acceptance succeeds.
324
+ - **`image download` failure / timeout**: Do NOT auto-retry. Give the command to the user to run manually in their terminal.
325
+ - **`emulator create` timeout**: Treat as user-action step. Ask user to open DevEco Studio -> Device Manager. Check `emulator list` after user confirms. Do NOT auto-retry or edit SDK files.
326
+ - **`image list` duplicate OS rows**: `phone`/`foldable`/`widefold`/`triplefold` share the same image. Download/remove ONCE per OS version.
327
+ - **`ui layout` missing expected node**: `layout` only returns on-screen nodes. Use `devecocli ui dircfling <direction>` or `devecocli ui swipe <x1> <y1> <x2> <y2>` to scroll the target into view, then retry `ui layout`. Specify `--device <name|serial>` when multiple devices are connected.