@yuanze_dev/cli 0.3.0 → 0.3.1
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/dist/index.js +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import{Command as e}from"commander";import t from"picocolors";import{z as n}from"zod";import{chmodSync as r,closeSync as i,copyFileSync as a,cpSync as o,existsSync as s,lstatSync as c,mkdirSync as l,openSync as ee,readFileSync as u,readdirSync as d,readlinkSync as te,renameSync as f,rmSync as p,statSync as ne,symlinkSync as re,writeFileSync as m,writeSync as ie}from"node:fs";import{basename as ae,dirname as h,join as g,resolve as oe}from"node:path";import{homedir as _,hostname as se,platform as v}from"node:os";import{createHash as ce,randomBytes as le}from"node:crypto";import{Readable as ue}from"node:stream";import{pipeline as de}from"node:stream/promises";import{x as fe}from"tar";import{createServer as pe}from"node:http";import{createInterface as me}from"node:readline/promises";import{execFileSync as he,spawn as ge}from"node:child_process";const _e=[`claude`,`codex`,`openclaw`],y=n.enum(_e),b=/^[a-z0-9]+(-[a-z0-9]+)*$/,x=/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;n.object({name:n.string().regex(b,`name 必须是 kebab-case`),displayName:n.string().min(1).max(128),description:n.string().min(1).max(2e3),version:n.string().regex(x,`version 必须是 semver`),targets:n.array(y).nonempty().default([..._e]),visibility:n.enum([`all`,`restricted`]).default(`restricted`),requiresBackend:n.boolean().default(!1),owner:n.string().max(64).optional()});const ve=n.object({name:n.string(),displayName:n.string(),description:n.string(),version:n.string(),hash:n.string().length(64),sizeBytes:n.number().int().nonnegative(),targets:n.array(y),requiresBackend:n.boolean(),onDemand:n.boolean().default(!1)});n.object({minCliVersion:n.string().regex(x),announcement:n.string().nullable(),skills:n.array(ve)});const ye=n.object({id:n.number().int(),name:n.string(),email:n.string().nullable(),avatarUrl:n.string().nullable(),status:n.enum([`active`,`disabled`])});n.object({code:n.string().min(8),deviceName:n.string().max(128).optional(),os:n.string().max(32).optional(),cliVersion:n.string().max(32).optional()}),n.object({token:n.string().startsWith(`yzc_`),account:ye}),n.object({account:ye,entitledSkills:n.array(n.string())}),n.object({cliVersion:n.string().optional(),os:n.string().optional(),installed:n.array(n.object({name:n.string(),version:n.string()})),removed:n.array(n.string()).default([]),agents:n.array(y).default([])}),n.object({name:n.string().regex(b),displayName:n.string(),description:n.string(),version:n.string().regex(x),contentHash:n.string().length(64),artifactKey:n.string().min(1),sizeBytes:n.number().int().nonnegative(),targets:n.array(y).nonempty(),requiresBackend:n.boolean(),visibilityDefault:n.enum([`all`,`restricted`]),owner:n.string().optional(),gitCommit:n.string().length(40).optional(),changelog:n.string().max(5e3).optional(),publishedBy:n.string().default(`ci`)}),n.object({error:n.string(),message:n.string()});function S(){return process.env.YZ_HOME??g(_(),`.yz`)}const C=()=>g(S(),`credentials.json`),be=()=>g(S(),`state.json`),xe=()=>g(S(),`config.json`),Se=()=>g(S(),`sync.lock`),w=()=>g(S(),`skills`),Ce=()=>g(S(),`cache`),T=()=>g(Ce(),`skills`),E=(e,t,n)=>g(T(),e,`${t}-${n.slice(0,8)}`);function D(e){if(!s(e))return null;try{return JSON.parse(u(e,`utf8`))}catch{return null}}function O(e,t,n){l(h(e),{recursive:!0}),m(e,`${JSON.stringify(t,null,2)}\n`),n!==void 0&&r(e,n)}const k=()=>D(C()),we=e=>O(C(),e,384),A=()=>p(C(),{force:!0}),j=()=>D(be())??{skills:{}},M=e=>O(be(),e),Te=()=>D(xe())??{};var N=class extends Error{constructor(e,t){super(e),this.code=t}};function P(){return(process.env.YZ_SERVER_URL??k()?.serverUrl??Te().serverUrl??`https://alifc.lusun.com/yz-cli/prod`).replace(/\/$/,``)}async function Ee(e){let t=`请求失败(HTTP ${e.status})`;try{let n=await e.json();return new N(n.message??t,n.error)}catch{return new N(t)}}async function F(e,t={}){let n=P(),r={...t.headers};if(t.auth!==!1){let e=k();if(!e)throw new N(`尚未登录,请先执行:yz-cli login`,`not_logged_in`);r.Authorization=`Bearer ${e.token}`}t.body!==void 0&&(r[`Content-Type`]=`application/json`);let i;try{i=await fetch(`${n}${e}`,{method:t.method??`GET`,headers:r,body:t.body===void 0?void 0:JSON.stringify(t.body)})}catch(e){throw new N(`无法连接服务端 ${n}:${e.message}`,`network`)}if(t.okOnly!==!1&&!i.ok&&i.status!==304)throw await Ee(i);return i}async function De(e){return await(await F(`/v1/auth/exchange`,{method:`POST`,body:e,auth:!1})).json()}async function I(){return await(await F(`/v1/me`)).json()}async function L(e){let t=await F(`/v1/manifest`,{headers:e?{"If-None-Match":e}:{}});return t.status===304||t.headers.get(`X-YZ-Not-Modified`)===`1`?{status:304}:{status:200,etag:t.headers.get(`ETag`)??void 0,manifest:await t.json()}}async function Oe(e){let t=await F(`/v1/skills/${e}/download`);if((t.headers.get(`Content-Type`)??``).includes(`application/json`)){let{url:e}=await t.json(),n;try{n=await fetch(e)}catch(e){throw new N(`下载产物失败:${e.message}`,`network`)}if(!n.ok)throw new N(`下载产物失败:HTTP ${n.status}`,`download`);return Buffer.from(await n.arrayBuffer())}return Buffer.from(await t.arrayBuffer())}async function ke(e,t){let n=new URLSearchParams({state:e});return t&&n.set(`port`,String(t)),(await(await F(`/v1/auth/feishu/authorize-url?${n}`,{auth:!1})).json()).url}async function Ae(e){await F(`/v1/sync/report`,{method:`POST`,body:e})}async function je(){await F(`/v1/auth/logout`,{method:`POST`})}const Me=`.yz-managed`,Ne=[{target:`claude`,displayName:`Claude Code`,detected:()=>s(g(_(),`.claude`))||!!process.env.YZ_AGENT_DIR_CLAUDE,skillsDir:()=>process.env.YZ_AGENT_DIR_CLAUDE??g(_(),`.claude`,`skills`)},{target:`codex`,displayName:`Codex`,detected:()=>s(g(_(),`.codex`))||!!process.env.YZ_AGENT_DIR_CODEX,skillsDir:()=>process.env.YZ_AGENT_DIR_CODEX??g(_(),`.codex`,`skills`)},{target:`openclaw`,displayName:`OpenClaw`,detected:()=>s(g(_(),`.openclaw`))||!!process.env.YZ_AGENT_DIR_OPENCLAW,skillsDir:()=>process.env.YZ_AGENT_DIR_OPENCLAW??g(_(),`.openclaw`,`skills`)}],R=()=>Ne.filter(e=>e.detected()),Pe=()=>process.platform===`win32`;function Fe(e){try{return c(e).isSymbolicLink()?oe(te(e)).startsWith(oe(w())):s(g(e,Me))}catch{return!1}}function Ie(e,t){let n=g(w(),e),r={linked:[],skipped:[]};for(let i of R()){if(!t.includes(i.target))continue;let a=i.skillsDir();l(a,{recursive:!0});let c=g(a,e);if(s(c)||z(c)){if(!Fe(c)){r.skipped.push({path:c,reason:`已存在非 yz 管理的同名 skill,跳过`});continue}p(c,{recursive:!0,force:!0})}Pe()?(o(n,c,{recursive:!0}),m(g(c,Me),`managed by yz-cli, do not edit
|
|
3
3
|
`)):re(n,c,`dir`),r.linked.push(c)}return r}function z(e){try{return c(e).isSymbolicLink()}catch{return!1}}function B(e,t){let n=new Set(t);for(let t of Ne)n.add(g(t.skillsDir(),e));for(let e of n)(s(e)||z(e))&&Fe(e)&&p(e,{recursive:!0,force:!0})}function Le(e){return ce(`sha256`).update(e).digest(`hex`)}async function Re(e,t){p(t,{recursive:!0,force:!0}),l(t,{recursive:!0}),await de(ue.from(e),fe({cwd:t}))}async function ze(e,t){l(w(),{recursive:!0});let n=!1;if(!t||t.hash!==e.hash){let t=await Oe(e.name),r=Le(t);if(r!==e.hash)throw new N(`${e.name} 校验失败(期望 ${e.hash.slice(0,8)},实际 ${r.slice(0,8)})`);let i=g(w(),e.name),a=`${i}.tmp-${process.pid}`;await Re(t,a),p(i,{recursive:!0,force:!0}),f(a,i),n=!0}let r=Ie(e.name,e.targets);return{installed:{version:e.version,hash:e.hash,links:r.linked},downloaded:n,skipped:r.skipped}}const V=e=>e.onDemand??!1;async function H(){let e=await L();if(!e.manifest)throw new N(`获取技能目录失败`,`manifest`);return e.manifest}function Be(e){let t=j().skills[e.name];return t?{kind:`installed`,fresh:t.hash===e.hash,localVersion:t.version}:s(E(e.name,e.version,e.hash))?{kind:`cached`}:{kind:`absent`}}function Ve(e){return e.kind===`installed`?e.fresh?t.green(`✓ 最新`):t.cyan(`↑ 可更新(本地 ${e.localVersion})`):t.dim(e.kind===`cached`?`已缓存`:`未装`)}function He(e,n){if(n){console.log(JSON.stringify(e.map(e=>{let t=Be(e);return{name:e.name,displayName:e.displayName,description:e.description,version:e.version,onDemand:V(e),requiresBackend:e.requiresBackend,targets:e.targets,installed:t.kind===`installed`,status:t.kind===`installed`?t.fresh?`installed`:`updatable`:t.kind,localVersion:t.kind===`installed`?t.localVersion:null}}),null,2));return}let r=Math.max(4,...e.map(e=>e.name.length))+2;for(let n of e){let e=V(n)?t.yellow(`按需`):t.green(`自动`),i=n.description.length>60?`${n.description.slice(0,60)}…`:n.description;console.log(`${n.name.padEnd(r)} ${e} ${n.version.padEnd(10)} ${Ve(Be(n)).padEnd(20)} ${t.dim(i)}`)}}async function Ue(e){let n=await H();if(!n.skills.length){console.log(`当前帐号暂无可用技能(全员可见技能发布后会自动出现)`);return}if(He(n.skills,e.json),!e.json){let e=n.skills.filter(V).length;console.log(t.dim(`\n共 ${n.skills.length} 个(自动 ${n.skills.length-e} · 按需 ${e});自动技能由 yz-cli skills sync 维护,其余用 yz-cli skills info <name> 取用、yz-cli skills install <name> 安装`))}}function We(e,t){let n=t.map(e=>e.toLowerCase()).filter(Boolean);return e.flatMap(e=>{let t=e.name.toLowerCase(),r=e.displayName.toLowerCase(),i=e.description.toLowerCase(),a=0;for(let e of n)if(t===e)a+=100;else if(t.includes(e))a+=60;else if(r.includes(e))a+=40;else if(i.includes(e))a+=20;else return[];return[{s:e,score:a}]}).sort((e,t)=>t.score-e.score||e.s.name.localeCompare(t.s.name)).slice(0,20).map(e=>e.s)}async function Ge(e,t){let n=We((await H()).skills,e);if(!n.length){console.log(`无匹配「${e.join(` `)}」;可换关键词重试,或 yz-cli skills list 查看全部`);return}He(n,t.json)}async function Ke(e){let t=j().skills[e.name];if(t&&t.hash===e.hash)return{dir:g(w(),e.name),source:`已安装`};let n=E(e.name,e.version,e.hash);if(s(n))return qe(n),{dir:n,source:`缓存`};let r=await Oe(e.name),i=Le(r);if(i!==e.hash)throw new N(`${e.name} 校验失败(期望 ${e.hash.slice(0,8)},实际 ${i.slice(0,8)})`);let a=`${n}.tmp-${process.pid}`;await Re(r,a);try{f(a,n)}catch{p(a,{recursive:!0,force:!0})}return qe(n),{dir:n,source:`已下载`}}function qe(e){let t=h(e);if(s(t))for(let n of d(t)){let r=g(t,n);r!==e&&p(r,{recursive:!0,force:!0})}}function Je(e,t=e){let n=[];for(let r of d(e)){let i=g(e,r);ne(i).isDirectory()?n.push(...Je(i,t)):n.push(i)}return n}async function Ye(e){if(!b.test(e))throw new N(`非法技能名:${e}`);let n=(await H()).skills.find(t=>t.name===e);if(!n)throw new N(`未找到技能「${e}」(或当前帐号未被授权);试试 yz-cli skills search <关键词>`);let{dir:r,source:i}=await Ke(n),a=g(r,`SKILL.md`);if(!s(a))throw new N(`${e} 内容异常:缺少 SKILL.md`);console.log(u(a,`utf8`).trimEnd());let o=Je(r).filter(e=>e!==a&&!e.endsWith(`/skill.yaml`)&&!e.endsWith(`skill.yaml`));if(console.log(`
|
|
4
|
-
---`),console.log(`[yz-cli] ${n.name}@${n.version}(${n.displayName})`),o.length){console.log(`[yz-cli] 附属文件(用你的文件工具按需读取):`);for(let e of o)console.log(e)}console.error(t.dim(`(来源:${i})`))}async function Xe(e){if(!b.test(e))throw new N(`非法技能名:${e}`);let n=(await H()).skills.find(t=>t.name===e);if(!n)throw new N(`未找到技能「${e}」(或当前帐号未被授权);试试 yz-cli skills search <关键词>`);let r=j(),i=r.skills[e],a=i?.hash===n.hash,{installed:o,skipped:s}=await ze(n,i);r.skills[e]=o,M(r);for(let n of s)console.log(t.yellow(`⚠ ${e}: ${n.reason}(${n.path})`));let c=R().map(e=>e.displayName).join(` / `);a?console.log(t.green(`✓ ${e}@${n.version} 已是最新(已确保链接到 ${c||`各 agent`})`)):(console.log(t.green(`✓ 已安装 ${e}@${n.version} → ${c||`(未检测到 agent,先 yz-cli login)`}`)),console.log(t.dim(`本次会话即用:yz-cli skills info ${e};新开会话自动生效`)))}const U=`0.3.
|
|
4
|
+
---`),console.log(`[yz-cli] ${n.name}@${n.version}(${n.displayName})`),o.length){console.log(`[yz-cli] 附属文件(用你的文件工具按需读取):`);for(let e of o)console.log(e)}console.error(t.dim(`(来源:${i})`))}async function Xe(e){if(!b.test(e))throw new N(`非法技能名:${e}`);let n=(await H()).skills.find(t=>t.name===e);if(!n)throw new N(`未找到技能「${e}」(或当前帐号未被授权);试试 yz-cli skills search <关键词>`);let r=j(),i=r.skills[e],a=i?.hash===n.hash,{installed:o,skipped:s}=await ze(n,i);r.skills[e]=o,M(r);for(let n of s)console.log(t.yellow(`⚠ ${e}: ${n.reason}(${n.path})`));let c=R().map(e=>e.displayName).join(` / `);a?console.log(t.green(`✓ ${e}@${n.version} 已是最新(已确保链接到 ${c||`各 agent`})`)):(console.log(t.green(`✓ 已安装 ${e}@${n.version} → ${c||`(未检测到 agent,先 yz-cli login)`}`)),console.log(t.dim(`本次会话即用:yz-cli skills info ${e};新开会话自动生效`)))}const U=`0.3.1`;function Ze(e,t){let n=e.split(`-`)[0].split(`.`).map(Number),r=t.split(`-`)[0].split(`.`).map(Number);for(let e=0;e<3;e++){let t=(n[e]??0)-(r[e]??0);if(t!==0)return t<0?-1:1}return 0}const W=`dev.yuanze.yzcli.sync`,Qe=`yz-cli skills sync`,$e=[`skills`,`sync`,`--quiet`];function et(){let e=process.execPath;if(ae(e).toLowerCase().startsWith(`yz`))return{program:e,prefix:[]};let t=process.argv[1];return{program:e,prefix:t?[t]:[]}}function tt(){let e=g(S(),`logs`);return l(e,{recursive:!0}),g(e,`scheduler.log`)}const nt=()=>g(_(),`Library`,`LaunchAgents`,`${W}.plist`);function G(e){return e.replace(/[&<>"']/g,e=>e===`&`?`&`:e===`<`?`<`:e===`>`?`>`:e===`"`?`"`:`'`)}function K(e){try{return he(`launchctl`,e,{stdio:`ignore`}),!0}catch{return!1}}function rt(){let{program:e,prefix:n}=et(),r=[e,...n,...$e],i=tt(),a=[`<?xml version="1.0" encoding="UTF-8"?>`,`<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">`,`<plist version="1.0">`,`<dict>`,` <key>Label</key>`,` <string>${W}</string>`,` <key>ProgramArguments</key>`,` <array>`,...r.map(e=>` <string>${G(e)}</string>`),` </array>`,` <key>StartInterval</key>`,` <integer>3600</integer>`,` <key>RunAtLoad</key>`,` <true/>`,` <key>ProcessType</key>`,` <string>Background</string>`,` <key>LowPriorityIO</key>`,` <true/>`,` <key>StandardOutPath</key>`,` <string>${G(i)}</string>`,` <key>StandardErrorPath</key>`,` <string>${G(i)}</string>`,`</dict>`,`</plist>`,``].join(`
|
|
5
5
|
`),o=nt();l(h(o),{recursive:!0}),m(o,a);let s=`gui/${process.getuid?.()??0}`;return K([`bootout`,`${s}/${W}`]),K([`bootstrap`,s,o])||K([`load`,`-w`,o])?t.green(`✓ launchd 定时同步已注册(每小时一次,未变更走 304)`):t.yellow(`· launchd plist 已写入但激活未确认:${o}(下次登录自动生效)`)}function it(){let e=nt();return s(e)?(K([`bootout`,`gui/${process.getuid?.()??0}/${W}`])||K([`unload`,`-w`,e]),p(e,{force:!0}),t.green(`✓ launchd 定时同步已移除`)):null}const q=e=>`'${e.replace(/'/g,`''`)}'`,at=e=>/[\s"]/.test(e)||e===``?`"${e.replace(/"/g,`\\"`)}"`:e;function ot(e){try{return he(`powershell`,[`-NoProfile`,`-NonInteractive`,`-ExecutionPolicy`,`Bypass`,`-Command`,e],{stdio:`ignore`}),!0}catch{return!1}}function st(){let{program:e,prefix:n}=et(),r=[...n,...$e].map(at).join(` `);return ot([`$ErrorActionPreference='Stop';`,`$a=New-ScheduledTaskAction -Execute ${q(e)} -Argument ${q(r)};`,`$rep=New-ScheduledTaskTrigger -Once -At (Get-Date);`,`$rep.Repetition=(New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Hours 1)).Repetition;`,`$logon=New-ScheduledTaskTrigger -AtLogOn;`,`$s=New-ScheduledTaskSettingsSet -Hidden -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Minutes 10);`,`Register-ScheduledTask -TaskName ${q(Qe)} -Action $a -Trigger $rep,$logon -Settings $s -Force | Out-Null;`].join(` `))?t.green(`✓ 计划任务已注册(每小时一次 + 登录时一次)`):t.yellow(`· 计划任务注册失败(可重跑 yz-cli login,或用「任务计划程序」手动添加)`)}function ct(){return ot(`Unregister-ScheduledTask -TaskName ${q(Qe)} -Confirm:$false -ErrorAction SilentlyContinue;`)?t.green(`✓ 计划任务已移除`):null}function lt(){try{return process.platform===`darwin`?rt():process.platform===`win32`?st():t.dim(`· 定时同步暂未覆盖当前平台(${process.platform}),由 agent hook 同步`)}catch(e){return t.yellow(`· 定时同步注册跳过:${e.message}`)}}function ut(){try{return process.platform===`darwin`?it():process.platform===`win32`?ct():null}catch{return null}}const J=`yz-cli skills sync`,Y=`${J} --quiet --if-stale 1h`;function dt(e){let n=e.hooks?.SessionStart;if(!Array.isArray(n))return null;let r=!1;for(let e of n){let t=e?.hooks;if(Array.isArray(t))for(let e of t){let t=e?.command;typeof t==`string`&&t.includes(J)&&t!==Y&&(e.command=Y,r=!0)}}return r?t.green(`✓ Claude Code:自动同步 hook 已更新为 ${Y}`):null}function ft(){let e=g(_(),`.claude`,`settings.json`),n={};if(s(e)){try{n=JSON.parse(u(e,`utf8`))}catch{return t.red(`✗ ${e} 不是合法 JSON,跳过 hook 写入(请手动处理)`)}let r=dt(n);if(r)return a(e,`${e}.bak-yz`),m(e,`${JSON.stringify(n,null,2)}\n`),r;if(JSON.stringify(n).includes(J))return t.dim(`· Claude Code SessionStart hook 已存在,跳过`);a(e,`${e}.bak-yz`)}else l(g(_(),`.claude`),{recursive:!0});let r=n.hooks??={};return(r.SessionStart??=[]).push({hooks:[{type:`command`,command:Y}]}),m(e,`${JSON.stringify(n,null,2)}\n`),t.green(`✓ Claude Code:SessionStart hook 已写入(${Y})`)}function pt(){let e=R();if(!e.length){console.log(t.yellow(`· 未检测到 Claude Code / Codex / OpenClaw;装好 agent 后重新 yz-cli login 即可接线`));return}for(let n of e)l(n.skillsDir(),{recursive:!0}),console.log(t.dim(`· ${n.displayName} skills 目录就绪:${n.skillsDir()}`));e.some(e=>e.target===`claude`)&&console.log(ft()),console.log(lt())}const mt=e=>e.onDemand??!1;function ht(e){let t=e.match(/^(\d+)([smhd])$/);if(!t)throw new N(`无法解析时长:${e}(支持 30m / 4h / 1d)`);let n={s:1e3,m:6e4,h:36e5,d:864e5}[t[2]];return Number(t[1])*n}function X(e,t){e||console.log(t)}function gt(e,n){for(let[t,n]of Object.entries(e.skills))B(t,n.links);M({skills:{}}),X(n,t.red(`帐号不可用,已清除本机全部受管 skills`))}function _t(e){try{let{at:t}=JSON.parse(u(e,`utf8`));return typeof t==`number`&&Date.now()-t<6e5}catch{return!1}}function vt(){let e=Se();l(h(e),{recursive:!0});for(let t=0;t<2;t++)try{let t=ee(e,`wx`);try{ie(t,JSON.stringify({pid:process.pid,at:Date.now()}))}finally{i(t)}return!0}catch(n){if(n.code!==`EEXIST`)throw n;if(t===0&&!_t(e)){p(e,{force:!0});continue}return!1}return!1}function yt(){p(Se(),{force:!0})}async function bt(e={}){if(e.ifStale&&!e.force){let{lastCheckAt:t}=j();if(t&&Date.now()-t<ht(e.ifStale))return}if(vt())try{await xt(e)}finally{yt()}}async function xt(e){let n=j(),r;try{r=await L(e.force?void 0:n.etag)}catch(t){if(t instanceof N&&t.code===`account_disabled`){gt(n,e.quiet);return}throw t}if(r.status===304){M({...n,lastCheckAt:Date.now()}),X(e.quiet,t.dim(`已是最新(manifest 未变化)`));return}let i=r.manifest;if(Ze(U,i.minCliVersion)<0&&(console.error(t.red(`当前 yz-cli ${U} 低于要求的最低版本 ${i.minCliVersion},请先升级:yz-cli update`)),!e.force))throw new N(`CLI 版本过低`,`outdated_cli`);i.announcement&&X(e.quiet,t.yellow(`📢 ${i.announcement}`)),l(w(),{recursive:!0});let a={lastCheckAt:Date.now(),etag:r.etag,skills:{}},o=[],c=[],ee=i.skills.filter(e=>!mt(e)||!!n.skills[e.name]);for(let t of ee){let r=n.skills[t.name];(!r||r.hash!==t.hash)&&X(e.quiet,`${r?`↑ 更新`:`+ 安装`} ${t.name}@${t.version} …`);let{installed:i,downloaded:s,skipped:l}=await ze(t,r);s&&o.push({name:t.name,version:t.version});for(let e of l)c.push(`${t.name}: ${e.reason}(${e.path})`);a.skills[t.name]=i}let u=[];for(let[r,a]of Object.entries(n.skills))i.skills.some(e=>e.name===r)||(B(r,a.links),p(g(w(),r),{recursive:!0,force:!0}),p(g(T(),r),{recursive:!0,force:!0}),u.push(r),X(e.quiet,`${t.red(`- 回收`)} ${r}(已撤权或下架)`));if(s(T()))for(let n of d(T()))i.skills.some(e=>e.name===n)||(p(g(T(),n),{recursive:!0,force:!0}),X(e.quiet,t.dim(`- 清理缓存 ${n}(已撤权或下架)`)));M(a);for(let n of c)X(e.quiet,t.yellow(`⚠ ${n}`));Ae({cliVersion:U,os:v(),installed:o,removed:u,agents:R().map(e=>e.target)}).catch(()=>{});let te=Object.keys(a.skills).length;X(e.quiet,t.green(`✓ 同步完成:共 ${te} 个 skills(新装/更新 ${o.length},回收 ${u.length})→ ${R().map(e=>e.displayName).join(` / `)||`未检测到 agent,先运行 yz-cli login`}`));let f=i.skills.filter(e=>mt(e)&&!a.skills[e.name]).length;f>0&&X(e.quiet,t.dim(`目录内另有 ${f} 个按需技能未安装(yz-cli skills list 查看,yz-cli skills install <name> 安装)`))}function St(e){ge(process.platform===`darwin`?`open`:process.platform===`win32`?`cmd`:`xdg-open`,process.platform===`win32`?[`/c`,`start`,``,e]:[e],{stdio:`ignore`,detached:!0}).unref()}function Ct(e){return new Promise(t=>{let n,r,i=new Promise((e,t)=>{n=e,r=t}),a=pe((t,i)=>{let o=new URL(t.url??`/`,`http://127.0.0.1`);if(o.pathname!==`/cb`){i.writeHead(404).end();return}let s=o.searchParams.get(`code`),c=o.searchParams.get(`state`);i.writeHead(200,{"Content-Type":`text/html; charset=utf-8`}),s&&c===e?(i.end(`<h3>✅ 登录成功,可以关闭此页面回到终端。</h3>`),n(s)):(i.end(`<h3>❌ 登录回调无效,请回终端重试。</h3>`),r(Error(`回调参数无效`))),setTimeout(()=>a.close(),100)});a.listen(0,`127.0.0.1`,()=>{let e=a.address(),n=typeof e==`object`&&e?e.port:0;setTimeout(()=>r(Error(`登录超时(5 分钟),请重试`)),3e5).unref(),t({port:n,waitCode:i})})})}async function wt(e){let n=P(),r=le(8).toString(`hex`),i;if(e.browser){let{port:e,waitCode:n}=await Ct(r),a=await ke(r,e);console.log(`正在打开浏览器进行飞书登录…\n若未自动打开,请手动访问:\n${t.cyan(a)}\n`),St(a),i=await n}else{let e=await ke(r);console.log(`请在任意设备的浏览器打开以下地址完成飞书登录:\n${t.cyan(e)}\n`);let n=me({input:process.stdin,output:process.stdout});if(i=(await n.question(`完成后把页面上显示的一次性代码粘贴到这里: `)).trim(),n.close(),!i)throw Error(`未输入代码`)}let{token:a,account:o}=await De({code:i,deviceName:se(),os:v(),cliVersion:U});we({token:a,account:o,serverUrl:n}),console.log(t.green(`✓ 登录成功:${o.name}${o.email?`(${o.email})`:``}`)),console.log(`
|
|
6
6
|
正在接线本机 agent…`),pt(),console.log(`
|
|
7
|
-
正在同步 skills…`),await bt({quiet:!1})}async function Tt(){let e=await I();console.log(`${t.bold(e.account.name)}${e.account.email?`(${e.account.email})`:``}`),console.log(t.dim(`服务端:${P()}`)),console.log(t.dim(`可用 skills:${e.entitledSkills.join(`, `)||`(无)`}`))}async function Et(){try{await je()}catch{}A(),console.log(t.green(`✓ 已退出登录(本机 token 已吊销)`))}async function Dt(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString(`utf8`)}async function Ot(e,n,r){let i=e.toUpperCase(),a=n.startsWith(`/`)?n:`/${n}`;if(r.params){let e;try{e=JSON.parse(r.params)}catch{throw new N(`--params 必须是合法 JSON 对象`)}let t=new URLSearchParams;for(let[n,r]of Object.entries(e))r!=null&&t.append(n,typeof r==`string`?r:JSON.stringify(r));let n=t.toString();n&&(a+=`${a.includes(`?`)?`&`:`?`}${n}`)}let o;if(r.data!==void 0){let e=r.data===`-`?await Dt():r.data.startsWith(`@`)?await import(`node:fs/promises`).then(e=>e.readFile(r.data.slice(1),`utf8`)):r.data;try{o=JSON.parse(e)}catch{throw new N(`--data 必须是合法 JSON(或 @文件路径 / - 从 stdin 读)`)}}if(r.dryRun){console.log(`${i} ${P()}${a}`),console.log(`Authorization: Bearer <token>`),o!==void 0&&(console.log(`Content-Type: application/json`),console.log(JSON.stringify(o,null,2)));return}let s=await F(a,{method:i,body:o,okOnly:!1}),c=await s.text();console.error(t.dim(`HTTP ${s.status}`));try{console.log(JSON.stringify(JSON.parse(c),null,2))}catch{console.log(c)}s.ok||(process.exitCode=1)}async function kt(){let e=(e,n=``)=>console.log(`${t.green(`✓`)} ${e}${n?t.dim(` ${n}`):``}`),n=(e,n=``)=>console.log(`${t.red(`✗`)} ${e}${n?t.dim(` ${n}`):``}`);console.log(t.bold(`yz-cli ${U}`)),console.log(t.dim(`YZ_HOME=${S()}`));let r=P();try{let t=Date.now(),i=await fetch(`${r}/healthz`);i.ok?e(`服务端可达`,`${r}(${Date.now()-t}ms)`):n(`服务端异常`,`HTTP ${i.status}`)}catch(e){n(`服务端不可达`,`${r}:${e.message}`)}if(!k())n(`未登录`,`运行 yz-cli login`);else try{let t=await I();e(`登录有效`,`${t.account.name},可用 skills ${t.entitledSkills.length} 个`)}catch(e){n(`登录失效`,e.message)}let i=R();i.length?e(`检测到 agent:${i.map(e=>e.displayName).join(` / `)}`):n(`未检测到任何 agent(Claude Code / Codex / OpenClaw)`);let a=j(),o=Object.keys(a.skills).length;e(`本地受管 skills:${o} 个`,o?w():``),a.lastCheckAt&&e(`上次同步检查`,new Date(a.lastCheckAt).toLocaleString())}function At(){
|
|
7
|
+
正在同步 skills…`),await bt({quiet:!1})}async function Tt(){let e=await I();console.log(`${t.bold(e.account.name)}${e.account.email?`(${e.account.email})`:``}`),console.log(t.dim(`服务端:${P()}`)),console.log(t.dim(`可用 skills:${e.entitledSkills.join(`, `)||`(无)`}`))}async function Et(){try{await je()}catch{}A(),console.log(t.green(`✓ 已退出登录(本机 token 已吊销)`))}async function Dt(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString(`utf8`)}async function Ot(e,n,r){let i=e.toUpperCase(),a=n.startsWith(`/`)?n:`/${n}`;if(r.params){let e;try{e=JSON.parse(r.params)}catch{throw new N(`--params 必须是合法 JSON 对象`)}let t=new URLSearchParams;for(let[n,r]of Object.entries(e))r!=null&&t.append(n,typeof r==`string`?r:JSON.stringify(r));let n=t.toString();n&&(a+=`${a.includes(`?`)?`&`:`?`}${n}`)}let o;if(r.data!==void 0){let e=r.data===`-`?await Dt():r.data.startsWith(`@`)?await import(`node:fs/promises`).then(e=>e.readFile(r.data.slice(1),`utf8`)):r.data;try{o=JSON.parse(e)}catch{throw new N(`--data 必须是合法 JSON(或 @文件路径 / - 从 stdin 读)`)}}if(r.dryRun){console.log(`${i} ${P()}${a}`),console.log(`Authorization: Bearer <token>`),o!==void 0&&(console.log(`Content-Type: application/json`),console.log(JSON.stringify(o,null,2)));return}let s=await F(a,{method:i,body:o,okOnly:!1}),c=await s.text();console.error(t.dim(`HTTP ${s.status}`));try{console.log(JSON.stringify(JSON.parse(c),null,2))}catch{console.log(c)}s.ok||(process.exitCode=1)}async function kt(){let e=(e,n=``)=>console.log(`${t.green(`✓`)} ${e}${n?t.dim(` ${n}`):``}`),n=(e,n=``)=>console.log(`${t.red(`✗`)} ${e}${n?t.dim(` ${n}`):``}`);console.log(t.bold(`yz-cli ${U}`)),console.log(t.dim(`YZ_HOME=${S()}`));let r=P();try{let t=Date.now(),i=await fetch(`${r}/healthz`);i.ok?e(`服务端可达`,`${r}(${Date.now()-t}ms)`):n(`服务端异常`,`HTTP ${i.status}`)}catch(e){n(`服务端不可达`,`${r}:${e.message}`)}if(!k())n(`未登录`,`运行 yz-cli login`);else try{let t=await I();e(`登录有效`,`${t.account.name},可用 skills ${t.entitledSkills.length} 个`)}catch(e){n(`登录失效`,e.message)}let i=R();i.length?e(`检测到 agent:${i.map(e=>e.displayName).join(` / `)}`):n(`未检测到任何 agent(Claude Code / Codex / OpenClaw)`);let a=j(),o=Object.keys(a.skills).length;e(`本地受管 skills:${o} 个`,o?w():``),a.lastCheckAt&&e(`上次同步检查`,new Date(a.lastCheckAt).toLocaleString())}function At(){let e=process.execPath??``;e.includes(`${S()}/bin`)||e.includes(`.yz\\bin`)?(console.log(`二进制安装:重新执行安装脚本即可升级:`),console.log(t.cyan(` curl -fsSL "https://yz-cli.oss-cn-beijing.aliyuncs.com/cli/install.sh" | sh`))):(console.log(`npm 安装:`),console.log(t.cyan(` npm i -g @yuanze_dev/cli@latest`)))}function jt(){let e=g(_(),`.claude`,`settings.json`);if(!s(e))return null;let n;try{n=JSON.parse(u(e,`utf8`))}catch{return t.yellow(`· ${e} 不是合法 JSON,跳过 hook 清理(请手动检查)`)}let r=n.hooks;if(!r?.SessionStart)return null;let i=JSON.stringify(r.SessionStart);return r.SessionStart=r.SessionStart.filter(e=>!JSON.stringify(e).includes(`yz-cli skills sync`)),JSON.stringify(r.SessionStart)===i?null:(r.SessionStart.length===0&&delete r.SessionStart,Object.keys(r).length===0&&delete n.hooks,a(e,`${e}.bak-yz`),m(e,`${JSON.stringify(n,null,2)}\n`),t.green(`✓ Claude Code:SessionStart 自动同步 hook 已移除`))}async function Mt(e){let n=j(),r=Object.keys(n.skills);if(!r.length&&!e.purge){console.log(`本机没有由 yz-cli 安装的 skills,无需卸载。`);return}let i=e.purge?`移除 ${r.length} 个受管 skills,并清除自动同步 hook、定时器、登录凭据与 ${S()}`:`移除 ${r.length} 个受管 skills(各 agent 内的链接 + 本地缓存)`;if(!e.yes){let e=me({input:process.stdin,output:process.stdout}),t=(await e.question(`将${i}。继续?(y/N) `)).trim().toLowerCase();if(e.close(),t!==`y`&&t!==`yes`){console.log(`已取消。`);return}}for(let e of r)B(e,n.skills[e]?.links??[]),console.log(t.dim(`- 已移除 ${e}`));if(p(w(),{recursive:!0,force:!0}),p(Ce(),{recursive:!0,force:!0}),M({skills:{}}),!e.purge){console.log(t.green(`✓ 卸载完成:${r.length} 个 skills 已从所有 agent 移除。`)),console.log(t.dim(`登录态、自动同步 hook 与定时器仍保留,执行 yz-cli skills sync 可随时全部装回;要彻底清除用 yz-cli skills uninstall --purge`));return}let a=jt();a&&console.log(a);let o=ut();if(o&&console.log(o),k()){try{await je()}catch{}A(),console.log(t.green(`✓ 已退出登录(本机 token 已吊销)`))}p(S(),{recursive:!0,force:!0}),console.log(t.green(`✓ 彻底清除完成(${S()} 已删除)。`)),console.log(t.dim(`CLI 本体如需卸载:npm 安装的执行 npm rm -g @yuanze_dev/cli;脚本安装的删除 ~/.yz/bin 及 PATH 配置`))}const Z=new e;Z.name(`yz-cli`).description([`原则 CLI:公司 Agent Skills 的分发与按需获取工具。`,``,`飞书登录后,授权技能分两类:`,` 自动 — skills sync 安装进本机 agents(Claude Code 开会话经 hook、各系统经定时器每小时自动同步)`,` 按需 — 不自动安装,skills search 发现、skills info 即时取用、skills install 持久安装`,`标注 requiresBackend 的技能经 yz-cli api 调用公司后端能力。`].join(`
|
|
8
8
|
`)).version(U),Z.addHelpText(`after`,[``,`常用流程:`,` 首次使用 yz-cli login 登录并自动接线(建目录 / 写自动同步 hook)`,` 找技能 yz-cli skills search 数据库 查询 多词 AND;yz-cli skills list 看全量`,` 用技能 yz-cli skills info db-query stdout 即技能全文,照内容执行`,` 装技能 yz-cli skills install db-query 常驻为本机可复用技能`,` 调后端 yz-cli api POST /skill-api/db-query/query -d '{"sql":"select 1"}'`,` 排障 yz-cli doctor`,``,`单命令详细说明:yz-cli help <command>(技能子命令:yz-cli skills help <command>)`].join(`
|
|
9
9
|
`)),Z.command(`login`).summary(`飞书扫码登录`).description([`打开浏览器完成飞书扫码登录,凭据写入本机 ~/.yz;登录成功后自动接线本机 agent`,`(建 skills 目录、为 Claude Code 写会话启动自动同步 hook)并做首次同步。`,`帐号为白名单制:登录被拦截时,把拦截页显示的 union_id 发给管理员,`,`在 hyperion-central 后台「原则 CLI → 员工帐号」添加后重试。`].join(`
|
|
10
10
|
`)).option(`--no-browser`,`不打开浏览器(SSH 等场景),手动粘贴一次性代码`).action(e=>$(()=>wt(e))),Z.command(`logout`).summary(`退出登录并吊销本机 token`).description(`通知服务端吊销本机 token,并删除本地凭据(服务端不可达时仅清本地)。`).action(()=>$(Et)),Z.command(`whoami`).summary(`查看当前帐号与授权技能`).description(`输出当前登录帐号(姓名/邮箱)、连接的服务端地址、被授权的技能名单。`).action(()=>$(Tt)),Z.command(`doctor`).summary(`环境自检`).description(`依次检查:服务端连通性、登录态有效性、本机 agent 检测结果、本地受管技能数量。排障第一步。`).action(()=>$(kt)),Z.command(`update`).summary(`查看升级方式`).description(`根据安装方式(二进制 / npm)输出对应的升级命令,不会自动执行。`).action(()=>$(async()=>At())),Z.command(`api`).summary(`带登录态调用后端接口`).description([`对后端任意接口发起带登录态的请求,响应输出到 stdout(默认 pretty JSON,HTTP 状态走 stderr)。`,`path 原样透传:/skill-api/<skill>/<endpoint> 调技能后端,/v1/* 调平台接口——`,`skills 对应的接口只是 yz-cli api 能触达的众多命名空间之一。`].join(`
|