@linkaicli/linkai-cli 1.0.21 → 1.0.22

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/AGENTS.md CHANGED
@@ -21,6 +21,7 @@
21
21
 
22
22
  - 所有命令输出 JSON 信封:成功 `{"ok":true,"data":…}`;失败退出码非 0,`{"ok":false,"error":{"code","message","hint?"}}`。日志走 stderr。
23
23
  - 发现命令:`linkai list <关键字>`;看参数:`linkai <site> <command> --help -f json`。不要凭记忆拼参数。
24
+ - **工作流**:每个业务任务(搭建、修改、导入)的第一条命令是 `linkai flow-start --goal "<一句话目标>" --type create|modify|import|query [--plan <planId>]`(修改已有方案必带 `--plan`),结束时 `linkai flow-end --status delivered|abandoned|failed`。
24
25
  - 写命令(help 中标 `[write]`)结果不确定时,先用只读命令确认现状,**不要重复执行来"修复"**。
25
26
  - **登录只使用 OAuth**:首次登录由用户执行 `linkai login`,在领谋浏览器页面完成授权;CLI 用刷新令牌自动续期。不索要账号密码。同账号切换租户用 `linkai login --tenant <租户名>`;用户要求切换账号时,先问是否要切换领谋账号,确认后执行 `linkai login --switch-account`。
26
27
  - 文件参数可用相对路径(相对当前目录)。
@@ -1,4 +1,4 @@
1
- @linkaicli/linkai-cli 1.0.21 安装包内含以下第三方软件(已打包进 dist/bundle)。
1
+ @linkaicli/linkai-cli 1.0.22 安装包内含以下第三方软件(已打包进 dist/bundle)。
2
2
 
3
3
  ── @colors/colors@1.5.0(MIT)
4
4
 
@@ -1,4 +1,4 @@
1
- var ce=Object.defineProperty;var r=(e,t)=>ce(e,"name",{value:t,configurable:!0});import k from"node:fs";import w from"node:path";import le from"node:os";import{fileURLToPath as de}from"node:url";import{cli as W,Strategy as ue}from"@linkaicli/linkai-cli/registry";import{ArgumentError as h,AuthRequiredError as pe,CliError as y}from"@linkaicli/linkai-cli/errors";import*as a from"./core.js";import*as Z from"./build.js";import*as G from"./deliverable.js";import*as ee from"./fill-card.js";import*as I from"./collab.js";import*as v from"./reuse.js";import*as x from"./dictionary.js";import*as f from"@linkaicli/linkai-cli/oplog";const l={site:a.SITE_ID,browser:!1,defaultFormat:"json"},fe={site:a.SITE_ID,domain:"bi.linkmore.com",strategy:ue.UI,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"json"},n=r((e,t,s=!0)=>({name:e,type:"string",required:s,help:t}),"str"),J=r((e,t)=>t?e.filter(s=>String(s.name).includes(t)):e,"filter"),A=r(e=>JSON.parse(a.readInputFile(e)),"json"),te=r(()=>new Date().toISOString().replace(/[-:]/g,"").replace("T","-").slice(0,15),"stamp"),Q=r((e,...t)=>e||a.artifactsDir(...t),"outPath");function d(e){const t=e.func,s=r(async i=>{try{return await i()}catch(u){throw u instanceof a.LoginRequiredError?new pe("bi.linkmore.com",u.message,"请用户执行 linkai login,在浏览器完成领谋授权"):u instanceof a.TenantMismatchError?new y("TENANT_MISMATCH",u.message,"先用 linkai login --tenant <租户名> 切换到方案规范指定的租户,再用 linkai status 确认;无法切换就停止并告诉用户"):u instanceof a.NetworkError?new y("NETWORK_ERROR",u.message,"网络暂时不通(已自动重试):等 1~2 分钟或请用户检查网络 / 代理后再执行;只读命令可直接重试,写命令先用只读命令确认结果再决定是否重做",1):(u instanceof a.ApiError||u instanceof a.KindError)&&u.kind?new y(u.kind,u.message,me[u.kind],1):u instanceof I.CollabConflictError?new y("COLLAB_CONFLICT",u.message,"别的执行者正在改这个对象:先 linkai collab-status --board <任务板> 看分工,改做别的任务,或等对方释放;不要绕过",1):u}},"guard"),o=r(i=>{e.access==="write"&&!e.name.startsWith("collab-")&&I.assertWritable(I.objectsForCommand(e.name,i||{},A))},"checkCollab"),c=e.browser?async(i,u)=>s(()=>(o(u),t(i,u))):async i=>s(()=>(o(i),t(i)));return W({...e,func:c})}r(d,"site");const me={SQL_INVALID:"SQL 里有视图中不存在的字段 / 表或语法错误:用 linkai kb-object / ds-tables 核对字段名后改 SQL,先 sql-check 通过再建或改数据集",IN_USE:"对象仍被引用:先用 linkai ds-refs --ds <数据集> 找到引用它的卡片,确认后先删卡片,再删数据集",NOT_FOUND:"对象不存在或已删除:用 plan-list / card-list / ds-list 重新确认精确 id,不要重复执行写命令",FORBIDDEN:"当前用户没有该对象的权限(如私有数据集):换用自己可见的对象,或请对象负责人共享",DUPLICATE_NAME:"同名对象已存在——很可能上一次已经创建成功:先用 card-list / ds-list / plan-list --search <名称> 确认并复用,不要换个名字或重复执行同样的创建",SQL_READONLY:"数据集只能是单条只读查询(SELECT 或 WITH … SELECT),不能改数据或建表;本 CLI 没有操作数据库的能力",SQL_TABLE_NOT_ALLOWED:"只能查询 linkai ds-tables 列出的租户视图:换用清单里的视图;确实需要新视图时停下,告诉用户联系领谋数据团队开放",READ_ONLY:"系统报表 / 系统方案(资源包下发到本租户的)只读:不能在本租户改,要到来源租户修改后重新下发;不要换个接口重复尝试,告诉用户",BAD_REQUEST:"请求体结构不对:先查 references/api-catalog.md,或回读一条现有数据照抄结构;优先用已封装的命令,不要反复试",KNOWLEDGE_INVALID:"方案知识文件不完整:按 message 补齐(每张线上业务卡片一段、cardId 必填、修改方案必须写 changeSummary),格式见 templates/report-spec/plan-knowledge.example.yaml",ASK_UNAVAILABLE:"问数服务还没有上线:告诉用户这个功能暂不可用;需要数据可以先搭看板或在领谋界面查看",CONFIG_INVALID:"配置违反领谋界面的保存规则(与在界面上点保存时的校验一致):按 message 逐条修正后再保存;规则见知识库 references/card.md、filters.md",FILE_NOT_FOUND:"文件路径不对:确认文件已写出、路径相对当前目录;不要原样重复执行"},Oe=d({...l,name:"login",access:"write",description:"首次打开领谋 OAuth 授权页;同账号切换租户复用授权,切换账号需显式 --switch-account",args:[n("tenant","目标租户(名称、编码或 id);已有授权时无需再打开浏览器",!1),{name:"timeout",type:"int",default:300,help:"等待浏览器完成 OAuth 授权的秒数"},{name:"no-browser",type:"boolean",default:!1,help:"只打印授权地址,不自动打开系统浏览器"},{name:"switch-account",type:"boolean",default:!1,help:"用户确认切换领谋账号后,重新打开 OAuth 授权页"}],func:r(async e=>{const t=Number(e.timeout||300);if(!Number.isInteger(t)||t<1||t>1800)throw new h("timeout 必须是 1-1800 秒");if(!e["switch-account"]){let o=null;try{o=await a.ensureSession()}catch(c){if(!(c instanceof a.LoginRequiredError))throw c}if(o){const c=e.tenant?await a.switchTenant(o,e.tenant):{session:o,switched:!1};return c.switched&&(x.resetReads("switch-tenant"),a.clearShortCache()),{login:c.switched?"oauth_tenant_switch":"oauth_existing",autoRelogin:!0,...a.spaceOf(c.session)}}}x.resetReads("login"),a.clearShortCache();const s=await a.oauthLogin({tenant:e.tenant,timeoutSeconds:t,...e["no-browser"]?{browser:r(async()=>!1,"browser")}:{},switchAccount:e["switch-account"]});return{login:"oauth",autoRelogin:!0,...a.spaceOf(s)}},"func")}),De=W({...l,name:"logout",access:"write",description:"退出登录:撤销 OAuth 会话并删除本机登录信息",args:[],func:r(async()=>{const e=await a.oauthLogout();return a.forgetCredentials(),a.clearSession(),{logout:!0,oauthRevoked:e}},"func")}),Te=d({...l,name:"status",access:"read",description:"查看登录状态、当前租户、是否自动续登、数据源与可用表数量",args:[],func:r(async()=>{const e=await a.ensureSession(),t=a.tokenPayload(e.token),s=await a.ctx(),o=await a.allowedTables();return{loggedIn:!0,...a.spaceOf(e),autoRelogin:!0,authMode:"oauth",tokenHoursLeft:t.exp?Math.round((t.exp*1e3-Date.now())/36e4)/10:null,dataSourceId:s.dataSourceId,dataSources:s.dataSources,tables:o.size,...we()}},"func")});function ae(e){const t=e.status==="interrupted"&&e.reason==="build-incomplete"?"用同一个 spec 再执行 build --execute 会断点续跑;不再继续就清理试搭对象后 flow-end --status abandoned":e.status==="interrupted"&&e.reason==="outcome-unknown"?"上次写操作结果未知:先用只读命令回读确认现状,不要直接重复执行写命令":`继续做就在命令上加 --flow ${e.id};不再继续就 linkai flow-end --flow ${e.id} --status abandoned`;return{flow:e.id,goal:e.goal,type:e.type,plan:e.plan,source:e.source,status:e.status,reason:e.reason,lastActivity:new Date(e.lastActivity).toISOString(),lastCommand:e.lastCommand,lastErrorCode:e.lastErrorCode,build:e.build,next:t}}r(ae,"describeFlow");function we(){try{const e=f.unfinishedFlows().filter(t=>t.id!==f.currentFlow()?.id).slice(0,5).map(ae);return e.length?{unfinishedFlows:e}:{}}catch{return{}}}r(we,"unfinishedFlowsHint");const Fe=d({...l,name:"tenants",access:"read",description:"查看可用租户、当前租户及下一步;用户完成 OAuth 授权后执行",args:[],func:r(async()=>{const e=await a.ensureSession(),t=await a.tenantList(e),s=t.find(i=>i.tenantId===e.tenantId),o=t.map(i=>({name:i.name,tenantId:i.tenantId,current:i.tenantId===e.tenantId}));let c;return t.length===1?c=`只有一个租户,当前租户是「${t[0].name}」。`:c=`当前租户是「${s?.name||""}」。同账号可直接执行 linkai login --tenant <租户名> 切换,无需重新授权。`,{tenantCount:t.length,single:t.length===1,current:s?.name||a.spaceOf(e).tenantName,tenants:o,next:c}},"func")}),Ne=d({...l,name:"kinds",access:"read",defaultFormat:"table",description:"列出 74 种图表类型、字段建议与可用状态",args:[],func:r(async()=>a.kinds(),"func")}),be={answered:"回答用户:给出数字或小表,写明口径(数据源 dataSource、用到的视图 / 字段中文名见 caliber)、时间范围和「数据截至」(dataAsOf);结果被截断时说明只看了部分行。直接在对话里回答,不要另生成文件",empty:"告诉用户「没有查到符合条件的数据」,不要说成 0;可以确认时间范围或条件是否太窄",refused:"小满答不了:按 linkai-ask 技能「兜底」读当前租户数据字典、写只读 SQL,再执行 linkai ask --question <同一问题> --sql <文件> --fallback <本次 askId>;字典里也没有相关数据就如实告诉用户",failed:"小满出错:按 linkai-ask 技能「兜底」读当前租户数据字典、写只读 SQL,再执行 linkai ask --question <同一问题> --sql <文件> --fallback <本次 askId>",rejected:"按 message 告诉用户原因(翻页取数、同类问题反复换条件被暂停、排队已满等);不要换个问法或改用兜底绕过"},he={answered:"回答用户:给出数字,写明「由 AI 按数据字典口径查询」和口径(用到的视图 / 字段见 caliber)、时间范围、「数据截至」(dataAsOf);结果被截断时说明只看了部分行",empty:"告诉用户「没有查到符合条件的数据」,不要说成 0;可以确认时间范围或条件是否太窄",rejected:"按 message 修改(换用可用视图、去掉分页偏移、改为汇总等);不要换个写法绕过安全规则",estimate_rejected:"预计扫描量过大:加上时间范围或筛选条件,或改为汇总后再查",failed:"按 message 里的数据库报错改 SQL(用 kb-object 核对字段名)后再查",timeout:"查询超时:缩小时间范围、加筛选条件或改为汇总后再查"},ne=new Set(["refused","failed"]),$e=d({...l,name:"ask",access:"read",description:"问数:先把问题交给领谋服务端(与小满问数同一套)查询;小满答不了时,带 --sql 和 --fallback 由 AI 按数据字典写只读 SQL 兜底。按当前用户的数据权限执行,有频率和行数限制",args:[n("question","用户的问题(一次一个问题;有歧义先和用户确认,再把确认后的完整问题传入)"),n("keywords","从问题里提炼的关键词,逗号分隔,如「采购数量,入库数量,今天」(用于统计)",!1),n("source","指定数据源 id(可选,仅问小满时;不传由服务端按问题自动选)",!1),n("sql","兜底:AI 写的 SQL 文件,1~3 个逗号分隔(必须同时给 --fallback)",!1),n("fallback","兜底:小满没答出来的那次 askId(refused / failed)",!1),n("template","兜底:使用的指标模板编号(如 DP-PUR-002),没用不填",!1),{name:"rows",type:"int",default:30,help:"每个结果最多返回几行给 AI(1~200);更多的写到 --output 目录的文件里"},n("output","完整结果保存目录(默认 ~/.linkai/artifacts/linkai/ask)",!1)],func:r(async e=>{const t=String(e.question||"").trim();if(!t)throw new h("--question 必填:传用户的问题");if(t.length>2e3)throw new h("--question 最长 2000 字");const s=String(e.keywords||"").split(/[,,、]/).map(m=>m.trim()).filter(Boolean).slice(0,20),o=Math.min(200,Math.max(1,Number(e.rows)||30)),c=await a.ensureSession(),i=f.currentCaller(),u=f.currentFlow()?.id??null,C={name:i.agent,version:i.toolVersion,model:i.model,sessionId:i.toolSessionId,flowId:u,runId:f.currentRunId(),cliVersion:i.cliVersion},p=Q(e.output,"ask"),S=r((m,j)=>{const q=m.rows||[];let B=null;return q.length>o&&(k.mkdirSync(p,{recursive:!0}),B=a.writeJsonFile(w.join(p,`${m.askId}.json`),{question:t,columns:m.columns,rows:q})),{askId:m.askId??null,status:m.status??null,message:m.message??null,columns:m.columns||[],rows:q.slice(0,o),rowCount:m.rowCount??0,rowLimit:m.rowLimit??null,truncated:!!m.truncated,...B?{file:B,shownRows:o}:{},dataAsOf:m.dataAsOf??null,caliber:m.explain??null,cached:!!m.cached,next:j[m.status]??null}},"shape"),E=r(m=>{if(m?.rateLimited)throw new y("RATE_LIMITED",m.message||"问数超过频率或数据量限制",`${m.retryAfterSeconds??60} 秒后再试,告诉用户原因;不要循环重试、换账号或拆分问题绕过。需要大量数据请改为汇总`,1)},"rateLimited");if(e.sql||e.fallback){if(!e.sql||!e.fallback)throw new h("兜底需要同时给 --sql(AI 写的 SQL 文件)和 --fallback(小满没答出来的那次 askId)");const m=a.xiaomanAttempt(e.fallback);if(!m||!ne.has(m.status))throw new y("ASK_XIAOMAN_FIRST",m?`这次小满已经回答(${m.status}),不需要兜底`:"没有找到这次小满问数记录(或已超过 2 小时)",'先执行 linkai ask --question "<问题>"(不带 --sql)问小满;只有返回 refused / failed 时,才带上它的 askId 兜底',1);const j=String(e.sql).split(/[,,]/).map(g=>g.trim()).filter(Boolean);if(!j.length||j.length>3)throw new h("--sql 需要 1~3 个 SQL 文件,逗号分隔");const q=[];for(const[g,z]of j.entries()){const O=a.readInputFile(z);if(a.hasOffset(O))throw new y("ASK_REJECTED",`第 ${g+1} 条 SQL 带分页偏移(OFFSET / LIMIT m,n):问数不支持翻页`,"改为汇总,或加时间范围 / 筛选条件缩小范围;需要完整明细请联系管理员导出",1);q.push(await a.localCheckSql(O))}await _({enforce:!0});for(const[g,z]of q.entries())try{await a.checkSql(z)}catch(O){throw O instanceof a.LoginRequiredError||O instanceof a.NetworkError?O:new y("SQL_INVALID",`第 ${g+1} 条 SQL 没有通过数据集运行校验:${String(O.message).slice(0,300)}`,"按报错核对视图与字段(kb-object),改好后再执行;不要绕过",1)}const B=await a.ctx(),F=await a.askQuery({question:t,dataSourceId:B.dataSourceId,queries:q.map(g=>({sql:g})),keywords:s,templateId:e.template||null,knowledgeRefs:[`xiaoman-ask:${e.fallback}`,...x.recentRefs({tenantId:c.tenantId,flowId:u})].slice(0,50),agent:C}),X=F?.results||[];return f.emit("ask",{requestId:F?.requestId??null,mode:"fallback",fallbackOf:e.fallback,queries:q.length,rateLimited:!!F?.rateLimited,results:X.map(g=>({askId:g.askId,status:g.status,rows:g.rowCount,cached:g.cached}))}),E(F),{mode:"fallback",fallbackOf:e.fallback,requestId:F.requestId,results:X.map(g=>S(g,he)),quota:F.quota??null}}const L=await a.askQuestion({question:t,keywords:s,dataSourceId:e.source||null,agent:C}),b=L?.results?.[0]||{};return f.emit("ask",{requestId:L?.requestId??null,mode:"question",rateLimited:!!L?.rateLimited,results:[{askId:b.askId??null,status:b.status??null,rows:b.rowCount??0,cached:!!b.cached,ms:b.elapsedMs??null}]}),E(L),b.askId&&a.recordXiaomanAttempt({askId:b.askId,status:b.status,question:t}),{mode:"question",...S(b,be),dataSource:b.dataSourceName??null,sql:b.sql??null,...ne.has(b.status)?{fallback:{allowed:!0,askId:b.askId}}:{},quota:L?.quota??null}},"func")}),_e=d({...l,name:"sql-check",access:"read",defaultFormat:"table",description:"校验数据集 SQL(只读、可用表)并列出字段;多个文件用逗号分隔一次校验(省去多次启动)",args:[n("file","SQL 文件路径;多个用逗号分隔,如 sql/a.sql,sql/b.sql")],func:r(async e=>{const t=String(e.file).split(/[,,]/).map(i=>i.trim()).filter(Boolean),s=r(i=>i.map(u=>({sort:u.sort_num,field:u.field_name,type:u.field_type,dbType:u.type_name})),"fields"),o=r(async()=>{try{await _({enforce:!1})}catch(i){if(i instanceof a.LoginRequiredError)throw i}},"dictNotice");if(t.length===1){ke();const i=a.readInputFile(t[0]);return a.precheckSql(i),await o(),s(await a.checkSql(i))}await o();const c=[];for(const i of t)try{c.push({file:i,ok:!0,fields:s(await a.checkSql(a.readInputFile(i)))})}catch(u){if(u instanceof a.LoginRequiredError)throw u;c.push({file:i,ok:!1,error:String(u.message).slice(0,500)})}return{total:t.length,failed:c.filter(i=>!i.ok).length,results:c}},"func")});function ke(){try{const e=w.join(le.homedir(),".linkai","oplog","hints.json");let t={};try{t=JSON.parse(k.readFileSync(e,"utf8"))||{}}catch{}const s=Date.now(),o=[...(t.sqlCheck||[]).filter(c=>s-c<5*6e4),s];t.sqlCheck=o.slice(-10),o.length>=3&&s-(t.sqlCheckNoticeAt||0)>10*6e4&&(t.sqlCheckNoticeAt=s,f.addNotice("最近几分钟在逐个校验 SQL:可以一次校验多个文件,如 linkai sql-check --file sql/a.sql,sql/b.sql,sql/c.sql(每条命令都有启动开销,逐个跑更慢),结果按文件分别列出。","sql-check-batch")),k.mkdirSync(w.dirname(e),{recursive:!0}),k.writeFileSync(e,JSON.stringify(t),{mode:384})}catch{}}r(ke,"nudgeBatchSqlCheck");const Re=d({...l,name:"fmt",access:"read",defaultFormat:"plain",description:"把 SQL 格式化为易读的多行文本",args:[n("file","SQL 文件路径")],func:r(async e=>a.formatSql(a.readInputFile(e.file)),"func")}),je=d({...l,name:"ds-list",access:"read",defaultFormat:"table",description:"列出当前租户的数据集",args:[n("search","按名称关键字过滤(可选)",!1)],func:r(async e=>J(await a.dsList(),e.search),"func")}),Be=d({...l,name:"ds-tables",access:"read",defaultFormat:"table",description:"列出数据集 SQL 可以使用的表(视图)",args:[n("search","按表名关键字过滤(可选)",!1)],func:r(async e=>[...await a.allowedTables()].sort().filter(t=>!e.search||t.includes(String(e.search).toLowerCase())).map(t=>({table:t})),"func")}),ve=d({...l,name:"ds-fields",access:"read",defaultFormat:"table",description:"列出数据集字段(列名就是卡片引用的映射键)",args:[n("ds","数据集的精确 id")],func:r(async e=>(await a.dsFields(e.ds)).map(t=>({sort:t.sortNum,column:t.tableFieldComment,type:t.tableFieldType,argumentType:t.argumentType})),"func")}),Qe=d({...l,name:"ds-sql",access:"read",description:"把数据集当前保存的 SQL 读出到文件",args:[n("ds","数据集的精确 id"),n("output","输出 SQL 路径(默认 ~/.linkai/artifacts/linkai/sql/<dsId>.sql)",!1)],func:r(async e=>{const t=await a.dsDetail(e.ds),s=a.writeJsonFile(Q(e.output,"sql",`${e.ds}.sql`),t.sql||"");return{name:t.name,lines:(t.sql||"").split(`
2
- `).length,output:s}},"func")}),Ke=d({...l,name:"ds-refs",access:"read",defaultFormat:"table",description:"列出引用该数据集的卡片",args:[n("ds","数据集的精确 id")],func:r(async e=>a.dsRefs(e.ds),"func")}),Pe=d({...l,name:"ds-match",access:"read",description:"复用查重:在当前租户里找与这段 SQL 完全一样或相似的数据集(建数据集前先查,不含 zz- 试搭对象)",args:[n("file","SQL 文件路径"),{name:"top",type:"int",default:3,help:"相似候选最多几个"}],func:r(async e=>{const t=v.matchDataset(a.readInputFile(e.file),await v.datasetIndex(),{top:Number(e.top)||3});return{...t,next:t.identical.length?`有完全一样的数据集,直接复用 ${t.identical[0].dsId}(build spec 的 datasets 写 id,或 spec 加 reuse: auto)`:t.similar.length?"有相似数据集:比较 columns 与口径后决定复用、在原数据集上调整(先 ds-refs 看影响),还是新建":"没有可复用的数据集,可以新建"}},"func")}),Ue=d({...l,name:"ds-create",access:"write",description:"新建 SQL 数据集(先做只读与可用表校验,并格式化 SQL);当前租户已有完全一样的数据集时拒绝,除非 --allow-duplicate",args:[n("name","数据集名称(租户内唯一;试验用 zz- 前缀)"),n("file","SQL 文件路径"),{name:"allow-duplicate",type:"boolean",default:!1,help:"已有完全一样的数据集仍要新建(需说明理由,如要独立演进口径)"}],func:r(async e=>{const t=a.readInputFile(e.file);a.precheckSql(t),await _({enforce:!0});let s=null;try{s=v.matchDataset(t,await v.datasetIndex())}catch(i){if(i instanceof a.LoginRequiredError)throw i}if(s?.identical.length&&!e["allow-duplicate"])throw new y("DUPLICATE_DATASET",`当前租户已有完全一样的数据集:${s.identical.map(i=>`「${i.name}」(${i.dsId})`).join("、")}`,"直接复用该数据集(build spec 的 datasets 写 id,或 spec 加 reuse: auto);确需独立一份才加 --allow-duplicate",1);const o=await a.dsSave({name:e.name,sql:t}),c=await a.dsFields(o);return{dsId:o,name:e.name,fields:c.map(i=>i.tableFieldComment),...s?.similar.length?{similar:s.similar,next:"已有相似数据集:确认口径确实不同;如可合并,后续改为复用"}:{}}},"func")}),Je=d({...l,name:"ds-update",access:"write",description:"原地更新数据集(id 不变,卡片映射不断)",args:[n("ds","数据集的精确 id"),n("name","数据集名称"),n("file","SQL 文件路径")],func:r(async e=>{const t=a.readInputFile(e.file);a.precheckSql(t);const s=await a.dsDetail(e.ds);if(s?.name===e.name&&s?.sql&&v.sameSql(t,s.sql)){const i=await a.dsFields(e.ds);return{dsId:e.ds,sameId:!0,unchanged:!0,fields:i.map(u=>u.tableFieldComment),next:"SQL 和名称与线上一致,未重复保存;不要再次执行同样的更新"}}await _({enforce:!0});const o=await a.dsSave({id:e.ds,name:e.name,sql:t,current:s}),c=await a.dsFields(o);return{dsId:o,sameId:o===e.ds,fields:c.map(i=>i.tableFieldComment)}},"func")}),Me=d({...l,name:"card-list",access:"read",defaultFormat:"table",description:"列出当前租户的卡片",args:[n("search","按名称关键字过滤(可选)",!1)],func:r(async e=>J(await a.cardList(),e.search),"func")}),ze=d({...l,name:"card-get",access:"read",description:"把卡片完整配置保存为 JSON 文件(修改前的基线,或 card-save 的输入)",args:[n("card","卡片的精确 id"),n("output","输出 JSON 路径(默认 ~/.linkai/artifacts/linkai/cards/<cardId>.json)",!1)],func:r(async e=>{const t=await a.cardDetail(e.card),s=a.writeJsonFile(Q(e.output,"cards",`${e.card}.json`),t);return{name:t.name,fields:(t.indexBlockDataSettingDtos||[]).length,output:s}},"func")}),We=d({...l,name:"card-save",access:"write",description:"保存完整的卡片配置 JSON(唯一能让配置生效的保存方式)",args:[n("file","修改后的卡片 JSON 文件路径")],func:r(async e=>{const t=A(e.file);if(!t.id)throw new Error("Card payload must contain id");const s=typeof t.chartSetting=="string"?JSON.parse(t.chartSetting||"{}"):t.chartSetting||{};t.chartSetting=JSON.stringify(s),t.chartType=t.chartType||s.id,t.chartParentType=t.chartParentType||s.parentId,await a.post("/index-block/update-new",t),String(t.chartType||s.id)==="agtable"&&s.config?.fitTableWidth!==!0&&f.addNotice("这张 AG 表格没有开启自适应宽度(chartSetting.config.fitTableWidth)。按 BI 操作规则应设为 true,列宽 agColumnWidths 只作为比例(如等分统一 50);用户明确要求固定宽度时除外。","ag-fit-width");const o=await a.cardDetail(t.id);return{cardId:t.id,name:o.name,fields:(o.indexBlockDataSettingDtos||[]).length}},"func")}),Ge=d({...l,name:"fill-write",access:"write",description:"写自由填报数据:一条记录一个元素、字段一次带齐(默认只校验并预览,加 --execute 才写入)",args:[n("card","填报卡片的精确 id(usedIndexBlockId)"),n("file",'记录 JSON 数组文件:[{"字段名":"值",…,"_id":"已有记录ID(可选)"}]'),n("key","填报块:fillfieldid(第一个块)、fillfieldid1(第二个块)…",!1),{name:"batch",type:"int",default:100,help:"每次请求的记录数(1-500)"},{name:"execute",type:"boolean",default:!1,help:"确认后真正写入"}],func:r(async e=>{const t=Number(e.batch||100);if(!Number.isInteger(t)||t<1||t>500)throw new h("batch 必须是 1-500");await a.assertCardInTenant(e.card);const s=await a.cardDetail(e.card);if(s?.isFreeFill===!1)throw new Error("该卡片不是自由填报卡片;fill-write 只支持自由填报");const o=ee.fillBlockOf(s,e.key||"fillfieldid"),c=ee.buildFillValues(o,A(e.file)),i=[];for(let p=0;p<c.length;p+=t)i.push(c.slice(p,p+t));const u={cardId:e.card,name:s.name,key:o.key,fields:o.fields,uniqueFields:o.uniqueFields,records:c.length,updates:c.filter(p=>p.value).length,batches:i.length,sample:c.slice(0,2)};if(!e.execute)return{...u,executed:!1,next:"核对字段与记录数后,用户确认再加 --execute"};const C=[];for(const[p,S]of i.entries())try{await a.post("/index-block/new-fill-field",{usedIndexBlockId:e.card,fillValues:S}),C.push(S.length)}catch(E){throw new Error(`第 ${p+1}/${i.length} 批写入失败(已成功 ${C.length} 批、${C.reduce((L,b)=>L+b,0)} 条):${E.message}。先回读填报表按依据字段核对,再只补未成功的部分,不要整批重跑`)}return{...u,executed:!0,written:C.reduce((p,S)=>p+S,0),next:"回读填报表本身:行数、依据字段无空值无重复、抽查整行字段完整;再核对看板"}},"func")}),Ve=d({...l,name:"card-chart",access:"write",description:"按图表类型新建或覆盖图表卡片(指定维度、数值、系列列)",args:[n("ds","数据集的精确 id"),n("name","卡片名称"),n("kind","图表类型,如 column/stacked(linkai kinds 查看全部)"),n("dim","维度列名,多个用逗号分隔",!1),n("meas","数值列名,多个用逗号分隔"),n("series","系列列名(可选)",!1),n("card","要覆盖的已有卡片 id(可选)",!1)],func:r(async e=>a.cardChart({dsId:e.ds,name:e.name,kind:e.kind,dim:e.dim,meas:e.meas,series:e.series,id:e.card}),"func")}),He=d({...l,name:"card-meter",access:"write",description:"新建或覆盖单数值指标卡(meter/list)",args:[n("ds","数据集的精确 id"),n("name","卡片名称"),n("col","数值列名"),n("subtitle","副标题",!1),n("color","数值颜色(十六进制,如 #E8611C)",!1),n("card","要覆盖的已有卡片 id(可选)",!1)],func:r(async e=>a.cardMeter({dsId:e.ds,name:e.name,col:e.col,subtitle:e.subtitle,color:e.color||void 0,id:e.card}),"func")}),Ye=d({...l,name:"card-table",access:"write",description:"按 spec JSON 新建或覆盖 AG 明细表(见 references/card.md)",args:[n("ds","数据集的精确 id"),n("spec","spec JSON 文件路径"),n("card","要覆盖的已有卡片 id(可选)",!1)],func:r(async e=>a.cardTable({dsId:e.ds,spec:A(e.spec),id:e.card}),"func")}),Xe=d({...l,name:"card-gantt",access:"write",description:"按 spec JSON 新建或覆盖甘特图卡片(见 references/gantt.md)",args:[n("ds","数据集的精确 id"),n("spec","spec JSON 文件路径"),n("card","要覆盖的已有卡片 id(可选)",!1)],func:r(async e=>a.cardGantt({dsId:e.ds,spec:A(e.spec),id:e.card}),"func")}),Ze=d({...l,name:"card-sort",access:"write",description:'设置卡片持久排序("列:asc,列:desc"、.json 文件或 clear),并回读确认排序生效',args:[n("card","卡片的精确 id"),n("spec",'"waxis3:desc,ID:asc" | sort.json | clear')],func:r(async e=>{const t=/\.json$/i.test(String(e.spec))?A(e.spec):e.spec;return a.cardSort(e.card,Array.isArray(t)?t:t.sortField||t)},"func")}),et=d({...l,name:"query",access:"read",description:"按页面方式查询卡片数据;返回状态码、报错信息、行数和实际执行的 SQL",args:[n("card","卡片的精确 id"),{name:"page-size",type:"int",default:20,help:"取数行数"},n("filters","筛选条件 JSON 文件路径",!1),n("output","数据行保存路径(不在屏幕上输出数据)",!1)],func:r(async e=>{const t=await a.queryCard({cardId:e.card,pageSize:e["page-size"],filters:e.filters?A(e.filters):[]});return e.output&&a.writeJsonFile(e.output,t.data),delete t.data,t},"func")}),tt=d({...l,name:"plan-list",access:"read",defaultFormat:"table",description:"列出分析方案(看板)",args:[n("search","按名称关键字过滤(可选)",!1)],func:r(async e=>J(await a.planList(),e.search),"func")}),at=d({...l,name:"plan-get",access:"read",description:"把方案详情保存为 JSON 文件(含租户模板筛选器)",args:[n("plan","方案的精确 id"),n("output","输出 JSON 路径(默认 ~/.linkai/artifacts/linkai/plans/<planId>.json)",!1),{name:"personal",type:"boolean",default:!1,help:"读取自己的个人筛选器副本"}],func:r(async e=>{const t=await a.planDetail(e.plan,!e.personal),s=a.writeJsonFile(Q(e.output,"plans",`${e.plan}.json`),t);return{planId:e.plan,cards:(t.pageCards||[]).map(o=>({pageCardId:o.id,name:o.indexBlock?.name})),output:s}},"func")}),nt=d({...l,name:"plan-create",access:"write",description:"新建空的分析方案(看板)",args:[n("name","方案名称")],func:r(async e=>{const t=await a.post("/page-setting/new-create-empty-plan",{name:e.name,isPrivate:!1,pageType:0});return f.addNotice(`手工搭建方案:完成后必须用 linkai plan-knowledge --plan ${t} --mode create --file <知识文件> 交付方案知识库(铁律 2)。新建看板优先用 linkai build(自动生成知识库并验收)。`,"plan-knowledge"),{planId:t,name:e.name}},"func")}),st=d({...l,name:"plan-add",access:"write",description:"把卡片放到方案的指定位置;返回 pageCardId(绑定筛选器时使用)",args:[n("plan","方案的精确 id"),n("card","卡片的精确 id"),{name:"x",type:"int",default:0,help:"横向位置(格,0–23)"},{name:"y",type:"int",default:0,help:"纵向位置(格)"},{name:"w",type:"int",default:24,help:"宽度(格,满宽 24)"},{name:"h",type:"int",default:20,help:"高度(格)"}],func:r(async e=>a.planAdd({planId:e.plan,cardId:e.card,x:e.x,y:e.y,w:e.w,h:e.h}),"func")}),ot=d({...l,name:"plan-filter",access:"write",description:"保存方案筛选器为租户模板(全量覆盖),并重置自己的个人副本",args:[n("plan","方案的精确 id"),n("file","筛选器请求体 JSON 文件路径")],func:r(async e=>a.planFilter(e.plan,A(e.file)),"func")}),rt=d({...l,name:"report-list",access:"read",defaultFormat:"table",description:"列出数据报表(每个报表对应一张卡片)",args:[n("search","按名称关键字过滤(可选)",!1)],func:r(async e=>J(await a.reportList(),e.search),"func")}),it=d({...l,name:"report-card",access:"read",description:"查询卡片对应报表的 userPageCardId(重置报表筛选器时使用)",args:[n("card","卡片的精确 id")],func:r(async e=>{const t=await a.reportDetail(e.card);return{cardId:e.card,userPageCardId:t.id}},"func")}),ct=d({...l,name:"report-publish",access:"write",description:"把卡片发布为数据报表",args:[n("card","卡片的精确 id"),n("name","报表名称")],func:r(async e=>{await a.post("/index-block/operate-report",{indexBlockId:e.card,name:e.name,tag:"",isPrivate:!1});const t=(await a.reportList()).filter(s=>s.cardId===e.card);return{cardId:e.card,published:t.length===1,url:`https://bi.linkmore.com/view/table?indexBlockId=${e.card}&status=view`}},"func")}),lt=d({...l,name:"reset-filter",access:"write",description:"丢弃自己的个人筛选器副本,按模板重建(方案或报表卡片)",args:[n("plan","方案 id(方案筛选器)",!1),n("page-card","报表 userPageCardId 或方案 pageCardId(卡片筛选器)",!1)],func:r(async e=>{if(!!e.plan==!!e["page-card"])throw new Error("Pass exactly one of --plan or --page-card");const t=e.plan?{userPageSettingId:e.plan,filterSourceType:4}:{userPageCardId:e["page-card"],filterSourceType:2};return{filters:(await a.post("/page-setting/reset-user-filter",t)||[]).map(o=>o.labelName)}},"func")}),V=w.resolve(w.dirname(de(import.meta.url)),"..","..");try{a.setClientVersion(JSON.parse(k.readFileSync(w.join(V,"package.json"),"utf8")).version)}catch{}const K={knowledge:w.join(V,"knowledge"),templates:w.join(V,"templates")};function M(e,t=e){return k.existsSync(e)?k.readdirSync(e,{withFileTypes:!0}).flatMap(s=>{const o=w.join(e,s.name);return s.isDirectory()?M(o,t):s.name.startsWith(".")?[]:[w.relative(t,o)]}):[]}r(M,"walk");function H(e,t){const s=K[e],o=w.resolve(s,t);if(o!==s&&!o.startsWith(s+w.sep))throw new h("路径必须在 knowledge/ 或 templates/ 之内");if(!k.existsSync(o)||k.statSync(o).isDirectory())throw new h(`文件不存在:${e}/${t}`,"执行 linkai kb 查看可用文件");return k.readFileSync(o,"utf8")}r(H,"readLocal");function Y(e){return new y("KB_UNAVAILABLE",`知识库服务暂不可用:${e}`,"稍后重试;持续出现请联系领谋",1)}r(Y,"kbUnavailable");function N(e){if(!(e instanceof a.AiKbError))return e;const t={400:"AI_KB_BAD_REQUEST",403:"AI_KB_FORBIDDEN",404:"AI_KB_NOT_FOUND",409:"AI_KB_CONFLICT",503:"AI_KB_UNAVAILABLE"},s={404:"确认 source、对象或文档属于当前租户;数据字典不会回退本地内容",409:"知识元数据未就绪时先联系知识库维护方;文档持续变化时稍后重试",503:"稍后重试;数据字典没有本地兜底,持续出现请联系领谋"};return new y(t[e.code]||"AI_KB_ERROR",e.message,s[e.code],1)}r(N,"aiKbFailure");function D(e,t,s,o,c){const i=e==null||e===""?t:Number(e);if(!Number.isInteger(i)||i<o||i>c)throw new h(`${s} 必须是 ${o}~${c} 的整数`);return i}r(D,"intOption");async function P(e){const t=await a.ctx();if(String(e).toLowerCase()!==String(t.dataSourceId).toLowerCase())throw new y("AI_KB_SOURCE_MISMATCH",`数据字典 source ${e} 与当前数据集 source ${t.dataSourceId} 不一致`,"不要跨 source 使用元数据;先确认服务端已为当前数据源完成探库,或确认 CLI 的数据源配置",1)}r(P,"requireDatasetSource");function se(){return M(w.join(K.knowledge,"report-builder")).map(e=>({file:`knowledge/report-builder/${e}`,title:"",summary:"",library:"bi-builder",scope:"public",source:"bundled-fallback"}))}r(se,"localBiDocs");function ye(e){return{file:`knowledge/${e.canonicalPath}`,title:"",library:e.library,scope:e.scope,source:"bundled-fallback",content:H("knowledge",e.canonicalPath)}}r(ye,"localBiDoc");function Se(e,t){const s=Array.isArray(t)?t:null,o=s?"bi-builder":t?.library||(String(t?.file||"").startsWith("templates/")?"templates":String(t?.file||"").includes("browser/")?"cli-docs":"bi-builder");return f.emit("kb.read",s?{library:o,op:e,docs:s.length,sources:[...new Set(s.map(c=>c?.source).filter(Boolean))]}:{library:o,op:e,file:t?.file??null,source:t?.source??null,chars:typeof t?.content=="string"?t.content.length:void 0}),t}r(Se,"logKb");function $(e,t,s,o={}){if(f.emit("kb.read",{library:"data-dictionary",op:e,source:t.source??null,...o,...e!=="sources"?{cached:a.kbCacheHit()}:{}}),e!=="sources"&&t.source){const c=a.readSession()?.tenantId,i=f.currentFlow()?.id??null;x.recordRead({tenantId:c,source:t.source,flowId:i,op:e}),e==="doc"&&t.doc&&x.recordRef({tenantId:c,flowId:i,ref:`doc:${t.doc}${o.title?` ${o.title}`:""}`}),e==="object"&&o.object&&x.recordRef({tenantId:c,flowId:i,ref:`object:${o.object}`})}return s}r($,"logDict");async function _({enforce:e}){const t=await a.ensureSession(),s=await a.ctx(),o=await x.dictionaryStatus({tenantId:t.tenantId,source:s.dataSourceId,flowId:f.currentFlow()?.id??null,fetchSources:r(()=>a.dictSourcesCached(),"fetchSources"),fetchDocs:r(async()=>(await a.dictDocsHead(s.dataSourceId)).items,"fetchDocs")});if(a.logGuard("dictionary-fresh",o.ok,{tenantId:t.tenantId,source:s.dataSourceId,reason:o.reason??o.skipped??null,readAt:o.readAt??null,dictUpdatedAt:o.dictUpdatedAt??null,enforce:e}),o.skipped&&f.addNotice(`数据字典:${o.skipped}。`,"dictionary-skipped"),!o.ok){if(e)throw new y("DICTIONARY_REQUIRED",o.reason,x.readHint(s.dataSourceId),1);f.addNotice(`数据字典:${o.reason}。正式写入(ds-create / ds-update / build --execute)前会被拒绝。${x.readHint(s.dataSourceId)}`,"dictionary-stale")}return o}r(_,"checkDictionary");const oe=r((e,t,s)=>Array.isArray(e)?e.slice(0,t).map(s):[],"top"),dt=d({...l,name:"kb",access:"read",description:"读取文件型 BI 搭建知识库(服务器优先、本地兜底);数据字典请使用 kb-sources / kb-search 等结构化命令",args:[n("file","文件路径,如 knowledge/report-builder/build-spec.md 或 templates/report-spec/report.example.yaml",!1),n("search","按关键字过滤目录",!1),n("library","文件型知识库:bi-builder;data-dictionary 请使用 kb-sources 等结构化命令;另预留 integration-flow | user",!1)],func:r(async e=>Se(e.file?"doc":"catalog",await(async()=>{const t=String(e.file||"").replace(/^\.?\//,"");if(t.startsWith("templates/"))return{file:t,source:"bundled",content:H("templates",t.slice(10))};if(t==="knowledge/browser/README.md"||t==="browser/README.md")return{file:"knowledge/browser/README.md",source:"bundled-cli",content:H("knowledge","browser/README.md")};const s=e.search?String(e.search):"",o=e.library?String(e.library):"";if(o&&!a.KB_LIBRARIES[o])throw new h(`未知知识库:${o}`);if(o==="data-dictionary"||/^(?:knowledge\/)?data-dictionary(?:\/|$)/.test(t))throw new y("KB_STRUCTURED_API","数据字典不再按 Markdown 文件路径读取","先执行 linkai kb-sources,再使用 kb-search / kb-catalog / kb-object / kb-docs / kb-doc",1);if(o&&!a.KB_LIBRARIES[o].enabled)throw new y("KB_NOT_IMPLEMENTED",`知识库 ${o} 已预留,当前版本尚未启用`,void 0,1);if(!t){const i=o?[o]:["bi-builder"],u=[];for(const p of i)try{const S=await a.kbCatalog(p);p==="bi-builder"&&!(S.docs||[]).length?u.push(...se()):u.push(...(S.docs||[]).map(E=>{const L=a.KB_LIBRARIES[p].pathPrefix,b=a.kbRequest(E.path,p).path;return{file:`knowledge/${L}/${b}`,title:E.title||"",summary:E.summary||"",library:p,scope:a.KB_LIBRARIES[p].scope,source:"server"}}))}catch(S){if(p==="bi-builder"&&S instanceof a.KbUnavailableError)u.push(...se());else throw S instanceof a.KbUnavailableError?Y(p):S}const C=!o||o==="bi-builder"?M(K.templates).map(p=>({file:`templates/${p}`,title:"报表模版",summary:"",source:"bundled"})):[];return[...u,...C].filter(p=>!s||p.file.includes(s)||p.title.includes(s))}const c=a.kbRequest(t,o||void 0);try{const i=await a.kbDoc(t,o||void 0);return{file:`knowledge/${i.path}`,title:i.title||"",library:i.library,scope:i.scope,source:"server",content:i.content||""}}catch(i){if(c.library==="bi-builder"&&(i instanceof a.KbUnavailableError||i instanceof a.KbDocumentNotFoundError)){if(!k.existsSync(w.join(K.knowledge,"report-builder")))throw Y(c.library);return ye(c)}throw i instanceof a.KbUnavailableError?Y(c.library):i}})()),"func")}),ut=d({...l,name:"kb-sources",access:"read",defaultFormat:"table",description:"列出当前租户可读的数据字典数据源,并标记当前数据集所用 source;后续命令必须显式传该 ID",args:[],func:r(async()=>{try{const[e,t]=await Promise.all([a.dictSourcesCached(),a.ctx()]),s=e.map(c=>({...c,selectedForDatasets:String(c.source).toLowerCase()===String(t.dataSourceId).toLowerCase()})),o=s.some(c=>c.selectedForDatasets);return $("sources",{},null,{sources:s.map(c=>({source:c.source,name:c.name})),aligned:o}),{datasetSourceId:t.dataSourceId,aligned:o,sources:s,...o?{}:{warning:"数据字典没有返回当前数据集 source;不要跨 source 使用元数据,请先确认服务端探库或数据源配置"}}}catch(e){throw N(e)}},"func")}),pt=d({...l,name:"kb-catalog",access:"read",description:"分页列出指定 source 的可用视图/对象元数据",args:[n("source","数据源 ID(来自 kb-sources,必须与后续数据集 source 一致)"),n("search","表名、中文名或说明关键字",!1),n("page","页码,默认 1",!1),n("page-size","每页 1~100,默认 30",!1)],func:r(async e=>{try{await P(e.source);const t={source:e.source,keyword:e.search,pageIndex:D(e.page,1,"page",1,2147483647),pageSize:D(e["page-size"],30,"page-size",1,100)},s=await a.kbCached(e.source,`catalog|${t.keyword??""}|${t.pageIndex}|${t.pageSize}`,()=>a.aiKbCatalog(t));return $("catalog",e,s,{keyword:e.search??null,total:s?.totalCount??null})}catch(t){throw N(t)}},"func")}),ft=d({...l,name:"kb-object",access:"read",description:"读取指定 source 中一个可见对象的字段与关联关系",args:[n("source","数据源 ID(来自 kb-sources)"),n("db","数据库/schema 名"),n("object","对象/视图名")],func:r(async e=>{try{await P(e.source);const t=await a.kbCached(e.source,`object|${String(e.db).toLowerCase()}.${String(e.object).toLowerCase()}`,()=>a.aiKbObject({source:e.source,db:e.db,object:e.object}));return $("object",e,t,{object:`${e.db}.${e.object}`})}catch(t){throw N(t)}},"func")}),mt=d({...l,name:"kb-docs",access:"read",description:"分页列出指定 source 的业务口径、枚举、关系、数据点等知识文档",args:[n("source","数据源 ID(来自 kb-sources)"),n("category","caliber | enum | relation | datapoint | other",!1),n("search","标题或正文关键字",!1),n("page","页码,默认 1",!1),n("page-size","每页 1~100,默认 30",!1)],func:r(async e=>{const t=e.category?String(e.category):void 0;if(t&&!["caliber","enum","relation","datapoint","other"].includes(t))throw new h("category 必须是 caliber、enum、relation、datapoint、other 之一");try{await P(e.source);const s={source:e.source,category:t,keyword:e.search,pageIndex:D(e.page,1,"page",1,2147483647),pageSize:D(e["page-size"],30,"page-size",1,100)},o=await a.kbCached(e.source,`docs|${t??""}|${s.keyword??""}|${s.pageIndex}|${s.pageSize}`,()=>a.aiKbDocs(s));return $("docs",e,o,{category:t??null,keyword:e.search??null,total:o?.totalCount??null})}catch(s){throw N(s)}},"func")}),wt=d({...l,name:"kb-doc",access:"read",description:"一致性读取一篇完整数据字典文档;自动按 nextOffset 分页,版本变化时从头重读",args:[n("source","数据源 ID(来自 kb-sources)"),n("doc","文档 ID(来自 kb-docs 或 kb-search)"),n("page-size","单次正文字符数,1~20000,默认 20000",!1)],func:r(async e=>{try{await P(e.source);const t=await a.kbCached(e.source,`doc|${String(e.doc).toLowerCase()}`,()=>a.aiKbDoc({source:e.source,doc:e.doc,limit:D(e["page-size"],2e4,"page-size",1,2e4)}));return $("doc",e,t,{doc:e.doc,title:t?.title??null,category:t?.category??null,chars:t?.totalChars??null})}catch(t){throw N(t)}},"func")}),bt=d({...l,name:"kb-search",access:"read",description:"按自然语言检索指定 source 的候选视图与业务知识;相关度不是正确率,使用前继续回读对象/文档核对",args:[n("source","数据源 ID(来自 kb-sources)"),n("question","业务问题,最长 2000 字"),n("top-tables","最多返回表数,1~10,默认 5",!1),n("top-docs","最多返回文档数,1~10,默认 3",!1)],func:r(async e=>{try{await P(e.source);const t={source:e.source,question:e.question,topTables:D(e["top-tables"],5,"top-tables",1,10),topDocs:D(e["top-docs"],3,"top-docs",1,10)},s=await a.kbCached(e.source,`search|${t.topTables}|${t.topDocs}|${a.normalizeQuestion(t.question)}`,()=>a.aiKbSearch(t));return $("search",e,s,{question:String(e.question||"").slice(0,200),hitTables:oe(s?.tables,10,o=>`${o.db}.${o.object}`),hitDocs:oe(s?.docs,10,o=>({doc:o.doc,title:o.title??null,category:o.category??null})),emptyReason:s?.emptyReason??null})}catch(t){throw N(t)}},"func")}),ht=W({...l,name:"template",access:"write",description:"复制报表 spec 与示例文件;SQL 示例必须由 AI 按用户需求、租户数据字典和确认口径重写,CLI 不生成业务 SQL",args:[n("output","目标目录(默认 ./linkai-report),需不存在或为空",!1)],func:r(async e=>{const t=w.resolve(String(e.output||"linkai-report"));if(k.existsSync(t)&&k.readdirSync(t).length)throw new h(`目录已存在且不为空:${t}`,"换一个 --output 目录");return k.cpSync(w.join(K.templates,"report-spec"),t,{recursive:!0}),{output:t,files:M(t),next:`修改 ${w.join(t,"report.example.yaml")} 与 sql/ 下的 SQL,然后 linkai build --spec ${w.join(t,"report.example.yaml")}`}},"func")}),kt=d({...l,name:"build",access:"write",description:"严格按 BI 公共知识库执行 AI 提供的 SQL 与报表 spec(CLI 不生成业务 SQL)。默认只校验并给出执行计划;--execute 才写入;含正式名还需 --confirm",args:[n("spec","报表 spec 文件(.yaml/.json),模版用 linkai template 复制"),{name:"execute",type:"boolean",default:!1,help:"真正写入(默认只校验并输出执行计划)"},{name:"confirm",type:"boolean",default:!1,help:"用户已明确授权使用正式名(非 zz- 前缀)"},{name:"no-style",type:"boolean",default:!1,help:"跳过页面样式步骤(暖色背景、卡位透明)"}],func:r(async e=>{const t=Z.loadSpec(e.spec),s=(t.spec.datasets||[]).some(i=>!i.id&&i.sql),o=r(async()=>{const i=await a.ensureSession(),u=a.spaceOf(i);if(t.spec.tenant&&![u.tenantId,u.tenantName,u.tenantCode].includes(String(t.spec.tenant)))throw new h(`当前登录的租户是「${u.tenantName}」,与 spec.tenant「${t.spec.tenant}」不一致`,"确认目标租户后执行 linkai login --tenant <租户名>,或修改 spec.tenant");s&&await _({enforce:!0})},"beforeWrite"),c=await Z.runBuild(t,{execute:!!e.execute,confirm:!!e.confirm,style:!e["no-style"],beforeWrite:o});if(!e.execute&&s&&c.ok!==!1)try{c.dictionary=await _({enforce:!1})}catch(i){if(i instanceof a.LoginRequiredError)c.dictionary={ok:!1,reason:"未登录"};else throw i}if(c.ok===!1&&(c.stage==="validate"||c.stage==="authorize"))throw new h(`build ${c.stage} failed:
1
+ var ce=Object.defineProperty;var r=(e,t)=>ce(e,"name",{value:t,configurable:!0});import y from"node:fs";import w from"node:path";import le from"node:os";import{fileURLToPath as de}from"node:url";import{cli as W,Strategy as ue}from"@linkaicli/linkai-cli/registry";import{ArgumentError as h,AuthRequiredError as pe,CliError as k}from"@linkaicli/linkai-cli/errors";import*as a from"./core.js";import*as Z from"./build.js";import*as G from"./deliverable.js";import*as ee from"./fill-card.js";import*as C from"./collab.js";import*as v from"./reuse.js";import*as x from"./dictionary.js";import*as f from"@linkaicli/linkai-cli/oplog";const l={site:a.SITE_ID,browser:!1,defaultFormat:"json"},fe={site:a.SITE_ID,domain:"bi.linkmore.com",strategy:ue.UI,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"json"},n=r((e,t,s=!0)=>({name:e,type:"string",required:s,help:t}),"str"),M=r((e,t)=>t?e.filter(s=>String(s.name).includes(t)):e,"filter"),A=r(e=>JSON.parse(a.readInputFile(e)),"json"),te=r(()=>new Date().toISOString().replace(/[-:]/g,"").replace("T","-").slice(0,15),"stamp"),P=r((e,...t)=>e||a.artifactsDir(...t),"outPath");function d(e){const t=e.func,s=r(async i=>{try{return await i()}catch(u){throw u instanceof a.LoginRequiredError?new pe("bi.linkmore.com",u.message,"请用户执行 linkai login,在浏览器完成领谋授权"):u instanceof a.TenantMismatchError?new k("TENANT_MISMATCH",u.message,"先用 linkai login --tenant <租户名> 切换到方案规范指定的租户,再用 linkai status 确认;无法切换就停止并告诉用户"):u instanceof a.NetworkError?new k("NETWORK_ERROR",u.message,"网络暂时不通(已自动重试):等 1~2 分钟或请用户检查网络 / 代理后再执行;只读命令可直接重试,写命令先用只读命令确认结果再决定是否重做",1):(u instanceof a.ApiError||u instanceof a.KindError)&&u.kind?new k(u.kind,u.message,me[u.kind],1):u instanceof C.CollabConflictError?new k("COLLAB_CONFLICT",u.message,"别的执行者正在改这个对象:先 linkai collab-status --board <任务板> 看分工,改做别的任务,或等对方释放;不要绕过",1):u}},"guard"),o=r(i=>{e.access==="write"&&!e.name.startsWith("collab-")&&C.assertWritable(C.objectsForCommand(e.name,i||{},A))},"checkCollab"),c=e.browser?async(i,u)=>s(()=>(o(u),t(i,u))):async i=>s(()=>(o(i),t(i)));return W({...e,func:c})}r(d,"site");const me={SQL_INVALID:"SQL 里有视图中不存在的字段 / 表或语法错误:用 linkai kb-object / ds-tables 核对字段名后改 SQL,先 sql-check 通过再建或改数据集",IN_USE:"对象仍被引用:先用 linkai ds-refs --ds <数据集> 找到引用它的卡片,确认后先删卡片,再删数据集",NOT_FOUND:"对象不存在或已删除:用 plan-list / card-list / ds-list 重新确认精确 id,不要重复执行写命令",FORBIDDEN:"当前用户没有该对象的权限(如私有数据集):换用自己可见的对象,或请对象负责人共享",DUPLICATE_NAME:"同名对象已存在——很可能上一次已经创建成功:先用 card-list / ds-list / plan-list --search <名称> 确认并复用,不要换个名字或重复执行同样的创建",SQL_READONLY:"数据集只能是单条只读查询(SELECT 或 WITH … SELECT),不能改数据或建表;本 CLI 没有操作数据库的能力",SQL_TABLE_NOT_ALLOWED:"只能查询 linkai ds-tables 列出的租户视图:换用清单里的视图;确实需要新视图时停下,告诉用户联系领谋数据团队开放",READ_ONLY:"这是领谋下发的系统分析方案 / 系统报表(只读):不能通过 CLI 修改(卡片、筛选器、排序、删除都不行),不要重试或换个接口再试。停下来告诉用户:如需按本租户调整,请在领谋网页上把它另存为本租户的分析方案,再对新方案操作",SYSTEM_PLAN_READONLY:"这是领谋下发的系统分析方案(只读):可以读取方案知识,但不能写入或修改。停下来告诉用户:如需按本租户调整,请在领谋网页上把它另存为本租户的分析方案,再对新方案操作",CLI_FORBIDDEN:"LinkAI CLI 不能调用这个接口:把 message 原样告诉用户,不要重试、不要换路径再试;问数请用 linkai ask,口径补充或纠正请用户在领谋网页上维护",BAD_REQUEST:"请求体结构不对:先查 references/api-catalog.md,或回读一条现有数据照抄结构;优先用已封装的命令,不要反复试",KNOWLEDGE_INVALID:"方案知识文件不完整:按 message 补齐(每张线上业务卡片一段、cardId 必填、修改方案必须写 changeSummary),格式见 templates/report-spec/plan-knowledge.example.yaml",ASK_UNAVAILABLE:"问数服务还没有上线:告诉用户这个功能暂不可用;需要数据可以先搭看板或在领谋界面查看",CONFIG_INVALID:"配置违反领谋界面的保存规则(与在界面上点保存时的校验一致):按 message 逐条修正后再保存;规则见知识库 references/card.md、filters.md",FILE_NOT_FOUND:"文件路径不对:确认文件已写出、路径相对当前目录;不要原样重复执行"},De=d({...l,name:"login",access:"write",description:"首次打开领谋 OAuth 授权页;同账号切换租户复用授权,切换账号需显式 --switch-account",args:[n("tenant","目标租户(名称、编码或 id);已有授权时无需再打开浏览器",!1),{name:"timeout",type:"int",default:300,help:"等待浏览器完成 OAuth 授权的秒数"},{name:"no-browser",type:"boolean",default:!1,help:"只打印授权地址,不自动打开系统浏览器"},{name:"switch-account",type:"boolean",default:!1,help:"用户确认切换领谋账号后,重新打开 OAuth 授权页"}],func:r(async e=>{const t=Number(e.timeout||300);if(!Number.isInteger(t)||t<1||t>1800)throw new h("timeout 必须是 1-1800 秒");if(!e["switch-account"]){let o=null;try{o=await a.ensureSession()}catch(c){if(!(c instanceof a.LoginRequiredError))throw c}if(o){const c=e.tenant?await a.switchTenant(o,e.tenant):{session:o,switched:!1};return c.switched&&(x.resetReads("switch-tenant"),a.clearShortCache()),{login:c.switched?"oauth_tenant_switch":"oauth_existing",autoRelogin:!0,...a.spaceOf(c.session)}}}x.resetReads("login"),a.clearShortCache();const s=await a.oauthLogin({tenant:e.tenant,timeoutSeconds:t,...e["no-browser"]?{browser:r(async()=>!1,"browser")}:{},switchAccount:e["switch-account"]});return{login:"oauth",autoRelogin:!0,...a.spaceOf(s)}},"func")}),Oe=W({...l,name:"logout",access:"write",description:"退出登录:撤销 OAuth 会话并删除本机登录信息",args:[],func:r(async()=>{const e=await a.oauthLogout();return a.forgetCredentials(),a.clearSession(),{logout:!0,oauthRevoked:e}},"func")}),Te=d({...l,name:"status",access:"read",description:"查看登录状态、当前租户、是否自动续登、数据源与可用表数量",args:[],func:r(async()=>{const e=await a.ensureSession(),t=a.tokenPayload(e.token),s=await a.ctx(),o=await a.allowedTables();return{loggedIn:!0,...a.spaceOf(e),autoRelogin:!0,authMode:"oauth",tokenHoursLeft:t.exp?Math.round((t.exp*1e3-Date.now())/36e4)/10:null,dataSourceId:s.dataSourceId,dataSources:s.dataSources,tables:o.size,...we()}},"func")});function ae(e){const t=e.status==="interrupted"&&e.reason==="build-incomplete"?"用同一个 spec 再执行 build --execute 会断点续跑;不再继续就清理试搭对象后 flow-end --status abandoned":e.status==="interrupted"&&e.reason==="outcome-unknown"?"上次写操作结果未知:先用只读命令回读确认现状,不要直接重复执行写命令":`继续做就在命令上加 --flow ${e.id};不再继续就 linkai flow-end --flow ${e.id} --status abandoned`;return{flow:e.id,goal:e.goal,type:e.type,plan:e.plan,source:e.source,status:e.status,reason:e.reason,lastActivity:new Date(e.lastActivity).toISOString(),lastCommand:e.lastCommand,lastErrorCode:e.lastErrorCode,build:e.build,next:t}}r(ae,"describeFlow");function we(){try{const e=f.unfinishedFlows().filter(t=>t.id!==f.currentFlow()?.id).slice(0,5).map(ae);return e.length?{unfinishedFlows:e}:{}}catch{return{}}}r(we,"unfinishedFlowsHint");const Fe=d({...l,name:"tenants",access:"read",description:"查看可用租户、当前租户及下一步;用户完成 OAuth 授权后执行",args:[],func:r(async()=>{const e=await a.ensureSession(),t=await a.tenantList(e),s=t.find(i=>i.tenantId===e.tenantId),o=t.map(i=>({name:i.name,tenantId:i.tenantId,current:i.tenantId===e.tenantId}));let c;return t.length===1?c=`只有一个租户,当前租户是「${t[0].name}」。`:c=`当前租户是「${s?.name||""}」。同账号可直接执行 linkai login --tenant <租户名> 切换,无需重新授权。`,{tenantCount:t.length,single:t.length===1,current:s?.name||a.spaceOf(e).tenantName,tenants:o,next:c}},"func")}),Ne=d({...l,name:"kinds",access:"read",defaultFormat:"table",description:"列出 74 种图表类型、字段建议与可用状态",args:[],func:r(async()=>a.kinds(),"func")}),be={answered:"回答用户:给出数字或小表,写明口径(数据源 dataSource、用到的视图 / 字段中文名见 caliber)、时间范围和「数据截至」(dataAsOf);结果被截断时说明只看了部分行。直接在对话里回答,不要另生成文件",empty:"告诉用户「没有查到符合条件的数据」,不要说成 0;可以确认时间范围或条件是否太窄",refused:"小满答不了:按 linkai-ask 技能「兜底」读当前租户数据字典、写只读 SQL,再执行 linkai ask --question <同一问题> --sql <文件> --fallback <本次 askId>;字典里也没有相关数据就如实告诉用户",failed:"小满出错:按 linkai-ask 技能「兜底」读当前租户数据字典、写只读 SQL,再执行 linkai ask --question <同一问题> --sql <文件> --fallback <本次 askId>",rejected:"按 message 告诉用户原因(翻页取数、同类问题反复换条件被暂停、排队已满等);不要换个问法或改用兜底绕过"},he={answered:"回答用户:给出数字,写明「由 AI 按数据字典口径查询」和口径(用到的视图 / 字段见 caliber)、时间范围、「数据截至」(dataAsOf);结果被截断时说明只看了部分行",empty:"告诉用户「没有查到符合条件的数据」,不要说成 0;可以确认时间范围或条件是否太窄",rejected:"按 message 修改(换用可用视图、去掉分页偏移、改为汇总等);不要换个写法绕过安全规则",estimate_rejected:"预计扫描量过大:加上时间范围或筛选条件,或改为汇总后再查",failed:"按 message 里的数据库报错改 SQL(用 kb-object 核对字段名)后再查",timeout:"查询超时:缩小时间范围、加筛选条件或改为汇总后再查"},ne=new Set(["refused","failed"]),$e=d({...l,name:"ask",access:"read",description:"问数:先把问题交给领谋服务端(与小满问数同一套)查询;小满答不了时,带 --sql 和 --fallback 由 AI 按数据字典写只读 SQL 兜底。按当前用户的数据权限执行,有频率和行数限制",args:[n("question","用户的问题(一次一个问题;有歧义先和用户确认,再把确认后的完整问题传入)"),n("keywords","从问题里提炼的关键词,逗号分隔,如「采购数量,入库数量,今天」(用于统计)",!1),n("source","指定数据源 id(可选,仅问小满时;不传由服务端按问题自动选)",!1),n("sql","兜底:AI 写的 SQL 文件,1~3 个逗号分隔(必须同时给 --fallback)",!1),n("fallback","兜底:小满没答出来的那次 askId(refused / failed)",!1),n("template","兜底:使用的指标模板编号(如 DP-PUR-002),没用不填",!1),{name:"rows",type:"int",default:30,help:"每个结果最多返回几行给 AI(1~200);更多的写到 --output 目录的文件里"},n("output","完整结果保存目录(默认 ~/.linkai/artifacts/linkai/ask)",!1)],func:r(async e=>{const t=String(e.question||"").trim();if(!t)throw new h("--question 必填:传用户的问题");if(t.length>2e3)throw new h("--question 最长 2000 字");const s=String(e.keywords||"").split(/[,,、]/).map(m=>m.trim()).filter(Boolean).slice(0,20),o=Math.min(200,Math.max(1,Number(e.rows)||30)),c=await a.ensureSession(),i=f.currentCaller(),u=f.currentFlow()?.id??null,S={name:i.agent,version:i.toolVersion,model:i.model,sessionId:i.toolSessionId,flowId:u,runId:f.currentRunId(),cliVersion:i.cliVersion},p=P(e.output,"ask"),g=r((m,B)=>{const q=m.rows||[];let j=null;return q.length>o&&(y.mkdirSync(p,{recursive:!0}),j=a.writeJsonFile(w.join(p,`${m.askId}.json`),{question:t,columns:m.columns,rows:q})),{askId:m.askId??null,status:m.status??null,message:m.message??null,columns:m.columns||[],rows:q.slice(0,o),rowCount:m.rowCount??0,rowLimit:m.rowLimit??null,truncated:!!m.truncated,...j?{file:j,shownRows:o}:{},dataAsOf:m.dataAsOf??null,caliber:m.explain??null,cached:!!m.cached,next:B[m.status]??null}},"shape"),E=r(m=>{if(m?.rateLimited)throw new k("RATE_LIMITED",m.message||"问数超过频率或数据量限制",`${m.retryAfterSeconds??60} 秒后再试,告诉用户原因;不要循环重试、换账号或拆分问题绕过。需要大量数据请改为汇总`,1)},"rateLimited");if(e.sql||e.fallback){if(!e.sql||!e.fallback)throw new h("兜底需要同时给 --sql(AI 写的 SQL 文件)和 --fallback(小满没答出来的那次 askId)");const m=a.xiaomanAttempt(e.fallback);if(!m||!ne.has(m.status))throw new k("ASK_XIAOMAN_FIRST",m?`这次小满已经回答(${m.status}),不需要兜底`:"没有找到这次小满问数记录(或已超过 2 小时)",'先执行 linkai ask --question "<问题>"(不带 --sql)问小满;只有返回 refused / failed 时,才带上它的 askId 兜底',1);const B=String(e.sql).split(/[,,]/).map(I=>I.trim()).filter(Boolean);if(!B.length||B.length>3)throw new h("--sql 需要 1~3 个 SQL 文件,逗号分隔");const q=[];for(const[I,z]of B.entries()){const D=a.readInputFile(z);if(a.hasOffset(D))throw new k("ASK_REJECTED",`第 ${I+1} 条 SQL 带分页偏移(OFFSET / LIMIT m,n):问数不支持翻页`,"改为汇总,或加时间范围 / 筛选条件缩小范围;需要完整明细请联系管理员导出",1);q.push(await a.localCheckSql(D))}await _({enforce:!0});for(const[I,z]of q.entries())try{await a.checkSql(z)}catch(D){throw D instanceof a.LoginRequiredError||D instanceof a.NetworkError?D:new k("SQL_INVALID",`第 ${I+1} 条 SQL 没有通过数据集运行校验:${String(D.message).slice(0,300)}`,"按报错核对视图与字段(kb-object),改好后再执行;不要绕过",1)}const j=await a.ctx(),F=await a.askQuery({question:t,dataSourceId:j.dataSourceId,queries:q.map(I=>({sql:I})),keywords:s,templateId:e.template||null,knowledgeRefs:[`xiaoman-ask:${e.fallback}`,...x.recentRefs({tenantId:c.tenantId,flowId:u})].slice(0,50),agent:S}),X=F?.results||[];return f.emit("ask",{requestId:F?.requestId??null,mode:"fallback",fallbackOf:e.fallback,queries:q.length,rateLimited:!!F?.rateLimited,results:X.map(I=>({askId:I.askId,status:I.status,rows:I.rowCount,cached:I.cached}))}),E(F),{mode:"fallback",fallbackOf:e.fallback,requestId:F.requestId,results:X.map(I=>g(I,he)),quota:F.quota??null}}const L=await a.askQuestion({question:t,keywords:s,dataSourceId:e.source||null,agent:S}),b=L?.results?.[0]||{};return f.emit("ask",{requestId:L?.requestId??null,mode:"question",rateLimited:!!L?.rateLimited,results:[{askId:b.askId??null,status:b.status??null,rows:b.rowCount??0,cached:!!b.cached,ms:b.elapsedMs??null}]}),E(L),b.askId&&a.recordXiaomanAttempt({askId:b.askId,status:b.status,question:t}),{mode:"question",...g(b,be),dataSource:b.dataSourceName??null,sql:b.sql??null,...ne.has(b.status)?{fallback:{allowed:!0,askId:b.askId}}:{},quota:L?.quota??null}},"func")}),_e=d({...l,name:"sql-check",access:"read",defaultFormat:"table",description:"校验数据集 SQL(只读、可用表)并列出字段;多个文件用逗号分隔一次校验(省去多次启动)",args:[n("file","SQL 文件路径;多个用逗号分隔,如 sql/a.sql,sql/b.sql")],func:r(async e=>{const t=String(e.file).split(/[,,]/).map(i=>i.trim()).filter(Boolean),s=r(i=>i.map(u=>({sort:u.sort_num,field:u.field_name,type:u.field_type,dbType:u.type_name})),"fields"),o=r(async()=>{try{await _({enforce:!1})}catch(i){if(i instanceof a.LoginRequiredError)throw i}},"dictNotice");if(t.length===1){ye();const i=a.readInputFile(t[0]);return a.precheckSql(i),await o(),s(await a.checkSql(i))}await o();const c=[];for(const i of t)try{c.push({file:i,ok:!0,fields:s(await a.checkSql(a.readInputFile(i)))})}catch(u){if(u instanceof a.LoginRequiredError)throw u;c.push({file:i,ok:!1,error:String(u.message).slice(0,500)})}return{total:t.length,failed:c.filter(i=>!i.ok).length,results:c}},"func")});function ye(){try{const e=w.join(le.homedir(),".linkai","oplog","hints.json");let t={};try{t=JSON.parse(y.readFileSync(e,"utf8"))||{}}catch{}const s=Date.now(),o=[...(t.sqlCheck||[]).filter(c=>s-c<5*6e4),s];t.sqlCheck=o.slice(-10),o.length>=3&&s-(t.sqlCheckNoticeAt||0)>10*6e4&&(t.sqlCheckNoticeAt=s,f.addNotice("最近几分钟在逐个校验 SQL:可以一次校验多个文件,如 linkai sql-check --file sql/a.sql,sql/b.sql,sql/c.sql(每条命令都有启动开销,逐个跑更慢),结果按文件分别列出。","sql-check-batch")),y.mkdirSync(w.dirname(e),{recursive:!0}),y.writeFileSync(e,JSON.stringify(t),{mode:384})}catch{}}r(ye,"nudgeBatchSqlCheck");const Re=d({...l,name:"fmt",access:"read",defaultFormat:"plain",description:"把 SQL 格式化为易读的多行文本",args:[n("file","SQL 文件路径")],func:r(async e=>a.formatSql(a.readInputFile(e.file)),"func")}),Be=d({...l,name:"ds-list",access:"read",defaultFormat:"table",description:"列出当前租户的数据集",args:[n("search","按名称关键字过滤(可选)",!1)],func:r(async e=>M(await a.dsList(),e.search),"func")}),je=d({...l,name:"ds-tables",access:"read",defaultFormat:"table",description:"列出数据集 SQL 可以使用的表(视图)",args:[n("search","按表名关键字过滤(可选)",!1)],func:r(async e=>[...await a.allowedTables()].sort().filter(t=>!e.search||t.includes(String(e.search).toLowerCase())).map(t=>({table:t})),"func")}),ve=d({...l,name:"ds-fields",access:"read",defaultFormat:"table",description:"列出数据集字段(列名就是卡片引用的映射键)",args:[n("ds","数据集的精确 id")],func:r(async e=>(await a.dsFields(e.ds)).map(t=>({sort:t.sortNum,column:t.tableFieldComment,type:t.tableFieldType,argumentType:t.argumentType})),"func")}),Pe=d({...l,name:"ds-sql",access:"read",description:"把数据集当前保存的 SQL 读出到文件",args:[n("ds","数据集的精确 id"),n("output","输出 SQL 路径(默认 ~/.linkai/artifacts/linkai/sql/<dsId>.sql)",!1)],func:r(async e=>{const t=await a.dsDetail(e.ds),s=a.writeJsonFile(P(e.output,"sql",`${e.ds}.sql`),t.sql||"");return{name:t.name,lines:(t.sql||"").split(`
2
+ `).length,output:s}},"func")}),Qe=d({...l,name:"ds-refs",access:"read",defaultFormat:"table",description:"列出引用该数据集的卡片",args:[n("ds","数据集的精确 id")],func:r(async e=>a.dsRefs(e.ds),"func")}),Ke=d({...l,name:"ds-match",access:"read",description:"复用查重:在当前租户里找与这段 SQL 完全一样或相似的数据集(建数据集前先查,不含 zz- 试搭对象)",args:[n("file","SQL 文件路径"),{name:"top",type:"int",default:3,help:"相似候选最多几个"}],func:r(async e=>{const t=v.matchDataset(a.readInputFile(e.file),await v.datasetIndex(),{top:Number(e.top)||3});return{...t,next:t.identical.length?`有完全一样的数据集,直接复用 ${t.identical[0].dsId}(build spec 的 datasets 写 id,或 spec 加 reuse: auto)`:t.similar.length?"有相似数据集:比较 columns 与口径后决定复用、在原数据集上调整(先 ds-refs 看影响),还是新建":"没有可复用的数据集,可以新建"}},"func")}),Ue=d({...l,name:"ds-create",access:"write",description:"新建 SQL 数据集(先做只读与可用表校验,并格式化 SQL);当前租户已有完全一样的数据集时拒绝,除非 --allow-duplicate",args:[n("name","数据集名称(租户内唯一;试验用 zz- 前缀)"),n("file","SQL 文件路径"),{name:"allow-duplicate",type:"boolean",default:!1,help:"已有完全一样的数据集仍要新建(需说明理由,如要独立演进口径)"}],func:r(async e=>{const t=a.readInputFile(e.file);a.precheckSql(t),await _({enforce:!0});let s=null;try{s=v.matchDataset(t,await v.datasetIndex())}catch(i){if(i instanceof a.LoginRequiredError)throw i}if(s?.identical.length&&!e["allow-duplicate"])throw new k("DUPLICATE_DATASET",`当前租户已有完全一样的数据集:${s.identical.map(i=>`「${i.name}」(${i.dsId})`).join("、")}`,"直接复用该数据集(build spec 的 datasets 写 id,或 spec 加 reuse: auto);确需独立一份才加 --allow-duplicate",1);const o=await a.dsSave({name:e.name,sql:t}),c=await a.dsFields(o);return{dsId:o,name:e.name,fields:c.map(i=>i.tableFieldComment),...s?.similar.length?{similar:s.similar,next:"已有相似数据集:确认口径确实不同;如可合并,后续改为复用"}:{}}},"func")}),Me=d({...l,name:"ds-update",access:"write",description:"原地更新数据集(id 不变,卡片映射不断)",args:[n("ds","数据集的精确 id"),n("name","数据集名称"),n("file","SQL 文件路径")],func:r(async e=>{const t=a.readInputFile(e.file);a.precheckSql(t);const s=await a.dsDetail(e.ds);if(s?.name===e.name&&s?.sql&&v.sameSql(t,s.sql)){const i=await a.dsFields(e.ds);return{dsId:e.ds,sameId:!0,unchanged:!0,fields:i.map(u=>u.tableFieldComment),next:"SQL 和名称与线上一致,未重复保存;不要再次执行同样的更新"}}await _({enforce:!0});const o=await a.dsSave({id:e.ds,name:e.name,sql:t,current:s}),c=await a.dsFields(o);return{dsId:o,sameId:o===e.ds,fields:c.map(i=>i.tableFieldComment)}},"func")}),Je=d({...l,name:"card-list",access:"read",defaultFormat:"table",description:"列出当前租户的卡片",args:[n("search","按名称关键字过滤(可选)",!1)],func:r(async e=>M(await a.cardList(),e.search),"func")}),ze=d({...l,name:"card-get",access:"read",description:"把卡片完整配置保存为 JSON 文件(修改前的基线,或 card-save 的输入)",args:[n("card","卡片的精确 id"),n("output","输出 JSON 路径(默认 ~/.linkai/artifacts/linkai/cards/<cardId>.json)",!1)],func:r(async e=>{const t=await a.cardDetail(e.card),s=a.writeJsonFile(P(e.output,"cards",`${e.card}.json`),t);return{name:t.name,fields:(t.indexBlockDataSettingDtos||[]).length,output:s}},"func")}),We=d({...l,name:"card-save",access:"write",description:"保存完整的卡片配置 JSON(唯一能让配置生效的保存方式)",args:[n("file","修改后的卡片 JSON 文件路径")],func:r(async e=>{const t=A(e.file);if(!t.id)throw new Error("Card payload must contain id");const s=typeof t.chartSetting=="string"?JSON.parse(t.chartSetting||"{}"):t.chartSetting||{};t.chartSetting=JSON.stringify(s),t.chartType=t.chartType||s.id,t.chartParentType=t.chartParentType||s.parentId,await a.post("/index-block/update-new",t),String(t.chartType||s.id)==="agtable"&&s.config?.fitTableWidth!==!0&&f.addNotice("这张 AG 表格没有开启自适应宽度(chartSetting.config.fitTableWidth)。按 BI 操作规则应设为 true,列宽 agColumnWidths 只作为比例(如等分统一 50);用户明确要求固定宽度时除外。","ag-fit-width");const o=await a.cardDetail(t.id);return{cardId:t.id,name:o.name,fields:(o.indexBlockDataSettingDtos||[]).length}},"func")}),Ge=d({...l,name:"fill-write",access:"write",description:"写自由填报数据:一条记录一个元素、字段一次带齐(默认只校验并预览,加 --execute 才写入)",args:[n("card","填报卡片的精确 id(usedIndexBlockId)"),n("file",'记录 JSON 数组文件:[{"字段名":"值",…,"_id":"已有记录ID(可选)"}]'),n("key","填报块:fillfieldid(第一个块)、fillfieldid1(第二个块)…",!1),{name:"batch",type:"int",default:100,help:"每次请求的记录数(1-500)"},{name:"execute",type:"boolean",default:!1,help:"确认后真正写入"}],func:r(async e=>{const t=Number(e.batch||100);if(!Number.isInteger(t)||t<1||t>500)throw new h("batch 必须是 1-500");await a.assertCardInTenant(e.card);const s=await a.cardDetail(e.card);if(s?.isFreeFill===!1)throw new Error("该卡片不是自由填报卡片;fill-write 只支持自由填报");const o=ee.fillBlockOf(s,e.key||"fillfieldid"),c=ee.buildFillValues(o,A(e.file)),i=[];for(let p=0;p<c.length;p+=t)i.push(c.slice(p,p+t));const u={cardId:e.card,name:s.name,key:o.key,fields:o.fields,uniqueFields:o.uniqueFields,records:c.length,updates:c.filter(p=>p.value).length,batches:i.length,sample:c.slice(0,2)};if(!e.execute)return{...u,executed:!1,next:"核对字段与记录数后,用户确认再加 --execute"};const S=[];for(const[p,g]of i.entries())try{await a.post("/index-block/new-fill-field",{usedIndexBlockId:e.card,fillValues:g}),S.push(g.length)}catch(E){throw new Error(`第 ${p+1}/${i.length} 批写入失败(已成功 ${S.length} 批、${S.reduce((L,b)=>L+b,0)} 条):${E.message}。先回读填报表按依据字段核对,再只补未成功的部分,不要整批重跑`)}return{...u,executed:!0,written:S.reduce((p,g)=>p+g,0),next:"回读填报表本身:行数、依据字段无空值无重复、抽查整行字段完整;再核对看板"}},"func")}),Ve=d({...l,name:"card-chart",access:"write",description:"按图表类型新建或覆盖图表卡片(指定维度、数值、系列列)",args:[n("ds","数据集的精确 id"),n("name","卡片名称"),n("kind","图表类型,如 column/stacked(linkai kinds 查看全部)"),n("dim","维度列名,多个用逗号分隔",!1),n("meas","数值列名,多个用逗号分隔"),n("series","系列列名(可选)",!1),n("card","要覆盖的已有卡片 id(可选)",!1)],func:r(async e=>a.cardChart({dsId:e.ds,name:e.name,kind:e.kind,dim:e.dim,meas:e.meas,series:e.series,id:e.card}),"func")}),He=d({...l,name:"card-meter",access:"write",description:"新建或覆盖单数值指标卡(meter/list)",args:[n("ds","数据集的精确 id"),n("name","卡片名称"),n("col","数值列名"),n("subtitle","副标题",!1),n("color","数值颜色(十六进制,如 #E8611C)",!1),n("card","要覆盖的已有卡片 id(可选)",!1)],func:r(async e=>a.cardMeter({dsId:e.ds,name:e.name,col:e.col,subtitle:e.subtitle,color:e.color||void 0,id:e.card}),"func")}),Ye=d({...l,name:"card-table",access:"write",description:"按 spec JSON 新建或覆盖 AG 明细表(见 references/card.md)",args:[n("ds","数据集的精确 id"),n("spec","spec JSON 文件路径"),n("card","要覆盖的已有卡片 id(可选)",!1)],func:r(async e=>a.cardTable({dsId:e.ds,spec:A(e.spec),id:e.card}),"func")}),Xe=d({...l,name:"card-gantt",access:"write",description:"按 spec JSON 新建或覆盖甘特图卡片(见 references/gantt.md)",args:[n("ds","数据集的精确 id"),n("spec","spec JSON 文件路径"),n("card","要覆盖的已有卡片 id(可选)",!1)],func:r(async e=>a.cardGantt({dsId:e.ds,spec:A(e.spec),id:e.card}),"func")}),Ze=d({...l,name:"card-sort",access:"write",description:'设置卡片持久排序("列:asc,列:desc"、.json 文件或 clear),并回读确认排序生效',args:[n("card","卡片的精确 id"),n("spec",'"waxis3:desc,ID:asc" | sort.json | clear')],func:r(async e=>{const t=/\.json$/i.test(String(e.spec))?A(e.spec):e.spec;return a.cardSort(e.card,Array.isArray(t)?t:t.sortField||t)},"func")}),et=d({...l,name:"query",access:"read",description:"按页面方式查询卡片数据;返回状态码、报错信息、行数和实际执行的 SQL",args:[n("card","卡片的精确 id"),{name:"page-size",type:"int",default:20,help:"取数行数"},n("filters","筛选条件 JSON 文件路径",!1),n("output","数据行保存路径(不在屏幕上输出数据)",!1)],func:r(async e=>{const t=await a.queryCard({cardId:e.card,pageSize:e["page-size"],filters:e.filters?A(e.filters):[]});return e.output&&a.writeJsonFile(e.output,t.data),delete t.data,t},"func")}),tt=d({...l,name:"plan-list",access:"read",defaultFormat:"table",description:"列出分析方案(看板)",args:[n("search","按名称关键字过滤(可选)",!1)],func:r(async e=>M(await a.planList(),e.search),"func")}),at=d({...l,name:"plan-get",access:"read",description:"把方案详情保存为 JSON 文件(含租户模板筛选器)",args:[n("plan","方案的精确 id"),n("output","输出 JSON 路径(默认 ~/.linkai/artifacts/linkai/plans/<planId>.json)",!1),{name:"personal",type:"boolean",default:!1,help:"读取自己的个人筛选器副本"}],func:r(async e=>{const t=await a.planDetail(e.plan,!e.personal),s=a.writeJsonFile(P(e.output,"plans",`${e.plan}.json`),t);return{planId:e.plan,cards:(t.pageCards||[]).map(o=>({pageCardId:o.id,name:o.indexBlock?.name})),output:s}},"func")}),nt=d({...l,name:"plan-create",access:"write",description:"新建空的分析方案(看板)",args:[n("name","方案名称")],func:r(async e=>{const t=await a.post("/page-setting/new-create-empty-plan",{name:e.name,isPrivate:!1,pageType:0});return f.addNotice(`手工搭建方案:完成后必须用 linkai plan-knowledge --plan ${t} --mode create --file <知识文件> 交付方案知识库(铁律 2)。新建看板优先用 linkai build(自动生成知识库并验收)。`,"plan-knowledge"),{planId:t,name:e.name}},"func")}),st=d({...l,name:"plan-add",access:"write",description:"把卡片放到方案的指定位置;返回 pageCardId(绑定筛选器时使用)",args:[n("plan","方案的精确 id"),n("card","卡片的精确 id"),{name:"x",type:"int",default:0,help:"横向位置(格,0–23)"},{name:"y",type:"int",default:0,help:"纵向位置(格)"},{name:"w",type:"int",default:24,help:"宽度(格,满宽 24)"},{name:"h",type:"int",default:20,help:"高度(格)"}],func:r(async e=>a.planAdd({planId:e.plan,cardId:e.card,x:e.x,y:e.y,w:e.w,h:e.h}),"func")}),ot=d({...l,name:"plan-filter",access:"write",description:"保存方案筛选器为租户模板(全量覆盖),并重置自己的个人副本",args:[n("plan","方案的精确 id"),n("file","筛选器请求体 JSON 文件路径")],func:r(async e=>a.planFilter(e.plan,A(e.file)),"func")}),rt=d({...l,name:"report-list",access:"read",defaultFormat:"table",description:"列出数据报表(每个报表对应一张卡片)",args:[n("search","按名称关键字过滤(可选)",!1)],func:r(async e=>M(await a.reportList(),e.search),"func")}),it=d({...l,name:"report-card",access:"read",description:"查询卡片对应报表的 userPageCardId(重置报表筛选器时使用)",args:[n("card","卡片的精确 id")],func:r(async e=>{const t=await a.reportDetail(e.card);return{cardId:e.card,userPageCardId:t.id}},"func")}),ct=d({...l,name:"report-publish",access:"write",description:"把卡片发布为数据报表",args:[n("card","卡片的精确 id"),n("name","报表名称")],func:r(async e=>{await a.post("/index-block/operate-report",{indexBlockId:e.card,name:e.name,tag:"",isPrivate:!1});const t=(await a.reportList()).filter(s=>s.cardId===e.card);return{cardId:e.card,published:t.length===1,url:`https://bi.linkmore.com/view/table?indexBlockId=${e.card}&status=view`}},"func")}),lt=d({...l,name:"reset-filter",access:"write",description:"丢弃自己的个人筛选器副本,按模板重建(方案或报表卡片)",args:[n("plan","方案 id(方案筛选器)",!1),n("page-card","报表 userPageCardId 或方案 pageCardId(卡片筛选器)",!1)],func:r(async e=>{if(!!e.plan==!!e["page-card"])throw new Error("Pass exactly one of --plan or --page-card");const t=e.plan?{userPageSettingId:e.plan,filterSourceType:4}:{userPageCardId:e["page-card"],filterSourceType:2};return{filters:(await a.post("/page-setting/reset-user-filter",t)||[]).map(o=>o.labelName)}},"func")}),V=w.resolve(w.dirname(de(import.meta.url)),"..","..");try{a.setClientVersion(JSON.parse(y.readFileSync(w.join(V,"package.json"),"utf8")).version)}catch{}const Q={knowledge:w.join(V,"knowledge"),templates:w.join(V,"templates")};function J(e,t=e){return y.existsSync(e)?y.readdirSync(e,{withFileTypes:!0}).flatMap(s=>{const o=w.join(e,s.name);return s.isDirectory()?J(o,t):s.name.startsWith(".")?[]:[w.relative(t,o)]}):[]}r(J,"walk");function H(e,t){const s=Q[e],o=w.resolve(s,t);if(o!==s&&!o.startsWith(s+w.sep))throw new h("路径必须在 knowledge/ 或 templates/ 之内");if(!y.existsSync(o)||y.statSync(o).isDirectory())throw new h(`文件不存在:${e}/${t}`,"执行 linkai kb 查看可用文件");return y.readFileSync(o,"utf8")}r(H,"readLocal");function Y(e){return new k("KB_UNAVAILABLE",`知识库服务暂不可用:${e}`,"稍后重试;持续出现请联系领谋",1)}r(Y,"kbUnavailable");function N(e){if(!(e instanceof a.AiKbError))return e;const t={400:"AI_KB_BAD_REQUEST",403:"AI_KB_FORBIDDEN",404:"AI_KB_NOT_FOUND",409:"AI_KB_CONFLICT",503:"AI_KB_UNAVAILABLE"},s={404:"确认 source、对象或文档属于当前租户;数据字典不会回退本地内容",409:"知识元数据未就绪时先联系知识库维护方;文档持续变化时稍后重试",503:"稍后重试;数据字典没有本地兜底,持续出现请联系领谋"};return new k(t[e.code]||"AI_KB_ERROR",e.message,s[e.code],1)}r(N,"aiKbFailure");function O(e,t,s,o,c){const i=e==null||e===""?t:Number(e);if(!Number.isInteger(i)||i<o||i>c)throw new h(`${s} 必须是 ${o}~${c} 的整数`);return i}r(O,"intOption");async function K(e){const t=await a.ctx();if(String(e).toLowerCase()!==String(t.dataSourceId).toLowerCase())throw new k("AI_KB_SOURCE_MISMATCH",`数据字典 source ${e} 与当前数据集 source ${t.dataSourceId} 不一致`,"不要跨 source 使用元数据;先确认服务端已为当前数据源完成探库,或确认 CLI 的数据源配置",1)}r(K,"requireDatasetSource");function se(){return J(w.join(Q.knowledge,"report-builder")).map(e=>({file:`knowledge/report-builder/${e}`,title:"",summary:"",library:"bi-builder",scope:"public",source:"bundled-fallback"}))}r(se,"localBiDocs");function ke(e){return{file:`knowledge/${e.canonicalPath}`,title:"",library:e.library,scope:e.scope,source:"bundled-fallback",content:H("knowledge",e.canonicalPath)}}r(ke,"localBiDoc");function Se(e,t){const s=Array.isArray(t)?t:null,o=s?"bi-builder":t?.library||(String(t?.file||"").startsWith("templates/")?"templates":String(t?.file||"").includes("browser/")?"cli-docs":"bi-builder");return f.emit("kb.read",s?{library:o,op:e,docs:s.length,sources:[...new Set(s.map(c=>c?.source).filter(Boolean))]}:{library:o,op:e,file:t?.file??null,source:t?.source??null,chars:typeof t?.content=="string"?t.content.length:void 0}),t}r(Se,"logKb");async function $(e,t,s,o={}){if(f.emit("kb.read",{library:"data-dictionary",op:e,source:t.source??null,...o,...e!=="sources"?{cached:a.kbCacheHit()}:{}}),e!=="sources"&&t.source){const c=a.readSession()?.tenantId,i=f.currentFlow()?.id??null;let u=null;try{u=(await a.dictSourcesCached()).find(S=>String(S.source).toLowerCase()===String(t.source).toLowerCase())?.revision||null}catch{}x.recordRead({tenantId:c,source:t.source,flowId:i,op:e,revision:u}),e==="doc"&&t.doc&&x.recordRef({tenantId:c,flowId:i,ref:`doc:${t.doc}${o.title?` ${o.title}`:""}`}),e==="object"&&o.object&&x.recordRef({tenantId:c,flowId:i,ref:`object:${o.object}`})}return s}r($,"logDict");async function _({enforce:e}){const t=await a.ensureSession(),s=await a.ctx(),o=await x.dictionaryStatus({tenantId:t.tenantId,source:s.dataSourceId,flowId:f.currentFlow()?.id??null,fetchSources:r(()=>a.dictSourcesCached(),"fetchSources"),fetchDocs:r(async()=>(await a.dictDocsHead(s.dataSourceId)).items,"fetchDocs")});if(a.logGuard("dictionary-fresh",o.ok,{tenantId:t.tenantId,source:s.dataSourceId,reason:o.reason??o.skipped??null,readAt:o.readAt??null,dictUpdatedAt:o.dictUpdatedAt??null,enforce:e}),o.skipped&&f.addNotice(`数据字典:${o.skipped}。`,"dictionary-skipped"),!o.ok){if(e)throw new k("DICTIONARY_REQUIRED",o.reason,x.readHint(s.dataSourceId),1);f.addNotice(`数据字典:${o.reason}。正式写入(ds-create / ds-update / build --execute)前会被拒绝。${x.readHint(s.dataSourceId)}`,"dictionary-stale")}return o}r(_,"checkDictionary");const oe=r((e,t,s)=>Array.isArray(e)?e.slice(0,t).map(s):[],"top"),dt=d({...l,name:"kb",access:"read",description:"读取文件型 BI 搭建知识库(服务器优先、本地兜底);数据字典请使用 kb-sources / kb-search 等结构化命令",args:[n("file","文件路径,如 knowledge/report-builder/build-spec.md 或 templates/report-spec/report.example.yaml",!1),n("search","按关键字过滤目录",!1),n("library","文件型知识库:bi-builder;data-dictionary 请使用 kb-sources 等结构化命令;另预留 integration-flow | user",!1)],func:r(async e=>Se(e.file?"doc":"catalog",await(async()=>{const t=String(e.file||"").replace(/^\.?\//,"");if(t.startsWith("templates/"))return{file:t,source:"bundled",content:H("templates",t.slice(10))};if(t==="knowledge/browser/README.md"||t==="browser/README.md")return{file:"knowledge/browser/README.md",source:"bundled-cli",content:H("knowledge","browser/README.md")};const s=e.search?String(e.search):"",o=e.library?String(e.library):"";if(o&&!a.KB_LIBRARIES[o])throw new h(`未知知识库:${o}`);if(o==="data-dictionary"||/^(?:knowledge\/)?data-dictionary(?:\/|$)/.test(t))throw new k("KB_STRUCTURED_API","数据字典不再按 Markdown 文件路径读取","先执行 linkai kb-sources,再使用 kb-search / kb-catalog / kb-object / kb-docs / kb-doc",1);if(o&&!a.KB_LIBRARIES[o].enabled)throw new k("KB_NOT_IMPLEMENTED",`知识库 ${o} 已预留,当前版本尚未启用`,void 0,1);if(!t){const i=o?[o]:["bi-builder"],u=[];for(const p of i)try{const g=await a.kbCatalog(p);p==="bi-builder"&&!(g.docs||[]).length?u.push(...se()):u.push(...(g.docs||[]).map(E=>{const L=a.KB_LIBRARIES[p].pathPrefix,b=a.kbRequest(E.path,p).path;return{file:`knowledge/${L}/${b}`,title:E.title||"",summary:E.summary||"",library:p,scope:a.KB_LIBRARIES[p].scope,source:"server"}}))}catch(g){if(p==="bi-builder"&&g instanceof a.KbUnavailableError)u.push(...se());else throw g instanceof a.KbUnavailableError?Y(p):g}const S=!o||o==="bi-builder"?J(Q.templates).map(p=>({file:`templates/${p}`,title:"报表模版",summary:"",source:"bundled"})):[];return[...u,...S].filter(p=>!s||p.file.includes(s)||p.title.includes(s))}const c=a.kbRequest(t,o||void 0);try{const i=await a.kbDoc(t,o||void 0);return{file:`knowledge/${i.path}`,title:i.title||"",library:i.library,scope:i.scope,source:"server",content:i.content||""}}catch(i){if(c.library==="bi-builder"&&(i instanceof a.KbUnavailableError||i instanceof a.KbDocumentNotFoundError)){if(!y.existsSync(w.join(Q.knowledge,"report-builder")))throw Y(c.library);return ke(c)}throw i instanceof a.KbUnavailableError?Y(c.library):i}})()),"func")}),ut=d({...l,name:"kb-sources",access:"read",defaultFormat:"table",description:"列出当前租户可读的数据字典数据源,并标记当前数据集所用 source;后续命令必须显式传该 ID",args:[],func:r(async()=>{try{const[e,t]=await Promise.all([a.dictSourcesCached(),a.ctx()]),s=e.map(c=>({...c,selectedForDatasets:String(c.source).toLowerCase()===String(t.dataSourceId).toLowerCase()})),o=s.some(c=>c.selectedForDatasets);return $("sources",{},null,{sources:s.map(c=>({source:c.source,name:c.name})),aligned:o}),{datasetSourceId:t.dataSourceId,aligned:o,sources:s,...o?{}:{warning:"数据字典没有返回当前数据集 source;不要跨 source 使用元数据,请先确认服务端探库或数据源配置"}}}catch(e){throw N(e)}},"func")}),pt=d({...l,name:"kb-catalog",access:"read",description:"分页列出指定 source 的可用视图/对象元数据",args:[n("source","数据源 ID(来自 kb-sources,必须与后续数据集 source 一致)"),n("search","表名、中文名或说明关键字",!1),n("page","页码,默认 1",!1),n("page-size","每页 1~100,默认 30",!1)],func:r(async e=>{try{await K(e.source);const t={source:e.source,keyword:e.search,pageIndex:O(e.page,1,"page",1,2147483647),pageSize:O(e["page-size"],30,"page-size",1,100)},s=await a.kbCached(e.source,`catalog|${t.keyword??""}|${t.pageIndex}|${t.pageSize}`,()=>a.aiKbCatalog(t));return $("catalog",e,s,{keyword:e.search??null,total:s?.totalCount??null})}catch(t){throw N(t)}},"func")}),ft=d({...l,name:"kb-object",access:"read",description:"读取指定 source 中一个可见对象的字段与关联关系",args:[n("source","数据源 ID(来自 kb-sources)"),n("db","数据库/schema 名"),n("object","对象/视图名")],func:r(async e=>{try{await K(e.source);const t=await a.kbCached(e.source,`object|${String(e.db).toLowerCase()}.${String(e.object).toLowerCase()}`,()=>a.aiKbObject({source:e.source,db:e.db,object:e.object}));return $("object",e,t,{object:`${e.db}.${e.object}`})}catch(t){throw N(t)}},"func")}),mt=d({...l,name:"kb-docs",access:"read",description:"分页列出指定 source 的业务口径、枚举、关系、数据点等知识文档",args:[n("source","数据源 ID(来自 kb-sources)"),n("category","caliber | enum | relation | datapoint | other",!1),n("search","标题或正文关键字",!1),n("page","页码,默认 1",!1),n("page-size","每页 1~100,默认 30",!1)],func:r(async e=>{const t=e.category?String(e.category):void 0;if(t&&!["caliber","enum","relation","datapoint","other"].includes(t))throw new h("category 必须是 caliber、enum、relation、datapoint、other 之一");try{await K(e.source);const s={source:e.source,category:t,keyword:e.search,pageIndex:O(e.page,1,"page",1,2147483647),pageSize:O(e["page-size"],30,"page-size",1,100)},o=await a.kbCached(e.source,`docs|${t??""}|${s.keyword??""}|${s.pageIndex}|${s.pageSize}`,()=>a.aiKbDocs(s));return $("docs",e,o,{category:t??null,keyword:e.search??null,total:o?.totalCount??null})}catch(s){throw N(s)}},"func")}),wt=d({...l,name:"kb-doc",access:"read",description:"一致性读取一篇完整数据字典文档;自动按 nextOffset 分页,版本变化时从头重读",args:[n("source","数据源 ID(来自 kb-sources)"),n("doc","文档 ID(来自 kb-docs 或 kb-search)"),n("page-size","单次正文字符数,1~20000,默认 20000",!1)],func:r(async e=>{try{await K(e.source);const t=await a.kbCached(e.source,`doc|${String(e.doc).toLowerCase()}`,()=>a.aiKbDoc({source:e.source,doc:e.doc,limit:O(e["page-size"],2e4,"page-size",1,2e4)}));return $("doc",e,t,{doc:e.doc,title:t?.title??null,category:t?.category??null,chars:t?.totalChars??null})}catch(t){throw N(t)}},"func")}),bt=d({...l,name:"kb-search",access:"read",description:"按自然语言检索指定 source 的候选视图与业务知识;相关度不是正确率,使用前继续回读对象/文档核对",args:[n("source","数据源 ID(来自 kb-sources)"),n("question","业务问题,最长 2000 字"),n("top-tables","最多返回表数,1~10,默认 5",!1),n("top-docs","最多返回文档数,1~10,默认 3",!1)],func:r(async e=>{try{await K(e.source);const t={source:e.source,question:e.question,topTables:O(e["top-tables"],5,"top-tables",1,10),topDocs:O(e["top-docs"],3,"top-docs",1,10)},s=await a.kbCached(e.source,`search|${t.topTables}|${t.topDocs}|${a.normalizeQuestion(t.question)}`,()=>a.aiKbSearch(t));return $("search",e,s,{question:String(e.question||"").slice(0,200),hitTables:oe(s?.tables,10,o=>`${o.db}.${o.object}`),hitDocs:oe(s?.docs,10,o=>({doc:o.doc,title:o.title??null,category:o.category??null})),emptyReason:s?.emptyReason??null})}catch(t){throw N(t)}},"func")}),ht=W({...l,name:"template",access:"write",description:"复制报表 spec 与示例文件;SQL 示例必须由 AI 按用户需求、租户数据字典和确认口径重写,CLI 不生成业务 SQL",args:[n("output","目标目录(默认 ./linkai-report),需不存在或为空",!1)],func:r(async e=>{const t=w.resolve(String(e.output||"linkai-report"));if(y.existsSync(t)&&y.readdirSync(t).length)throw new h(`目录已存在且不为空:${t}`,"换一个 --output 目录");return y.cpSync(w.join(Q.templates,"report-spec"),t,{recursive:!0}),{output:t,files:J(t),next:`修改 ${w.join(t,"report.example.yaml")} 与 sql/ 下的 SQL,然后 linkai build --spec ${w.join(t,"report.example.yaml")}`}},"func")}),yt=d({...l,name:"build",access:"write",description:"严格按 BI 公共知识库执行 AI 提供的 SQL 与报表 spec(CLI 不生成业务 SQL)。默认只校验并给出执行计划;--execute 才写入;含正式名还需 --confirm",args:[n("spec","报表 spec 文件(.yaml/.json),模版用 linkai template 复制"),{name:"execute",type:"boolean",default:!1,help:"真正写入(默认只校验并输出执行计划)"},{name:"confirm",type:"boolean",default:!1,help:"用户已明确授权使用正式名(非 zz- 前缀)"},{name:"no-style",type:"boolean",default:!1,help:"跳过页面样式步骤(暖色背景、卡位透明)"}],func:r(async e=>{const t=Z.loadSpec(e.spec),s=(t.spec.datasets||[]).some(i=>!i.id&&i.sql),o=r(async()=>{const i=await a.ensureSession(),u=a.spaceOf(i);if(t.spec.tenant&&![u.tenantId,u.tenantName,u.tenantCode].includes(String(t.spec.tenant)))throw new h(`当前登录的租户是「${u.tenantName}」,与 spec.tenant「${t.spec.tenant}」不一致`,"确认目标租户后执行 linkai login --tenant <租户名>,或修改 spec.tenant");s&&await _({enforce:!0})},"beforeWrite"),c=await Z.runBuild(t,{execute:!!e.execute,confirm:!!e.confirm,style:!e["no-style"],beforeWrite:o});if(!e.execute&&s&&c.ok!==!1)try{c.dictionary=await _({enforce:!1})}catch(i){if(i instanceof a.LoginRequiredError)c.dictionary={ok:!1,reason:"未登录"};else throw i}if(c.ok===!1&&(c.stage==="validate"||c.stage==="authorize"))throw new h(`build ${c.stage} failed:
3
3
  - ${c.errors.join(`
4
- - `)}`,c.stage==="authorize"?"把正式名清单给用户确认,得到明确授权后再加 --confirm":"按错误逐条修改 spec 后重新 dry-run");return c},"func")});async function re(e){const t=String(e.mode||"");if(!["create","update"].includes(t))throw new h("必须明确 --mode create 或 --mode update;用户未说明新建还是修改时,先主动询问用户");if(!e.file)throw new h("缺少 --file <方案知识 YAML/JSON>","文件必须描述方案内容、解决的问题,以及每张业务卡片的内容、数据来源、粒度和取数逻辑");const s=G.readKnowledgeFile(e.file);return G.publishPlanKnowledge({planId:e.plan,operation:t,authoring:s.knowledge||s})}r(re,"publishPlanKnowledgeFromFile");const yt=d({...l,name:"plan-knowledge",access:"write",description:"生成、版本化并保存完整方案知识库;服务器不可用时内部留存本地。最终返回方案地址和完整知识内容,不返回 SQL 或上传信息",args:[n("plan","方案的精确 id"),n("mode","create(新方案)或 update(修改原方案)",!1),n("file","AI 编写的方案知识 YAML/JSON 文件",!1)],func:r(async e=>{const t=await re(e);return f.emit("kb.write",{library:"plan-knowledge",op:e.mode||"create",plan:e.plan,version:t?.version??t?.planKnowledge?.version??null}),t},"func")}),St=d({...l,name:"plan-knowledge-get",access:"read",description:"读取方案当前知识库;修改原方案前先读。服务器不可用时读取本地待同步版本,不展示存储信息",args:[n("plan","方案的精确 id")],func:r(async e=>{const t=await G.readCurrentPlanKnowledge(e.plan);return f.emit("kb.read",{library:"plan-knowledge",op:"doc",plan:e.plan,version:t?.version??null,exists:t?.exists??null,chars:typeof t?.content=="string"?t.content.length:null}),t},"func")}),gt=d({...l,name:"deliverable",access:"write",description:"兼容命令:等同 plan-knowledge;需要 --mode 与 --file,不再输出数据集 SQL",args:[n("plan","方案的精确 id"),n("mode","create 或 update",!1),n("file","方案知识 YAML/JSON 文件",!1)],func:re}),It=d({...l,name:"backup",access:"read",description:"修改已有对象前保存带时间戳的备份(卡片/方案 JSON 或数据集 SQL),存到 ~/.linkai/artifacts/linkai/backups/",args:[n("type","card(卡片)| plan(方案)| ds(数据集)"),n("id","对象的精确 id")],func:r(async e=>{const t=r(s=>a.artifactsDir("backups",`${e.type}-${e.id}-${te()}.${s}`),"at");if(e.type==="card")return{type:"card",output:a.writeJsonFile(t("json"),await a.cardDetail(e.id))};if(e.type==="plan")return{type:"plan",output:a.writeJsonFile(t("json"),await a.planDetail(e.id,!0))};if(e.type==="ds")return{type:"ds",output:a.writeJsonFile(t("sql"),(await a.dsDetail(e.id)).sql||"")};throw new Error("type must be card, plan or ds")},"func")}),Ct=d({...l,name:"remove",access:"write",description:"按 folderId 删除数据集/卡片/方案/报表(id 来自各 *-list 命令)。需要用户明确授权",args:[n("folder","精确的 folderId,多个用逗号分隔")],func:r(async e=>{const t=String(e.folder).split(",").map(s=>s.trim()).filter(Boolean);return await a.post("/index-block/delete-folder-bussiness",t),{deleted:t.length,recycleBin:"POST /index-block/get-recycle-station-page-list"}},"func")}),Lt=d({...l,name:"clean",access:"write",description:"按 zz- 前缀清理试验对象,按依赖顺序删除:报表 → 方案 → 卡片 → 数据集",args:[n("prefix","以 zz- 开头的前缀,如 zz-销售看板-")],func:r(async e=>a.cleanPrefix(e.prefix),"func")}),xt=d({...l,name:"api-get",access:"read",description:"调用未封装的读接口(GET,路径可省略 /api/app);先查 references/api-catalog.md。数据源、SQL 类接口不开放",args:[n("path","接口路径,如 /index-block/folders?type=1"),{name:"raw",type:"boolean",default:!1,help:"返回完整响应"}],func:r(async e=>(a.assertApiAllowed(e.path),a.api("GET",e.path,void 0,{raw:!!e.raw})),"func")}),ge=/\/(get-|query|search)[\w-]*|\/[\w-]*-(list|detail|info)(?:[/?]|$)/i,At=d({...l,name:"api",access:"write",accessOf:r(e=>ge.test(String(e.path||""))?"read":"write","accessOf"),description:"调用未封装的写接口(POST/PUT/DELETE,可能新建或修改数据);先查 references/api-catalog.md。数据源、SQL 类接口不开放",args:[n("method","POST、PUT 或 DELETE"),n("path","接口路径"),n("body","请求体 JSON 文件路径",!1),{name:"raw",type:"boolean",default:!1,help:"返回完整响应(code/message/extendData)"}],func:r(async e=>{const t=String(e.method).toUpperCase();if(!["POST","PUT","DELETE"].includes(t))throw new Error("method must be POST, PUT or DELETE; use api-get for GET");return a.assertApiAllowed(e.path),a.api(t,e.path,e.body?A(e.body):t==="POST"?{}:void 0,{raw:!!e.raw})},"func")}),ie="create(新建方案)| modify(修改已有方案)| import(导入/更新数据)| query(只读查询分析)",Et=d({...l,name:"flow-start",access:"write",description:"开始一个工作流:接到业务任务时执行一次,之后的命令自动归入它;完成后用 flow-end 结束",args:[n("goal","这个任务要完成什么(用户原话概括)"),n("type",ie),n("plan","目标方案 id(修改已有方案时)",!1),n("model",'你(AI)当前使用的具体模型与版本,如 "Claude Opus 5.5"、"gpt-5.6";不确定就写你所知道的名称',!1)],func:r(async e=>{const t=f.startFlow({goal:e.goal,type:String(e.type||"").trim(),plan:e.plan||null,model:e.model||null});return{flow:t.id,goal:t.goal,type:t.type,plan:t.plan,model:f.currentModel(),next:"同一会话里的后续命令自动归入此工作流;并行开多个任务时,在命令上加 --flow <id> 指明。完成后 linkai flow-end --status delivered"}},"func")}),qt=d({...l,name:"flow-end",access:"write",description:"结束工作流:delivered(已交付)| abandoned(放弃)| failed(失败);默认结束当前工作流,或用 --flow <id> 指定",args:[n("status","delivered | abandoned | failed"),n("plan","交付的方案 id",!1),n("knowledge-version","方案知识库版本,如 v2",!1),n("note","结果说明或失败原因",!1)],func:r(async e=>{const t=Object.fromEntries(Object.entries({plan:e.plan,knowledgeVersion:e["knowledge-version"],note:e.note}).filter(([,o])=>o!=null&&o!=="")),s=f.endFlow({id:process.env.LINKAI_FLOW||void 0,status:String(e.status||"").trim(),result:t});return{flow:s.id,status:s.status,goal:s.goal,runs:s.runs,durationMinutes:Math.round(((s.endedAt??Date.now())-s.startedAt)/6e3)/10,result:s.result}},"func")}),Ot=d({...l,name:"flow-status",access:"read",description:"查看本机工作流:当前工作流、未完成或中断的工作流(含中断原因与下一步);--all 含已结束的",args:[{name:"all",type:"boolean",default:!1,help:"包括已结束和自动关闭的工作流(最近 7 天)"}],func:r(async e=>{const t=f.currentFlow(),s=(e.all?f.listFlows():f.unfinishedFlows()).slice(0,30).map(o=>({...ae(o),runs:o.runs,startedAt:new Date(o.startedAt).toISOString(),endedAt:o.endedAt?new Date(o.endedAt).toISOString():null,result:o.result}));return{current:t,idleMinutes:f.flowIdleMinutes(),flows:s}},"func")}),R={name:"worker",type:"string",required:!1,help:"执行者名称(也可用全局参数 --worker);同一会话保持不变,如 主控、sql-1、card-2"},T=r(e=>String(e.worker||I.currentWorker()||""),"workerOf"),U=n("board","任务板 id(collab-start 返回,如 b-1a2b3c4d)"),Dt=d({...l,name:"collab-start",access:"write",description:"多会话协同:创建任务板(协调者执行一次),返回任务板 id 交给各执行者",args:[n("goal","这次协同要完成什么"),n("plan","目标方案 id(修改已有方案时)",!1),n("type",`工作流类型:${ie};默认有 --plan 为 modify,否则 create`,!1),R],func:r(async e=>{const t=I.startBoard({goal:e.goal,plan:e.plan,worker:T(e)});return f.startFlow({id:t.id,source:"board",goal:t.goal,type:e.type||(e.plan?"modify":"create"),plan:e.plan||null}),{board:t.id,coordinator:t.coordinator,goal:t.goal,next:"用 collab-task 拆分任务;各执行者写共享对象前先 collab-claim,成果用 collab-note 共享;读命令不用认领"}},"func")}),Tt=d({...l,name:"collab-task",access:"write",description:"多会话协同:新增任务(--title),或更新任务状态(--id --status todo|doing|done|blocked --result)",args:[U,n("title","新任务标题(新增时)",!1),{name:"id",type:"int",required:!1,help:"要更新的任务编号"},n("status","todo | doing | done | blocked",!1),n("result","结果或阻塞原因(文件路径、对象 id、结论)",!1),n("assignee","分配给哪个执行者",!1),n("object","任务涉及的共享对象,逗号分隔,如 card:<id>,ds:<id>",!1),R],func:r(async e=>e.id?I.updateTask({board:e.board,worker:T(e),id:e.id,status:e.status,result:e.result,assignee:e.assignee}):I.addTask({board:e.board,worker:T(e),title:e.title,assignee:e.assignee,objects:e.object}),"func")}),Ft=d({...l,name:"collab-claim",access:"write",description:"多会话协同:写共享对象前认领(全部成功或全部不认领;重复认领即续期);被别人认领时返回 COLLAB_CONFLICT",args:[U,n("object","对象,逗号分隔:card:<id> ds:<id> plan:<id> knowledge:<planId> spec:<文件> report:<id> filter:<planId>"),{name:"ttl",type:"int",default:I.DEFAULT_TTL_SECONDS,help:"认领有效秒数(60-7200),到期自动释放"},n("note","在做什么",!1),R],func:r(async e=>I.claim({board:e.board,worker:T(e),objects:e.object,ttl:e.ttl,note:e.note}),"func")}),Nt=d({...l,name:"collab-release",access:"write",description:"多会话协同:释放认领(不指定对象则释放自己在该任务板上的全部认领)",args:[U,n("object","要释放的对象,逗号分隔",!1),R],func:r(async e=>I.release({board:e.board,worker:T(e),objects:e.object}),"func")}),$t=d({...l,name:"collab-note",access:"write",description:"多会话协同:共享成果或发现(已验证的 SQL 文件、建好的对象 id、口径结论、交接、阻塞),避免别人重复做",args:[U,n("text","内容"),n("kind","finding | result | handoff | blocker | decision(默认 finding)",!1),n("ref","相关文件路径或对象 id",!1),R],func:r(async e=>I.note({board:e.board,worker:T(e),text:e.text,kind:e.kind||"finding",ref:e.ref}),"func")}),_t=d({...l,name:"collab-status",access:"read",description:"多会话协同:查看任务板(任务、有效认领、最近成果);不带 --board 列出全部进行中的任务板",args:[n("board","任务板 id",!1),{name:"notes",type:"int",default:30,help:"显示最近多少条成果记录"}],func:r(async e=>I.status({board:e.board,notes:e.notes}),"func")}),Rt=d({...l,name:"collab-close",access:"write",description:"多会话协同:关闭任务板并释放其上全部认领,同时结束对应工作流(协调者在交付后执行)",args:[U,n("status","工作流结束状态:delivered | abandoned | failed(默认 delivered)",!1),n("note","结果说明",!1),R],func:r(async e=>{const t=I.closeBoard({board:e.board,worker:T(e)});let s=null;try{s=f.endFlow({id:t.board,status:e.status||"delivered",result:e.note?{note:e.note}:null}).status}catch(o){s=`未结束:${o.message}`}return{...t,flow:s}},"func")}),jt=d({...fe,name:"shot",access:"write",description:"在浏览器中打开报表/方案页面并截图(需要 Chrome 与 LinkAI 扩展)",args:[n("url","页面地址,如 https://bi.linkmore.com/view/plan?plan=<planId>"),n("output","截图保存路径(默认 ~/.linkai/artifacts/linkai/shots/<时间>.png)",!1),{name:"wait",type:"int",default:12,help:"等待页面渲染的秒数"}],func:r(async(e,t)=>{const s=a.readConfig(),o=new URL(t.url);if(o.origin!==s.origin)throw new Error(`URL must be on ${s.origin}`);if(/\/login/i.test(o.pathname))throw new Error("Do not open /login: it clears localStorage");const c=await a.ensureSession();await e.goto(`${s.origin}/`),await e.evaluate(a.browserSessionScript(c)),await e.goto(o.href),await e.sleep(Number(t.wait));const i=await e.evaluate("(() => ({ title: document.title, onLogin: /login/i.test(location.pathname) }))()");if(i?.onLogin)throw new Error("Page redirected to /login: the synced session was rejected; run: linkai login");let u=null,C=null;try{const p=w.resolve(Q(t.output,"shots",`${te()}.png`));k.mkdirSync(w.dirname(p),{recursive:!0}),u=await e.screenshot({path:p,fullPage:!1,format:"png"})||p}catch(p){C=String(p?.message||p).slice(0,200)}return{url:await e.getCurrentUrl(),title:i?.title,loggedIn:!0,output:u,screenshotError:C}},"func")});export{At as apiCommand,xt as apiGetCommand,$e as askCommand,It as backupCommand,kt as buildCommand,Ve as cardChartCommand,Xe as cardGanttCommand,ze as cardGetCommand,Me as cardListCommand,He as cardMeterCommand,We as cardSaveCommand,Ze as cardSortCommand,Ye as cardTableCommand,Lt as cleanCommand,Ft as collabClaimCommand,Rt as collabCloseCommand,$t as collabNoteCommand,Nt as collabReleaseCommand,Dt as collabStartCommand,_t as collabStatusCommand,Tt as collabTaskCommand,gt as deliverableCommand,Te as doctorCommand,Ue as dsCreateCommand,ve as dsFieldsCommand,je as dsListCommand,Pe as dsMatchCommand,Ke as dsRefsCommand,Qe as dsSqlCommand,Be as dsTablesCommand,Je as dsUpdateCommand,Ge as fillWriteCommand,qt as flowEndCommand,Et as flowStartCommand,Ot as flowStatusCommand,Re as fmtCommand,pt as kbCatalogCommand,dt as kbCommand,wt as kbDocCommand,mt as kbDocsCommand,ft as kbObjectCommand,bt as kbSearchCommand,ut as kbSourcesCommand,Ne as kindsCommand,Oe as loginCommand,De as logoutCommand,st as planAddCommand,nt as planCreateCommand,ot as planFilterCommand,at as planGetCommand,yt as planKnowledgeCommand,St as planKnowledgeGetCommand,tt as planListCommand,et as queryCommand,Ct as removeCommand,it as reportCardCommand,rt as reportListCommand,ct as reportPublishCommand,lt as resetFilterCommand,jt as shotCommand,_e as sqlCheckCommand,ht as templateCommand,Fe as tenantsCommand};
4
+ - `)}`,c.stage==="authorize"?"把正式名清单给用户确认,得到明确授权后再加 --confirm":"按错误逐条修改 spec 后重新 dry-run");return c},"func")});async function re(e){const t=String(e.mode||"");if(!["create","update"].includes(t))throw new h("必须明确 --mode create 或 --mode update;用户未说明新建还是修改时,先主动询问用户");if(!e.file)throw new h("缺少 --file <方案知识 YAML/JSON>","文件必须描述方案内容、解决的问题,以及每张业务卡片的内容、数据来源、粒度和取数逻辑");const s=G.readKnowledgeFile(e.file);return G.publishPlanKnowledge({planId:e.plan,operation:t,authoring:s.knowledge||s})}r(re,"publishPlanKnowledgeFromFile");const kt=d({...l,name:"plan-knowledge",access:"write",description:"生成、版本化并保存完整方案知识库;服务器不可用时内部留存本地。最终返回方案地址和完整知识内容,不返回 SQL 或上传信息",args:[n("plan","方案的精确 id"),n("mode","create(新方案)或 update(修改原方案)",!1),n("file","AI 编写的方案知识 YAML/JSON 文件",!1)],func:r(async e=>{const t=await re(e);return f.emit("kb.write",{library:"plan-knowledge",op:e.mode||"create",plan:e.plan,version:t?.version??t?.planKnowledge?.version??null}),t},"func")}),St=d({...l,name:"plan-knowledge-get",access:"read",description:"读取方案当前知识库;修改原方案前先读。服务器不可用时读取本地待同步版本,不展示存储信息",args:[n("plan","方案的精确 id")],func:r(async e=>{const t=await G.readCurrentPlanKnowledge(e.plan);return f.emit("kb.read",{library:"plan-knowledge",op:"doc",plan:e.plan,version:t?.version??null,exists:t?.exists??null,chars:typeof t?.content=="string"?t.content.length:null}),t},"func")}),gt=d({...l,name:"deliverable",access:"write",description:"兼容命令:等同 plan-knowledge;需要 --mode 与 --file,不再输出数据集 SQL",args:[n("plan","方案的精确 id"),n("mode","create 或 update",!1),n("file","方案知识 YAML/JSON 文件",!1)],func:re}),It=d({...l,name:"backup",access:"read",description:"修改已有对象前保存带时间戳的备份(卡片/方案 JSON 或数据集 SQL),存到 ~/.linkai/artifacts/linkai/backups/",args:[n("type","card(卡片)| plan(方案)| ds(数据集)"),n("id","对象的精确 id")],func:r(async e=>{const t=r(s=>a.artifactsDir("backups",`${e.type}-${e.id}-${te()}.${s}`),"at");if(e.type==="card")return{type:"card",output:a.writeJsonFile(t("json"),await a.cardDetail(e.id))};if(e.type==="plan")return{type:"plan",output:a.writeJsonFile(t("json"),await a.planDetail(e.id,!0))};if(e.type==="ds")return{type:"ds",output:a.writeJsonFile(t("sql"),(await a.dsDetail(e.id)).sql||"")};throw new Error("type must be card, plan or ds")},"func")}),Ct=d({...l,name:"remove",access:"write",description:"按 folderId 删除数据集/卡片/方案/报表(id 来自各 *-list 命令)。需要用户明确授权",args:[n("folder","精确的 folderId,多个用逗号分隔")],func:r(async e=>{const t=String(e.folder).split(",").map(s=>s.trim()).filter(Boolean);return await a.post("/index-block/delete-folder-bussiness",t),{deleted:t.length,recycleBin:"POST /index-block/get-recycle-station-page-list"}},"func")}),Lt=d({...l,name:"clean",access:"write",description:"按 zz- 前缀清理试验对象,按依赖顺序删除:报表 → 方案 → 卡片 → 数据集",args:[n("prefix","以 zz- 开头的前缀,如 zz-销售看板-")],func:r(async e=>a.cleanPrefix(e.prefix),"func")}),xt=d({...l,name:"api-get",access:"read",description:"调用未封装的读接口(GET,路径可省略 /api/app);先查 references/api-catalog.md。数据源、SQL 类接口不开放",args:[n("path","接口路径,如 /index-block/folders?type=1"),{name:"raw",type:"boolean",default:!1,help:"返回完整响应"}],func:r(async e=>(await a.assertApiAllowedByPolicy(e.path),a.api("GET",e.path,void 0,{raw:!!e.raw})),"func")}),ge=/\/(get-|query|search)[\w-]*|\/[\w-]*-(list|detail|info)(?:[/?]|$)/i,At=d({...l,name:"api",access:"write",accessOf:r(e=>ge.test(String(e.path||""))?"read":"write","accessOf"),description:"调用未封装的写接口(POST/PUT/DELETE,可能新建或修改数据);先查 references/api-catalog.md。数据源、SQL 类接口不开放",args:[n("method","POST、PUT 或 DELETE"),n("path","接口路径"),n("body","请求体 JSON 文件路径",!1),{name:"raw",type:"boolean",default:!1,help:"返回完整响应(code/message/extendData)"}],func:r(async e=>{const t=String(e.method).toUpperCase();if(!["POST","PUT","DELETE"].includes(t))throw new Error("method must be POST, PUT or DELETE; use api-get for GET");return await a.assertApiAllowedByPolicy(e.path),a.api(t,e.path,e.body?A(e.body):t==="POST"?{}:void 0,{raw:!!e.raw})},"func")}),ie="create(新建方案)| modify(修改已有方案)| import(导入/更新数据)| query(只读查询分析)",Et=d({...l,name:"flow-start",access:"write",description:"开始一个工作流:接到业务任务时执行一次,之后的命令自动归入它;完成后用 flow-end 结束",args:[n("goal","这个任务要完成什么(用户原话概括)"),n("type",ie),n("plan","目标方案 id(修改已有方案时)",!1),n("model",'你(AI)当前使用的具体模型与版本,如 "Claude Opus 5.5"、"gpt-5.6";不确定就写你所知道的名称',!1)],func:r(async e=>{const t=f.startFlow({goal:e.goal,type:String(e.type||"").trim(),plan:e.plan||null,model:e.model||null});return{flow:t.id,goal:t.goal,type:t.type,plan:t.plan,model:f.currentModel(),next:"同一会话里的后续命令自动归入此工作流;并行开多个任务时,在命令上加 --flow <id> 指明。完成后 linkai flow-end --status delivered"}},"func")}),qt=d({...l,name:"flow-end",access:"write",description:"结束工作流:delivered(已交付)| abandoned(放弃)| failed(失败);默认结束当前工作流,或用 --flow <id> 指定",args:[n("status","delivered | abandoned | failed"),n("plan","交付的方案 id",!1),n("knowledge-version","方案知识库版本,如 v2",!1),n("note","结果说明或失败原因",!1)],func:r(async e=>{const t=Object.fromEntries(Object.entries({plan:e.plan,knowledgeVersion:e["knowledge-version"],note:e.note}).filter(([,o])=>o!=null&&o!=="")),s=f.endFlow({id:process.env.LINKAI_FLOW||void 0,status:String(e.status||"").trim(),result:t});return{flow:s.id,status:s.status,goal:s.goal,runs:s.runs,durationMinutes:Math.round(((s.endedAt??Date.now())-s.startedAt)/6e3)/10,result:s.result}},"func")}),Dt=d({...l,name:"flow-status",access:"read",description:"查看本机工作流:当前工作流、未完成或中断的工作流(含中断原因与下一步);--all 含已结束的",args:[{name:"all",type:"boolean",default:!1,help:"包括已结束和自动关闭的工作流(最近 7 天)"}],func:r(async e=>{const t=f.currentFlow(),s=(e.all?f.listFlows():f.unfinishedFlows()).slice(0,30).map(o=>({...ae(o),runs:o.runs,startedAt:new Date(o.startedAt).toISOString(),endedAt:o.endedAt?new Date(o.endedAt).toISOString():null,result:o.result}));return{current:t,idleMinutes:f.flowIdleMinutes(),flows:s}},"func")}),R={name:"worker",type:"string",required:!1,help:"执行者名称(也可用全局参数 --worker);同一会话保持不变,如 主控、sql-1、card-2"},T=r(e=>String(e.worker||C.currentWorker()||""),"workerOf"),U=n("board","任务板 id(collab-start 返回,如 b-1a2b3c4d)"),Ot=d({...l,name:"collab-start",access:"write",description:"多会话协同:创建任务板(协调者执行一次),返回任务板 id 交给各执行者",args:[n("goal","这次协同要完成什么"),n("plan","目标方案 id(修改已有方案时)",!1),n("type",`工作流类型:${ie};默认有 --plan 为 modify,否则 create`,!1),R],func:r(async e=>{const t=C.startBoard({goal:e.goal,plan:e.plan,worker:T(e)});return f.startFlow({id:t.id,source:"board",goal:t.goal,type:e.type||(e.plan?"modify":"create"),plan:e.plan||null}),{board:t.id,coordinator:t.coordinator,goal:t.goal,next:"用 collab-task 拆分任务;各执行者写共享对象前先 collab-claim,成果用 collab-note 共享;读命令不用认领"}},"func")}),Tt=d({...l,name:"collab-task",access:"write",description:"多会话协同:新增任务(--title),或更新任务状态(--id --status todo|doing|done|blocked --result)",args:[U,n("title","新任务标题(新增时)",!1),{name:"id",type:"int",required:!1,help:"要更新的任务编号"},n("status","todo | doing | done | blocked",!1),n("result","结果或阻塞原因(文件路径、对象 id、结论)",!1),n("assignee","分配给哪个执行者",!1),n("object","任务涉及的共享对象,逗号分隔,如 card:<id>,ds:<id>",!1),R],func:r(async e=>e.id?C.updateTask({board:e.board,worker:T(e),id:e.id,status:e.status,result:e.result,assignee:e.assignee}):C.addTask({board:e.board,worker:T(e),title:e.title,assignee:e.assignee,objects:e.object}),"func")}),Ft=d({...l,name:"collab-claim",access:"write",description:"多会话协同:写共享对象前认领(全部成功或全部不认领;重复认领即续期);被别人认领时返回 COLLAB_CONFLICT",args:[U,n("object","对象,逗号分隔:card:<id> ds:<id> plan:<id> knowledge:<planId> spec:<文件> report:<id> filter:<planId>"),{name:"ttl",type:"int",default:C.DEFAULT_TTL_SECONDS,help:"认领有效秒数(60-7200),到期自动释放"},n("note","在做什么",!1),R],func:r(async e=>C.claim({board:e.board,worker:T(e),objects:e.object,ttl:e.ttl,note:e.note}),"func")}),Nt=d({...l,name:"collab-release",access:"write",description:"多会话协同:释放认领(不指定对象则释放自己在该任务板上的全部认领)",args:[U,n("object","要释放的对象,逗号分隔",!1),R],func:r(async e=>C.release({board:e.board,worker:T(e),objects:e.object}),"func")}),$t=d({...l,name:"collab-note",access:"write",description:"多会话协同:共享成果或发现(已验证的 SQL 文件、建好的对象 id、口径结论、交接、阻塞),避免别人重复做",args:[U,n("text","内容"),n("kind","finding | result | handoff | blocker | decision(默认 finding)",!1),n("ref","相关文件路径或对象 id",!1),R],func:r(async e=>C.note({board:e.board,worker:T(e),text:e.text,kind:e.kind||"finding",ref:e.ref}),"func")}),_t=d({...l,name:"collab-status",access:"read",description:"多会话协同:查看任务板(任务、有效认领、最近成果);不带 --board 列出全部进行中的任务板",args:[n("board","任务板 id",!1),{name:"notes",type:"int",default:30,help:"显示最近多少条成果记录"}],func:r(async e=>C.status({board:e.board,notes:e.notes}),"func")}),Rt=d({...l,name:"collab-close",access:"write",description:"多会话协同:关闭任务板并释放其上全部认领,同时结束对应工作流(协调者在交付后执行)",args:[U,n("status","工作流结束状态:delivered | abandoned | failed(默认 delivered)",!1),n("note","结果说明",!1),R],func:r(async e=>{const t=C.closeBoard({board:e.board,worker:T(e)});let s=null;try{s=f.endFlow({id:t.board,status:e.status||"delivered",result:e.note?{note:e.note}:null}).status}catch(o){s=`未结束:${o.message}`}return{...t,flow:s}},"func")}),Bt=d({...fe,name:"shot",access:"write",description:"在浏览器中打开报表/方案页面并截图(需要 Chrome 与 LinkAI 扩展)",args:[n("url","页面地址,如 https://bi.linkmore.com/view/plan?plan=<planId>"),n("output","截图保存路径(默认 ~/.linkai/artifacts/linkai/shots/<时间>.png)",!1),{name:"wait",type:"int",default:12,help:"等待页面渲染的秒数"}],func:r(async(e,t)=>{const s=a.readConfig(),o=new URL(t.url);if(o.origin!==s.origin)throw new Error(`URL must be on ${s.origin}`);if(/\/login/i.test(o.pathname))throw new Error("Do not open /login: it clears localStorage");const c=await a.ensureSession();await e.goto(`${s.origin}/`),await e.evaluate(a.browserSessionScript(c)),await e.goto(o.href),await e.sleep(Number(t.wait));const i=await e.evaluate("(() => ({ title: document.title, onLogin: /login/i.test(location.pathname) }))()");if(i?.onLogin)throw new Error("Page redirected to /login: the synced session was rejected; run: linkai login");let u=null,S=null;try{const p=w.resolve(P(t.output,"shots",`${te()}.png`));y.mkdirSync(w.dirname(p),{recursive:!0}),u=await e.screenshot({path:p,fullPage:!1,format:"png"})||p}catch(p){S=String(p?.message||p).slice(0,200)}return{url:await e.getCurrentUrl(),title:i?.title,loggedIn:!0,output:u,screenshotError:S}},"func")});export{At as apiCommand,xt as apiGetCommand,$e as askCommand,It as backupCommand,yt as buildCommand,Ve as cardChartCommand,Xe as cardGanttCommand,ze as cardGetCommand,Je as cardListCommand,He as cardMeterCommand,We as cardSaveCommand,Ze as cardSortCommand,Ye as cardTableCommand,Lt as cleanCommand,Ft as collabClaimCommand,Rt as collabCloseCommand,$t as collabNoteCommand,Nt as collabReleaseCommand,Ot as collabStartCommand,_t as collabStatusCommand,Tt as collabTaskCommand,gt as deliverableCommand,Te as doctorCommand,Ue as dsCreateCommand,ve as dsFieldsCommand,Be as dsListCommand,Ke as dsMatchCommand,Qe as dsRefsCommand,Pe as dsSqlCommand,je as dsTablesCommand,Me as dsUpdateCommand,Ge as fillWriteCommand,qt as flowEndCommand,Et as flowStartCommand,Dt as flowStatusCommand,Re as fmtCommand,pt as kbCatalogCommand,dt as kbCommand,wt as kbDocCommand,mt as kbDocsCommand,ft as kbObjectCommand,bt as kbSearchCommand,ut as kbSourcesCommand,Ne as kindsCommand,De as loginCommand,Oe as logoutCommand,st as planAddCommand,nt as planCreateCommand,ot as planFilterCommand,at as planGetCommand,kt as planKnowledgeCommand,St as planKnowledgeGetCommand,tt as planListCommand,et as queryCommand,Ct as removeCommand,it as reportCardCommand,rt as reportListCommand,ct as reportPublishCommand,lt as resetFilterCommand,Bt as shotCommand,_e as sqlCheckCommand,ht as templateCommand,Fe as tenantsCommand};
@@ -1,12 +1,12 @@
1
- var Je=Object.defineProperty;var o=(t,e)=>Je(t,"name",{value:e,configurable:!0});import p from"node:fs";import I from"node:path";import fe from"node:os";import j from"node:crypto";import{authorizeTenantSession as Ve,loginWithOauth as qe,refreshOauthSession as Ne,revokeOauthSession as J}from"./oauth.js";import{validateFillCardPayload as Qe}from"./fill-card.js";import{checkCardPayload as Ce,checkFilterPayload as Oe}from"./card-rules.js";import*as C from"@linkaicli/linkai-cli/oplog";import*as _e from"./collab.js";import Ae from"./templates.js";const v="linkai",b="00000000-0000-0000-0000-000000000000",Ye="https://bi.linkmore.com/",Xe="https://bi-api.linkmore.com",Ze=/UND_ERR_CONNECT_TIMEOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|Connect Timeout/,et=/EPIPE|ECONNRESET|socket hang up|UND_ERR_SOCKET|other side closed/i,tt=new Set(["GET","HEAD"]);class nt extends Error{static{o(this,"NetworkError")}constructor(e,n){super(e,n),this.name="NetworkError"}}async function rt(t,e,n=3){for(let r=1;;r++)try{return await fetch(t,e)}catch(a){if(e?.signal?.aborted)throw a;const s=`${a?.cause?.code||""} ${a?.cause?.message||""} ${a?.message||""}`,l=Ze.test(s)||tt.has(String(e?.method||"GET").toUpperCase())&&et.test(s);if(r>=n||!l)throw new nt(`无法连接领谋 BI 服务器,请检查网络后重试(${s.trim().replace(/\s+/g," ").slice(0,120)})`,{cause:a});await new Promise(i=>setTimeout(i,1500*r))}}o(rt,"fetchRetry");function V(){const t=process.env.LINKAI_SITE_CONFIG_DIR||I.join(fe.homedir(),".linkai","sites");return p.mkdirSync(t,{recursive:!0,mode:448}),at(t),t}o(V,"configDir");let $e=!1;function at(t){if(!$e){$e=!0;for(const[e,n]of[["lmbi.json",`${v}.json`],["lmbi.session.json",`${v}.session.json`]]){const r=I.join(t,e),a=I.join(t,n);try{p.existsSync(r)&&!p.existsSync(a)&&p.renameSync(r,a)}catch{}}if(!process.env.LINKAI_ARTIFACTS_DIR){const e=I.join(fe.homedir(),".linkai","artifacts","lmbi"),n=ot();try{p.existsSync(e)&&!p.existsSync(n)&&p.renameSync(e,n)}catch{}}}}o(at,"migrateLegacyFiles");const pe=o(()=>I.join(V(),`${v}.json`),"configFile");function ot(...t){return I.join(process.env.LINKAI_ARTIFACTS_DIR||I.join(fe.homedir(),".linkai","artifacts",v),...t)}o(ot,"artifactsDir");function x(){const t=me();if("username"in t||"password"in t||"tenantConfirmed"in t){const{username:e,password:n,tenantConfirmed:r,...a}=t;return we(a),Le(a)}return Le(t)}o(x,"readConfig");function Le(t){const e=new URL(t.url||Ye);if(!["http:","https:"].includes(e.protocol))throw new Error("LMBI url must use http:// or https://");return{...t,url:e.href,origin:e.origin,apiBase:(t.apiBase||Xe).replace(/\/$/,"")}}o(Le,"configFrom");const ee=o(()=>I.join(V(),`${v}.session.json`),"sessionFile"),it="请用户执行 linkai login,在浏览器完成领谋授权";function A(){try{const t=JSON.parse(p.readFileSync(ee(),"utf8"));return t.authMode==="oauth"&&t.token&&t.refreshToken&&t.tenantId?t:null}catch{return null}}o(A,"readSession");const q=o(()=>I.join(V(),`${v}.cache.json`),"shortCacheFile");async function Q(t,e,n){const r=A()?.tenantId;if(!r||process.env.LINKAI_NO_CACHE==="1")return n();let a={};try{a=JSON.parse(p.readFileSync(q(),"utf8"))||{}}catch{}a.tenantId!==r&&(a={tenantId:r,entries:{}});const s=a.entries?.[t];if(s&&Date.now()-s.at>=0&&Date.now()-s.at<e)return s.value;const l=await n();try{try{a=JSON.parse(p.readFileSync(q(),"utf8"))||{}}catch{a={}}a.tenantId!==r&&(a={tenantId:r,entries:{}}),a.entries??={},a.entries[t]={at:Date.now(),value:l};const i=`${q()}.${process.pid}.tmp`;p.writeFileSync(i,JSON.stringify(a),{mode:384}),p.renameSync(i,q())}catch{}return l}o(Q,"shortCache");function on(){try{p.rmSync(q(),{force:!0})}catch{}}o(on,"clearShortCache");function te(t){if(t?.authMode!=="oauth"||!t.token||!t.refreshToken||!t.tenantId||!t.agentId)throw new Error("只允许保存完整的 OAuth 会话");const e=ee(),n=`${e}.${process.pid}.tmp`,r={token:t.token,tenantId:t.tenantId,agentId:t.agentId||"",...t.authMode==="oauth"&&t.refreshToken?{authMode:"oauth",refreshToken:t.refreshToken}:{},savedAt:new Date().toISOString()};return p.writeFileSync(n,JSON.stringify(r),{mode:384}),p.renameSync(n,e),$=r,z=null,G=null,r}o(te,"saveSession");function st(){try{p.rmSync(ee())}catch{}$=null,z=null,G=null}o(st,"clearSession");function P(t){try{return JSON.parse(Buffer.from(String(t).split(".")[1],"base64").toString())}catch{return{}}}o(P,"tokenPayload");function Y(t){const e=P(t);return e.exp?(e.exp*1e3-Date.now())/36e5:-1}o(Y,"tokenHoursLeft");function he(t){const e=P(t?.token);let n={};try{n=JSON.parse(e.TenantInfo||"[]")[0]||{}}catch{}return{tenantId:t?.tenantId,tenantName:n.tenantName||"",tenantCode:n.tenantCode||"",user:e.UserName||""}}o(he,"spaceOf");class k extends Error{static{o(this,"LoginRequiredError")}constructor(e){super(`${e}。${it}`),this.name="LoginRequiredError"}}function me(){try{return JSON.parse(p.readFileSync(pe(),"utf8"))||{}}catch{return{}}}o(me,"readConfigFile");function we(t){const e=pe(),n=`${e}.${process.pid}.tmp`;p.writeFileSync(n,JSON.stringify(t,null,2),{mode:384}),p.renameSync(n,e)}o(we,"writeConfigFile");function ge(){const{username:t,password:e,tenantId:n,tenantConfirmed:r,...a}=me();if(Object.keys(a).length)we(a);else try{p.rmSync(pe())}catch{}}o(ge,"forgetCredentials");function lt(){ge();const t=me();if(typeof t.oauthAgentId=="string"&&/^[A-Za-z0-9._:-]{8,128}$/.test(t.oauthAgentId))return t.oauthAgentId;const e=j.randomUUID().replace(/-/g,"");return we({...t,oauthAgentId:e}),e}o(lt,"oauthAgentId");async function sn({tenant:t,switchAccount:e=!1,timeoutSeconds:n=300,browser:r}={}){const a=A();return qe(x(),{agentId:lt(),tenant:t,switchAccount:e,timeoutSeconds:n,browser:r,onAuthorized:o(async s=>{if(e&&a){const i=P(a.token).UserId,u=P(s.token).UserId;if(i&&u&&i===u)throw await J(x(),s).catch(()=>{}),new Error("授权后仍是原领谋账号;请在授权页切换账号后重试")}if(t)try{const i=await De(s),u=String(t).trim(),c=i.filter(f=>f.tenantId===u||f.code===u||f.name===u),d=c.length?c:i.filter(f=>f.name.includes(u));if(d.length!==1||d[0].tenantId!==s.tenantId)throw new Error(`授权的租户不是「${u}」,请在领谋授权页选择目标租户后重试`)}catch(i){throw await J(x(),s).catch(()=>{}),i}ge();const l=te(s);return a?.refreshToken&&await J(x(),a).catch(()=>{}),{session:l,tenantName:he(l).tenantName||l.tenantId}},"onAuthorized")})}o(sn,"oauthLogin");async function ln(){const t=A();if(!t?.refreshToken)return!1;try{return await J(x(),t)}catch{return!1}}o(ln,"oauthLogout");let $=null,ne=null;async function Fe(t){const e=`${ee()}.refresh.lock`,n=Date.now()+3e4;let r;for(;r===void 0;)try{r=p.openSync(e,"wx",384),p.writeFileSync(r,String(process.pid))}catch(a){if(a.code!=="EEXIST")throw a;try{const s=Number(p.readFileSync(e,"utf8"));if(Number.isInteger(s)&&s>0){let l=!0;try{process.kill(s,0)}catch(i){i.code==="ESRCH"&&(l=!1)}if(!l){p.rmSync(e);continue}}}catch{}if(Date.now()>=n)throw new Error("等待 OAuth 会话操作超时,请稍后重试");await new Promise(s=>setTimeout(s,150))}try{return await t()}finally{p.closeSync(r),p.rmSync(e)}}o(Fe,"withSessionLock");async function ct(t){return Fe(async()=>{const e=A();if(!e?.refreshToken)throw new k("OAuth 会话已在本机退出");return e.refreshToken!==t.refreshToken&&Y(e.token)>.05?($=e,e):te(await Ne(x(),e))})}o(ct,"refreshOauthLocked");async function M({refresh:t=!1}={}){if(ge(),$&&!t&&Y($.token)>.05)return $;const e=$||A();if(e&&!t&&Y(e.token)>.05)return $=e,e;if(e?.authMode==="oauth"&&e.refreshToken)return ne||(ne=(async()=>{try{return await ct(e)}catch(n){if(["invalid_grant","invalid_client","oauth_exchange_unknown"].includes(n.code)){const r=A();if(r&&r.refreshToken!==e.refreshToken&&Y(r.token)>.05)return $=r,r;throw st(),new k(n.code==="oauth_exchange_unknown"?"OAuth 续期结果无法确认":"OAuth 授权已失效")}throw n}})().finally(()=>{ne=null})),ne;throw new k(e?"登录已过期":"尚未登录领谋 BI")}o(M,"ensureSession");async function De(t){t=t||await M();const e=P(t.token),n=await X(x(),t,"GET",`/api/app/account/user-bind-tenant-list/${e.UserId||e.sub||e.userId}`);if(n.code!==200)throw new k(`读取租户列表失败(${n.message||n.code})`);return(n.data||[]).map(r=>({tenantId:r.tenantId,name:r.spaceName||r.tenantName||"",code:r.tenantCode||""})).filter(r=>!dt(r))}o(De,"tenantList");function dt(t){return t.tenantId===b||t.code==="0000"||t.name==="全域租户"||t.name==="全域"}o(dt,"isPlatformTenant");async function cn(t,e){if(t?.authMode!=="oauth")throw new k("当前没有 OAuth 授权");return Fe(async()=>{let n=A();if(!n)throw new k("当前没有 OAuth 授权");if(P(n.token).UserId!==P(t.token).UserId)throw new Error("领谋账号已切换,请重新读取当前租户");Y(n.token)<=.05&&(n=te(await Ne(x(),n)));const r=await De(n),a=String(e).trim(),s=r.filter(c=>c.tenantId===a||c.code===a||c.name===a),l=s.length?s:r.filter(c=>c.name.includes(a));if(l.length!==1)throw new Error(`租户「${a}」${l.length?"匹配到多个":"不存在"};可选:${r.map(c=>`${c.name}(${c.tenantId})`).join("、")}`);const i=l[0];if(i.tenantId===n.tenantId)return{session:n,tenant:i,switched:!1};const u=await Ve(x(),n,i.tenantId);return te(u),Z=null,await J(x(),n).catch(()=>{}),{session:u,tenant:i,switched:!0}})}o(cn,"switchTenant");let ye="";function dn(t){ye=String(t||"")}o(dn,"setClientVersion");function ve(t){const[e,n=""]=String(t).split("?"),r=n?[...new URLSearchParams(n).keys()]:[];return{path:e,query:r.length?r:void 0}}o(ve,"logPath");function ut(t){if(!t)return null;const e=P(t.token);return{userId:String(e.UserId||e.sub||e.userId||""),tenantId:t.tenantId,tenantCode:he(t).tenantCode}}o(ut,"identityOf");function B(t,e,n={}){C.emit("guard",{check:t,pass:e,...n})}o(B,"logGuard");async function X(t,e,n,r,a,{log:s=!0,signal:l}={}){const i=Date.now(),u=a!==void 0?JSON.stringify(a):void 0;s&&C.setIdentity(ut(e));let c;try{c=await rt(t.apiBase+r,{method:n,headers:{"X-LinkAI-Run-Id":C.currentRunId(),...ye?{"X-LinkAI-Version":ye}:{},Authorization:"Bearer "+e.token,Tenantid:e.tenantId,Agentid:e.agentId||"","Content-Type":"application/json"},...u!==void 0?{body:u}:{},...l?{signal:l}:{}})}catch(m){throw s&&C.emit("api.call",{method:n,...ve(r),ok:!1,network:!0,ms:Date.now()-i,reqBytes:u?Buffer.byteLength(u):0,message:String(m.message).slice(0,300)}),m}const d=await c.text();let f;try{f=JSON.parse(d),f.__status=c.status}catch{f={code:c.status,message:`HTTP ${c.status} non-JSON: ${d.slice(0,200)}`,__status:c.status}}if(s){const m=c.status<400&&f.code===200;C.emit("api.call",{method:n,...ve(r),status:c.status,code:f.code??null,ok:m,ms:Date.now()-i,reqBytes:u?Buffer.byteLength(u):0,resBytes:Buffer.byteLength(d),...m?{}:{message:String(f.message||"").slice(0,300)}})}return f}o(X,"rawFetch");const Pe="/api/app/linkai-oplog/events";C.setTransport(async(t,e)=>{if(!A())return!1;const n=o(r=>r.__status===401||r.code===401||r.__status===406||r.code===406,"expired");try{let r=await M(),a=await X(x(),r,"POST",Pe,{events:t},{log:!1,signal:e});return n(a)&&(r=await M({refresh:!0}),a=await X(x(),r,"POST",Pe,{events:t},{log:!1,signal:e})),a.__status<400&&a.code===200?(C.saveServerConfig(a.data||{}),!0):!1}catch{return!1}});const ft=[/data-?source/i,/sql/i,/table-field/i,/table-info/i,/er-table/i,/field-info-by/i,/data-info-by/i,/index-block-data-argument/i,/copy-index-block-data/i,/\/export\/import/i,/task-sync/i,/data-warehouse/i,/authority-variable/i,/\/test\//i,/package/i,/subscribe/i,/distribute/i];function un(t){let e=String(t);try{for(let r=0;r<3;r++)e=decodeURIComponent(e)}catch{}const n=ft.find(r=>r.test(e)||r.test(String(t)));if(n)throw B("api-blocklist",!1,{path:String(t).split("?")[0],rule:String(n)}),new Error(`Rejected: ${t} touches data sources / SQL / database structure, which this CLI does not allow. Use ds-create / ds-update for datasets; database changes must go through the LinkMore data team.`)}o(un,"assertApiAllowed");async function E(t,e,n,{raw:r=!1}={}){const a=x();e=String(e).trim(),e.startsWith("/")||(e="/"+e),e.startsWith("/api/")||(e="/api/app"+e);try{Qe(t,e,n)}catch(c){throw B("fill-card-structure",!1,{path:e.split("?")[0],message:c.message.slice(0,300)}),c}try{let c;try{c=Ce(t,e,n)}catch(f){const m=/update-new/i.test(e)&&n?.id&&n.id!==b?n.id:null,w=m&&f.errors?await Ie(m).catch(()=>null):null;if(!w)throw f;c=Ce(t,e,n,{previous:w})}for(const f of c)C.addNotice(`卡片保存:${f}。`,"card-rules");let d;try{d=Oe(t,e,n)}catch(f){const m=n?.userPageSettingId&&Number(n.filterSourceType)!==2?n.userPageSettingId:null,w=m&&f.errors?(await Pt(m).catch(()=>null))?.filters:null;if(!w)throw f;d=Oe(t,e,n,{previous:w})}for(const f of d)C.addNotice(`筛选条件:${f}。`,"filter-rules")}catch(c){throw B(/filter/.test(e)?"filter-rules":"card-rules",!1,{path:e.split("?")[0],message:c.message.slice(0,300)}),new O("CONFIG_INVALID",c.message)}const s=Lt(t,e,n);if(s){_e.assertWritable([_e.normalizeObject(`card:${s}`)]);try{await Ft(s),B("tenant-ownership",!0,{target:s,path:e.split("?")[0]})}catch(c){throw c instanceof Be&&B("tenant-ownership",!1,{target:s,path:e.split("?")[0]}),c}}let l=await M(),i=await X(a,l,t,e,n);const u=o(c=>c.__status===401||c.code===401||c.__status===406||c.code===406,"expired");if(u(i)){if(!l.refreshToken)throw new k("OAuth 授权已失效");if(l=await M({refresh:!0}),i=await X(a,l,t,e,n),u(i))throw new k("自动续登后仍被拒绝")}if(delete i.__status,r)return i;if(i.code!==200)throw new re(t,e,i);return i.data}o(E,"api");class O extends Error{static{o(this,"KindError")}constructor(e,n){super(n),this.name="KindError",this.kind=e}}const pt=[["SQL_INVALID",/cannot be resolved|analyzing error|syntax|语法错误|Unknown column|doesn't exist/i],["IN_USE",/被.{0,40}引用.{0,20}请先删除/],["DUPLICATE_NAME",/名称不能重复|名称已存在|已存在同名/],["READ_ONLY",/系统报表不能更改|系统方案或大屏不能|系统.{0,6}(只读|不能(更改|修改|新增|删除))/],["NOT_FOUND",/已被删除|不存在|Id有误|未获取到该信息|未找到该数据/],["FORBIDDEN",/私有|暂无权限|没有权限/],["BAD_REQUEST",/ModelState is not valid|Cannot deserialize/]];function Re(t){return String(t||"").split(/\s*STACK TRACE:/)[0].replace(/\s*\w*Exception:\s*/g," ").replace(/\s+/g," ").trim()}o(Re,"cleanApiMessage");class re extends Error{static{o(this,"ApiError")}constructor(e,n,r={}){const a=Re(r.message),s=Re(r.error&&r.error.details||""),l=s&&!a.includes(s)&&!s.includes(a)?`${a} ${s}`:s.length>a.length?s:a;super(`LMBI API error [${e} ${n.split("?")[0]}]: ${l.slice(0,400)}`),this.name="ApiError",this.path=n.split("?")[0],this.status=r.code,this.kind=pt.find(([,i])=>i.test(l))?.[0]||null}}const S=o((t,e)=>E("POST",t,e),"post"),R=o(t=>E("GET",t),"get");let z=null;async function ae(){if(z)return z;const t=x();let e=process.env.LM_DATASOURCE||t.dataSourceId||"",n=[];if(!e){if(n=await Q("dataSources",30*6e4,async()=>{const a=await R("/index-block/index-block-data-source");return(Array.isArray(a)?a:a.items||[]).map(s=>({id:s.id,name:s.name}))}),!n.length)throw new Error("当前租户没有可用的数据源,请联系领谋开通");e=n[0].id}const r=await Q("rootFolders",30*6e4,async()=>{const[a,s,l]=await Promise.all([R("/index-block/folders?type=0"),R("/index-block/folders?type=1"),R("/index-block/folders?type=3")]);return{planFolder:a[0]?.id||b,cardFolder:s[0]?.id||b,dsFolder:l[0]?.id||b}});return z={dataSourceId:e,dataSources:n,...r},z}o(ae,"ctx");function ht(t){const e=t.replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ").replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/`/g,""),n=new Set([...e.matchAll(/(?:\bWITH(?:\s+RECURSIVE)?|,)\s*([A-Za-z_]\w*)\s*(?:\([^()]*\))?\s*AS\s*(?:NOT\s+)?(?:MATERIALIZED\s*)?\(/gi)].map(l=>l[1].toLowerCase())),a="([A-Za-z_][\\w.]*)(?:\\s+(?:AS\\s+)?(?!(?:WHERE|GROUP|ORDER|HAVING|LIMIT|UNION|JOIN|LEFT|RIGHT|INNER|CROSS|FULL|ON)\\b)[A-Za-z_]\\w*)?",s=[];for(const l of e.matchAll(new RegExp(`\\b(?:FROM|JOIN)\\s+${a}((?:\\s*,\\s*${a})*)`,"gi"))){s.push(l[1]);for(const i of(l[2]||"").matchAll(new RegExp(`,\\s*${a}`,"gi")))s.push(i[1])}return[...new Set(s)].filter(l=>!n.has(l.toLowerCase()))}o(ht,"sqlTables");let G=null;async function mt(){if(G)return G;const t=await ae(),e=await Q(`tables:${t.dataSourceId}`,10*6e4,async()=>[...new Set((await S(`/index-block/get-data-source-table-field/${t.dataSourceId}`)).map(n=>(n.table_name||"").toLowerCase()))]);return G=new Set(e),G}o(mt,"allowedTables");function wt(t){const e=t.split(`
1
+ var Je=Object.defineProperty;var o=(t,e)=>Je(t,"name",{value:e,configurable:!0});import p from"node:fs";import I from"node:path";import fe from"node:os";import M from"node:crypto";import{authorizeTenantSession as Ve,loginWithOauth as qe,refreshOauthSession as _e,revokeOauthSession as V}from"./oauth.js";import{validateFillCardPayload as Qe}from"./fill-card.js";import{checkCardPayload as Ce,checkFilterPayload as Ne}from"./card-rules.js";import*as N from"@linkaicli/linkai-cli/oplog";import*as Oe from"./collab.js";import Ae from"./templates.js";const D="linkai",b="00000000-0000-0000-0000-000000000000",Ye="https://bi.linkmore.com/",Xe="https://bi-api.linkmore.com",Ze=/UND_ERR_CONNECT_TIMEOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|Connect Timeout/,et=/EPIPE|ECONNRESET|socket hang up|UND_ERR_SOCKET|other side closed/i,tt=new Set(["GET","HEAD"]);class nt extends Error{static{o(this,"NetworkError")}constructor(e,n){super(e,n),this.name="NetworkError"}}async function rt(t,e,n=3){for(let r=1;;r++)try{return await fetch(t,e)}catch(a){if(e?.signal?.aborted)throw a;const s=`${a?.cause?.code||""} ${a?.cause?.message||""} ${a?.message||""}`,l=Ze.test(s)||tt.has(String(e?.method||"GET").toUpperCase())&&et.test(s);if(r>=n||!l)throw new nt(`无法连接领谋 BI 服务器,请检查网络后重试(${s.trim().replace(/\s+/g," ").slice(0,120)})`,{cause:a});await new Promise(i=>setTimeout(i,1500*r))}}o(rt,"fetchRetry");function q(){const t=process.env.LINKAI_SITE_CONFIG_DIR||I.join(fe.homedir(),".linkai","sites");return p.mkdirSync(t,{recursive:!0,mode:448}),at(t),t}o(q,"configDir");let $e=!1;function at(t){if(!$e){$e=!0;for(const[e,n]of[["lmbi.json",`${D}.json`],["lmbi.session.json",`${D}.session.json`]]){const r=I.join(t,e),a=I.join(t,n);try{p.existsSync(r)&&!p.existsSync(a)&&p.renameSync(r,a)}catch{}}if(!process.env.LINKAI_ARTIFACTS_DIR){const e=I.join(fe.homedir(),".linkai","artifacts","lmbi"),n=ot();try{p.existsSync(e)&&!p.existsSync(n)&&p.renameSync(e,n)}catch{}}}}o(at,"migrateLegacyFiles");const pe=o(()=>I.join(q(),`${D}.json`),"configFile");function ot(...t){return I.join(process.env.LINKAI_ARTIFACTS_DIR||I.join(fe.homedir(),".linkai","artifacts",D),...t)}o(ot,"artifactsDir");function x(){const t=me();if("username"in t||"password"in t||"tenantConfirmed"in t){const{username:e,password:n,tenantConfirmed:r,...a}=t;return we(a),Le(a)}return Le(t)}o(x,"readConfig");function Le(t){const e=new URL(t.url||Ye);if(!["http:","https:"].includes(e.protocol))throw new Error("LMBI url must use http:// or https://");return{...t,url:e.href,origin:e.origin,apiBase:(t.apiBase||Xe).replace(/\/$/,"")}}o(Le,"configFrom");const ee=o(()=>I.join(q(),`${D}.session.json`),"sessionFile"),it="请用户执行 linkai login,在浏览器完成领谋授权";function A(){try{const t=JSON.parse(p.readFileSync(ee(),"utf8"));return t.authMode==="oauth"&&t.token&&t.refreshToken&&t.tenantId?t:null}catch{return null}}o(A,"readSession");const Q=o(()=>I.join(q(),`${D}.cache.json`),"shortCacheFile");async function B(t,e,n){const r=A()?.tenantId;if(!r||process.env.LINKAI_NO_CACHE==="1")return n();let a={};try{a=JSON.parse(p.readFileSync(Q(),"utf8"))||{}}catch{}a.tenantId!==r&&(a={tenantId:r,entries:{}});const s=a.entries?.[t];if(s&&Date.now()-s.at>=0&&Date.now()-s.at<e)return s.value;const l=await n();try{try{a=JSON.parse(p.readFileSync(Q(),"utf8"))||{}}catch{a={}}a.tenantId!==r&&(a={tenantId:r,entries:{}}),a.entries??={},a.entries[t]={at:Date.now(),value:l};const i=`${Q()}.${process.pid}.tmp`;p.writeFileSync(i,JSON.stringify(a),{mode:384}),p.renameSync(i,Q())}catch{}return l}o(B,"shortCache");function pn(){try{p.rmSync(Q(),{force:!0})}catch{}}o(pn,"clearShortCache");function te(t){if(t?.authMode!=="oauth"||!t.token||!t.refreshToken||!t.tenantId||!t.agentId)throw new Error("只允许保存完整的 OAuth 会话");const e=ee(),n=`${e}.${process.pid}.tmp`,r={token:t.token,tenantId:t.tenantId,agentId:t.agentId||"",...t.authMode==="oauth"&&t.refreshToken?{authMode:"oauth",refreshToken:t.refreshToken}:{},savedAt:new Date().toISOString()};return p.writeFileSync(n,JSON.stringify(r),{mode:384}),p.renameSync(n,e),$=r,K=null,G=null,r}o(te,"saveSession");function st(){try{p.rmSync(ee())}catch{}$=null,K=null,G=null}o(st,"clearSession");function v(t){try{return JSON.parse(Buffer.from(String(t).split(".")[1],"base64").toString())}catch{return{}}}o(v,"tokenPayload");function Y(t){const e=v(t);return e.exp?(e.exp*1e3-Date.now())/36e5:-1}o(Y,"tokenHoursLeft");function he(t){const e=v(t?.token);let n={};try{n=JSON.parse(e.TenantInfo||"[]")[0]||{}}catch{}return{tenantId:t?.tenantId,tenantName:n.tenantName||"",tenantCode:n.tenantCode||"",user:e.UserName||""}}o(he,"spaceOf");class k extends Error{static{o(this,"LoginRequiredError")}constructor(e){super(`${e}。${it}`),this.name="LoginRequiredError"}}function me(){try{return JSON.parse(p.readFileSync(pe(),"utf8"))||{}}catch{return{}}}o(me,"readConfigFile");function we(t){const e=pe(),n=`${e}.${process.pid}.tmp`;p.writeFileSync(n,JSON.stringify(t,null,2),{mode:384}),p.renameSync(n,e)}o(we,"writeConfigFile");function ge(){const{username:t,password:e,tenantId:n,tenantConfirmed:r,...a}=me();if(Object.keys(a).length)we(a);else try{p.rmSync(pe())}catch{}}o(ge,"forgetCredentials");function lt(){ge();const t=me();if(typeof t.oauthAgentId=="string"&&/^[A-Za-z0-9._:-]{8,128}$/.test(t.oauthAgentId))return t.oauthAgentId;const e=M.randomUUID().replace(/-/g,"");return we({...t,oauthAgentId:e}),e}o(lt,"oauthAgentId");async function hn({tenant:t,switchAccount:e=!1,timeoutSeconds:n=300,browser:r}={}){const a=A();return qe(x(),{agentId:lt(),tenant:t,switchAccount:e,timeoutSeconds:n,browser:r,onAuthorized:o(async s=>{if(e&&a){const i=v(a.token).UserId,u=v(s.token).UserId;if(i&&u&&i===u)throw await V(x(),s).catch(()=>{}),new Error("授权后仍是原领谋账号;请在授权页切换账号后重试")}if(t)try{const i=await Pe(s),u=String(t).trim(),c=i.filter(f=>f.tenantId===u||f.code===u||f.name===u),d=c.length?c:i.filter(f=>f.name.includes(u));if(d.length!==1||d[0].tenantId!==s.tenantId)throw new Error(`授权的租户不是「${u}」,请在领谋授权页选择目标租户后重试`)}catch(i){throw await V(x(),s).catch(()=>{}),i}ge();const l=te(s);return a?.refreshToken&&await V(x(),a).catch(()=>{}),{session:l,tenantName:he(l).tenantName||l.tenantId}},"onAuthorized")})}o(hn,"oauthLogin");async function mn(){const t=A();if(!t?.refreshToken)return!1;try{return await V(x(),t)}catch{return!1}}o(mn,"oauthLogout");let $=null,ne=null;async function Fe(t){const e=`${ee()}.refresh.lock`,n=Date.now()+3e4;let r;for(;r===void 0;)try{r=p.openSync(e,"wx",384),p.writeFileSync(r,String(process.pid))}catch(a){if(a.code!=="EEXIST")throw a;try{const s=Number(p.readFileSync(e,"utf8"));if(Number.isInteger(s)&&s>0){let l=!0;try{process.kill(s,0)}catch(i){i.code==="ESRCH"&&(l=!1)}if(!l){p.rmSync(e);continue}}}catch{}if(Date.now()>=n)throw new Error("等待 OAuth 会话操作超时,请稍后重试");await new Promise(s=>setTimeout(s,150))}try{return await t()}finally{p.closeSync(r),p.rmSync(e)}}o(Fe,"withSessionLock");async function ct(t){return Fe(async()=>{const e=A();if(!e?.refreshToken)throw new k("OAuth 会话已在本机退出");return e.refreshToken!==t.refreshToken&&Y(e.token)>.05?($=e,e):te(await _e(x(),e))})}o(ct,"refreshOauthLocked");async function z({refresh:t=!1}={}){if(ge(),$&&!t&&Y($.token)>.05)return $;const e=$||A();if(e&&!t&&Y(e.token)>.05)return $=e,e;if(e?.authMode==="oauth"&&e.refreshToken)return ne||(ne=(async()=>{try{return await ct(e)}catch(n){if(["invalid_grant","invalid_client","oauth_exchange_unknown"].includes(n.code)){const r=A();if(r&&r.refreshToken!==e.refreshToken&&Y(r.token)>.05)return $=r,r;throw st(),new k(n.code==="oauth_exchange_unknown"?"OAuth 续期结果无法确认":"OAuth 授权已失效")}throw n}})().finally(()=>{ne=null})),ne;throw new k(e?"登录已过期":"尚未登录领谋 BI")}o(z,"ensureSession");async function Pe(t){t=t||await z();const e=v(t.token),n=await X(x(),t,"GET",`/api/app/account/user-bind-tenant-list/${e.UserId||e.sub||e.userId}`);if(n.code!==200)throw new k(`读取租户列表失败(${n.message||n.code})`);return(n.data||[]).map(r=>({tenantId:r.tenantId,name:r.spaceName||r.tenantName||"",code:r.tenantCode||""})).filter(r=>!dt(r))}o(Pe,"tenantList");function dt(t){return t.tenantId===b||t.code==="0000"||t.name==="全域租户"||t.name==="全域"}o(dt,"isPlatformTenant");async function wn(t,e){if(t?.authMode!=="oauth")throw new k("当前没有 OAuth 授权");return Fe(async()=>{let n=A();if(!n)throw new k("当前没有 OAuth 授权");if(v(n.token).UserId!==v(t.token).UserId)throw new Error("领谋账号已切换,请重新读取当前租户");Y(n.token)<=.05&&(n=te(await _e(x(),n)));const r=await Pe(n),a=String(e).trim(),s=r.filter(c=>c.tenantId===a||c.code===a||c.name===a),l=s.length?s:r.filter(c=>c.name.includes(a));if(l.length!==1)throw new Error(`租户「${a}」${l.length?"匹配到多个":"不存在"};可选:${r.map(c=>`${c.name}(${c.tenantId})`).join("、")}`);const i=l[0];if(i.tenantId===n.tenantId)return{session:n,tenant:i,switched:!1};const u=await Ve(x(),n,i.tenantId);return te(u),Z=null,await V(x(),n).catch(()=>{}),{session:u,tenant:i,switched:!0}})}o(wn,"switchTenant");let ye="";function gn(t){ye=String(t||"")}o(gn,"setClientVersion");function De(t){const[e,n=""]=String(t).split("?"),r=n?[...new URLSearchParams(n).keys()]:[];return{path:e,query:r.length?r:void 0}}o(De,"logPath");function ut(t){if(!t)return null;const e=v(t.token);return{userId:String(e.UserId||e.sub||e.userId||""),tenantId:t.tenantId,tenantCode:he(t).tenantCode}}o(ut,"identityOf");function R(t,e,n={}){N.emit("guard",{check:t,pass:e,...n})}o(R,"logGuard");async function X(t,e,n,r,a,{log:s=!0,signal:l}={}){const i=Date.now(),u=a!==void 0?JSON.stringify(a):void 0;s&&N.setIdentity(ut(e));let c;try{c=await rt(t.apiBase+r,{method:n,headers:{"X-LinkAI-Run-Id":N.currentRunId(),...ye?{"X-LinkAI-Version":ye}:{},Authorization:"Bearer "+e.token,Tenantid:e.tenantId,Agentid:e.agentId||"","Content-Type":"application/json"},...u!==void 0?{body:u}:{},...l?{signal:l}:{}})}catch(m){throw s&&N.emit("api.call",{method:n,...De(r),ok:!1,network:!0,ms:Date.now()-i,reqBytes:u?Buffer.byteLength(u):0,message:String(m.message).slice(0,300)}),m}const d=await c.text();let f;try{f=JSON.parse(d),f.__status=c.status}catch{f={code:c.status,message:`HTTP ${c.status} non-JSON: ${d.slice(0,200)}`,__status:c.status}}if(s){const m=c.status<400&&f.code===200;N.emit("api.call",{method:n,...De(r),status:c.status,code:f.code??null,ok:m,ms:Date.now()-i,reqBytes:u?Buffer.byteLength(u):0,resBytes:Buffer.byteLength(d),...m?{}:{message:String(f.message||"").slice(0,300)}})}return f}o(X,"rawFetch");const ve="/api/app/linkai-oplog/events";N.setTransport(async(t,e)=>{if(!A())return!1;const n=o(r=>r.__status===401||r.code===401||r.__status===406||r.code===406,"expired");try{let r=await z(),a=await X(x(),r,"POST",ve,{events:t},{log:!1,signal:e});return n(a)&&(r=await z({refresh:!0}),a=await X(x(),r,"POST",ve,{events:t},{log:!1,signal:e})),a.__status<400&&a.code===200?(N.saveServerConfig(a.data||{}),!0):!1}catch{return!1}});const ft=[/data-?source/i,/sql/i,/table-field/i,/table-info/i,/er-table/i,/field-info-by/i,/data-info-by/i,/index-block-data-argument/i,/copy-index-block-data/i,/\/export\/import/i,/task-sync/i,/data-warehouse/i,/authority-variable/i,/\/test\//i,/package/i,/subscribe/i,/distribute/i];function pt(t){let e=String(t);try{for(let r=0;r<3;r++)e=decodeURIComponent(e)}catch{}const n=ft.find(r=>r.test(e)||r.test(String(t)));if(n)throw R("api-blocklist",!1,{path:String(t).split("?")[0],rule:String(n)}),new Error(`Rejected: ${t} touches data sources / SQL / database structure, which this CLI does not allow. Use ds-create / ds-update for datasets; database changes must go through the LinkMore data team.`)}o(pt,"assertApiAllowed");const ht=10*6e4;async function mt(){try{return await B("cli-policy",ht,async()=>{const t=await E("GET","/linkai-kb/cli-policy",void 0,{raw:!0});if(t.code!==200||!Array.isArray(t.data?.blockedPathPrefixes))throw new Error("接口禁止列表不可用");return{prefixes:t.data.blockedPathPrefixes.map(String).filter(Boolean),revision:t.data.revision??null}})}catch(t){if(t instanceof k)throw t;return null}}o(mt,"cliPolicy");function wt(t){let e=String(t||"").trim();try{for(let n=0;n<3;n++)e=decodeURIComponent(e)}catch{}return e=e.split("?")[0].toLowerCase(),e.startsWith("/")||(e="/"+e),e.startsWith("/api/")||(e="/api/app"+e),e.replace(/\/+$/,"")}o(wt,"normalizeApiPath");function gt(t,e){const n=wt(t);return(e||[]).find(r=>{const a=String(r).trim().replace(/\/+$/,"").toLowerCase();return a&&(n===a||n.startsWith(`${a}/`))})??null}o(gt,"policyHit");async function yn(t){pt(t);const e=await mt(),n=e?gt(t,e.prefixes):null;if(n)throw R("api-blocklist",!1,{path:String(t).split("?")[0],rule:n,source:"server",revision:e.revision}),new T("CLI_FORBIDDEN",`LinkAI CLI 不能调用这个接口(${n})。问数请用 linkai ask;要补充或纠正口径,请告诉用户在领谋网页上维护。`)}o(yn,"assertApiAllowedByPolicy");async function E(t,e,n,{raw:r=!1}={}){const a=x();e=String(e).trim(),e.startsWith("/")||(e="/"+e),e.startsWith("/api/")||(e="/api/app"+e);try{Qe(t,e,n)}catch(c){throw R("fill-card-structure",!1,{path:e.split("?")[0],message:c.message.slice(0,300)}),c}try{let c;try{c=Ce(t,e,n)}catch(f){const m=/update-new/i.test(e)&&n?.id&&n.id!==b?n.id:null,w=m&&f.errors?await Ie(m).catch(()=>null):null;if(!w)throw f;c=Ce(t,e,n,{previous:w})}for(const f of c)N.addNotice(`卡片保存:${f}。`,"card-rules");let d;try{d=Ne(t,e,n)}catch(f){const m=n?.userPageSettingId&&Number(n.filterSourceType)!==2?n.userPageSettingId:null,w=m&&f.errors?(await Bt(m).catch(()=>null))?.filters:null;if(!w)throw f;d=Ne(t,e,n,{previous:w})}for(const f of d)N.addNotice(`筛选条件:${f}。`,"filter-rules")}catch(c){throw R(/filter/.test(e)?"filter-rules":"card-rules",!1,{path:e.split("?")[0],message:c.message.slice(0,300)}),new T("CONFIG_INVALID",c.message)}const s=Rt(t,e,n);if(s){Oe.assertWritable([Oe.normalizeObject(`card:${s}`)]);try{await Ut(s),R("tenant-ownership",!0,{target:s,path:e.split("?")[0]})}catch(c){throw c instanceof Be&&R("tenant-ownership",!1,{target:s,path:e.split("?")[0]}),c}}let l=await z(),i=await X(a,l,t,e,n);const u=o(c=>c.__status===401||c.code===401||c.__status===406||c.code===406,"expired");if(u(i)){if(!l.refreshToken)throw new k("OAuth 授权已失效");if(l=await z({refresh:!0}),i=await X(a,l,t,e,n),u(i))throw new k("自动续登后仍被拒绝")}if(delete i.__status,r)return i;if(i.code!==200)throw new re(t,e,i);return i.data}o(E,"api");class T extends Error{static{o(this,"KindError")}constructor(e,n){super(n),this.name="KindError",this.kind=e}}const yt=[["SQL_INVALID",/cannot be resolved|analyzing error|syntax|语法错误|Unknown column|doesn't exist/i],["IN_USE",/被.{0,40}引用.{0,20}请先删除/],["DUPLICATE_NAME",/名称不能重复|名称已存在|已存在同名/],["READ_ONLY",/系统报表不能更改|系统方案或大屏不能|系统分析方案|系统.{0,6}(只读|不能(更改|修改|新增|删除))/],["NOT_FOUND",/已被删除|不存在|Id有误|未获取到该信息|未找到该数据/],["FORBIDDEN",/私有|暂无权限|没有权限/],["BAD_REQUEST",/ModelState is not valid|Cannot deserialize/]];function Re(t){return String(t||"").split(/\s*STACK TRACE:/)[0].replace(/\s*\w*Exception:\s*/g," ").replace(/\s+/g," ").trim()}o(Re,"cleanApiMessage");class re extends Error{static{o(this,"ApiError")}constructor(e,n,r={}){const a=Re(r.message),s=Re(r.error&&r.error.details||""),l=s&&!a.includes(s)&&!s.includes(a)?`${a} ${s}`:s.length>a.length?s:a;super(`LMBI API error [${e} ${n.split("?")[0]}]: ${l.slice(0,400)}`),this.name="ApiError",this.path=n.split("?")[0],this.status=r.code,this.kind=r.errorCode==="CLI_FORBIDDEN"?"CLI_FORBIDDEN":yt.find(([,i])=>i.test(l))?.[0]||null}}const S=o((t,e)=>E("POST",t,e),"post"),U=o(t=>E("GET",t),"get");let K=null;async function ae(){if(K)return K;const t=x();let e=process.env.LM_DATASOURCE||t.dataSourceId||"",n=[];if(!e){if(n=await B("dataSources",30*6e4,async()=>{const a=await U("/index-block/index-block-data-source");return(Array.isArray(a)?a:a.items||[]).map(s=>({id:s.id,name:s.name}))}),!n.length)throw new Error("当前租户没有可用的数据源,请联系领谋开通");e=n[0].id}const r=await B("rootFolders",30*6e4,async()=>{const[a,s,l]=await Promise.all([U("/index-block/folders?type=0"),U("/index-block/folders?type=1"),U("/index-block/folders?type=3")]);return{planFolder:a[0]?.id||b,cardFolder:s[0]?.id||b,dsFolder:l[0]?.id||b}});return K={dataSourceId:e,dataSources:n,...r},K}o(ae,"ctx");function St(t){const e=t.replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ").replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/`/g,""),n=new Set([...e.matchAll(/(?:\bWITH(?:\s+RECURSIVE)?|,)\s*([A-Za-z_]\w*)\s*(?:\([^()]*\))?\s*AS\s*(?:NOT\s+)?(?:MATERIALIZED\s*)?\(/gi)].map(l=>l[1].toLowerCase())),a="([A-Za-z_][\\w.]*)(?:\\s+(?:AS\\s+)?(?!(?:WHERE|GROUP|ORDER|HAVING|LIMIT|UNION|JOIN|LEFT|RIGHT|INNER|CROSS|FULL|ON)\\b)[A-Za-z_]\\w*)?",s=[];for(const l of e.matchAll(new RegExp(`\\b(?:FROM|JOIN)\\s+${a}((?:\\s*,\\s*${a})*)`,"gi"))){s.push(l[1]);for(const i of(l[2]||"").matchAll(new RegExp(`,\\s*${a}`,"gi")))s.push(i[1])}return[...new Set(s)].filter(l=>!n.has(l.toLowerCase()))}o(St,"sqlTables");let G=null;async function bt(){if(G)return G;const t=await ae(),e=await B(`tables:${t.dataSourceId}`,10*6e4,async()=>[...new Set((await S(`/index-block/get-data-source-table-field/${t.dataSourceId}`)).map(n=>(n.table_name||"").toLowerCase()))]);return G=new Set(e),G}o(bt,"allowedTables");function It(t){const e=t.split(`
2
2
  `).filter(l=>l.trim()),n=t.length/Math.max(e.length,1);if(e.length>3&&n<100)return t;const r=[];let a=t.replace(/'(?:[^'\\]|\\.)*'/g,l=>`${r.push(l)-1}`);a=" "+a.replace(/\s+/g," ")+" ";const s=["SELECT","FROM","LEFT JOIN","RIGHT JOIN","INNER JOIN","JOIN","WHERE","GROUP BY","HAVING","ORDER BY","LIMIT","UNION ALL","UNION"];for(const l of s){const i=l.replace(" ","");a=a.replace(new RegExp(`\\s${l.replace(" ","\\s+")}\\s`,"gi"),`
3
3
  ${i} `)}return a=a.replace(/\u0002/g," "),a=a.replace(/\nSELECT ([^\n]*)/g,(l,i)=>{let u="",c=0;for(const d of i)d==="("&&c++,d===")"&&c--,u+=d,d===","&&c===0&&(u+=`
4
4
  `);return`
5
5
  SELECT
6
6
  `+u.trim()}),a=a.replace(/\u0001(\d+)\u0001/g,(l,i)=>r[+i]),a.trim()+`
7
- `}o(wt,"formatSql");function Se(t){const e=t.replace(/'(?:[^'\\]|\\.)*'/g,a=>" ".repeat(a.length)).replace(/\/\*[\s\S]*?\*\//g,a=>" ".repeat(a.length)).replace(/--[^\n]*/g,a=>" ".repeat(a.length)),n=[...e.matchAll(/;/g)].map(a=>a.index);if(!n.length)return t;const r=n[n.length-1];if(e.slice(r+1).trim()||n.length>1)throw new Error('Dataset SQL must be a single SELECT statement (found ";" inside)');return t.slice(0,r)+t.slice(r+1)}o(Se,"stripTrailingSemicolon");const gt=/^(information_schema|mysql|performance_schema|sys)\./i;function yt(t){const e=String(t).replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ").trim();if(!/^(SELECT|WITH)\b/i.test(e))throw new O("SQL_READONLY","Rejected: dataset SQL must be a single read-only SELECT (or WITH … SELECT) query");if(/\bINTO\s+(OUTFILE|DUMPFILE)\b/i.test(e))throw new O("SQL_READONLY","Rejected: SELECT … INTO OUTFILE/DUMPFILE is not allowed");Se(t)}o(yt,"assertReadOnlySql");function St(t){try{yt(t)}catch(e){throw B("sql-readonly",!1,{sqlSha256:C.sha256(String(t)),chars:String(t).length}),e}}o(St,"precheckSql");async function bt(t){St(t),t=Se(t);const e=ht(t),n=t.replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/`/g,""),r=[...new Set([...e.filter(a=>gt.test(a)),...[...n.matchAll(/\b(information_schema|mysql|performance_schema|sys)\s*\.\s*\w+/gi)].map(a=>a[0])])];if(r.length)throw new O("SQL_TABLE_NOT_ALLOWED",`Rejected: system schema access is not allowed (${r.join(", ")})`);if(e.length){const a=await mt(),s=e.filter(l=>!a.has(l.replace(/^[\w]+\./,"").toLowerCase()));if(s.length)throw new O("SQL_TABLE_NOT_ALLOWED",`Rejected: not in the space's table list (${s.join(", ")}). Only tenant views listed by \`linkai ds-tables\` can be used; to use another view, ask the LinkMore data team to expose it (this CLI cannot operate the database).`)}return t}o(bt,"localCheckSql");async function It(t){t=await bt(t);const e=await ae();return S("/index-block/get-field-info-by-data-set-sql",{dataSourceId:e.dataSourceId,sql:t,mode:2})}o(It,"checkSql");function fn(t){const e=String(t||"").replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ").replace(/'(?:[^'\\]|\\.|'')*'/g,"''");return/\boffset\b/i.test(e)||/\blimit\s+[^\s,;)]+\s*,/i.test(e)}o(fn,"hasOffset");const xt=2*36e5,Ue=o(()=>I.join(V(),`${v}.ask-xiaoman.json`),"xiaomanFile");function je(){try{const t=JSON.parse(p.readFileSync(Ue(),"utf8"));return Array.isArray(t)?t.filter(e=>Date.now()-e.at<xt):[]}catch{return[]}}o(je,"readXiaoman");function pn({askId:t,status:e,question:n}){try{const r=[{askId:t,status:e,question:String(n).slice(0,200),at:Date.now()},...je().filter(a=>a.askId!==t)].slice(0,50);p.writeFileSync(Ue(),JSON.stringify(r),{mode:384})}catch{}}o(pn,"recordXiaomanAttempt");function hn(t){return je().find(e=>e.askId===t)??null}o(hn,"xiaomanAttempt");async function mn(t){const e=await E("POST","/linkai-ask/ask",t,{raw:!0});if(K(e))throw new O("ASK_UNAVAILABLE","问数服务暂未开通(服务端接口尚未上线)");if(e.code!==200)throw new re("POST","/api/app/linkai-ask/ask",e);return e.data}o(mn,"askQuestion");async function wn(t){const e=await E("POST","/linkai-ask/query",t,{raw:!0});if(K(e))throw new O("ASK_UNAVAILABLE","问数服务暂未开通(服务端接口尚未上线)");if(e.code!==200)throw new re("POST","/api/app/linkai-ask/query",e);return e.data}o(wn,"askQuery");const kt=99,Et=2,Tt=o(t=>[...t].sort((e,n)=>Number(e.sort_num)-Number(n.sort_num)).map((e,n)=>{const r=["int","decimal"].includes(e.field_type);return{id:j.randomUUID(),tableFieldName:e.field_name,aliasCnName:e.field_name,fieldComment:"",argumentType:r?0:2,calculationMode:r?Et:kt,tableFieldType:r?"decimal":e.field_type,tableFieldTypeName:e.type_name,sortNum:n,sqlType:0,isCustomize:!1}}),"fieldColumns");async function gn({id:t,name:e,sql:n,remark:r,noFormat:a=!1,current:s=null}){const l=await ae(),i=t&&t!==b?s||await Ct(t):null;!a&&process.env.LM_NO_FMT!=="1"&&(n=wt(n)),n=Se(n);const u=await It(n);if(!u?.length)throw new O("SQL_INVALID","运行 SQL 没有返回任何字段,无法生成映射字段");const c=Date.now(),d=[{id:j.randomUUID(),tableName:`sql_name_${c}`,tableComment:`sql_comment_${c}`,sortNum:0,fieldColumns:Tt(u)}];try{await S("/index-block/get-data-set",{tables:d,joinOns:[],mode:2,dataSourceId:l.dataSourceId,isNewDataSet:!0,filters:[],sortFields:[],sql:n})}catch(f){throw f instanceof re?new O("SQL_INVALID",`数据集取数验证失败,未保存:${f.message}`):f}return S("/index-block-data-argument/add-or-update",{indexBlockDataId:t||b,isNewDataSet:!0,mode:2,dataSourceId:l.dataSourceId,sql:n,name:e,parentId:i?.parentId||l.dsFolder,tag:i?.tag??"",remark:r??i?.remark??"",isPrivate:i?.isPrivate??!1,joinOns:[],tables:d})}o(gn,"dsSave");async function Nt({withMeta:t=!1}={}){const n=[];for(let r=1;r<=200;r++){const a=await S("/index-block/get-index-block-data-folder-bussiness-info",{page:{pageIndex:r,pageSize:500}}),s=a.items||[];n.push(...s.map(i=>({name:i.name,dsId:i.objId,folderId:i.id,...t?{editTime:i.editTime||null,addUser:i.addUser||null}:{}})));const l=Number(a.totalCount??a.total??0);if(s.length<500||l&&n.length>=l)break}return n}o(Nt,"dsList");const Ct=o(t=>R(`/index-block-data-argument/data-set-detail/${t}`),"dsDetail"),Ot=o(t=>R(`/index-block-data-argument/data-set-fields/${t}`),"dsFields");async function yn(t){return(await R(`/index-block/data-set-quoted-infos/${t}`)||[]).map(n=>({name:n.name,cardId:n.indexBlockId}))}o(yn,"dsRefs");function Sn(){return Object.entries(Ae).sort(([t],[e])=>t.localeCompare(e)).map(([t,e])=>({kind:t,name:e._name||"",status:e._disabled?"disabled":e._minimal?"minimal":"full",tip:e._tip||""}))}o(Sn,"kinds");const _t={dim:{prefix:"waxis",axis:"",argumentType:2},meas:{prefix:"yaxis",axis:"y1",argumentType:1},series:{prefix:"waxis_type",axis:"",argumentType:3}};function oe({field:t,col:e,axis:n="",argumentType:r=2,tableFieldType:a="string",title:s,orderByType:l=0,i=0}){return{id:j.randomUUID(),format:"default",axis:n,field:t,title:s??e,sortNum:i,calculationMode:0,seniorMode:0,periods:0,periodType:0,sqlText:"",comment:e,argumentType:r,orderByType:l,tableFieldId:b,tableFieldGroupId:b,periodsWithTableFieldId:b,decimal:0,thousandSeparator:!0,unit:"",tableFieldType:a,isCustomize:!1,isAggregation:!1,isUseSnow:!1,parent:null,nullZero:!1,isVirtual:!1,isFill:!1,uniqueId:j.randomUUID(),isCustomizeFill:!1,isInnerFill:!1,fieldDesc:""}}o(oe,"baseDto");const At=o(t=>({fieldExpression:"",aliasName:t.title,sortNum:t.sortNum,argumentType:t.argumentType,orderByType:t.orderByType,sqlText:"",tableFieldId:b,tableFieldName:null,tableFieldComment:t.comment,tableFieldType:t.tableFieldType,isCustomize:!1,fieldExpressionDesc:"",isAggregation:!1,isVirtual:!1,isFill:!1,uniqueId:t.uniqueId,isCustomizeFill:!1,isInnerFill:!1,isNeedTransferJson:!1,jsonFatherId:b,children:null}),"orderField");async function ie({dsId:t,name:e,remark:n,chartParentType:r,chartType:a,dtos:s,chartSetting:l,limitCount:i=500,id:u}){const c=await ae(),d=u&&u!==b?await Ie(u):null,f={indexBlockDataId:t,isMulData:!1,indexBlockShowOrderFields:s.map(At),filters:[],isFreeFill:!1,id:u||b,indexBlockType:0,name:e,tag:d?.tag??"",remark:n??d?.remark??"",parentId:d?.parentId||c.cardFolder,isPrivate:d?.isPrivate??!1,chartParentType:r,chartType:a,limitCount:i,indexBlockDataSettingDtos:s,chartSetting:JSON.stringify(l)};let m=u;return(!m||m===b)&&(m=await S("/index-block/new",f)),f.id=m,await S("/index-block/update-new",f),{cardId:m,name:e,chartType:`${r}/${a}`}}o(ie,"saveCard");async function be(t){const e=await Ot(t);return{fields:e,meta:Object.fromEntries(e.map(n=>[n.tableFieldComment,n]))}}o(be,"datasetMeta");async function bn({dsId:t,name:e,kind:n,dim:r,meas:a,series:s,id:l}){const i=Ae[n];if(!i)throw new Error(`Chart kind "${n}" is not in the v2 chart registry; run: linkai kinds`);if(i._disabled)throw new Error(`Chart kind "${n}" (${i._name}) is disabled: ${i._disabled}`);const{fields:u,meta:c}=await be(t),d=o((T,L,H,h)=>{const g=c[H];if(!g)throw new Error(`Dataset has no column "${H}". Available: ${u.map(F=>F.tableFieldComment).join(" / ")}`);const y=_t[T];return oe({field:y.prefix+L,col:H,axis:y.axis,argumentType:y.argumentType,tableFieldType:g.tableFieldType,i:h})},"dto"),f=String(r||"").split(",").filter(Boolean),m=String(a||"").split(",").filter(Boolean);if(!m.length)throw new Error("At least one measure column is required");const w=f.map((T,L)=>d("dim",L+1,T,L));return m.forEach((T,L)=>w.push(d("meas",L+1,T,w.length))),s&&w.push(d("series",1,s,w.length)),ie({dsId:t,name:e,chartParentType:i.chartParentType,chartType:i.chartType,dtos:w,chartSetting:i.chartSetting,limitCount:0,id:l})}o(bn,"cardChart");async function In({dsId:t,name:e,col:n,subtitle:r,color:a="#E8611C",id:s}){const l=[oe({field:"yaxis1",col:n,axis:"y1",argumentType:1,tableFieldType:"decimal",title:r||n})];return ie({dsId:t,name:e,chartParentType:"meter",chartType:"list",dtos:l,chartSetting:{id:"list",parentId:"meter",config:{group:{type:"value",groups:[]},content:{type:"main",position:"left",bgColor:"lucency",icon:{show:!1,img:"",postion:"left"},labelText:{fontSize:12,fill:"rgb(153,153,153)"},valueText:{fontSize:14,fontWeight:800,fill:"rgba(0,0,0,0.85)"},labelTextMain:{fontSize:12,fill:"rgb(153,153,153)",fontStyle:"italic"},valueTextMain:{fontSize:30,fontWeight:800,fill:a},textPadding:0}}},id:s})}o(In,"cardMeter");async function xn({dsId:t,spec:e,id:n}){const{meta:r}=await be(t),a={},s=[];let l=0;const i=o(h=>{if(a[h])return a[h];const g=r[h];if(!g)throw new Error(`Dataset has no column "${h}". Available: ${Object.keys(r).join(" / ")}`);const y=`waxis${++l}`;return s.push(oe({field:y,col:h,argumentType:g.argumentType,tableFieldType:g.tableFieldType,i:l-1})),a[h]=y,y},"reg"),u=o(()=>j.randomUUID(),"u"),c=o(h=>({fontSize:12,fill:"#1F2329",...h}),"st"),d={b:c({fontSize:15,fontWeight:600,lineHeight:22}),h:c({fontSize:13,fontWeight:600,lineHeight:20}),grey:c({fill:"#8F959E",lineHeight:18}),"":c({lineHeight:18})},f=o(()=>({id:u(),type:"labelValue",field:"",layout:"left",arrangement:"horizontal",fieldStyle:d[""],valueStyle:d[""],showLabel:!1,iconPosition:"left",labelValueGap:4,showColon:!1}),"emptyItem"),m=o((h,g,y)=>({id:"column_"+u(),field:[h],fixed:!1,enableCustomRender:!1,areas:[{id:u(),width:null,rows:[{id:u(),itemGap:10,items:[f()]}]}],name:g,contentAlign:"left",spanRows:!1,hidden:y,isPivot:!1,childPivotFields:[],topValueField:!1}),"plainColumn"),w=[];for(const h of e.columns||[]){if(h.plain){w.push(m(i(h.plain),h.header,!1));continue}const g=[],y=[],F=o(N=>{const[_,D=""]=N.split(":"),ke=i(_);g.push(ke);const ue=D==="image",Ee=d[ue?"":D]||d[""],Te={id:u(),type:ue?"image":"labelValue",field:ke,layout:"left",arrangement:"horizontal",fieldStyle:Ee,valueStyle:Ee,showLabel:!1,iconPosition:"left",labelValueGap:4,showColon:!1};return ue&&(Te.imgWidth=h.imgWidth||112),Te},"mkItem");h.image&&y.push({id:u(),width:(h.imgWidth||112)+16,rows:[{id:u(),itemGap:10,items:[F(`${h.image}:image`)]}]}),y.push({id:u(),width:null,rows:(h.rows||[]).map(N=>({id:u(),itemGap:10,items:(Array.isArray(N)?N:[N]).map(F)}))}),w.push({id:"column_"+u(),field:g,fixed:!1,enableCustomRender:!0,areas:y,name:h.header,contentAlign:"left",spanRows:!1,hidden:!1,isPivot:!1,childPivotFields:[],topValueField:!1})}for(const h of e.hidden||[])w.push(m(i(h),h,!0));const T=o(h=>(e[h]||[]).flatMap(g=>{const y=a[g.dep]||i(g.dep),F=a[g.col]||i(g.col),N=[];for(const[_,D]of Object.entries(g.eq||{}))N.push({field:F,color:D,depField:y,operator:"==",value:isNaN(+_)?_:+_});for(const[_,D]of Object.entries(g.gte||{}))N.push({field:F,color:D,depField:y,operator:">=",value:+_});for(const[_,D]of Object.entries(g.ne||{}))N.push({field:F,color:D,depField:y,operator:"!=",value:_});return N}),"conds"),L=e.rowGrey?[{field:"",color:"#F7F8FA",depField:a[e.rowGrey.dep]||i(e.rowGrey.dep),operator:">=",value:e.rowGrey.gte}]:[];e.order&&i(e.order);const H={id:"agtable",parentId:"table",config:{sheetType:"table",showPagination:!0,pagination:{pageSize:e.pageSize||20},fitTableWidth:e.fitTableWidth!==!1,showHeader:!0,headerHeight:36,rowHeight:e.rowHeight||150,imgHeight:e.imgHeight||120,showLineNumber:!1,lineNumberTextAlign:"left",actionColTextAlign:"left",cellTextAlign:"left",cellBorder:{visible:!0,color:"#EDEFF2",width:1},total:{show:!1,labelColumn:"",labelText:"总计",rowtotalType:"all"},agColumnWidths:(e.columns||[]).map(h=>({key:w.find(g=>g.name===h.header).field[0],width:h.width||200})),conditionStyle:{value:{conditions:T("pills")},arrow:{conditions:[]},fontColor:{conditions:T("fontColor")},rowMark:{conditions:L},cellMark:{conditions:[]}},bgMark:{visible:!1,fields:[]},mobileViewFields:[],report:{visible:!1,fillField:{fields:[],fillFieldProperties:[]}},syncReport:{visible:!1,syncConfig:{webHookUrl:"",webHookId:"",relations:[],fields:[]}},wd:{rows:[],values:[],columns:w}}};return ie({dsId:t,name:e.name,remark:e.remark,chartParentType:"table",chartType:"agtable",dtos:s,chartSetting:H,limitCount:e.limitCount||2e3,id:n})}o(xn,"cardTable");async function kn({dsId:t,spec:e,id:n}){const{meta:r}=await be(t),a={},s=[];let l=0;const i=o(c=>{if(!c)return"";if(a[c])return a[c];const d=r[c];if(!d)throw new Error(`Dataset has no column "${c}". Available: ${Object.keys(r).join(" / ")}`);const f=`waxis${++l}`;return s.push(oe({field:f,col:c,argumentType:d.argumentType,tableFieldType:d.tableFieldType,i:l-1})),a[c]=f,f},"reg"),u={id:"gantt",parentId:"table",config:{ganttRenderConfig:{showIcon:!0,style:{image:i(e.image),main:i(e.main),assist:i(e.assist),status:i(e.status),tags:(e.tags||[]).map(i),desc:(e.desc||[]).map(i),list:(e.list||[]).map(i),progress:i(e.progress),taskBar:{textFields:(e.taskBarText||[]).map(i)},fieldMap:{startDate:i(e.start),endDate:i(e.end),plannedStart:i(e.plannedStart),plannedEnd:i(e.plannedEnd)}},dynamic:{main:[],levels:{}}},conditionStyle:{value:{conditions:[]},arrow:{conditions:[]},fontColor:{conditions:[]}},ganttGridTitle:e.title||"任务",ganttGridMinWidth:452,projectRowHeight:e.rowHeight||202,defaultZoomLevel:e.zoom||"day",pagination:{pageSize:e.pageSize||20},showPagination:!1}};return e.order&&i(e.order),ie({dsId:t,name:e.name,remark:e.remark,chartParentType:"table",chartType:"gantt",dtos:s,chartSetting:u,id:n})}o(kn,"cardGantt");async function Me(){const e=[];for(let n=1;n<=200;n++){const r=await S("/index-block/get-index-block-folder-bussiness-info",{page:{pageIndex:n,pageSize:500}}),a=r.items||[];e.push(...a.map(l=>({name:l.name,cardId:l.objId,folderId:l.id})));const s=Number(r.totalCount??r.total??0);if(a.length<500||s&&e.length>=s)break}return e}o(Me,"cardList");class Be extends Error{static{o(this,"TenantMismatchError")}constructor(e,n){super(`卡片 ${e} 不属于当前租户${n?`「${n}」`:""},已拒绝写入(写进去的数据会落在当前租户名下)`),this.name="TenantMismatchError",this.cardId=e}}const $t=/\/index-block\/(?:update-new|new-fill-field|add-fill-field|fill-field|add-fill-field-by-s-c-m|new-import-fill-data|import-fill-data)(?:\?|$)/i;let Z=null;function Lt(t,e,n){if(String(t).toUpperCase()==="GET"||!$t.test(String(e))||!n||typeof n!="object"||Array.isArray(n))return"";const r=o((...a)=>{for(const s of a){const l=Object.keys(n).find(i=>i.toLowerCase()===s);if(l&&n[l])return String(n[l])}return""},"pick");return/update-new/i.test(e)?r("id"):r("usedindexblockid")}o(Lt,"tenantGuardTarget");async function Ft(t){if(!t)return;const e=String(t).toLowerCase(),n=o(async()=>{Z=new Set((await Me()).map(r=>String(r.cardId).toLowerCase()))},"load");if(Z?Z.has(e)||await n():await n(),!Z.has(e))throw new Be(t,he(await M()).tenantName)}o(Ft,"assertCardInTenant");const Ie=o(t=>S("/index-block/get-new-index-block-detail",{id:t}),"cardDetail");async function En(t,e){let n;if(e==="clear")n=[];else if(Array.isArray(e))n=e;else{const s=(await Ie(t)).indexBlockDataSettingDtos||[];n=String(e).split(",").map(l=>l.trim()).filter(Boolean).map(l=>{const[i,u="asc"]=l.split(":"),c=s.find(d=>d.field===i)||s.find(d=>d.comment===i);if(!c)throw new Error(`Card has no field "${i}". Available: ${s.map(d=>`${d.field}=${d.comment}`).join(" / ")}`);if(!["asc","desc"].includes(u.toLowerCase()))throw new Error(`Sort direction must be asc or desc: ${l}`);return{field:c.field,type:1,sort:u.toLowerCase(),sortFields:[]}})}const r=n.map((s,l)=>({type:1,sortFields:[],...s,sortNum:l}));await E("PUT","/page-setting/page-card-index-block-sort-field",{usedIndexBlockId:t,sortField:r});const a=await Dt({cardId:t,pageSize:1});return{cardId:t,requested:r.map(s=>`${s.field} ${s.type===2?"manual":s.sort}`),sqlHasOrderBy:/\bORDER\s+BY\s+(?:W\.)?(?:waxis|yaxis|waxis_type|order)\w*/i.test(a.sql||""),queryCode:a.code}}o(En,"cardSort");async function Dt({cardId:t,pageSize:e=20,filters:n=[]}){const r=await E("POST","/page-setting/get-new-index-block-data",{usedIndexBlockId:t,filters:n,page:{pageIndex:1,pageSize:+e}},{raw:!0}),a=Array.isArray(r.data)?r.data:[];return{code:r.code,message:r.code===200?"":r.message,total:r.dataCount,rows:a.length,columns:a[0]?Object.keys(a[0]):[],sql:r.extendData?.sql||"",data:a}}o(Dt,"queryCard");async function vt(){return((await S("/index-block/get-page-setting-folder-bussiness-info",{isOnlySeeSelf:!1,page:{pageIndex:1,pageSize:500}})).items||[]).map(e=>({name:e.name,planId:e.objId,folderId:e.id}))}o(vt,"planList");const Pt=o((t,e=!0)=>S(`/page-setting/get-new-page-plan/${t}?isEdit=${e}`,{}),"planDetail");async function Tn({planId:t,cardId:e,x:n=0,y:r=0,w:a=24,h:s=20}){const l=await S("/page-setting/new-page-card",{indexBlockId:e,sourceType:0,x:+n,y:+r,width:+a,height:+s,mobileX:0,mobileY:+r,mobileWidth:4,mobileHeight:+s,cardType:1,bindFilterIds:[],userPageSettingId:t});return{pageCardId:l.id,x:l.x,y:l.y,width:l.width,height:l.height}}o(Tn,"planAdd");async function Nn(t,e){if(e.isTenantSystem!==!0)throw new Error("Plan filters must be saved with isTenantSystem:true (tenant template); see references/filters.md");await S("/page-setting/batch-add-or-update-filter",e);const n=await S("/page-setting/reset-user-filter",{userPageSettingId:t,filterSourceType:4});return{planId:t,filters:(n||[]).map(r=>r.labelName)}}o(Nn,"planFilter");async function Rt(){return((await S("/index-block/get-index-block-report-folder-bussiness-info",{})).items||[]).map(e=>({name:e.name,cardId:e.indexBlockId,reportId:e.objId,folderId:e.id}))}o(Rt,"reportList");const Cn=o(t=>S("/page-setting/get-new-page-card-report",{indexBlockId:t,isSubWarn:!1}),"reportDetail");async function On(t){if(!t||!t.startsWith("zz-"))throw new Error('clean only accepts prefixes starting with "zz-"');const e={},n=o(async(r,a)=>{e[r]=a.length,a.length&&await S("/index-block/delete-folder-bussiness",a)},"del");return await n("reports",(await Rt()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("plans",(await vt()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("cards",(await Me()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("datasets",(await Nt()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),{prefix:t,removed:e}}o(On,"cleanPrefix");function _n(t){return`(() => { const s = ${JSON.stringify({t:t.token,c:t.tenantId,a:t.agentId})};
7
+ `}o(It,"formatSql");function Se(t){const e=t.replace(/'(?:[^'\\]|\\.)*'/g,a=>" ".repeat(a.length)).replace(/\/\*[\s\S]*?\*\//g,a=>" ".repeat(a.length)).replace(/--[^\n]*/g,a=>" ".repeat(a.length)),n=[...e.matchAll(/;/g)].map(a=>a.index);if(!n.length)return t;const r=n[n.length-1];if(e.slice(r+1).trim()||n.length>1)throw new Error('Dataset SQL must be a single SELECT statement (found ";" inside)');return t.slice(0,r)+t.slice(r+1)}o(Se,"stripTrailingSemicolon");const xt=/^(information_schema|mysql|performance_schema|sys)\./i;function kt(t){const e=String(t).replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ").trim();if(!/^(SELECT|WITH)\b/i.test(e))throw new T("SQL_READONLY","Rejected: dataset SQL must be a single read-only SELECT (or WITH … SELECT) query");if(/\bINTO\s+(OUTFILE|DUMPFILE)\b/i.test(e))throw new T("SQL_READONLY","Rejected: SELECT … INTO OUTFILE/DUMPFILE is not allowed");Se(t)}o(kt,"assertReadOnlySql");function Et(t){try{kt(t)}catch(e){throw R("sql-readonly",!1,{sqlSha256:N.sha256(String(t)),chars:String(t).length}),e}}o(Et,"precheckSql");async function Tt(t){Et(t),t=Se(t);const e=St(t),n=t.replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/`/g,""),r=[...new Set([...e.filter(a=>xt.test(a)),...[...n.matchAll(/\b(information_schema|mysql|performance_schema|sys)\s*\.\s*\w+/gi)].map(a=>a[0])])];if(r.length)throw new T("SQL_TABLE_NOT_ALLOWED",`Rejected: system schema access is not allowed (${r.join(", ")})`);if(e.length){const a=await bt(),s=e.filter(l=>!a.has(l.replace(/^[\w]+\./,"").toLowerCase()));if(s.length)throw new T("SQL_TABLE_NOT_ALLOWED",`Rejected: not in the space's table list (${s.join(", ")}). Only tenant views listed by \`linkai ds-tables\` can be used; to use another view, ask the LinkMore data team to expose it (this CLI cannot operate the database).`)}return t}o(Tt,"localCheckSql");async function _t(t){t=await Tt(t);const e=await ae();return S("/index-block/get-field-info-by-data-set-sql",{dataSourceId:e.dataSourceId,sql:t,mode:2})}o(_t,"checkSql");function Sn(t){const e=String(t||"").replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ").replace(/'(?:[^'\\]|\\.|'')*'/g,"''");return/\boffset\b/i.test(e)||/\blimit\s+[^\s,;)]+\s*,/i.test(e)}o(Sn,"hasOffset");const Ct=2*36e5,Ue=o(()=>I.join(q(),`${D}.ask-xiaoman.json`),"xiaomanFile");function je(){try{const t=JSON.parse(p.readFileSync(Ue(),"utf8"));return Array.isArray(t)?t.filter(e=>Date.now()-e.at<Ct):[]}catch{return[]}}o(je,"readXiaoman");function bn({askId:t,status:e,question:n}){try{const r=[{askId:t,status:e,question:String(n).slice(0,200),at:Date.now()},...je().filter(a=>a.askId!==t)].slice(0,50);p.writeFileSync(Ue(),JSON.stringify(r),{mode:384})}catch{}}o(bn,"recordXiaomanAttempt");function In(t){return je().find(e=>e.askId===t)??null}o(In,"xiaomanAttempt");async function xn(t){const e=await E("POST","/linkai-ask/ask",t,{raw:!0});if(W(e))throw new T("ASK_UNAVAILABLE","问数服务暂未开通(服务端接口尚未上线)");if(e.code!==200)throw new re("POST","/api/app/linkai-ask/ask",e);return e.data}o(xn,"askQuestion");async function kn(t){const e=await E("POST","/linkai-ask/query",t,{raw:!0});if(W(e))throw new T("ASK_UNAVAILABLE","问数服务暂未开通(服务端接口尚未上线)");if(e.code!==200)throw new re("POST","/api/app/linkai-ask/query",e);return e.data}o(kn,"askQuery");const Nt=99,Ot=2,At=o(t=>[...t].sort((e,n)=>Number(e.sort_num)-Number(n.sort_num)).map((e,n)=>{const r=["int","decimal"].includes(e.field_type);return{id:M.randomUUID(),tableFieldName:e.field_name,aliasCnName:e.field_name,fieldComment:"",argumentType:r?0:2,calculationMode:r?Ot:Nt,tableFieldType:r?"decimal":e.field_type,tableFieldTypeName:e.type_name,sortNum:n,sqlType:0,isCustomize:!1}}),"fieldColumns");async function En({id:t,name:e,sql:n,remark:r,noFormat:a=!1,current:s=null}){const l=await ae(),i=t&&t!==b?s||await Lt(t):null;!a&&process.env.LM_NO_FMT!=="1"&&(n=It(n)),n=Se(n);const u=await _t(n);if(!u?.length)throw new T("SQL_INVALID","运行 SQL 没有返回任何字段,无法生成映射字段");const c=Date.now(),d=[{id:M.randomUUID(),tableName:`sql_name_${c}`,tableComment:`sql_comment_${c}`,sortNum:0,fieldColumns:At(u)}];try{await S("/index-block/get-data-set",{tables:d,joinOns:[],mode:2,dataSourceId:l.dataSourceId,isNewDataSet:!0,filters:[],sortFields:[],sql:n})}catch(f){throw f instanceof re?new T("SQL_INVALID",`数据集取数验证失败,未保存:${f.message}`):f}return S("/index-block-data-argument/add-or-update",{indexBlockDataId:t||b,isNewDataSet:!0,mode:2,dataSourceId:l.dataSourceId,sql:n,name:e,parentId:i?.parentId||l.dsFolder,tag:i?.tag??"",remark:r??i?.remark??"",isPrivate:i?.isPrivate??!1,joinOns:[],tables:d})}o(En,"dsSave");async function $t({withMeta:t=!1}={}){const n=[];for(let r=1;r<=200;r++){const a=await S("/index-block/get-index-block-data-folder-bussiness-info",{page:{pageIndex:r,pageSize:500}}),s=a.items||[];n.push(...s.map(i=>({name:i.name,dsId:i.objId,folderId:i.id,...t?{editTime:i.editTime||null,addUser:i.addUser||null}:{}})));const l=Number(a.totalCount??a.total??0);if(s.length<500||l&&n.length>=l)break}return n}o($t,"dsList");const Lt=o(t=>U(`/index-block-data-argument/data-set-detail/${t}`),"dsDetail"),Ft=o(t=>U(`/index-block-data-argument/data-set-fields/${t}`),"dsFields");async function Tn(t){return(await U(`/index-block/data-set-quoted-infos/${t}`)||[]).map(n=>({name:n.name,cardId:n.indexBlockId}))}o(Tn,"dsRefs");function _n(){return Object.entries(Ae).sort(([t],[e])=>t.localeCompare(e)).map(([t,e])=>({kind:t,name:e._name||"",status:e._disabled?"disabled":e._minimal?"minimal":"full",tip:e._tip||""}))}o(_n,"kinds");const Pt={dim:{prefix:"waxis",axis:"",argumentType:2},meas:{prefix:"yaxis",axis:"y1",argumentType:1},series:{prefix:"waxis_type",axis:"",argumentType:3}};function oe({field:t,col:e,axis:n="",argumentType:r=2,tableFieldType:a="string",title:s,orderByType:l=0,i=0}){return{id:M.randomUUID(),format:"default",axis:n,field:t,title:s??e,sortNum:i,calculationMode:0,seniorMode:0,periods:0,periodType:0,sqlText:"",comment:e,argumentType:r,orderByType:l,tableFieldId:b,tableFieldGroupId:b,periodsWithTableFieldId:b,decimal:0,thousandSeparator:!0,unit:"",tableFieldType:a,isCustomize:!1,isAggregation:!1,isUseSnow:!1,parent:null,nullZero:!1,isVirtual:!1,isFill:!1,uniqueId:M.randomUUID(),isCustomizeFill:!1,isInnerFill:!1,fieldDesc:""}}o(oe,"baseDto");const Dt=o(t=>({fieldExpression:"",aliasName:t.title,sortNum:t.sortNum,argumentType:t.argumentType,orderByType:t.orderByType,sqlText:"",tableFieldId:b,tableFieldName:null,tableFieldComment:t.comment,tableFieldType:t.tableFieldType,isCustomize:!1,fieldExpressionDesc:"",isAggregation:!1,isVirtual:!1,isFill:!1,uniqueId:t.uniqueId,isCustomizeFill:!1,isInnerFill:!1,isNeedTransferJson:!1,jsonFatherId:b,children:null}),"orderField");async function ie({dsId:t,name:e,remark:n,chartParentType:r,chartType:a,dtos:s,chartSetting:l,limitCount:i=500,id:u}){const c=await ae(),d=u&&u!==b?await Ie(u):null,f={indexBlockDataId:t,isMulData:!1,indexBlockShowOrderFields:s.map(Dt),filters:[],isFreeFill:!1,id:u||b,indexBlockType:0,name:e,tag:d?.tag??"",remark:n??d?.remark??"",parentId:d?.parentId||c.cardFolder,isPrivate:d?.isPrivate??!1,chartParentType:r,chartType:a,limitCount:i,indexBlockDataSettingDtos:s,chartSetting:JSON.stringify(l)};let m=u;return(!m||m===b)&&(m=await S("/index-block/new",f)),f.id=m,await S("/index-block/update-new",f),{cardId:m,name:e,chartType:`${r}/${a}`}}o(ie,"saveCard");async function be(t){const e=await Ft(t);return{fields:e,meta:Object.fromEntries(e.map(n=>[n.tableFieldComment,n]))}}o(be,"datasetMeta");async function Cn({dsId:t,name:e,kind:n,dim:r,meas:a,series:s,id:l}){const i=Ae[n];if(!i)throw new Error(`Chart kind "${n}" is not in the v2 chart registry; run: linkai kinds`);if(i._disabled)throw new Error(`Chart kind "${n}" (${i._name}) is disabled: ${i._disabled}`);const{fields:u,meta:c}=await be(t),d=o((_,L,J,h)=>{const g=c[J];if(!g)throw new Error(`Dataset has no column "${J}". Available: ${u.map(F=>F.tableFieldComment).join(" / ")}`);const y=Pt[_];return oe({field:y.prefix+L,col:J,axis:y.axis,argumentType:y.argumentType,tableFieldType:g.tableFieldType,i:h})},"dto"),f=String(r||"").split(",").filter(Boolean),m=String(a||"").split(",").filter(Boolean);if(!m.length)throw new Error("At least one measure column is required");const w=f.map((_,L)=>d("dim",L+1,_,L));return m.forEach((_,L)=>w.push(d("meas",L+1,_,w.length))),s&&w.push(d("series",1,s,w.length)),ie({dsId:t,name:e,chartParentType:i.chartParentType,chartType:i.chartType,dtos:w,chartSetting:i.chartSetting,limitCount:0,id:l})}o(Cn,"cardChart");async function Nn({dsId:t,name:e,col:n,subtitle:r,color:a="#E8611C",id:s}){const l=[oe({field:"yaxis1",col:n,axis:"y1",argumentType:1,tableFieldType:"decimal",title:r||n})];return ie({dsId:t,name:e,chartParentType:"meter",chartType:"list",dtos:l,chartSetting:{id:"list",parentId:"meter",config:{group:{type:"value",groups:[]},content:{type:"main",position:"left",bgColor:"lucency",icon:{show:!1,img:"",postion:"left"},labelText:{fontSize:12,fill:"rgb(153,153,153)"},valueText:{fontSize:14,fontWeight:800,fill:"rgba(0,0,0,0.85)"},labelTextMain:{fontSize:12,fill:"rgb(153,153,153)",fontStyle:"italic"},valueTextMain:{fontSize:30,fontWeight:800,fill:a},textPadding:0}}},id:s})}o(Nn,"cardMeter");async function On({dsId:t,spec:e,id:n}){const{meta:r}=await be(t),a={},s=[];let l=0;const i=o(h=>{if(a[h])return a[h];const g=r[h];if(!g)throw new Error(`Dataset has no column "${h}". Available: ${Object.keys(r).join(" / ")}`);const y=`waxis${++l}`;return s.push(oe({field:y,col:h,argumentType:g.argumentType,tableFieldType:g.tableFieldType,i:l-1})),a[h]=y,y},"reg"),u=o(()=>M.randomUUID(),"u"),c=o(h=>({fontSize:12,fill:"#1F2329",...h}),"st"),d={b:c({fontSize:15,fontWeight:600,lineHeight:22}),h:c({fontSize:13,fontWeight:600,lineHeight:20}),grey:c({fill:"#8F959E",lineHeight:18}),"":c({lineHeight:18})},f=o(()=>({id:u(),type:"labelValue",field:"",layout:"left",arrangement:"horizontal",fieldStyle:d[""],valueStyle:d[""],showLabel:!1,iconPosition:"left",labelValueGap:4,showColon:!1}),"emptyItem"),m=o((h,g,y)=>({id:"column_"+u(),field:[h],fixed:!1,enableCustomRender:!1,areas:[{id:u(),width:null,rows:[{id:u(),itemGap:10,items:[f()]}]}],name:g,contentAlign:"left",spanRows:!1,hidden:y,isPivot:!1,childPivotFields:[],topValueField:!1}),"plainColumn"),w=[];for(const h of e.columns||[]){if(h.plain){w.push(m(i(h.plain),h.header,!1));continue}const g=[],y=[],F=o(C=>{const[O,P=""]=C.split(":"),ke=i(O);g.push(ke);const ue=P==="image",Ee=d[ue?"":P]||d[""],Te={id:u(),type:ue?"image":"labelValue",field:ke,layout:"left",arrangement:"horizontal",fieldStyle:Ee,valueStyle:Ee,showLabel:!1,iconPosition:"left",labelValueGap:4,showColon:!1};return ue&&(Te.imgWidth=h.imgWidth||112),Te},"mkItem");h.image&&y.push({id:u(),width:(h.imgWidth||112)+16,rows:[{id:u(),itemGap:10,items:[F(`${h.image}:image`)]}]}),y.push({id:u(),width:null,rows:(h.rows||[]).map(C=>({id:u(),itemGap:10,items:(Array.isArray(C)?C:[C]).map(F)}))}),w.push({id:"column_"+u(),field:g,fixed:!1,enableCustomRender:!0,areas:y,name:h.header,contentAlign:"left",spanRows:!1,hidden:!1,isPivot:!1,childPivotFields:[],topValueField:!1})}for(const h of e.hidden||[])w.push(m(i(h),h,!0));const _=o(h=>(e[h]||[]).flatMap(g=>{const y=a[g.dep]||i(g.dep),F=a[g.col]||i(g.col),C=[];for(const[O,P]of Object.entries(g.eq||{}))C.push({field:F,color:P,depField:y,operator:"==",value:isNaN(+O)?O:+O});for(const[O,P]of Object.entries(g.gte||{}))C.push({field:F,color:P,depField:y,operator:">=",value:+O});for(const[O,P]of Object.entries(g.ne||{}))C.push({field:F,color:P,depField:y,operator:"!=",value:O});return C}),"conds"),L=e.rowGrey?[{field:"",color:"#F7F8FA",depField:a[e.rowGrey.dep]||i(e.rowGrey.dep),operator:">=",value:e.rowGrey.gte}]:[];e.order&&i(e.order);const J={id:"agtable",parentId:"table",config:{sheetType:"table",showPagination:!0,pagination:{pageSize:e.pageSize||20},fitTableWidth:e.fitTableWidth!==!1,showHeader:!0,headerHeight:36,rowHeight:e.rowHeight||150,imgHeight:e.imgHeight||120,showLineNumber:!1,lineNumberTextAlign:"left",actionColTextAlign:"left",cellTextAlign:"left",cellBorder:{visible:!0,color:"#EDEFF2",width:1},total:{show:!1,labelColumn:"",labelText:"总计",rowtotalType:"all"},agColumnWidths:(e.columns||[]).map(h=>({key:w.find(g=>g.name===h.header).field[0],width:h.width||200})),conditionStyle:{value:{conditions:_("pills")},arrow:{conditions:[]},fontColor:{conditions:_("fontColor")},rowMark:{conditions:L},cellMark:{conditions:[]}},bgMark:{visible:!1,fields:[]},mobileViewFields:[],report:{visible:!1,fillField:{fields:[],fillFieldProperties:[]}},syncReport:{visible:!1,syncConfig:{webHookUrl:"",webHookId:"",relations:[],fields:[]}},wd:{rows:[],values:[],columns:w}}};return ie({dsId:t,name:e.name,remark:e.remark,chartParentType:"table",chartType:"agtable",dtos:s,chartSetting:J,limitCount:e.limitCount||2e3,id:n})}o(On,"cardTable");async function An({dsId:t,spec:e,id:n}){const{meta:r}=await be(t),a={},s=[];let l=0;const i=o(c=>{if(!c)return"";if(a[c])return a[c];const d=r[c];if(!d)throw new Error(`Dataset has no column "${c}". Available: ${Object.keys(r).join(" / ")}`);const f=`waxis${++l}`;return s.push(oe({field:f,col:c,argumentType:d.argumentType,tableFieldType:d.tableFieldType,i:l-1})),a[c]=f,f},"reg"),u={id:"gantt",parentId:"table",config:{ganttRenderConfig:{showIcon:!0,style:{image:i(e.image),main:i(e.main),assist:i(e.assist),status:i(e.status),tags:(e.tags||[]).map(i),desc:(e.desc||[]).map(i),list:(e.list||[]).map(i),progress:i(e.progress),taskBar:{textFields:(e.taskBarText||[]).map(i)},fieldMap:{startDate:i(e.start),endDate:i(e.end),plannedStart:i(e.plannedStart),plannedEnd:i(e.plannedEnd)}},dynamic:{main:[],levels:{}}},conditionStyle:{value:{conditions:[]},arrow:{conditions:[]},fontColor:{conditions:[]}},ganttGridTitle:e.title||"任务",ganttGridMinWidth:452,projectRowHeight:e.rowHeight||202,defaultZoomLevel:e.zoom||"day",pagination:{pageSize:e.pageSize||20},showPagination:!1}};return e.order&&i(e.order),ie({dsId:t,name:e.name,remark:e.remark,chartParentType:"table",chartType:"gantt",dtos:s,chartSetting:u,id:n})}o(An,"cardGantt");async function Me(){const e=[];for(let n=1;n<=200;n++){const r=await S("/index-block/get-index-block-folder-bussiness-info",{page:{pageIndex:n,pageSize:500}}),a=r.items||[];e.push(...a.map(l=>({name:l.name,cardId:l.objId,folderId:l.id})));const s=Number(r.totalCount??r.total??0);if(a.length<500||s&&e.length>=s)break}return e}o(Me,"cardList");class Be extends Error{static{o(this,"TenantMismatchError")}constructor(e,n){super(`卡片 ${e} 不属于当前租户${n?`「${n}」`:""},已拒绝写入(写进去的数据会落在当前租户名下)`),this.name="TenantMismatchError",this.cardId=e}}const vt=/\/index-block\/(?:update-new|new-fill-field|add-fill-field|fill-field|add-fill-field-by-s-c-m|new-import-fill-data|import-fill-data)(?:\?|$)/i;let Z=null;function Rt(t,e,n){if(String(t).toUpperCase()==="GET"||!vt.test(String(e))||!n||typeof n!="object"||Array.isArray(n))return"";const r=o((...a)=>{for(const s of a){const l=Object.keys(n).find(i=>i.toLowerCase()===s);if(l&&n[l])return String(n[l])}return""},"pick");return/update-new/i.test(e)?r("id"):r("usedindexblockid")}o(Rt,"tenantGuardTarget");async function Ut(t){if(!t)return;const e=String(t).toLowerCase(),n=o(async()=>{Z=new Set((await Me()).map(r=>String(r.cardId).toLowerCase()))},"load");if(Z?Z.has(e)||await n():await n(),!Z.has(e))throw new Be(t,he(await z()).tenantName)}o(Ut,"assertCardInTenant");const Ie=o(t=>S("/index-block/get-new-index-block-detail",{id:t}),"cardDetail");async function $n(t,e){let n;if(e==="clear")n=[];else if(Array.isArray(e))n=e;else{const s=(await Ie(t)).indexBlockDataSettingDtos||[];n=String(e).split(",").map(l=>l.trim()).filter(Boolean).map(l=>{const[i,u="asc"]=l.split(":"),c=s.find(d=>d.field===i)||s.find(d=>d.comment===i);if(!c)throw new Error(`Card has no field "${i}". Available: ${s.map(d=>`${d.field}=${d.comment}`).join(" / ")}`);if(!["asc","desc"].includes(u.toLowerCase()))throw new Error(`Sort direction must be asc or desc: ${l}`);return{field:c.field,type:1,sort:u.toLowerCase(),sortFields:[]}})}const r=n.map((s,l)=>({type:1,sortFields:[],...s,sortNum:l}));await E("PUT","/page-setting/page-card-index-block-sort-field",{usedIndexBlockId:t,sortField:r});const a=await jt({cardId:t,pageSize:1});return{cardId:t,requested:r.map(s=>`${s.field} ${s.type===2?"manual":s.sort}`),sqlHasOrderBy:/\bORDER\s+BY\s+(?:W\.)?(?:waxis|yaxis|waxis_type|order)\w*/i.test(a.sql||""),queryCode:a.code}}o($n,"cardSort");async function jt({cardId:t,pageSize:e=20,filters:n=[]}){const r=await E("POST","/page-setting/get-new-index-block-data",{usedIndexBlockId:t,filters:n,page:{pageIndex:1,pageSize:+e}},{raw:!0}),a=Array.isArray(r.data)?r.data:[];return{code:r.code,message:r.code===200?"":r.message,total:r.dataCount,rows:a.length,columns:a[0]?Object.keys(a[0]):[],sql:r.extendData?.sql||"",data:a}}o(jt,"queryCard");async function Mt(){return((await S("/index-block/get-page-setting-folder-bussiness-info",{isOnlySeeSelf:!1,page:{pageIndex:1,pageSize:500}})).items||[]).map(e=>({name:e.name,planId:e.objId,folderId:e.id}))}o(Mt,"planList");const Bt=o((t,e=!0)=>S(`/page-setting/get-new-page-plan/${t}?isEdit=${e}`,{}),"planDetail");async function Ln({planId:t,cardId:e,x:n=0,y:r=0,w:a=24,h:s=20}){const l=await S("/page-setting/new-page-card",{indexBlockId:e,sourceType:0,x:+n,y:+r,width:+a,height:+s,mobileX:0,mobileY:+r,mobileWidth:4,mobileHeight:+s,cardType:1,bindFilterIds:[],userPageSettingId:t});return{pageCardId:l.id,x:l.x,y:l.y,width:l.width,height:l.height}}o(Ln,"planAdd");async function Fn(t,e){if(e.isTenantSystem!==!0)throw new Error("Plan filters must be saved with isTenantSystem:true (tenant template); see references/filters.md");await S("/page-setting/batch-add-or-update-filter",e);const n=await S("/page-setting/reset-user-filter",{userPageSettingId:t,filterSourceType:4});return{planId:t,filters:(n||[]).map(r=>r.labelName)}}o(Fn,"planFilter");async function zt(){return((await S("/index-block/get-index-block-report-folder-bussiness-info",{})).items||[]).map(e=>({name:e.name,cardId:e.indexBlockId,reportId:e.objId,folderId:e.id}))}o(zt,"reportList");const Pn=o(t=>S("/page-setting/get-new-page-card-report",{indexBlockId:t,isSubWarn:!1}),"reportDetail");async function Dn(t){if(!t||!t.startsWith("zz-"))throw new Error('clean only accepts prefixes starting with "zz-"');const e={},n=o(async(r,a)=>{e[r]=a.length,a.length&&await S("/index-block/delete-folder-bussiness",a)},"del");return await n("reports",(await zt()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("plans",(await Mt()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("cards",(await Me()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("datasets",(await $t()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),{prefix:t,removed:e}}o(Dn,"cleanPrefix");function vn(t){return`(() => { const s = ${JSON.stringify({t:t.token,c:t.tenantId,a:t.agentId})};
8
8
  localStorage['Access-Token'] = JSON.stringify(s.t);
9
9
  localStorage['agentId'] = JSON.stringify(s.a);
10
10
  localStorage['LM_COMPANY'] = JSON.stringify([{ id: s.c, name: '-' }]);
11
11
  sessionStorage['LM_COMPANY_SELECT'] = JSON.stringify(s.c);
12
- return true; })()`}o(_n,"browserSessionScript");function An(t,e){return t=I.resolve(String(t)),p.mkdirSync(I.dirname(t),{recursive:!0}),p.writeFileSync(t,typeof e=="string"?e:JSON.stringify(e,null,1)),t}o(An,"writeJsonFile");function $n(t){const e=I.resolve(String(t).trim());if(!p.existsSync(e))throw new O("FILE_NOT_FOUND",`文件不存在:${e}(相对路径按当前目录 ${process.cwd()} 解析)`);return p.readFileSync(e,"utf8")}o($n,"readInputFile");const xe="/linkai-kb",se=Object.freeze({"bi-builder":{pathPrefix:"report-builder",scope:"public",enabled:!0,fallback:"bundled"},"data-dictionary":{pathPrefix:"data-dictionary",scope:"tenant",enabled:!0,fallback:"none"},"integration-flow":{pathPrefix:"integration-flow",scope:"tenant",enabled:!1,fallback:"none"},user:{pathPrefix:"user",scope:"user",enabled:!1,fallback:"none"}});class ze extends Error{static{o(this,"KbUnavailableError")}constructor(){super("知识库服务暂不可用"),this.name="KbUnavailableError"}}class Ut extends Error{static{o(this,"KbDocumentNotFoundError")}constructor(e,n){super(`知识库文档不存在:${e}/${n}`),this.name="KbDocumentNotFoundError"}}const K=o(t=>(t.code===404||t.__status===404)&&/non-JSON/.test(String(t.message||"")),"endpointMissing");function jt(t){const e=String(t||"").trim().replace(/\\/g,"/").replace(/^\.?\//,"").replace(/^knowledge\//,"");if(!e||e.split("/").some(n=>n===".."||n==="")||!e.endsWith(".md"))throw new Error(`知识库路径不合法:${t}(例:knowledge/report-builder/SOP.md)`);return e}o(jt,"normalizeKbPath");function Mt(t,e){const n=jt(t);let r=e?String(e):"",a=n;const s=Object.entries(se).find(([,i])=>n===i.pathPrefix||n.startsWith(`${i.pathPrefix}/`));r||(r=s?.[0]||"bi-builder",s&&(a=n.slice(s[1].pathPrefix.length).replace(/^\//,"")));const l=se[r];if(!l)throw new Error(`未知知识库:${r}(支持:${Object.keys(se).join(", ")})`);if(!l.enabled)throw new Error(`知识库 ${r} 已预留,当前版本尚未启用`);if(s&&s[0]!==r)throw new Error(`知识库路径 ${n} 不属于 ${r}`);if((n===l.pathPrefix||n.startsWith(`${l.pathPrefix}/`))&&(a=n.slice(l.pathPrefix.length).replace(/^\//,"")),!a||a.split("/").some(i=>i===".."||i==="")||!a.endsWith(".md"))throw new Error(`知识库路径不合法:${t}`);return{library:r,path:a,canonicalPath:`${l.pathPrefix}/${a}`,...l}}o(Mt,"kbRequest");async function Ln(t="bi-builder"){const e=se[t];if(!e)throw new Error(`未知知识库:${t}`);if(!e.enabled)throw new Error(`知识库 ${t} 已预留,当前版本尚未启用`);if(t==="data-dictionary")throw new Error("数据字典使用结构化只读接口;请执行 linkai kb-sources,再按需使用 kb-search / kb-catalog / kb-object / kb-docs / kb-doc");const n=await E("GET",`${xe}/catalog?library=${encodeURIComponent(t)}`,void 0,{raw:!0});if(K(n))throw new ze;if(n.code!==200)throw new Error(`读取知识库目录失败:${n.message||n.code}`);return{...n.data||{docs:[]},library:t,scope:e.scope}}o(Ln,"kbCatalog");async function Fn(t,e){const n=Mt(t,e);if(n.library==="data-dictionary")throw new Error("数据字典不再按 Markdown 文件路径读取;请使用 kb-search / kb-object / kb-docs / kb-doc");const r=await E("GET",`${xe}/doc?library=${encodeURIComponent(n.library)}&path=${encodeURIComponent(n.path)}`,void 0,{raw:!0});if(K(r))throw new ze;if(r.code===404||r.__status===404)throw new Ut(n.library,n.path);if(r.code!==200)throw new Error(r.message||`读取知识库失败:${n.library}/${n.path}`);return{...r.data,library:n.library,scope:n.scope,path:n.canonicalPath}}o(Fn,"kbDoc");const Bt="/ai-kb";class U extends Error{static{o(this,"AiKbError")}constructor(e,n){super(n||`数据字典服务返回错误:${e}`),this.name="AiKbError",this.code=Number(e)||500}}function le(t){const e=new URLSearchParams;for(const[n,r]of Object.entries(t))r!=null&&r!==""&&e.set(n,String(r));return e.toString()}o(le,"query");async function W(t,e,n){let r;try{r=await E(t,`${Bt}/${e}`,n,{raw:!0})}catch(a){throw a instanceof k?a:new U(503,`数据字典服务暂不可用:${a.message||a}`)}if(K(r))throw new U(503,"数据字典接口尚未部署或当前网关不可用");if(r.code!==200)throw new U(r.code,r.message);return r.data}o(W,"aiKbRequest");const zt=o(()=>W("GET","sources"),"aiKbSources");function Dn({source:t,keyword:e,pageIndex:n=1,pageSize:r=30}){return W("GET",`catalog?${le({source:t,keyword:e,pageIndex:n,pageSize:r})}`)}o(Dn,"aiKbCatalog");function vn({source:t,db:e,object:n}){return W("GET",`object?${le({source:t,db:e,object:n})}`)}o(vn,"aiKbObject");function Gt({source:t,category:e,keyword:n,pageIndex:r=1,pageSize:a=30}){return W("GET",`docs?${le({source:t,category:e,keyword:n,pageIndex:r,pageSize:a})}`)}o(Gt,"aiKbDocs");function Kt({source:t,doc:e,offset:n=0,limit:r=2e4,expectedVersion:a}){return W("GET",`doc?${le({source:t,doc:e,offset:n,limit:r,expectedVersion:a})}`)}o(Kt,"aiKbDocPage");async function Pn({source:t,doc:e,limit:n=2e4,maxRestarts:r=3}){for(let a=0;a<=r;a++){let s=0,l,i;const u=[];let c=0;try{for(;;){const d=await Kt({source:t,doc:e,offset:s,limit:n,expectedVersion:l});if(c++,i||=d,l||=d.contentVersion,d.contentVersion!==l)throw new U(409,"文档内容版本在分页读取过程中发生变化");if(u.push(d.content||""),d.nextOffset===null||d.nextOffset===void 0)return{...i,content:u.join(""),contentVersion:l,offset:0,nextOffset:null,totalChars:d.totalChars,truncated:!1,pages:c};const f=Number(d.nextOffset);if(!Number.isInteger(f)||f<=s)throw new U(503,"数据字典服务返回了无效的 nextOffset");s=f}}catch(d){if(!(d instanceof U)||d.code!==409||a===r)throw d}}throw new U(409,"文档持续变化,无法完成一致性读取")}o(Pn,"aiKbDoc");const Ge=2*6e4,Ke=20*1024*1024;let ce=null;const Rn=o(()=>ce,"kbCacheHit"),Wt=o(()=>Q("dict:sources",Ge,async()=>(await zt()).sources||[]),"dictSourcesCached"),Ht=o(t=>Q(`dict:docs:${String(t).toLowerCase()}`,Ge,async()=>{const e=await Gt({source:t,pageIndex:1,pageSize:20});return{items:(e?.items||[]).map(n=>({updatedAt:n.updatedAt})),total:e?.totalCount??null}}),"dictDocsHead");async function Jt(t){const[e,n]=await Promise.all([Wt(),Ht(t)]),r=e.find(s=>String(s.source).toLowerCase()===String(t).toLowerCase());if(!r)return null;const a=(n.items||[]).map(s=>String(s.updatedAt||"")).sort().pop()||"";return[r.lastSuccessAt||"",r.revision??"",a,n.total??""].join("|")}o(Jt,"dictVersion");const Vt=o(t=>I.join(V(),`${v}.kbcache-${String(t).replace(/[^\w-]/g,"")}.json`),"kbCacheFile");async function Un(t,e,n){const r=A()?.tenantId;let a=null;if(r&&process.env.LINKAI_NO_CACHE!=="1")try{a=await Jt(t)}catch{a=null}if(!a)return ce=!1,n();const s=Vt(r),l=`${String(t).toLowerCase()}|${e}`;let i={};try{i=JSON.parse(p.readFileSync(s,"utf8"))||{}}catch{}const u=i[l];if(u&&u.v===a)return ce=!0,u.value;const c=await n();ce=!1;try{try{i=JSON.parse(p.readFileSync(s,"utf8"))||{}}catch{i={}}for(const[m,w]of Object.entries(i))w?.v!==a&&m.startsWith(`${String(t).toLowerCase()}|`)&&delete i[m];i[l]={v:a,at:Date.now(),value:c};let d=JSON.stringify(i);if(d.length>Ke){const m=Object.keys(i).sort((w,T)=>(i[w].at||0)-(i[T].at||0));for(;m.length>1&&d.length>Ke;)delete i[m.shift()],d=JSON.stringify(i)}const f=`${s}.${process.pid}.tmp`;p.writeFileSync(f,d,{mode:384}),p.renameSync(f,s)}catch{}return c}o(Un,"kbCached");const jn=o(t=>String(t||"").trim().replace(/\s+/g," ").toLowerCase(),"normalizeQuestion");function Mn({source:t,question:e,topTables:n=5,topDocs:r=3}){return W("POST","search",{source:t,question:e,topTables:n,topDocs:r})}o(Mn,"aiKbSearch");const We=`${xe}/plan-knowledge`;class de extends Error{static{o(this,"PlanKnowledgeUnavailableError")}constructor(){super("方案知识库服务暂不可用"),this.name="PlanKnowledgeUnavailableError"}}class qt extends Error{static{o(this,"PlanKnowledgeConflictError")}constructor(e="方案知识库版本冲突,请重新读取当前版本后再更新"){super(e),this.name="PlanKnowledgeConflictError"}}const He=o(t=>K(t)||t.code===429||Number(t.code)>=500,"planKnowledgeUnavailable");async function Bn(t){let e;try{e=await E("GET",`${We}?planId=${encodeURIComponent(t)}`,void 0,{raw:!0})}catch(n){throw n instanceof k?n:new de}if(He(e))throw new de;if(e.code===404)return null;if(e.code!==200)throw new Error(e.message||`读取方案知识库失败:${t}`);return e.data||null}o(Bn,"planKnowledgeGet");async function zn(t){let e;try{e=await E("POST",We,t,{raw:!0})}catch(r){throw r instanceof k?r:new de}if(He(e))throw new de;if(e.code===409)throw new qt(e.message||void 0);if(e.code!==200)throw new Error(e.message||`保存方案知识库失败:${t.planId}`);const n=e.data||{};if(Number(n.version)!==Number(t.version))throw new Error(`方案知识库版本异常:请求 v${t.version},服务器返回 v${n.version||"?"}`);return n}o(zn,"planKnowledgePut");export{U as AiKbError,re as ApiError,Ge as DICT_VERSION_TTL_MS,se as KB_LIBRARIES,Ut as KbDocumentNotFoundError,ze as KbUnavailableError,O as KindError,k as LoginRequiredError,nt as NetworkError,qt as PlanKnowledgeConflictError,de as PlanKnowledgeUnavailableError,v as SITE_ID,Be as TenantMismatchError,b as Z,Dn as aiKbCatalog,Pn as aiKbDoc,Kt as aiKbDocPage,Gt as aiKbDocs,vn as aiKbObject,Mn as aiKbSearch,zt as aiKbSources,mt as allowedTables,E as api,ot as artifactsDir,wn as askQuery,mn as askQuestion,un as assertApiAllowed,Ft as assertCardInTenant,yt as assertReadOnlySql,_n as browserSessionScript,bn as cardChart,Ie as cardDetail,kn as cardGantt,Me as cardList,In as cardMeter,En as cardSort,xn as cardTable,It as checkSql,Re as cleanApiMessage,On as cleanPrefix,st as clearSession,on as clearShortCache,V as configDir,pe as configFile,ae as ctx,Ht as dictDocsHead,Wt as dictSourcesCached,Jt as dictVersion,Ct as dsDetail,Ot as dsFields,Nt as dsList,yn as dsRefs,gn as dsSave,M as ensureSession,Tt as fieldColumns,ge as forgetCredentials,wt as formatSql,R as get,fn as hasOffset,dt as isPlatformTenant,Rn as kbCacheHit,Un as kbCached,Ln as kbCatalog,Fn as kbDoc,Mt as kbRequest,Sn as kinds,bt as localCheckSql,B as logGuard,jt as normalizeKbPath,jn as normalizeQuestion,sn as oauthLogin,ln as oauthLogout,Tn as planAdd,Pt as planDetail,Nn as planFilter,Bn as planKnowledgeGet,zn as planKnowledgePut,vt as planList,S as post,St as precheckSql,Dt as queryCard,x as readConfig,$n as readInputFile,A as readSession,pn as recordXiaomanAttempt,Cn as reportDetail,Rt as reportList,te as saveSession,dn as setClientVersion,Q as shortCache,he as spaceOf,ht as sqlTables,Se as stripTrailingSemicolon,cn as switchTenant,Lt as tenantGuardTarget,De as tenantList,P as tokenPayload,An as writeJsonFile,hn as xiaomanAttempt};
12
+ return true; })()`}o(vn,"browserSessionScript");function Rn(t,e){return t=I.resolve(String(t)),p.mkdirSync(I.dirname(t),{recursive:!0}),p.writeFileSync(t,typeof e=="string"?e:JSON.stringify(e,null,1)),t}o(Rn,"writeJsonFile");function Un(t){const e=I.resolve(String(t).trim());if(!p.existsSync(e))throw new T("FILE_NOT_FOUND",`文件不存在:${e}(相对路径按当前目录 ${process.cwd()} 解析)`);return p.readFileSync(e,"utf8")}o(Un,"readInputFile");const xe="/linkai-kb",se=Object.freeze({"bi-builder":{pathPrefix:"report-builder",scope:"public",enabled:!0,fallback:"bundled"},"data-dictionary":{pathPrefix:"data-dictionary",scope:"tenant",enabled:!0,fallback:"none"},"integration-flow":{pathPrefix:"integration-flow",scope:"tenant",enabled:!1,fallback:"none"},user:{pathPrefix:"user",scope:"user",enabled:!1,fallback:"none"}});class ze extends Error{static{o(this,"KbUnavailableError")}constructor(){super("知识库服务暂不可用"),this.name="KbUnavailableError"}}class Kt extends Error{static{o(this,"KbDocumentNotFoundError")}constructor(e,n){super(`知识库文档不存在:${e}/${n}`),this.name="KbDocumentNotFoundError"}}const W=o(t=>(t.code===404||t.__status===404)&&/non-JSON/.test(String(t.message||"")),"endpointMissing");function Gt(t){const e=String(t||"").trim().replace(/\\/g,"/").replace(/^\.?\//,"").replace(/^knowledge\//,"");if(!e||e.split("/").some(n=>n===".."||n==="")||!e.endsWith(".md"))throw new Error(`知识库路径不合法:${t}(例:knowledge/report-builder/SOP.md)`);return e}o(Gt,"normalizeKbPath");function Wt(t,e){const n=Gt(t);let r=e?String(e):"",a=n;const s=Object.entries(se).find(([,i])=>n===i.pathPrefix||n.startsWith(`${i.pathPrefix}/`));r||(r=s?.[0]||"bi-builder",s&&(a=n.slice(s[1].pathPrefix.length).replace(/^\//,"")));const l=se[r];if(!l)throw new Error(`未知知识库:${r}(支持:${Object.keys(se).join(", ")})`);if(!l.enabled)throw new Error(`知识库 ${r} 已预留,当前版本尚未启用`);if(s&&s[0]!==r)throw new Error(`知识库路径 ${n} 不属于 ${r}`);if((n===l.pathPrefix||n.startsWith(`${l.pathPrefix}/`))&&(a=n.slice(l.pathPrefix.length).replace(/^\//,"")),!a||a.split("/").some(i=>i===".."||i==="")||!a.endsWith(".md"))throw new Error(`知识库路径不合法:${t}`);return{library:r,path:a,canonicalPath:`${l.pathPrefix}/${a}`,...l}}o(Wt,"kbRequest");async function jn(t="bi-builder"){const e=se[t];if(!e)throw new Error(`未知知识库:${t}`);if(!e.enabled)throw new Error(`知识库 ${t} 已预留,当前版本尚未启用`);if(t==="data-dictionary")throw new Error("数据字典使用结构化只读接口;请执行 linkai kb-sources,再按需使用 kb-search / kb-catalog / kb-object / kb-docs / kb-doc");const n=await E("GET",`${xe}/catalog?library=${encodeURIComponent(t)}`,void 0,{raw:!0});if(W(n))throw new ze;if(n.code!==200)throw new Error(`读取知识库目录失败:${n.message||n.code}`);return{...n.data||{docs:[]},library:t,scope:e.scope}}o(jn,"kbCatalog");async function Mn(t,e){const n=Wt(t,e);if(n.library==="data-dictionary")throw new Error("数据字典不再按 Markdown 文件路径读取;请使用 kb-search / kb-object / kb-docs / kb-doc");const r=await E("GET",`${xe}/doc?library=${encodeURIComponent(n.library)}&path=${encodeURIComponent(n.path)}`,void 0,{raw:!0});if(W(r))throw new ze;if(r.code===404||r.__status===404)throw new Kt(n.library,n.path);if(r.code!==200)throw new Error(r.message||`读取知识库失败:${n.library}/${n.path}`);return{...r.data,library:n.library,scope:n.scope,path:n.canonicalPath}}o(Mn,"kbDoc");const Ht="/ai-kb";class j extends Error{static{o(this,"AiKbError")}constructor(e,n){super(n||`数据字典服务返回错误:${e}`),this.name="AiKbError",this.code=Number(e)||500}}function le(t){const e=new URLSearchParams;for(const[n,r]of Object.entries(t))r!=null&&r!==""&&e.set(n,String(r));return e.toString()}o(le,"query");async function H(t,e,n){let r;try{r=await E(t,`${Ht}/${e}`,n,{raw:!0})}catch(a){throw a instanceof k?a:new j(503,`数据字典服务暂不可用:${a.message||a}`)}if(W(r))throw new j(503,"数据字典接口尚未部署或当前网关不可用");if(r.code!==200)throw new j(r.code,r.message);return r.data}o(H,"aiKbRequest");const Jt=o(()=>H("GET","sources"),"aiKbSources");function Bn({source:t,keyword:e,pageIndex:n=1,pageSize:r=30}){return H("GET",`catalog?${le({source:t,keyword:e,pageIndex:n,pageSize:r})}`)}o(Bn,"aiKbCatalog");function zn({source:t,db:e,object:n}){return H("GET",`object?${le({source:t,db:e,object:n})}`)}o(zn,"aiKbObject");function Vt({source:t,category:e,keyword:n,pageIndex:r=1,pageSize:a=30}){return H("GET",`docs?${le({source:t,category:e,keyword:n,pageIndex:r,pageSize:a})}`)}o(Vt,"aiKbDocs");function qt({source:t,doc:e,offset:n=0,limit:r=2e4,expectedVersion:a}){return H("GET",`doc?${le({source:t,doc:e,offset:n,limit:r,expectedVersion:a})}`)}o(qt,"aiKbDocPage");async function Kn({source:t,doc:e,limit:n=2e4,maxRestarts:r=3}){for(let a=0;a<=r;a++){let s=0,l,i;const u=[];let c=0;try{for(;;){const d=await qt({source:t,doc:e,offset:s,limit:n,expectedVersion:l});if(c++,i||=d,l||=d.contentVersion,d.contentVersion!==l)throw new j(409,"文档内容版本在分页读取过程中发生变化");if(u.push(d.content||""),d.nextOffset===null||d.nextOffset===void 0)return{...i,content:u.join(""),contentVersion:l,offset:0,nextOffset:null,totalChars:d.totalChars,truncated:!1,pages:c};const f=Number(d.nextOffset);if(!Number.isInteger(f)||f<=s)throw new j(503,"数据字典服务返回了无效的 nextOffset");s=f}}catch(d){if(!(d instanceof j)||d.code!==409||a===r)throw d}}throw new j(409,"文档持续变化,无法完成一致性读取")}o(Kn,"aiKbDoc");const Ke=2*6e4,Ge=20*1024*1024;let ce=null;const Gn=o(()=>ce,"kbCacheHit"),Qt=o(()=>B("dict:sources",Ke,async()=>(await Jt()).sources||[]),"dictSourcesCached"),Yt=o(t=>B(`dict:docs:${String(t).toLowerCase()}`,Ke,async()=>{const e=await Vt({source:t,pageIndex:1,pageSize:20});return{items:(e?.items||[]).map(n=>({updatedAt:n.updatedAt})),total:e?.totalCount??null}}),"dictDocsHead");async function Xt(t){const[e,n]=await Promise.all([Qt(),Yt(t)]),r=e.find(s=>String(s.source).toLowerCase()===String(t).toLowerCase());if(!r)return null;const a=(n.items||[]).map(s=>String(s.updatedAt||"")).sort().pop()||"";return[r.lastSuccessAt||"",r.revision??"",a,n.total??""].join("|")}o(Xt,"dictVersion");const Zt=o(t=>I.join(q(),`${D}.kbcache-${String(t).replace(/[^\w-]/g,"")}.json`),"kbCacheFile");async function Wn(t,e,n){const r=A()?.tenantId;let a=null;if(r&&process.env.LINKAI_NO_CACHE!=="1")try{a=await Xt(t)}catch{a=null}if(!a)return ce=!1,n();const s=Zt(r),l=`${String(t).toLowerCase()}|${e}`;let i={};try{i=JSON.parse(p.readFileSync(s,"utf8"))||{}}catch{}const u=i[l];if(u&&u.v===a)return ce=!0,u.value;const c=await n();ce=!1;try{try{i=JSON.parse(p.readFileSync(s,"utf8"))||{}}catch{i={}}for(const[m,w]of Object.entries(i))w?.v!==a&&m.startsWith(`${String(t).toLowerCase()}|`)&&delete i[m];i[l]={v:a,at:Date.now(),value:c};let d=JSON.stringify(i);if(d.length>Ge){const m=Object.keys(i).sort((w,_)=>(i[w].at||0)-(i[_].at||0));for(;m.length>1&&d.length>Ge;)delete i[m.shift()],d=JSON.stringify(i)}const f=`${s}.${process.pid}.tmp`;p.writeFileSync(f,d,{mode:384}),p.renameSync(f,s)}catch{}return c}o(Wn,"kbCached");const Hn=o(t=>String(t||"").trim().replace(/\s+/g," ").toLowerCase(),"normalizeQuestion");function Jn({source:t,question:e,topTables:n=5,topDocs:r=3}){return H("POST","search",{source:t,question:e,topTables:n,topDocs:r})}o(Jn,"aiKbSearch");const We=`${xe}/plan-knowledge`;class de extends Error{static{o(this,"PlanKnowledgeUnavailableError")}constructor(){super("方案知识库服务暂不可用"),this.name="PlanKnowledgeUnavailableError"}}class en extends Error{static{o(this,"PlanKnowledgeConflictError")}constructor(e="方案知识库版本冲突,请重新读取当前版本后再更新"){super(e),this.name="PlanKnowledgeConflictError"}}class tn extends Error{static{o(this,"PlanKnowledgeRejectedError")}constructor(e,n){super(n||`方案知识库被服务器拒绝(${e})`),this.name="PlanKnowledgeRejectedError",this.status=e}}const Vn="这是领谋下发的系统分析方案(只读):可以用方案知识回答「怎么取数」等问题,但不能修改它(卡片、筛选器、排序、删除、方案知识都不行)。用户要修改时停止,告诉用户:如需按本租户调整,请在领谋网页上把它另存为本租户的分析方案,再对新方案操作",nn=o(t=>/系统分析方案|系统方案不能更改|系统报表不能更改/.test(String(t||"")),"isSystemPlanMessage"),He=o(t=>W(t)||t.code===429||Number(t.code)>=500,"planKnowledgeUnavailable");async function qn(t){let e;try{e=await E("GET",`${We}?planId=${encodeURIComponent(t)}`,void 0,{raw:!0})}catch(n){throw n instanceof k?n:new de}if(He(e))throw new de;if(e.code===404)return nn(e.message)?{missing:!0,systemPlan:!0,readOnly:!0,message:e.message}:null;if(e.code!==200)throw new Error(e.message||`读取方案知识库失败:${t}`);return e.data||null}o(qn,"planKnowledgeGet");async function Qn(t){let e;try{e=await E("POST",We,t,{raw:!0})}catch(a){throw a instanceof k?a:new de}if(He(e))throw new de;if(e.code===409)throw new en(e.message||void 0);const n=[400,403,404].includes(Number(e.code))?Number(e.code):[400,403,404].includes(Number(e.__status))?Number(e.__status):null;if(n)throw new tn(n,e.message);if(e.code!==200)throw new Error(e.message||`保存方案知识库失败:${t.planId}`);const r=e.data||{};if(Number(r.version)!==Number(t.version))throw new Error(`方案知识库版本异常:请求 v${t.version},服务器返回 v${r.version||"?"}`);return r}o(Qn,"planKnowledgePut");export{j as AiKbError,re as ApiError,Ke as DICT_VERSION_TTL_MS,se as KB_LIBRARIES,Kt as KbDocumentNotFoundError,ze as KbUnavailableError,T as KindError,k as LoginRequiredError,nt as NetworkError,en as PlanKnowledgeConflictError,tn as PlanKnowledgeRejectedError,de as PlanKnowledgeUnavailableError,D as SITE_ID,Vn as SYSTEM_PLAN_NEXT,Be as TenantMismatchError,b as Z,Bn as aiKbCatalog,Kn as aiKbDoc,qt as aiKbDocPage,Vt as aiKbDocs,zn as aiKbObject,Jn as aiKbSearch,Jt as aiKbSources,bt as allowedTables,E as api,ot as artifactsDir,kn as askQuery,xn as askQuestion,pt as assertApiAllowed,yn as assertApiAllowedByPolicy,Ut as assertCardInTenant,kt as assertReadOnlySql,vn as browserSessionScript,Cn as cardChart,Ie as cardDetail,An as cardGantt,Me as cardList,Nn as cardMeter,$n as cardSort,On as cardTable,_t as checkSql,Re as cleanApiMessage,Dn as cleanPrefix,st as clearSession,pn as clearShortCache,mt as cliPolicy,q as configDir,pe as configFile,ae as ctx,Yt as dictDocsHead,Qt as dictSourcesCached,Xt as dictVersion,Lt as dsDetail,Ft as dsFields,$t as dsList,Tn as dsRefs,En as dsSave,z as ensureSession,At as fieldColumns,ge as forgetCredentials,It as formatSql,U as get,Sn as hasOffset,dt as isPlatformTenant,Gn as kbCacheHit,Wn as kbCached,jn as kbCatalog,Mn as kbDoc,Wt as kbRequest,_n as kinds,Tt as localCheckSql,R as logGuard,wt as normalizeApiPath,Gt as normalizeKbPath,Hn as normalizeQuestion,hn as oauthLogin,mn as oauthLogout,Ln as planAdd,Bt as planDetail,Fn as planFilter,qn as planKnowledgeGet,Qn as planKnowledgePut,Mt as planList,gt as policyHit,S as post,Et as precheckSql,jt as queryCard,x as readConfig,Un as readInputFile,A as readSession,bn as recordXiaomanAttempt,Pn as reportDetail,zt as reportList,te as saveSession,gn as setClientVersion,B as shortCache,he as spaceOf,St as sqlTables,Se as stripTrailingSemicolon,wn as switchTenant,Rt as tenantGuardTarget,Pe as tenantList,v as tokenPayload,Rn as writeJsonFile,In as xiaomanAttempt};
@@ -1,2 +1,2 @@
1
- var D=Object.defineProperty;var s=(e,r)=>D(e,"name",{value:r,configurable:!0});import I from"node:crypto";import u from"node:fs";import f from"node:path";import{parseYaml as L}from"@linkaicli/linkai-cli/utils";import*as c from"./core.js";const N=s(e=>e&&e!==c.Z,"isRealId"),$=s(e=>Array.isArray(e)?e.map(String).map(r=>r.trim()).filter(Boolean):e?[String(e).trim()]:[],"list"),K=s(e=>String(e||"").replace(/[\\/:*?"<>|\s]+/g,"_").slice(0,120)||"plan","safe"),E=s(e=>/```\s*sql|\bselect\b[\s\S]{0,2000}\bfrom\b/i.test(JSON.stringify(e||{})),"containsFullSql");function U(e){const r=f.resolve(String(e)),n=u.readFileSync(r,"utf8");return/\.json$/i.test(r)?JSON.parse(n):L(n)}s(U,"readKnowledgeFile");function j(e){const r=[],n=s((t,o)=>{String(t||"").trim()||r.push(o)},"need");n(e?.summary,"knowledge.summary(方案内容)必填"),n(e?.problem,"knowledge.problem(解决的问题)必填"),(!Array.isArray(e?.cards)||!e.cards.length)&&r.push("knowledge.cards 至少需要一张业务卡片");for(const[t,o]of(e?.cards||[]).entries())n(o.cardId,`knowledge.cards[${t}].cardId 必填`),n(o.content,`knowledge.cards[${t}].content(卡片内容)必填`),n(o.source,`knowledge.cards[${t}].source(数据来源)必填`),n(o.grain,`knowledge.cards[${t}].grain(数据粒度)必填`),n(o.dataLogic,`knowledge.cards[${t}].dataLogic(取数逻辑)必填`);return E(e)&&r.push("方案知识库不得包含完整 SQL;请改写为自然语言取数逻辑和规则"),r}s(j,"validateKnowledgeAuthoring");function q(e,r){return{summary:e.knowledge?.summary,problem:e.knowledge?.problem,rules:$(e.knowledge?.rules),changeSummary:e.knowledge?.changeSummary||"首次创建",cards:(e.cards||[]).map(n=>({cardId:r.cards[n.key],content:n.knowledge?.content,source:n.knowledge?.source,grain:n.knowledge?.grain,dataLogic:n.knowledge?.dataLogic,rules:$(n.knowledge?.rules)}))}}s(q,"knowledgeAuthoringFromBuild");async function F(e){const r=c.readConfig(),n=await c.planDetail(e,!0),t=[];for(const o of n.pageCards||[]){const i=o.indexBlockId||o.indexBlock?.id;if(!N(i))continue;const l=await c.cardDetail(i),g=l.indexBlockDataId,a=N(g)?await c.dsDetail(g):null;t.push({pageCardId:o.id,cardId:i,name:l.name,chartType:`${l.chartParentType||""}/${l.chartType||""}`.replace(/^\/$/,""),dataset:a?{datasetId:g,name:a.name}:null})}return{plan:{planId:e,name:n.name,url:`${r.origin}/view/plan?plan=${e}`},cards:t}}s(F,"readPlanFacts");function A(e,r,{operation:n,baseline:t=!1}={}){const o=new Map((r.cards||[]).map(a=>[String(a.cardId),a])),i=new Set(e.cards.map(a=>String(a.cardId))),l=e.cards.filter(a=>!o.has(String(a.cardId))).map(a=>`${a.name}(${a.cardId})`),g=[...o.keys()].filter(a=>!i.has(a));if(l.length||g.length){const a=[];throw l.length&&a.push(`以下线上业务卡片缺少知识段落:${l.join("、")}`),g.length&&a.push(`知识文件包含不在方案中的卡片:${g.join("、")}`),new c.KindError("KNOWLEDGE_INVALID",a.join(";"))}return{schemaVersion:1,name:`${e.plan.name}方案知识库`,plan:e.plan,operation:n,baseline:t,summary:String(r.summary).trim(),problem:String(r.problem).trim(),rules:$(r.rules),changeSummary:String(r.changeSummary||(n==="create"?"首次创建":"方案更新")).trim(),cards:e.cards.map((a,h)=>{const m=o.get(String(a.cardId));return{order:h+1,...a,content:String(m.content).trim(),source:String(m.source).trim(),grain:String(m.grain).trim(),dataLogic:String(m.dataLogic).trim(),rules:$(m.rules)}})}}s(A,"assembleDocument");function y(e,r){const n=[];if(n.push(`# ${e.name}`,""),n.push(`- 版本:v${r}`),n.push(`- 方案名称:${e.plan.name}`),n.push(`- 方案 ID:${e.plan.planId}`),n.push(`- 方案地址:${e.plan.url}`,""),n.push("## 方案说明","","### 方案内容","",e.summary,"","### 解决的问题","",e.problem,""),n.push("### 公共规则",""),e.rules.length)for(const t of e.rules)n.push(`- ${t}`);else n.push("(无额外公共规则)");n.push("","## 卡片与数据","");for(const t of e.cards){if(n.push(`## ${String(t.order).padStart(2,"0")} ${t.name}`,""),n.push("### 卡片内容","",`- 卡片 ID:${t.cardId}`,`- 图表类型:${t.chartType||"未标注"}`,"",t.content,""),n.push("### 数据集",""),n.push(`- 数据集名称:${t.dataset?.name||"(无独立数据集)"}`),n.push(`- 数据集 ID:${t.dataset?.datasetId||"(无)"}`),n.push(`- 数据来源:${t.source}`),n.push(`- 数据粒度:${t.grain}`,""),n.push("### 取数逻辑与规则","",t.dataLogic,""),t.rules.length)for(const o of t.rules)n.push(`- ${o}`);n.push("")}return n.push("## 版本记录","",`- v${r}:${e.baseline?"历史方案首次纳管;":""}${e.changeSummary}`,""),n.join(`
2
- `)}s(y,"renderPlanKnowledge");function O(e){const{operation:r,baseline:n,...t}=e;return I.createHash("sha256").update(JSON.stringify(t)).digest("hex")}s(O,"contentHash");function b(e){const r=c.readSession()?.tenantId||"unknown-tenant";return c.artifactsDir("plan-knowledge",K(r),K(e))}s(b,"localPlanDir");function v(e){const r=b(e);return u.existsSync(r)?u.readdirSync(r).filter(n=>/^v\d+\.pending\.json$/.test(n)).sort((n,t)=>Number(n.slice(1))-Number(t.slice(1))).map(n=>f.join(r,n)):[]}s(v,"pendingFiles");function P(e){const r=b(e);return u.existsSync(r)?u.readdirSync(r).filter(n=>/^v\d+\.md$/.test(n)).sort((n,t)=>Number(n.slice(1))-Number(t.slice(1))).map(n=>f.join(r,n)):[]}s(P,"localMarkdownFiles");function C(e){const r=[...v(e),...P(e)].map(n=>Number(f.basename(n).match(/^v(\d+)/)?.[1]||0));return Math.max(0,...r)}s(C,"highestLocalVersion");function J(e){const r=v(e).at(-1);if(!r)return null;try{return{file:r,...JSON.parse(u.readFileSync(r,"utf8"))}}catch{return null}}s(J,"latestPending");async function W(e){try{const r=await c.planKnowledgeGet(e);return r?{planId:e,exists:!0,version:`v${Number(r.version)||1}`,content:r.markdown||y(r.document,Number(r.version)||1)}:{planId:e,exists:!1}}catch(r){if(!(r instanceof c.PlanKnowledgeUnavailableError))throw r;const n=P(e).at(-1);if(!n)return{planId:e,exists:!1,next:"当前没有可读取的方案知识库:照常完成修改,最后用 plan-knowledge --mode update 同步,CLI 会自动补建 v1 基线"};const t=Number(f.basename(n).match(/^v(\d+)/)?.[1]||1);return{planId:e,exists:!0,version:`v${t}`,content:u.readFileSync(n,"utf8")}}}s(W,"readCurrentPlanKnowledge");function V(e,r,n,t){const o=b(e);u.mkdirSync(o,{recursive:!0,mode:448});const i=f.join(o,`v${r}`);u.writeFileSync(`${i}.pending.json`,JSON.stringify({payload:n,createdAt:new Date().toISOString()},null,2),{mode:384}),u.writeFileSync(`${i}.md`,t,{mode:384})}s(V,"savePending");async function B(e){for(const r of v(e)){let n;try{n=JSON.parse(u.readFileSync(r,"utf8"))}catch{continue}try{await c.planKnowledgePut(n.payload),u.rmSync(r)}catch(t){if(t instanceof c.PlanKnowledgeUnavailableError)return;if(t instanceof c.PlanKnowledgeConflictError){u.renameSync(r,r.replace(/\.pending\.json$/,".conflict.json"));continue}return}}}s(B,"syncPending");async function Q({planId:e,operation:r,authoring:n}){if(!["create","update"].includes(r))throw new Error("operation 必须是 create 或 update;不明确时先询问用户");const t=j(n);if(r==="update"&&!String(n?.changeSummary||"").trim()&&t.push("修改方案时 knowledge.changeSummary(本次修改说明)必填"),t.length)throw new c.KindError("KNOWLEDGE_INVALID",t.join(";"));await B(e);const o=await F(e);let i=null,l=!1;try{i=await c.planKnowledgeGet(e)}catch(d){if(!(d instanceof c.PlanKnowledgeUnavailableError))throw d;l=!0}const a=A(o,n,{operation:r,baseline:r==="update"&&!i}),h=O(a);if(i?.contentHash===h){const d=Number(i.version)||1,w=i.document||a;return{plan:o.plan,planKnowledge:{name:w.name,version:`v${d}`,content:i.markdown||y(w,d)}}}const m=i?null:J(e);if(m?.payload?.contentHash===h){const d=Number(m.payload.version)||1,w=m.payload.document||a;return{plan:o.plan,planKnowledge:{name:w.name,version:`v${d}`,content:m.payload.markdown||y(w,d)}}}if(r==="create"&&i)throw new Error(`方案 ${e} 已有方案知识库,请使用 update`);const k=Math.max(Number(i?.version)||0,C(e));let p=k+1,S=y(a,p);const x={planId:e,operation:r,expectedVersion:k,version:p,contentHash:h,document:a,markdown:S};if(!l)try{const d=await c.planKnowledgePut(x);p=Number(d?.version)||p,S=y(a,p)}catch(d){if(!(d instanceof c.PlanKnowledgeUnavailableError))throw d;l=!0}return l&&V(e,p,x,S),{plan:o.plan,planKnowledge:{name:a.name,version:`v${p}`,content:S}}}s(Q,"publishPlanKnowledge");export{E as containsFullSql,q as knowledgeAuthoringFromBuild,Q as publishPlanKnowledge,W as readCurrentPlanKnowledge,U as readKnowledgeFile,y as renderPlanKnowledge,j as validateKnowledgeAuthoring};
1
+ var K=Object.defineProperty;var c=(e,r)=>K(e,"name",{value:r,configurable:!0});import L from"node:crypto";import d from"node:fs";import f from"node:path";import{parseYaml as j}from"@linkaicli/linkai-cli/utils";import*as D from"@linkaicli/linkai-cli/oplog";import*as i from"./core.js";const x=c(e=>e&&e!==i.Z,"isRealId"),$=c(e=>Array.isArray(e)?e.map(String).map(r=>r.trim()).filter(Boolean):e?[String(e).trim()]:[],"list"),E=c(e=>String(e||"").replace(/[\\/:*?"<>|\s]+/g,"_").slice(0,120)||"plan","safe"),A=c(e=>/```\s*sql|\bselect\b[\s\S]{0,2000}\bfrom\b/i.test(JSON.stringify(e||{})),"containsFullSql");function U(e){const r=f.resolve(String(e)),n=d.readFileSync(r,"utf8");return/\.json$/i.test(r)?JSON.parse(n):j(n)}c(U,"readKnowledgeFile");function O(e){const r=[],n=c((t,a)=>{String(t||"").trim()||r.push(a)},"need");n(e?.summary,"knowledge.summary(方案内容)必填"),n(e?.problem,"knowledge.problem(解决的问题)必填"),(!Array.isArray(e?.cards)||!e.cards.length)&&r.push("knowledge.cards 至少需要一张业务卡片");for(const[t,a]of(e?.cards||[]).entries())n(a.cardId,`knowledge.cards[${t}].cardId 必填`),n(a.content,`knowledge.cards[${t}].content(卡片内容)必填`),n(a.source,`knowledge.cards[${t}].source(数据来源)必填`),n(a.grain,`knowledge.cards[${t}].grain(数据粒度)必填`),n(a.dataLogic,`knowledge.cards[${t}].dataLogic(取数逻辑)必填`);return A(e)&&r.push("方案知识库不得包含完整 SQL;请改写为自然语言取数逻辑和规则"),r}c(O,"validateKnowledgeAuthoring");function q(e,r){return{summary:e.knowledge?.summary,problem:e.knowledge?.problem,rules:$(e.knowledge?.rules),changeSummary:e.knowledge?.changeSummary||"首次创建",cards:(e.cards||[]).map(n=>({cardId:r.cards[n.key],content:n.knowledge?.content,source:n.knowledge?.source,grain:n.knowledge?.grain,dataLogic:n.knowledge?.dataLogic,rules:$(n.knowledge?.rules)}))}}c(q,"knowledgeAuthoringFromBuild");async function I(e){const r=i.readConfig(),n=await i.planDetail(e,!0),t=[];for(const a of n.pageCards||[]){const o=a.indexBlockId||a.indexBlock?.id;if(!x(o))continue;const u=await i.cardDetail(o),g=u.indexBlockDataId,s=x(g)?await i.dsDetail(g):null;t.push({pageCardId:a.id,cardId:o,name:u.name,chartType:`${u.chartParentType||""}/${u.chartType||""}`.replace(/^\/$/,""),dataset:s?{datasetId:g,name:s.name}:null})}return{plan:{planId:e,name:n.name,url:`${r.origin}/view/plan?plan=${e}`},cards:t}}c(I,"readPlanFacts");function _(e,r,{operation:n,baseline:t=!1}={}){const a=new Map((r.cards||[]).map(s=>[String(s.cardId),s])),o=new Set(e.cards.map(s=>String(s.cardId))),u=e.cards.filter(s=>!a.has(String(s.cardId))).map(s=>`${s.name}(${s.cardId})`),g=[...a.keys()].filter(s=>!o.has(s));if(u.length||g.length){const s=[];throw u.length&&s.push(`以下线上业务卡片缺少知识段落:${u.join("、")}`),g.length&&s.push(`知识文件包含不在方案中的卡片:${g.join("、")}`),new i.KindError("KNOWLEDGE_INVALID",s.join(";"))}return{schemaVersion:1,name:`${e.plan.name}方案知识库`,plan:e.plan,operation:n,baseline:t,summary:String(r.summary).trim(),problem:String(r.problem).trim(),rules:$(r.rules),changeSummary:String(r.changeSummary||(n==="create"?"首次创建":"方案更新")).trim(),cards:e.cards.map((s,h)=>{const m=a.get(String(s.cardId));return{order:h+1,...s,content:String(m.content).trim(),source:String(m.source).trim(),grain:String(m.grain).trim(),dataLogic:String(m.dataLogic).trim(),rules:$(m.rules)}})}}c(_,"assembleDocument");function y(e,r){const n=[];if(n.push(`# ${e.name}`,""),n.push(`- 版本:v${r}`),n.push(`- 方案名称:${e.plan.name}`),n.push(`- 方案 ID:${e.plan.planId}`),n.push(`- 方案地址:${e.plan.url}`,""),n.push("## 方案说明","","### 方案内容","",e.summary,"","### 解决的问题","",e.problem,""),n.push("### 公共规则",""),e.rules.length)for(const t of e.rules)n.push(`- ${t}`);else n.push("(无额外公共规则)");n.push("","## 卡片与数据","");for(const t of e.cards){if(n.push(`## ${String(t.order).padStart(2,"0")} ${t.name}`,""),n.push("### 卡片内容","",`- 卡片 ID:${t.cardId}`,`- 图表类型:${t.chartType||"未标注"}`,"",t.content,""),n.push("### 数据集",""),n.push(`- 数据集名称:${t.dataset?.name||"(无独立数据集)"}`),n.push(`- 数据集 ID:${t.dataset?.datasetId||"(无)"}`),n.push(`- 数据来源:${t.source}`),n.push(`- 数据粒度:${t.grain}`,""),n.push("### 取数逻辑与规则","",t.dataLogic,""),t.rules.length)for(const a of t.rules)n.push(`- ${a}`);n.push("")}return n.push("## 版本记录","",`- v${r}:${e.baseline?"历史方案首次纳管;":""}${e.changeSummary}`,""),n.join(`
2
+ `)}c(y,"renderPlanKnowledge");function F(e){const{operation:r,baseline:n,...t}=e;return L.createHash("sha256").update(JSON.stringify(t)).digest("hex")}c(F,"contentHash");function b(e){const r=i.readSession()?.tenantId||"unknown-tenant";return i.artifactsDir("plan-knowledge",E(r),E(e))}c(b,"localPlanDir");function v(e){const r=b(e);return d.existsSync(r)?d.readdirSync(r).filter(n=>/^v\d+\.pending\.json$/.test(n)).sort((n,t)=>Number(n.slice(1))-Number(t.slice(1))).map(n=>f.join(r,n)):[]}c(v,"pendingFiles");function P(e){const r=b(e);return d.existsSync(r)?d.readdirSync(r).filter(n=>/^v\d+\.md$/.test(n)).sort((n,t)=>Number(n.slice(1))-Number(t.slice(1))).map(n=>f.join(r,n)):[]}c(P,"localMarkdownFiles");function T(e){const r=[...v(e),...P(e)].map(n=>Number(f.basename(n).match(/^v(\d+)/)?.[1]||0));return Math.max(0,...r)}c(T,"highestLocalVersion");function M(e){const r=v(e).at(-1);if(!r)return null;try{return{file:r,...JSON.parse(d.readFileSync(r,"utf8"))}}catch{return null}}c(M,"latestPending");async function W(e){try{const r=await i.planKnowledgeGet(e);if(!r)return{planId:e,exists:!1};if(r.missing)return{planId:e,exists:!1,systemPlan:!0,readOnly:!0,message:r.message,next:i.SYSTEM_PLAN_NEXT};const n=r.readOnly===!0||r.systemPlan===!0;return{planId:e,exists:!0,version:`v${Number(r.version)||1}`,content:r.markdown||y(r.document,Number(r.version)||1),...n?{systemPlan:!0,readOnly:!0,next:i.SYSTEM_PLAN_NEXT}:{}}}catch(r){if(!(r instanceof i.PlanKnowledgeUnavailableError))throw r;const n=P(e).at(-1);if(!n)return{planId:e,exists:!1,next:"当前没有可读取的方案知识库:照常完成修改,最后用 plan-knowledge --mode update 同步,CLI 会自动补建 v1 基线"};const t=Number(f.basename(n).match(/^v(\d+)/)?.[1]||1);return{planId:e,exists:!0,version:`v${t}`,content:d.readFileSync(n,"utf8")}}}c(W,"readCurrentPlanKnowledge");function Y(e,r,n,t){const a=b(e);d.mkdirSync(a,{recursive:!0,mode:448});const o=f.join(a,`v${r}`);d.writeFileSync(`${o}.pending.json`,JSON.stringify({payload:n,createdAt:new Date().toISOString()},null,2),{mode:384}),d.writeFileSync(`${o}.md`,t,{mode:384})}c(Y,"savePending");function C(e,r){d.renameSync(e,e.replace(/\.pending\.json$/,`.${r}.json`));const n=e.replace(/\.pending\.json$/,".md");d.existsSync(n)&&d.renameSync(n,n.replace(/\.md$/,`.${r}.md`))}c(C,"setAside");async function J(e){const r=[];for(const n of v(e)){let t;try{t=JSON.parse(d.readFileSync(n,"utf8"))}catch{continue}try{await i.planKnowledgePut(t.payload),d.rmSync(n)}catch(a){if(a instanceof i.PlanKnowledgeUnavailableError)break;if(a instanceof i.LoginRequiredError)throw a;const o=`v${t?.payload?.version??f.basename(n).match(/^v(\d+)/)?.[1]}`;C(n,a instanceof i.PlanKnowledgeConflictError?"conflict":"rejected"),r.push({version:o,reason:String(a.message||a).slice(0,200)})}}return r.length&&D.addNotice(`方案知识有 ${r.length} 个本机暂存版本被服务器拒绝,已跳过:${r.map(n=>`${n.version}(${n.reason})`).join(";")}。`,"plan-knowledge-skipped"),r}c(J,"syncPending");async function X({planId:e,operation:r,authoring:n}){if(!["create","update"].includes(r))throw new Error("operation 必须是 create 或 update;不明确时先询问用户");const t=O(n);if(r==="update"&&!String(n?.changeSummary||"").trim()&&t.push("修改方案时 knowledge.changeSummary(本次修改说明)必填"),t.length)throw new i.KindError("KNOWLEDGE_INVALID",t.join(";"));await J(e);const a=await I(e);let o=null,u=!1;try{o=await i.planKnowledgeGet(e)}catch(l){if(!(l instanceof i.PlanKnowledgeUnavailableError))throw l;u=!0}if(o?.systemPlan||o?.readOnly)throw new i.KindError("SYSTEM_PLAN_READONLY",o.message||"这是领谋下发的系统分析方案(只读),不能写入方案知识");const s=_(a,n,{operation:r,baseline:r==="update"&&!o}),h=F(s);if(o?.contentHash===h){const l=Number(o.version)||1,w=o.document||s;return{plan:a.plan,planKnowledge:{name:w.name,version:`v${l}`,content:o.markdown||y(w,l)}}}const m=o?null:M(e);if(m?.payload?.contentHash===h){const l=Number(m.payload.version)||1,w=m.payload.document||s;return{plan:a.plan,planKnowledge:{name:w.name,version:`v${l}`,content:m.payload.markdown||y(w,l)}}}if(r==="create"&&o)throw new Error(`方案 ${e} 已有方案知识库,请使用 update`);const k=u?Math.max(Number(o?.version)||0,T(e)):Number(o?.version)||0;let p=k+1,S=y(s,p);const N={planId:e,operation:r,expectedVersion:k,version:p,contentHash:h,document:s,markdown:S};if(!u)try{const l=await i.planKnowledgePut(N);p=Number(l?.version)||p,S=y(s,p)}catch(l){if(l instanceof i.PlanKnowledgeRejectedError&&(l.status===403||/系统分析方案/.test(l.message)))throw new i.KindError("SYSTEM_PLAN_READONLY",l.message);if(!(l instanceof i.PlanKnowledgeUnavailableError))throw l;u=!0}return u&&Y(e,p,N,S),{plan:a.plan,planKnowledge:{name:s.name,version:`v${p}`,content:S}}}c(X,"publishPlanKnowledge");export{A as containsFullSql,q as knowledgeAuthoringFromBuild,X as publishPlanKnowledge,W as readCurrentPlanKnowledge,U as readKnowledgeFile,y as renderPlanKnowledge,O as validateKnowledgeAuthoring};
@@ -1 +1 @@
1
- var D=Object.defineProperty;var n=(e,t)=>D(e,"name",{value:t,configurable:!0});import S from"node:fs";import y from"node:path";import L from"node:os";const b=2*3600*1e3,k=n(()=>y.join(L.homedir(),".linkai","cache","dict-reads.json"),"file");function m(){try{return JSON.parse(S.readFileSync(k(),"utf8"))||{}}catch{return{}}}n(m,"readState");function w(e){try{S.mkdirSync(y.dirname(k()),{recursive:!0,mode:448}),S.writeFileSync(k(),JSON.stringify(e),{mode:384})}catch{}}n(w,"writeState");function O({tenantId:e,source:t,flowId:s=null,op:a,now:r=Date.now()}){if(!e||!t)return;const o=m();o.reads??={},o.reads[e]??={},o.reads[e][String(t).toLowerCase()]={readAt:r,flowId:s,op:a},w(o)}n(O,"recordRead");function C({tenantId:e,flowId:t=null,ref:s,now:a=Date.now()}){if(!e||!s)return;const r=m();r.refs??={};const o=(r.refs[e]||[]).filter(d=>d.ref!==s);o.push({ref:String(s).slice(0,200),flowId:t,at:a}),r.refs[e]=o.slice(-100),w(r)}n(C,"recordRef");function T({tenantId:e,flowId:t=null,now:s=Date.now()}){return(m().refs?.[e]||[]).filter(r=>t&&r.flowId===t||s-r.at<=b).slice(-50).map(r=>r.ref)}n(T,"recentRefs");function _(e,t=Date.now()){w({reads:{},resetAt:t,resetReason:e})}n(_,"resetReads");function x(e){if(!e)return null;const t=Date.parse(`${String(e).trim().replace(" ","T")}+08:00`);return Number.isFinite(t)?t:null}n(x,"parseServerTime");const A=n(e=>new Date(e+8*3600*1e3).toISOString().slice(0,19).replace("T"," "),"fmt");async function j({tenantId:e,source:t,flowId:s,fetchSources:a,fetchDocs:r,now:o=Date.now()}){let d;try{d=await a()}catch(i){return{ok:!0,skipped:`数据字典服务暂不可用(${String(i?.message||i).slice(0,80)}),本次未校验`}}const f=(d||[]).find(i=>String(i.source).toLowerCase()===String(t).toLowerCase());if(!f||f.metadataReady===!1)return{ok:!0,skipped:"当前数据集所用数据源没有可用的数据字典,本次未校验"};let g=[];try{g=await r()||[]}catch{}const h=[x(f.lastSuccessAt),...g.map(i=>x(i.updatedAt))].filter(Number.isFinite),c=h.length?Math.max(...h):null,u=m().reads?.[e]?.[String(t).toLowerCase()],p={source:t,sourceName:f.name??null,dictUpdatedAt:c?A(c):null};if(!u)return{...p,ok:!1,reason:"当前租户还没有读取数据字典(切换租户或重新登录后必须重新读取)"};const l=A(u.readAt);return c&&u.readAt<c?{...p,readAt:l,ok:!1,reason:`数据字典在 ${A(c)} 更新过,晚于上次读取(${l}),必须重新读取`}:u.flowId!==s&&o-u.readAt>b?{...p,readAt:l,ok:!1,reason:`上次读取数据字典是 ${l},不在当前任务内且已超过 2 小时,必须重新读取`}:{...p,readAt:l,ok:!0}}n(j,"dictionaryStatus");function M(e){return`先读取当前租户的最新数据字典:linkai kb-sources 确认数据源,再按需 linkai kb-search --source ${e} --question "<业务问题>"、kb-object / kb-docs / kb-doc 核对本次 SQL 用到的视图、字段与口径,然后重新执行。不能沿用其它租户或旧的数据字典结论。`}n(M,"readHint");export{b as MAX_AGE_OUTSIDE_FLOW_MS,j as dictionaryStatus,x as parseServerTime,M as readHint,T as recentRefs,O as recordRead,C as recordRef,_ as resetReads};
1
+ var x=Object.defineProperty;var n=(e,t)=>x(e,"name",{value:t,configurable:!0});import S from"node:fs";import y from"node:path";import D from"node:os";const b=2*3600*1e3,k=n(()=>y.join(D.homedir(),".linkai","cache","dict-reads.json"),"file");function m(){try{return JSON.parse(S.readFileSync(k(),"utf8"))||{}}catch{return{}}}n(m,"readState");function w(e){try{S.mkdirSync(y.dirname(k()),{recursive:!0,mode:448}),S.writeFileSync(k(),JSON.stringify(e),{mode:384})}catch{}}n(w,"writeState");function N({tenantId:e,source:t,flowId:s=null,op:u,revision:r=null,now:l=Date.now()}){if(!e||!t)return;const i=m();i.reads??={},i.reads[e]??={},i.reads[e][String(t).toLowerCase()]={readAt:l,flowId:s,op:u,...r?{revision:r}:{}},w(i)}n(N,"recordRead");function O({tenantId:e,flowId:t=null,ref:s,now:u=Date.now()}){if(!e||!s)return;const r=m();r.refs??={};const l=(r.refs[e]||[]).filter(i=>i.ref!==s);l.push({ref:String(s).slice(0,200),flowId:t,at:u}),r.refs[e]=l.slice(-100),w(r)}n(O,"recordRef");function C({tenantId:e,flowId:t=null,now:s=Date.now()}){return(m().refs?.[e]||[]).filter(r=>t&&r.flowId===t||s-r.at<=b).slice(-50).map(r=>r.ref)}n(C,"recentRefs");function T(e,t=Date.now()){w({reads:{},resetAt:t,resetReason:e})}n(T,"resetReads");function v(e){if(!e)return null;const t=Date.parse(`${String(e).trim().replace(" ","T")}+08:00`);return Number.isFinite(t)?t:null}n(v,"parseServerTime");const A=n(e=>new Date(e+8*3600*1e3).toISOString().slice(0,19).replace("T"," "),"fmt");async function _({tenantId:e,source:t,flowId:s,fetchSources:u,fetchDocs:r,now:l=Date.now()}){let i;try{i=await u()}catch(d){return{ok:!0,skipped:`数据字典服务暂不可用(${String(d?.message||d).slice(0,80)}),本次未校验`}}const o=(i||[]).find(d=>String(d.source).toLowerCase()===String(t).toLowerCase());if(!o||o.metadataReady===!1)return{ok:!0,skipped:"当前数据集所用数据源没有可用的数据字典,本次未校验"};let g=[];try{g=await r()||[]}catch{}const h=[v(o.lastSuccessAt),...g.map(d=>v(d.updatedAt))].filter(Number.isFinite),p=h.length?Math.max(...h):null,a=m().reads?.[e]?.[String(t).toLowerCase()],f={source:t,sourceName:o.name??null,dictUpdatedAt:p?A(p):null};if(!a)return{...f,ok:!1,reason:"当前租户还没有读取数据字典(切换租户或重新登录后必须重新读取)"};const c=A(a.readAt);return o.revision&&a.revision&&o.revision!==a.revision?{...f,readAt:c,ok:!1,reason:`数据字典在上次读取(${c})之后更新过(文档、段落或排除规则有变化),必须重新读取`}:o.revision&&!a.revision?{...f,readAt:c,ok:!1,reason:"上次读取数据字典时没有记下版本标识,无法确认之后是否有文档删除或停用,请重新读取"}:!o.revision&&p&&a.readAt<p?{...f,readAt:c,ok:!1,reason:`数据字典在 ${A(p)} 更新过,晚于上次读取(${c}),必须重新读取`}:a.flowId!==s&&l-a.readAt>b?{...f,readAt:c,ok:!1,reason:`上次读取数据字典是 ${c},不在当前任务内且已超过 2 小时,必须重新读取`}:{...f,readAt:c,ok:!0}}n(_,"dictionaryStatus");function j(e){return`先读取当前租户的最新数据字典:linkai kb-sources 确认数据源,再按需 linkai kb-search --source ${e} --question "<业务问题>"、kb-object / kb-docs / kb-doc 核对本次 SQL 用到的视图、字段与口径,然后重新执行。不能沿用其它租户或旧的数据字典结论。`}n(j,"readHint");export{b as MAX_AGE_OUTSIDE_FLOW_MS,_ as dictionaryStatus,v as parseServerTime,j as readHint,C as recentRefs,N as recordRead,O as recordRef,T as resetReads};
@@ -1,8 +1,8 @@
1
- var j=Object.defineProperty;var c=(e,n)=>j(e,"name",{value:n,configurable:!0});import E from"node:crypto";import{spawn as B}from"node:child_process";const m="linkai-cli",z="linkai offline_access",b=c(e=>Buffer.from(e).toString("base64url"),"base64url"),T=c(()=>b(E.randomBytes(32)),"randomValue");function y(e,n,o){let t;try{t=new URL(e)}catch{throw new Error(`OAuth 发现文档缺少有效的 ${o}`)}if(t.origin!==n||!["http:","https:"].includes(t.protocol)||t.username||t.password||t.hash)throw new Error(`OAuth 发现文档的 ${o} 地址与领谋配置不匹配`);return t}c(y,"endpoint");async function $(e){const n=new URL(e.apiBase).origin,o=await fetch(`${e.apiBase}/.well-known/oauth-authorization-server`,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(15e3)});if(!o.ok)throw new Error(`领谋 OAuth 接口暂不可用(HTTP ${o.status}),请确认后端已部署`);const t=await o.json().catch(()=>null);if(!t||t.issuer!==e.apiBase||!Array.isArray(t.code_challenge_methods_supported)||!t.code_challenge_methods_supported.includes("S256")||!Array.isArray(t.grant_types_supported)||!t.grant_types_supported.includes("authorization_code")||!t.grant_types_supported.includes("refresh_token"))throw new Error("领谋 OAuth 发现文档不符合 CLI 的授权码与 PKCE 要求");const r=y(t.authorization_endpoint,e.origin,"authorization_endpoint"),s=y(t.token_endpoint,n,"token_endpoint"),u=y(t.revocation_endpoint,n,"revocation_endpoint"),a=t.cli_session_endpoint?y(t.cli_session_endpoint,n,"cli_session_endpoint"):null;if(s.pathname!=="/oauth/token"||u.pathname!=="/oauth/revoke")throw new Error("领谋 OAuth 发现文档中的令牌接口路径不符合预期");if(a&&(a.pathname!=="/oauth/cli/sessions"||a.search))throw new Error("领谋线上授权会话接口路径不符合预期");return{authorization:r,token:s,revocation:u,online:a}}c($,"oauthMetadata");function H(e){const[n,o]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["rundll32.exe",["url.dll,FileProtocolHandler",e]]:["xdg-open",[e]];return new Promise(t=>{const r=B(n,o,{stdio:"ignore",detached:!0});r.once("error",()=>t(!1)),r.once("spawn",()=>{r.unref(),t(!0)})})}c(H,"openSystemBrowser");async function v(e,n){let o;try{o=await fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams(n),signal:AbortSignal.timeout(15e3)})}catch{const r=new Error("领谋 OAuth 请求结果无法确认,请重新授权");throw r.code="oauth_exchange_unknown",r}const t=await o.json().catch(()=>null);if(!o.ok){const r=typeof t?.error=="string"?t.error:`HTTP_${o.status}`,s=new Error(`领谋 OAuth 请求失败:${r}`);throw s.code=o.status>=500?"oauth_exchange_unknown":r,s}return t}c(v,"oauthForm");async function O(e,n,o=15e3){const t=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(n),signal:AbortSignal.timeout(o)}),r=await t.json().catch(()=>null);if(!t.ok){const s=new Error(`领谋线上授权请求失败:${r?.error||`HTTP_${t.status}`}`);throw s.code=r?.error||"oauth_online_error",s}return r}c(O,"onlineRequest");async function R(e,n){if(!e.ok||!e.body)throw new Error(`领谋线上授权事件连接失败(HTTP ${e.status})`);const o=e.body.getReader(),t=new TextDecoder;let r="";try{for(;;){const{value:s,done:u}=await o.read();if(u)throw new Error("领谋线上授权事件连接已断开,请重新执行 linkai login");if(r=(r+t.decode(s,{stream:!0})).replace(/\r\n/g,`
2
- `),r.length>65536)throw new Error("领谋线上授权事件无效");let a;for(;(a=r.indexOf(`
1
+ var B=Object.defineProperty;var s=(e,r)=>B(e,"name",{value:r,configurable:!0});import E from"node:crypto";import{spawn as R}from"node:child_process";const m="linkai-cli",z="linkai offline_access",v=s(e=>Buffer.from(e).toString("base64url"),"base64url"),T=s(()=>v(E.randomBytes(32)),"randomValue");function y(e,r,o){let t;try{t=new URL(e)}catch{throw new Error(`OAuth 发现文档缺少有效的 ${o}`)}if(t.origin!==r||!["http:","https:"].includes(t.protocol)||t.username||t.password||t.hash)throw new Error(`OAuth 发现文档的 ${o} 地址与领谋配置不匹配`);return t}s(y,"endpoint");async function O(e){const r=new URL(e.apiBase).origin,o=await fetch(`${e.apiBase}/.well-known/oauth-authorization-server`,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(15e3)});if(!o.ok)throw new Error(`领谋 OAuth 接口暂不可用(HTTP ${o.status}),请确认后端已部署`);const t=await o.json().catch(()=>null);if(!t||t.issuer!==e.apiBase||!Array.isArray(t.code_challenge_methods_supported)||!t.code_challenge_methods_supported.includes("S256")||!Array.isArray(t.grant_types_supported)||!t.grant_types_supported.includes("authorization_code")||!t.grant_types_supported.includes("refresh_token"))throw new Error("领谋 OAuth 发现文档不符合 CLI 的授权码与 PKCE 要求");const n=y(t.authorization_endpoint,e.origin,"authorization_endpoint"),c=y(t.token_endpoint,r,"token_endpoint"),d=y(t.revocation_endpoint,r,"revocation_endpoint"),a=t.cli_session_endpoint?y(t.cli_session_endpoint,r,"cli_session_endpoint"):null;if(c.pathname!=="/oauth/token"||d.pathname!=="/oauth/revoke")throw new Error("领谋 OAuth 发现文档中的令牌接口路径不符合预期");if(a&&(a.pathname!=="/oauth/cli/sessions"||a.search))throw new Error("领谋线上授权会话接口路径不符合预期");return{authorization:n,token:c,revocation:d,online:a}}s(O,"oauthMetadata");function H(e){const[r,o]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["rundll32.exe",["url.dll,FileProtocolHandler",e]]:["xdg-open",[e]];return new Promise(t=>{const n=R(r,o,{stdio:"ignore",detached:!0});n.once("error",()=>t(!1)),n.once("spawn",()=>{n.unref(),t(!0)})})}s(H,"openSystemBrowser");async function b(e,r){let o;try{o=await fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams(r),signal:AbortSignal.timeout(15e3)})}catch{const n=new Error("领谋 OAuth 请求结果无法确认,请重新授权");throw n.code="oauth_exchange_unknown",n}const t=await o.json().catch(()=>null);if(!o.ok){const n=typeof t?.error=="string"?t.error:`HTTP_${o.status}`,c=new Error(`领谋 OAuth 请求失败:${n}`);throw c.code=o.status>=500?"oauth_exchange_unknown":n,c}return t}s(b,"oauthForm");function C(e,r){const o=typeof e?.error=="string"&&e.error||typeof e?.error?.message=="string"&&e.error.message||typeof e?.message=="string"&&e.message||`HTTP_${r}`;return String(o).replace(/\s+/g," ").trim().slice(0,300)}s(C,"serverErrorText");async function S(e,r,o=15e3){const t=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(r),signal:AbortSignal.timeout(o)}),n=await t.json().catch(()=>null);if(!t.ok){const c=new Error(`领谋线上授权请求失败:${C(n,t.status)}`);throw c.code=typeof n?.error=="string"?n.error:"oauth_online_error",c}return n}s(S,"onlineRequest");async function F(e,r){if(!e.ok||!e.body)throw new Error(`领谋线上授权事件连接失败(HTTP ${e.status})`);const o=e.body.getReader(),t=new TextDecoder;let n="";try{for(;;){const{value:c,done:d}=await o.read();if(d)throw new Error("领谋线上授权事件连接已断开,请重新执行 linkai login");if(n=(n+t.decode(c,{stream:!0})).replace(/\r\n/g,`
2
+ `),n.length>65536)throw new Error("领谋线上授权事件无效");let a;for(;(a=n.indexOf(`
3
3
 
4
- `))!==-1;){const h=r.slice(0,a);if(r=r.slice(a+2),!h.split(`
5
- `).some(d=>d==="event: authorization"))continue;const p=h.split(`
6
- `).find(d=>d.startsWith("data: "));let l;try{l=JSON.parse(p?.slice(6))}catch{throw new Error("领谋线上授权事件无效")}if(l?.state!==n)throw new Error("领谋线上授权事件 state 不匹配");if(l.status==="approved")return;throw l.status==="expired"?new Error("等待领谋 OAuth 授权超时,请重新执行 linkai login"):new Error("领谋线上授权未完成,请重新执行 linkai login")}}}finally{await o.cancel().catch(()=>{})}}c(R,"waitForOnlineApproval");function P(e,n){if(!e||typeof e.access_token!="string"||typeof e.refresh_token!="string"||typeof e.tenant_id!="string"||e.token_type?.toLowerCase()!=="bearer")throw new Error("领谋 OAuth 令牌响应缺少必要字段");return{token:e.access_token,refreshToken:e.refresh_token,tenantId:e.tenant_id,agentId:n,authMode:"oauth"}}c(P,"tokenSession");async function M(e,n){const{token:o}=await $(e),t=await v(o,{grant_type:"refresh_token",client_id:m,refresh_token:n.refreshToken});try{return P(t,n.agentId)}catch{const r=new Error("领谋 OAuth 续期响应不完整,请重新授权");throw r.code="oauth_exchange_unknown",r}}c(M,"refreshOauthSession");async function C(e,n){if(!n?.refreshToken)return!1;const{revocation:o}=await $(e);return await v(o,{client_id:m,token:n.refreshToken}),!0}c(C,"revokeOauthSession");async function W(e,n,o){const t=await $(e),r=T(),s=T(),u=b(E.createHash("sha256").update(s,"ascii").digest()),a="http://127.0.0.1:49152/callback",h=await fetch(new URL("/oauth/authorize",e.apiBase),{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",Authorization:`Bearer ${n.token}`,Agentid:n.agentId,Tenantid:n.tenantId},body:JSON.stringify({response_type:"code",client_id:m,redirect_uri:a,scope:z,state:r,code_challenge:u,code_challenge_method:"S256",agent_id:n.agentId,tenant_id:o}),signal:AbortSignal.timeout(15e3)}),p=await h.json().catch(()=>null);if(!h.ok||typeof p?.redirect_uri!="string")throw new Error(`切换租户授权失败:${p?.error||`HTTP_${h.status}`}`);const l=new URL(p.redirect_uri),d=new URL(a);if(l.origin!==d.origin||l.pathname!==d.pathname||l.searchParams.getAll("state").length!==1||l.searchParams.get("state")!==r||l.searchParams.getAll("code").length!==1||!l.searchParams.get("code"))throw new Error("切换租户授权回调不符合预期");const i=await v(t.token,{grant_type:"authorization_code",client_id:m,code:l.searchParams.get("code"),redirect_uri:a,code_verifier:s}),w=P(i,n.agentId);if(w.tenantId.toLowerCase()!==String(o).toLowerCase())throw await C(e,w).catch(()=>{}),new Error("切换租户后令牌的租户与目标不一致");return w}c(W,"authorizeTenantSession");async function q(e,{agentId:n,tenant:o,switchAccount:t=!1,timeoutSeconds:r=300,browser:s=H,onAuthorized:u=c(async a=>({session:a,tenantName:a.tenantId}),"onAuthorized")}={}){const a=await $(e);if(!a.online)throw new Error("领谋线上授权会话接口尚未部署,请稍后重试");const h=T(),p=T(),l=b(E.createHash("sha256").update(p,"ascii").digest()),d=n||E.randomUUID().replace(/-/g,"");let i=null,w=null,k=!1,_=null,A=null;try{if(i=await O(a.online,{client_id:m,scope:z,state:h,code_challenge:l,code_challenge_method:"S256",agent_id:d,timeout_seconds:r}),!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(i?.session_id)||!/^[A-Za-z0-9_-]{43}$/.test(i?.client_secret)||!/^[0-9A-F]{4}-[0-9A-F]{4}$/.test(i?.verification_code))throw new Error("领谋线上授权会话响应不完整");const f=y(i.authorization_url,e.origin,"authorization_url");if(f.pathname!==a.authorization.pathname||f.searchParams.get("session_id")!==i.session_id)throw new Error("领谋线上授权页地址无效");o&&f.searchParams.set("tenant",o),t&&f.searchParams.set("prompt","login");const g=`${a.online.href}/${i.session_id}`;_=new AbortController,A=setTimeout(()=>_.abort(),r*1e3),A.unref();const L=await fetch(`${g}/events`,{headers:{Accept:"text/event-stream","X-Cli-Session":i.client_secret},signal:_.signal});process.stderr.write(`请在浏览器中完成领谋授权:${f.href}
4
+ `))!==-1;){const h=n.slice(0,a);if(n=n.slice(a+2),!h.split(`
5
+ `).some(u=>u==="event: authorization"))continue;const p=h.split(`
6
+ `).find(u=>u.startsWith("data: "));let l;try{l=JSON.parse(p?.slice(6))}catch{throw new Error("领谋线上授权事件无效")}if(l?.state!==r)throw new Error("领谋线上授权事件 state 不匹配");if(l.status==="approved")return;throw l.status==="expired"?new Error("等待领谋 OAuth 授权超时,请重新执行 linkai login"):new Error("领谋线上授权未完成,请重新执行 linkai login")}}}finally{await o.cancel().catch(()=>{})}}s(F,"waitForOnlineApproval");function x(e,r){if(!e||typeof e.access_token!="string"||typeof e.refresh_token!="string"||typeof e.tenant_id!="string"||e.token_type?.toLowerCase()!=="bearer")throw new Error("领谋 OAuth 令牌响应缺少必要字段");return{token:e.access_token,refreshToken:e.refresh_token,tenantId:e.tenant_id,agentId:r,authMode:"oauth"}}s(x,"tokenSession");async function W(e,r){const{token:o}=await O(e),t=await b(o,{grant_type:"refresh_token",client_id:m,refresh_token:r.refreshToken});try{return x(t,r.agentId)}catch{const n=new Error("领谋 OAuth 续期响应不完整,请重新授权");throw n.code="oauth_exchange_unknown",n}}s(W,"refreshOauthSession");async function L(e,r){if(!r?.refreshToken)return!1;const{revocation:o}=await O(e);return await b(o,{client_id:m,token:r.refreshToken}),!0}s(L,"revokeOauthSession");async function q(e,r,o){const t=await O(e),n=T(),c=T(),d=v(E.createHash("sha256").update(c,"ascii").digest()),a="http://127.0.0.1:49152/callback",h=await fetch(new URL("/oauth/authorize",e.apiBase),{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",Authorization:`Bearer ${r.token}`,Agentid:r.agentId,Tenantid:r.tenantId},body:JSON.stringify({response_type:"code",client_id:m,redirect_uri:a,scope:z,state:n,code_challenge:d,code_challenge_method:"S256",agent_id:r.agentId,tenant_id:o}),signal:AbortSignal.timeout(15e3)}),p=await h.json().catch(()=>null);if(!h.ok||typeof p?.redirect_uri!="string")throw new Error(`切换租户授权失败:${C(p,h.status)}`);const l=new URL(p.redirect_uri),u=new URL(a);if(l.origin!==u.origin||l.pathname!==u.pathname||l.searchParams.getAll("state").length!==1||l.searchParams.get("state")!==n||l.searchParams.getAll("code").length!==1||!l.searchParams.get("code"))throw new Error("切换租户授权回调不符合预期");const i=await b(t.token,{grant_type:"authorization_code",client_id:m,code:l.searchParams.get("code"),redirect_uri:a,code_verifier:c}),w=x(i,r.agentId);if(w.tenantId.toLowerCase()!==String(o).toLowerCase())throw await L(e,w).catch(()=>{}),new Error("切换租户后令牌的租户与目标不一致");return w}s(q,"authorizeTenantSession");async function K(e,{agentId:r,tenant:o,switchAccount:t=!1,timeoutSeconds:n=300,browser:c=H,onAuthorized:d=s(async a=>({session:a,tenantName:a.tenantId}),"onAuthorized")}={}){const a=await O(e);if(!a.online)throw new Error("领谋线上授权会话接口尚未部署,请稍后重试");const h=T(),p=T(),l=v(E.createHash("sha256").update(p,"ascii").digest()),u=r||E.randomUUID().replace(/-/g,"");let i=null,w=null,k=!1,_=null,A=null;try{if(i=await S(a.online,{client_id:m,scope:z,state:h,code_challenge:l,code_challenge_method:"S256",agent_id:u,timeout_seconds:n}),!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(i?.session_id)||!/^[A-Za-z0-9_-]{43}$/.test(i?.client_secret)||!/^[0-9A-F]{4}-[0-9A-F]{4}$/.test(i?.verification_code))throw new Error("领谋线上授权会话响应不完整");const f=y(i.authorization_url,e.origin,"authorization_url");if(f.pathname!==a.authorization.pathname||f.searchParams.get("session_id")!==i.session_id)throw new Error("领谋线上授权页地址无效");o&&f.searchParams.set("tenant",o),t&&f.searchParams.set("prompt","login");const g=`${a.online.href}/${i.session_id}`;_=new AbortController,A=setTimeout(()=>_.abort(),n*1e3),A.unref();const I=await fetch(`${g}/events`,{headers:{Accept:"text/event-stream","X-Cli-Session":i.client_secret},signal:_.signal});process.stderr.write(`请在浏览器中完成领谋授权:${f.href}
7
7
  请核对页面中的授权码:${i.verification_code}
8
- `),await s(f.href),await R(L,h);const I=await O(`${g}/token`,{client_secret:i.client_secret,code_verifier:p});w=P(I,d);const U=await u(w);k=!0;let S=!1;for(let x=0;x<2&&!S;x++)try{S=(await O(`${g}/complete`,{client_secret:i.client_secret,success:!0},5e3))?.status==="completed"}catch{}if(!S)throw new Error("CLI 已保存授权,但网页确认通知失败;请执行 linkai status 核对登录状态");return U.session}catch(f){if(w&&!k&&await C(e,w).catch(()=>{}),i?.session_id&&i?.client_secret&&!k){const g=`${a.online.href}/${i.session_id}`;await O(`${g}/complete`,{client_secret:i.client_secret,success:!1}).catch(()=>{})}throw!k&&_?.signal.aborted?new Error("等待领谋 OAuth 授权超时,请重新执行 linkai login"):f}finally{A&&clearTimeout(A),_?.abort()}}c(q,"loginWithOauth");export{W as authorizeTenantSession,q as loginWithOauth,$ as oauthMetadata,H as openSystemBrowser,M as refreshOauthSession,C as revokeOauthSession};
8
+ `),await c(f.href),await F(I,h);const U=await S(`${g}/token`,{client_secret:i.client_secret,code_verifier:p});w=x(U,u);const j=await d(w);k=!0;let $=!1;for(let P=0;P<2&&!$;P++)try{$=(await S(`${g}/complete`,{client_secret:i.client_secret,success:!0},5e3))?.status==="completed"}catch{}if(!$)throw new Error("CLI 已保存授权,但网页确认通知失败;请执行 linkai status 核对登录状态");return j.session}catch(f){if(w&&!k&&await L(e,w).catch(()=>{}),i?.session_id&&i?.client_secret&&!k){const g=`${a.online.href}/${i.session_id}`;await S(`${g}/complete`,{client_secret:i.client_secret,success:!1}).catch(()=>{})}throw!k&&_?.signal.aborted?new Error("等待领谋 OAuth 授权超时,请重新执行 linkai login"):f}finally{A&&clearTimeout(A),_?.abort()}}s(K,"loginWithOauth");export{q as authorizeTenantSession,K as loginWithOauth,O as oauthMetadata,H as openSystemBrowser,W as refreshOauthSession,L as revokeOauthSession,C as serverErrorText};
@@ -37,7 +37,7 @@ complete -c linkai -f -a '(
37
37
  `,{mode:384})}catch{}}function QN(t,e,r){let n=Ar();return n.n+=1,{schema:xN,eventId:Ld(),type:t,ts:new Date().toISOString(),runId:n.runId,sessionId:n.sessionId,runSeq:n.runSeq,n:n.n,installId:n.installId,cli:{version:st,node:process.versions.node,os:`${process.platform}-${process.arch}`},caller:n.caller,identity:n.identity,flow:r??n.flow,data:e}}function Hr(t,e={},r){if(hl())return;let n=Ar();if(t==="command.start"&&typeof e.command=="string"&&(n.command=e.command,n.access=typeof e.access=="string"?e.access:null),t==="build.stage"&&n.flow&&typeof e.stage=="string"&&e.stage!=="validate")try{cN(n.flow.id,e.stage,e.ok!==!1)}catch{}let i=QN(t,e,r);P6(JSON.stringify(i)),bN()}function V6(t){let e=Ar(),r=JSON.stringify(e.identity)!==JSON.stringify(t);e.identity=t,r&&t&&Hr("identity",{...t})}function q6(t){Ar().transport=t,bN()}function bN(){let t=Ar();!t.transport||t.flushTimer||Od()||Ud()||(t.flushTimer=setTimeout(()=>{t.flushTimer=null,pC()},b6),t.flushTimer.unref?.())}function pC({force:t=!1}={}){let e=Ar();if(!e.transport||!t&&Ud())return Promise.resolve();if(e.flushing)return e.flushing;let r=new AbortController;return e.abort=r,e.flushing=(async()=>{try{let n=is(),i=Date.now(),s=[];for(let c of Ze.existsSync(n)?Ze.readdirSync(n):[]){if(!c.startsWith("sending-"))continue;let l=Xt.join(n,c);try{i-Ze.statSync(l).mtimeMs>D6&&s.push(l)}catch{}}let a=Xt.join(n,`sending-${process.pid}-${i}.jsonl`);try{Ze.renameSync(BA(),a),s.push(a)}catch{}for(let c of s){let l;try{l=Ze.readFileSync(c,"utf8").split(`
38
38
  `).filter(Boolean)}catch{continue}let u=l.map(h=>{try{return JSON.parse(h)}catch{return null}}).filter(h=>h!==null).map(h=>h.identity||h.runId!==e.runId||!e.identity?h:{...h,identity:e.identity}),A=-1;for(let h=0;h<u.length;h+=IN){let g=!1;try{g=!r.signal.aborted&&await e.transport(u.slice(h,h+IN),r.signal)}catch{g=!1}if(!g){A=h;break}}if(r.signal.aborted||N6(A<0),A>=0){let h=u.slice(A).map(g=>JSON.stringify(g)).join(`
39
39
  `);try{Ze.appendFileSync(BA(),h+`
40
- `,{mode:384})}catch{}}try{Ze.rmSync(c,{force:!0})}catch{}if(A>=0)break}}finally{e.flushing=null,e.abort=null}})(),e.flushing}function Y6(t){let e=Ar();e.n>0||(e.argv=t,hl()||W6(),Hr("run.start",{argv:gC(t),cwdHash:To(process.cwd()).slice(0,16),pid:process.pid,worker:process.env.LINKAI_COLLAB_WORKER||null,processUptimeMs:Math.round(process.uptime()*1e3),parentRunId:process.env.LINKAI_PARENT_RUN_ID||null}),process.env.LINKAI_PARENT_RUN_ID=e.runId,process.once("exit",r=>{if(!e.ended){e.ended=!0;let n=kN(e,r);Hr("run.end",{exitCode:r,durationMs:Date.now()-e.startedAt,...n,abrupt:!0}),DN(e,n)}}))}function W6(){let t=Ar();try{let e=G6(),r=process.env.LINKAI_COLLAB_WORKER||null;t.flowKey=nN({installId:t.installId,agent:t.caller.agent,cwdHash:To(process.cwd()).slice(0,16),worker:r,toolSessionId:t.caller.toolSessionId??null});let n=vd(process.env.LINKAI_AI_MODEL),i=aN({key:t.flowKey,agent:t.caller.agent,worker:r,idleMs:e,explicitId:process.env.LINKAI_FLOW||null,board:rN(r),runId:t.runId,pid:process.pid,model:n});for(let s of i.stalled)Hr("flow.stalled",{status:s.status,reason:s.reason,source:s.source,goal:s.goal,type:s.type,plan:s.plan,lastActivity:new Date(s.lastActivity).toISOString(),lastCommand:s.lastCommand,lastErrorCode:s.lastErrorCode,build:s.build,openRuns:Object.keys(s.runsOpen).length,idleMinutes:e/6e4},{id:s.id,attach:"detected"});t.flow={id:i.id,attach:i.attach},t.caller.model=i.model?{name:i.model,source:"self-reported"}:null}catch{}}function DN(t,e){if(t.flow)try{nC(t.flow.id,t.runId,{command:t.command??gC(t.argv).command,ok:typeof e.ok=="boolean"?e.ok:null,errorCode:typeof e.errorCode=="string"?e.errorCode:null,outcomeUnknown:e.outcomeUnknown===!0})}catch{}}function mC(){let t=Ar();if(!t.flow||!t.command||J6.test(t.command)||t.access!=="write"&&t.command!=="linkai/status")return null;try{if(iC(t.flow.id)?.source!=="auto")return null}catch{return null}return j6(t.flow.id)?'\u5F53\u524D\u4EFB\u52A1\u5C1A\u672A\u5F00\u59CB\u5DE5\u4F5C\u6D41\u3002\u6BCF\u4E2A\u4E1A\u52A1\u4EFB\u52A1\u8BF7\u5148\u6267\u884C linkai flow-start --goal "<\u7528\u6237\u76EE\u6807>" --type create|modify|import|query --model "<\u4F60\u7684\u6A21\u578B\u4E0E\u7248\u672C>"\uFF0C\u4E4B\u540E\u7684\u547D\u4EE4\u81EA\u52A8\u5F52\u5165\uFF1B\u5B8C\u6210\u540E linkai flow-end --status delivered\uFF08\u653E\u5F03\u6216\u5931\u8D25\u7528 abandoned / failed\uFF09\u3002':null}function j6(t,e=Date.now()){let r=Xt.join(is(),"hints.json");try{let n={};try{n=JSON.parse(Ze.readFileSync(r,"utf8"))||{}}catch{}let i=n.flowNotice?.[t]??0;if(e-i>=0&&e-i<$6)return!1;let s=Object.entries(n.flowNotice||{}).sort((a,c)=>c[1]-a[1]).slice(0,19);return n.flowNotice=Object.fromEntries([...s,[t,e]]),Ze.writeFileSync(r,JSON.stringify(n),{mode:384}),!0}catch{return!0}}function SN(){return Ar().caller.model?.name??null}function z6(){return Ar().flow}function K6(t){let e=Ar();if(!ZE.includes(t.type))throw new Error(`\u5DE5\u4F5C\u6D41\u7C7B\u578B\u53EA\u80FD\u662F ${ZE.join(" / ")}`);if(!String(t.goal||"").trim())throw new Error("\u9700\u8981 --goal\uFF08\u8FD9\u4E2A\u5DE5\u4F5C\u6D41\u8981\u5B8C\u6210\u4EC0\u4E48\uFF09");let r=process.env.LINKAI_COLLAB_WORKER||null,n=e.flow,i=lN({id:t.id,adoptId:n?.id??null,source:t.source??"explicit",goal:String(t.goal).trim().slice(0,500),type:t.type,plan:t.plan??null,key:e.flowKey,agent:e.caller.agent,worker:r}),s=n?.id===i.id;if(n&&n.id!==i.id){try{nC(n.id,e.runId,{})}catch{}try{sN(i.id,e.runId,process.pid)}catch{}}e.flow={id:i.id,attach:t.source==="board"?"board":"flag"};let a=vd(t.model)??vd(process.env.LINKAI_AI_MODEL)??(n?iC(n.id)?.model:null)??null;return a&&(AN(i.id,a),e.caller.model={name:a,source:"self-reported"}),Hr("flow.start",{goal:i.goal,type:i.type,plan:i.plan,source:i.source,model:a,previousFlow:n?.id!==i.id?n?.id??null:null,adopted:s}),i}function X6(t){let e=Ar(),r=t.id||e.flow?.id;if(!r)throw new Error("\u6CA1\u6709\u5F53\u524D\u5DE5\u4F5C\u6D41\uFF1B\u7528 --flow <id> \u6307\u5B9A");if(!eC.includes(t.status))throw new Error(`\u7ED3\u675F\u72B6\u6001\u53EA\u80FD\u662F ${eC.join(" / ")}`);let n=uN(r,t.status,t.result??null);return Hr("flow.end",{status:n.status,result:n.result,goal:n.goal,type:n.type,plan:n.plan,source:n.source,runs:n.runs,durationMs:(n.endedAt??Date.now())-n.startedAt},{id:n.id,attach:e.flow?.id===n.id?e.flow.attach:"flag"}),n}function n$(t){let e=Ar();e.marks[t]=Date.now()-e.startedAt}function EC(){let t=Ar();return{...t.marks,total:Date.now()-t.startedAt,sinceProcessStart:Math.round(process.uptime()*1e3)}}function kN(t,e){if(t.outcome)return t.outcome;let r=t.argv.some(n=>i$.has(n));return{ok:Number(e??0)===0,...r?{kind:"help"}:{}}}function Hd(t){Ar().outcome=t}function uC(){try{return Ze.existsSync(BA())&&Ze.statSync(BA()).size>0?!0:Ze.readdirSync(is()).some(t=>t.startsWith("sending-"))}catch{return!1}}function s$(){try{let{pid:t,at:e}=JSON.parse(Ze.readFileSync(lC(),"utf8"));return Date.now()-e>BN+5e3?!1:(process.kill(t,0),!0)}catch{return!1}}function NN(t=process.argv[1]){if(Od()||hl()||!t||Ud()||!uC()||s$())return!1;try{return x6(process.execPath,[t,"__oplog-upload"],{detached:!0,stdio:"ignore",windowsHide:!0,env:{...process.env,LINKAI_OPLOG_UPLOADER:"1"}}).unref(),!0}catch{return!1}}async function o$(t=BN){let e=Ar();if(!e.transport)return;try{hC(),Ze.writeFileSync(lC(),JSON.stringify({pid:process.pid,at:Date.now()}),{mode:384})}catch{}let r=Date.now()+t,n=setTimeout(()=>e.abort?.abort(),t);try{for(;Date.now()<r&&uC()&&!Ud();)await pC(),uC()&&await new Promise(i=>setTimeout(i,500))}finally{clearTimeout(n);try{Ze.rmSync(lC(),{force:!0})}catch{}}}async function a$(t=300){let e=Ar();if(e.ended)return!1;e.ended=!0,e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);let r=kN(e,process.exitCode);Hr("run.end",{exitCode:process.exitCode??0,durationMs:Date.now()-e.startedAt,...r}),DN(e,r),e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);let n=!1;if(e.flushing){let i=e.flushing,s;await Promise.race([i,new Promise(a=>{s=setTimeout(()=>{n=!0,e.abort?.abort(),a()},t)})]),clearTimeout(s),await i.catch(()=>{})}return NN(),n}function c$(){let t=globalThis.__linkai_oplog__;t?.flushTimer&&clearTimeout(t.flushTimer),globalThis.__linkai_oplog__=void 0}var xN,w6,Q6,IN,b6,D6,S6,k6,Md,BA,Od,hl,BN,lC,To,T6,R6,v6,O6,yN,G6,J6,$6,Z6,e$,t$,r$,i$,fl=re(()=>{"use strict";ei();fN();CN();xA();xN="linkai.oplog/1",w6=1800*1e3,Q6=20*1024*1024,IN=200,b6=300,D6=60*1e3,S6=60*1e3,k6=3600*1e3,Md=o(()=>Xt.join(is(),"upload-state.json"),"backoffFile");o(Ud,"inBackoff");o(N6,"recordUpload");o(is,"oplogDir");BA=o(()=>Xt.join(is(),"spool.jsonl"),"spoolFile"),Od=o(()=>!!process.env.VITEST,"testMode"),hl=o(()=>process.env.LINKAI_OPLOG_UPLOADER==="1","uploaderMode"),BN=30*1e3,lC=o(()=>Xt.join(is(),"uploader.lock"),"lockFile");o(hC,"ensureDir");o(fC,"readJson");o(_6,"installId");To=o(t=>B6("sha256").update(t).digest("hex"),"sha256"),T6=[[/WORKBUDDY/i,"workbuddy"],[/^CLAUDECODE$|^CLAUDE_CODE_/,"claude"],[/^CODEX_/,"codex"],[/CODEBUDDY/i,"codebuddy"],[/^CURSOR_/,"cursor"]],R6=[[/claude/i,"claude"],[/codex/i,"codex"],[/workbuddy/i,"workbuddy"],[/codebuddy/i,"codebuddy"],[/cursor/i,"cursor"],[/deepseek/i,"deepseek"]];o(wN,"detectCaller");v6=tC;o(F6,"nextSession");o(M6,"nextSessionUnlocked");o(Ar,"state");o(L6,"currentRunId");o(U6,"currentCaller");O6=/pass(word)?|token|secret|cookie|credential|authorization|api[-_]?key/i,yN=/^(file|spec|body|sql|input|output|dir|image|path|config)$/i;o(H6,"fileInfo");o(dC,"sanitizeArgs");o(gC,"describeArgv");o(P6,"appendSpool");o(QN,"buildEvent");o(Hr,"emit");o(V6,"setIdentity");o(q6,"setTransport");o(bN,"scheduleFlush");o(pC,"flush");o(Y6,"startRun");G6=o(()=>rC()*60*1e3,"flowIdleMs");o(W6,"attachFlow");o(DN,"flowRunEnded");J6=/^linkai\/(flow-|collab-|login$|logout$)/;o(mC,"flowNotice");$6=10*6e4;o(j6,"flowNoticeDue");o(SN,"currentModel");o(z6,"currentFlow");o(K6,"startFlow");o(X6,"endFlow");Z6=tN,e$=sC,t$=hN,r$=rC;o(n$,"mark");o(EC,"startupMarks");i$=new Set(["--help","-h","help"]);o(kN,"finalOutcome");o(Hd,"setOutcome");o(uC,"pendingUpload");o(s$,"uploaderRunning");o(NN,"spawnUploader");o(o$,"runUploader");o(a$,"endRun");o(c$,"_resetForTests")});var wA={};At(wA,{emitFailure:()=>ce,emitResult:()=>Ae,resultShape:()=>Vd});function _N(t){for(let e of t)Hr("notice",{kind:e.kind,text:e.text.slice(0,200),chars:e.text.length});return t.length?t.map(e=>e.text).join(" "):null}function l$(){let t=mC();return _N([...Fd(),...t?[{text:t,kind:"flow-start"}]:[]])}function Vd(t){return Array.isArray(t)?{kind:"array",rows:t.length}:t&&typeof t=="object"?{kind:"object",keys:Object.keys(t).slice(0,30)}:{kind:t==null?"null":typeof t}}function Ae(t){let e=l$(),r=JSON.stringify({ok:!0,data:t??null,...e?{notice:e}:{}});Hd({ok:!0,outputBytes:Buffer.byteLength(r),result:Vd(t)}),console.log(r)}function ce(t,e){let r=t,n=r?.ok===!1&&r.error&&typeof r.error=="object"?r:void 0;n&&(t=n.error);let i=JE(t);if(n?.trace&&(i.trace=n.trace),t&&typeof t=="object"){let l=t;for(let u of["code","message","hint","candidates","matches_n","details","expected","actual","available","available_keys","download"])["code","message","hint"].includes(u)&&typeof l[u]!="string"||l[u]!==void 0&&(i.error[u]=l[u])}["command_result_unknown","command_lost","result_evicted","outcome_unknown","detached_mid_command","cdp_timeout"].includes(i.error.code)&&(i.error.outcome="unknown");let s=i.error;i.error.help&&(s.hint??=i.error.help,delete i.error.help),(i.error.code==="usage_error"||i.error.code.startsWith("commander."))&&(i.error.exitCode=2),e!==void 0&&(i.error.exitCode=e),i.error.code==="UNKNOWN"&&i.error.exitCode===2&&(i.error.code="usage_error");let a=_N(Fd()),c=JSON.stringify(a?{...i,notice:a}:i);Hd({ok:!1,errorCode:i.error.code,errorExitCode:i.error.exitCode||1,message:String(i.error.message||"").slice(0,500),outcomeUnknown:s.outcome==="unknown"||void 0}),console.log(c),process.exitCode=i.error.exitCode||1}var fn=re(()=>{"use strict";Ct();fl();xA();o(_N,"deliver");o(l$,"notices");o(Vd,"resultShape");o(Ae,"emitResult");o(ce,"emitFailure")});var bA={};At(bA,{GATE_EXEMPT:()=>GN,PACKAGE_NAME:()=>yC,UpgradeRequiredError:()=>Gd,autoUpgradeEnabled:()=>HN,belowMinVersion:()=>p$,checkAndUpgrade:()=>qN,checkDue:()=>YN,compareVersions:()=>gl,enforceMinVersion:()=>B$,fetchLatest:()=>xC,installPrefix:()=>Wd,npmCommand:()=>VN,readState:()=>Hs,reexec:()=>E$,registryUrl:()=>QA,spawnBackgroundUpgrade:()=>g$,takeResult:()=>m$,verifyIntegrity:()=>PN});import{spawn as u$,spawnSync as FN}from"node:child_process";import{createHash as TN}from"node:crypto";import*as Zt from"node:fs";import*as MN from"node:os";import*as Pr from"node:path";function Hs(){try{return JSON.parse(Zt.readFileSync(CC(),"utf8"))||{}}catch{return{}}}function Ro(t){let e={...Hs(),...t};try{Zt.mkdirSync(dl(),{recursive:!0,mode:448});let r=`${CC()}.${process.pid}.tmp`;Zt.writeFileSync(r,JSON.stringify(e),{mode:384}),Zt.renameSync(r,CC())}catch{}return e}function gl(t,e){let r=o(s=>String(s).replace(/^v/,"").split("-")[0].split(".").map(a=>Number(a)||0),"p"),[n,i]=[r(t),r(e)];for(let s=0;s<3;s++)if((n[s]??0)!==(i[s]??0))return(n[s]??0)-(i[s]??0);return 0}function QA(t=process.env,e=mA){if(t.LINKAI_REGISTRY)return t.LINKAI_REGISTRY.replace(/\/+$/,"");try{let r=JSON.parse(Zt.readFileSync(Pr.join(e,"package.json"),"utf8"));return String(r.publishConfig?.registry||"").replace(/\/+$/,"")}catch{return""}}function Wd(t=mA,e=process.platform){let r=(e==="win32"?Pr.win32:Pr.posix).resolve(t).split(/[\\/]+/),n=r.length;if(n<4||r[n-1]!=="linkai-cli"||r[n-2]!=="@linkaicli"||r[n-3]!=="node_modules"||r.includes("_npx")||r.includes(".npm"))return null;let i=e==="win32"?"\\":"/",s=r.slice(0,n-3);if(e!=="win32"){if(s[s.length-1]!=="lib")return null;s.pop()}return s.join(i)||i}function HN(t=process.env){return t.LINKAI_NO_AUTO_UPGRADE==="1"||t.CI||t.CONTINUOUS_INTEGRATION||t.VITEST?!1:!!QA(t)&&Wd()!==null}async function xC(t=QA(),e=UN){let r={Accept:"application/json"};process.env.LINKAI_REGISTRY_TOKEN&&(r.Authorization=`Bearer ${process.env.LINKAI_REGISTRY_TOKEN}`);let n=await fetch(`${t}/${yC.replace("/","%2f")}`,{headers:r,signal:AbortSignal.timeout(e)});if(!n.ok)throw new Error(`\u7248\u672C\u67E5\u8BE2\u5931\u8D25\uFF1AHTTP ${n.status}`);let i=await n.json(),s=i["dist-tags"]?.latest,a=s?i.versions?.[s]:void 0;if(!s||!a?.dist?.tarball)throw new Error("\u6E90\u4E0A\u6CA1\u6709\u53EF\u7528\u7684\u7248\u672C\u4FE1\u606F");return{version:s,tarball:a.dist.tarball,integrity:a.dist.integrity,shasum:a.dist.shasum,minVersion:a.linkaiUpgrade?.minVersion||null}}function PN(t,e){let r=String(e.integrity||"").split(/\s+/).find(n=>n.startsWith("sha512-"));return r?`sha512-${TN("sha512").update(t).digest("base64")}`===r:e.shasum?TN("sha1").update(t).digest("hex")===e.shasum:!1}async function RN(t){let e={};process.env.LINKAI_REGISTRY_TOKEN&&(e.Authorization=`Bearer ${process.env.LINKAI_REGISTRY_TOKEN}`);let r=await fetch(t.tarball,{headers:e,signal:AbortSignal.timeout(ON)});if(!r.ok)throw new Error(`\u4E0B\u8F7D\u5931\u8D25\uFF1AHTTP ${r.status}`);let n=Buffer.from(await r.arrayBuffer());if(!PN(n,t))throw new Error("\u5B89\u88C5\u5305\u6821\u9A8C\u5931\u8D25\uFF08integrity \u4E0D\u5339\u914D\uFF09\uFF0C\u672A\u5B89\u88C5");Zt.mkdirSync(dl(),{recursive:!0,mode:448});let i=Pr.join(dl(),`linkai-cli-${t.version}.tgz`);return Zt.writeFileSync(i,n,{mode:384}),i}function VN(t=process.execPath,e=process.platform){let r=Pr.dirname(t),i=(e==="win32"?[Pr.join(r,"node_modules","npm","bin","npm-cli.js")]:[Pr.join(r,"..","lib","node_modules","npm","bin","npm-cli.js"),Pr.join(r,"node_modules","npm","bin","npm-cli.js")]).find(s=>Zt.existsSync(s));return i?{cmd:t,args:[i],shell:!1}:{cmd:e==="win32"?"npm.cmd":"npm",args:[],shell:e==="win32"}}function vN(t,e){let r=VN(),n=FN(r.cmd,[...r.args,"install","-g",t,"--prefix",e,"--no-audit","--no-fund","--loglevel=error"],{encoding:"utf8",timeout:ON,shell:r.shell,windowsHide:!0,env:{...process.env,npm_config_update_notifier:"false"}});if(n.status!==0)throw new Error(`npm \u5B89\u88C5\u5931\u8D25\uFF1A${String(n.stderr||n.error?.message||n.status).trim().slice(-300)}`)}function IC(t){try{return JSON.parse(Zt.readFileSync(Pr.join(t,"package.json"),"utf8")).version||null}catch{return null}}function d$(){try{Zt.mkdirSync(dl(),{recursive:!0,mode:448});try{let t=Zt.statSync(Yd());if(Date.now()-t.mtimeMs<f$)return!1;Zt.rmSync(Yd(),{force:!0})}catch{}return Zt.writeFileSync(Yd(),String(process.pid),{flag:"wx"}),!0}catch{return!1}}async function qN({force:t=!1,pkgDir:e=mA,current:r=st,registry:n=QA()}={}){let i=Wd(e);if(!n||!i||!d$())return null;let s=Date.now(),a;try{Ro({lastAttempt:s}),a=await xC(n);let u=Hs();if(Ro({lastCheck:Date.now(),failures:0,latest:a.version,minVersion:a.minVersion}),gl(a.version,r)<=0||!t&&u.skip?.version===a.version&&Date.now()<u.skip.until)return qd(),null}catch{let u=(Hs().failures||0)+1;return Ro({failures:u}),qd(),null}let c=r,l;try{let u=await RN(a);vN(u,i);let A=IC(e);if(A!==a.version)throw new Error(`\u5B89\u88C5\u540E\u7248\u672C\u662F ${A??"\u672A\u77E5"}\uFF0C\u4E0D\u662F ${a.version}`);l={ok:!0,from:c,to:a.version,at:Date.now(),ms:Date.now()-s,prefix:i};try{Zt.rmSync(u,{force:!0})}catch{}}catch(u){let A=!1;if(IC(e)!==c)try{let g=(await fetch(`${n}/${yC.replace("/","%2f")}`,{signal:AbortSignal.timeout(UN)}).then(d=>d.json())).versions?.[c]?.dist;g?.tarball&&(vN(await RN({version:c,tarball:g.tarball,integrity:g.integrity,shasum:g.shasum,minVersion:null}),i),A=!0)}catch{}l={ok:!1,from:c,to:a.version,at:Date.now(),ms:Date.now()-s,error:String(u.message).slice(0,300),rolledBack:A,prefix:i},Ro({skip:{version:a.version,until:Date.now()+LN}})}return Ro({result:l}),qd(),l}function YN(t=Hs(),e=Date.now()){if(e-(t.lastCheck||0)<A$)return!1;let r=Math.min(LN,h$*2**Math.max(0,(t.failures||0)-1));return!t.failures||e-(t.lastAttempt||0)>=r}function g$(t=process.argv[1]){if(!t||!HN()||process.env.LINKAI_UPGRADER==="1")return!1;let e=Hs();if(!(e.latest&&gl(e.latest,st)>0&&!(e.skip?.version===e.latest&&Date.now()<e.skip.until))&&!YN(e))return!1;try{return u$(process.execPath,[t,"__self-upgrade"],{detached:!0,stdio:"ignore",windowsHide:!0,env:{...process.env,LINKAI_UPGRADER:"1"}}).unref(),!0}catch{return!1}}function p$(t=Hs()){return!!(t.minVersion&&gl(t.minVersion,st)>0)}function m$(){let t=Hs();return t.result?(Ro({result:null}),t.result):null}function E$(t,e){return FN(process.execPath,[t,...e],{stdio:"inherit",env:{...process.env,LINKAI_REEXEC:"1"}}).status??1}async function B$(t,{pkgDir:e=mA,current:r=st,registry:n=QA(),env:i=process.env,now:s=Date.now()}={}){if(!t.length||t[0].startsWith("__")||GN.has(t[0])||i.LINKAI_SKIP_UPGRADE==="1"||i.LINKAI_REEXEC==="1"||i.LINKAI_NO_AUTO_UPGRADE==="1"||i.CI||i.CONTINUOUS_INTEGRATION||i.VITEST||!n||Wd(e)===null)return"run";let a=Hs(),c=s-(a.lastCheck||0)>=C$,l=!a.failures||s-(a.lastAttempt||0)>=I$;if(c&&l)try{let h=await xC(n,y$);a=Ro({lastCheck:Date.now(),failures:0,latest:h.version,minVersion:h.minVersion})}catch{return Ro({lastAttempt:Date.now(),failures:(a.failures||0)+1}),"run"}let u=a.minVersion;if(!u||gl(u,r)<=0)return"run";let A=await qN({force:!0,pkgDir:e,current:r,registry:n});if(A?.ok)return"reexec";if(!A){let h=Date.now()+x$;for(;Date.now()<h;){let g=IC(e);if(g&&gl(g,u)>=0)return"reexec";await new Promise(d=>setTimeout(d,1e3))}}throw new Gd(u,r,A?.error||"\u7B49\u5F85\u5176\u5B83\u8FDB\u7A0B\u5B8C\u6210\u5347\u7EA7\u8D85\u65F6")}var yC,A$,h$,LN,f$,UN,ON,dl,CC,Yd,qd,C$,I$,y$,x$,GN,Gd,DA=re(()=>{"use strict";ei();yC="@linkaicli/linkai-cli",A$=4*36e5,h$=30*6e4,LN=24*36e5,f$=10*6e4,UN=15e3,ON=18e4,dl=o(()=>Pr.join(MN.homedir(),".linkai","upgrade"),"dir"),CC=o(()=>Pr.join(dl(),"state.json"),"stateFile"),Yd=o(()=>Pr.join(dl(),"lock"),"lockFile");o(Hs,"readState");o(Ro,"writeState");o(gl,"compareVersions");o(QA,"registryUrl");o(Wd,"installPrefix");o(HN,"autoUpgradeEnabled");o(xC,"fetchLatest");o(PN,"verifyIntegrity");o(RN,"download");o(VN,"npmCommand");o(vN,"npmInstall");o(IC,"installedVersion");o(d$,"acquireLock");qd=o(()=>{try{Zt.rmSync(Yd(),{force:!0})}catch{}},"releaseLock");o(qN,"checkAndUpgrade");o(YN,"checkDue");o(g$,"spawnBackgroundUpgrade");o(p$,"belowMinVersion");o(m$,"takeResult");o(E$,"reexec");C$=10*6e4,I$=10*6e4,y$=5e3,x$=6e4,GN=new Set(["-V","--version","-h","--help","help","doctor","daemon"]),Gd=class extends Error{static{o(this,"UpgradeRequiredError")}minVersion;current;reason;constructor(e,r,n){super(`LinkAI-CLI ${r} \u4F4E\u4E8E\u8981\u6C42\u7684\u6700\u4F4E\u7248\u672C ${e}\uFF0C\u81EA\u52A8\u5347\u7EA7\u5931\u8D25\uFF1A${n}`),this.minVersion=e,this.current=r,this.reason=n,this.name="UpgradeRequiredError"}};o(B$,"enforceMinVersion")});function w$(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}function SA(){if(WN)return vo;WN=1;function t(a){return typeof a>"u"||a===null}o(t,"isNothing");function e(a){return typeof a=="object"&&a!==null}o(e,"isObject");function r(a){return Array.isArray(a)?a:t(a)?[]:[a]}o(r,"toArray");function n(a,c){if(c){let l=Object.keys(c);for(let u=0,A=l.length;u<A;u+=1){let h=l[u];a[h]=c[h]}}return a}o(n,"extend");function i(a,c){let l="";for(let u=0;u<c;u+=1)l+=a;return l}o(i,"repeat");function s(a){return a===0&&Number.NEGATIVE_INFINITY===1/a}return o(s,"isNegativeZero"),vo.isNothing=t,vo.isObject=e,vo.toArray=r,vo.repeat=i,vo.isNegativeZero=s,vo.extend=n,vo}function kA(){if(JN)return BC;JN=1;function t(r,n){let i="",s=r.reason||"(unknown reason)";return r.mark?(r.mark.name&&(i+='in "'+r.mark.name+'" '),i+="("+(r.mark.line+1)+":"+(r.mark.column+1)+")",!n&&r.mark.snippet&&(i+=`
40
+ `,{mode:384})}catch{}}try{Ze.rmSync(c,{force:!0})}catch{}if(A>=0)break}}finally{e.flushing=null,e.abort=null}})(),e.flushing}function Y6(t){let e=Ar();e.n>0||(e.argv=t,hl()||W6(),Hr("run.start",{argv:gC(t),cwdHash:To(process.cwd()).slice(0,16),pid:process.pid,worker:process.env.LINKAI_COLLAB_WORKER||null,processUptimeMs:Math.round(process.uptime()*1e3),parentRunId:process.env.LINKAI_PARENT_RUN_ID||null}),process.env.LINKAI_PARENT_RUN_ID=e.runId,process.once("exit",r=>{if(!e.ended){e.ended=!0;let n=kN(e,r);Hr("run.end",{exitCode:r,durationMs:Date.now()-e.startedAt,...n,abrupt:!0}),DN(e,n)}}))}function W6(){let t=Ar();try{let e=G6(),r=process.env.LINKAI_COLLAB_WORKER||null;t.flowKey=nN({installId:t.installId,agent:t.caller.agent,cwdHash:To(process.cwd()).slice(0,16),worker:r,toolSessionId:t.caller.toolSessionId??null});let n=vd(process.env.LINKAI_AI_MODEL),i=aN({key:t.flowKey,agent:t.caller.agent,worker:r,idleMs:e,explicitId:process.env.LINKAI_FLOW||null,board:rN(r),runId:t.runId,pid:process.pid,model:n});for(let s of i.stalled)Hr("flow.stalled",{status:s.status,reason:s.reason,source:s.source,goal:s.goal,type:s.type,plan:s.plan,lastActivity:new Date(s.lastActivity).toISOString(),lastCommand:s.lastCommand,lastErrorCode:s.lastErrorCode,build:s.build,openRuns:Object.keys(s.runsOpen).length,idleMinutes:e/6e4},{id:s.id,attach:"detected"});t.flow={id:i.id,attach:i.attach},t.caller.model=i.model?{name:i.model,source:"self-reported"}:null}catch{}}function DN(t,e){if(t.flow)try{nC(t.flow.id,t.runId,{command:t.command??gC(t.argv).command,ok:typeof e.ok=="boolean"?e.ok:null,errorCode:typeof e.errorCode=="string"?e.errorCode:null,outcomeUnknown:e.outcomeUnknown===!0})}catch{}}function mC(){let t=Ar();if(!t.flow||!t.command||J6.test(t.command)||t.access!=="write"&&t.command!=="linkai/status")return null;try{if(iC(t.flow.id)?.source!=="auto")return null}catch{return null}return j6(t.flow.id)?'\u5F53\u524D\u4EFB\u52A1\u5C1A\u672A\u5F00\u59CB\u5DE5\u4F5C\u6D41\uFF1A\u73B0\u5728\u5C31\u6267\u884C linkai flow-start --goal "<\u7528\u6237\u76EE\u6807\uFF0C\u4E00\u53E5\u8BDD>" --type create|modify|import|query [--plan <planId>\uFF08\u4FEE\u6539\u5DF2\u6709\u65B9\u6848\u65F6\u5FC5\u5E26\uFF09] --model "<\u4F60\u7684\u6A21\u578B\u4E0E\u7248\u672C>"\uFF0C\u6B64\u524D\u7684\u547D\u4EE4\u4F1A\u4E00\u8D77\u5F52\u5165\uFF1B\u5B8C\u6210\u540E linkai flow-end --status delivered\uFF08\u653E\u5F03\u6216\u5931\u8D25\u7528 abandoned / failed\uFF09\u3002\u4E0D\u5F00\u5DE5\u4F5C\u6D41\uFF0C\u9886\u8C0B\u7F51\u9875\u4E0A\u770B\u4E0D\u51FA\u8FD9\u4E2A\u4EFB\u52A1\u7684\u76EE\u6807\u548C\u65B9\u6848\u3002':null}function j6(t,e=Date.now()){let r=Xt.join(is(),"hints.json");try{let n={};try{n=JSON.parse(Ze.readFileSync(r,"utf8"))||{}}catch{}let i=n.flowNotice?.[t]??0;if(e-i>=0&&e-i<$6)return!1;let s=Object.entries(n.flowNotice||{}).sort((a,c)=>c[1]-a[1]).slice(0,19);return n.flowNotice=Object.fromEntries([...s,[t,e]]),Ze.writeFileSync(r,JSON.stringify(n),{mode:384}),!0}catch{return!0}}function SN(){return Ar().caller.model?.name??null}function z6(){return Ar().flow}function K6(t){let e=Ar();if(!ZE.includes(t.type))throw new Error(`\u5DE5\u4F5C\u6D41\u7C7B\u578B\u53EA\u80FD\u662F ${ZE.join(" / ")}`);if(!String(t.goal||"").trim())throw new Error("\u9700\u8981 --goal\uFF08\u8FD9\u4E2A\u5DE5\u4F5C\u6D41\u8981\u5B8C\u6210\u4EC0\u4E48\uFF09");let r=process.env.LINKAI_COLLAB_WORKER||null,n=e.flow,i=lN({id:t.id,adoptId:n?.id??null,source:t.source??"explicit",goal:String(t.goal).trim().slice(0,500),type:t.type,plan:t.plan??null,key:e.flowKey,agent:e.caller.agent,worker:r}),s=n?.id===i.id;if(n&&n.id!==i.id){try{nC(n.id,e.runId,{})}catch{}try{sN(i.id,e.runId,process.pid)}catch{}}e.flow={id:i.id,attach:t.source==="board"?"board":"flag"};let a=vd(t.model)??vd(process.env.LINKAI_AI_MODEL)??(n?iC(n.id)?.model:null)??null;return a&&(AN(i.id,a),e.caller.model={name:a,source:"self-reported"}),Hr("flow.start",{goal:i.goal,type:i.type,plan:i.plan,source:i.source,model:a,previousFlow:n?.id!==i.id?n?.id??null:null,adopted:s}),i}function X6(t){let e=Ar(),r=t.id||e.flow?.id;if(!r)throw new Error("\u6CA1\u6709\u5F53\u524D\u5DE5\u4F5C\u6D41\uFF1B\u7528 --flow <id> \u6307\u5B9A");if(!eC.includes(t.status))throw new Error(`\u7ED3\u675F\u72B6\u6001\u53EA\u80FD\u662F ${eC.join(" / ")}`);let n=uN(r,t.status,t.result??null);return Hr("flow.end",{status:n.status,result:n.result,goal:n.goal,type:n.type,plan:n.plan,source:n.source,runs:n.runs,durationMs:(n.endedAt??Date.now())-n.startedAt},{id:n.id,attach:e.flow?.id===n.id?e.flow.attach:"flag"}),n}function n$(t){let e=Ar();e.marks[t]=Date.now()-e.startedAt}function EC(){let t=Ar();return{...t.marks,total:Date.now()-t.startedAt,sinceProcessStart:Math.round(process.uptime()*1e3)}}function kN(t,e){if(t.outcome)return t.outcome;let r=t.argv.some(n=>i$.has(n));return{ok:Number(e??0)===0,...r?{kind:"help"}:{}}}function Hd(t){Ar().outcome=t}function uC(){try{return Ze.existsSync(BA())&&Ze.statSync(BA()).size>0?!0:Ze.readdirSync(is()).some(t=>t.startsWith("sending-"))}catch{return!1}}function s$(){try{let{pid:t,at:e}=JSON.parse(Ze.readFileSync(lC(),"utf8"));return Date.now()-e>BN+5e3?!1:(process.kill(t,0),!0)}catch{return!1}}function NN(t=process.argv[1]){if(Od()||hl()||!t||Ud()||!uC()||s$())return!1;try{return x6(process.execPath,[t,"__oplog-upload"],{detached:!0,stdio:"ignore",windowsHide:!0,env:{...process.env,LINKAI_OPLOG_UPLOADER:"1"}}).unref(),!0}catch{return!1}}async function o$(t=BN){let e=Ar();if(!e.transport)return;try{hC(),Ze.writeFileSync(lC(),JSON.stringify({pid:process.pid,at:Date.now()}),{mode:384})}catch{}let r=Date.now()+t,n=setTimeout(()=>e.abort?.abort(),t);try{for(;Date.now()<r&&uC()&&!Ud();)await pC(),uC()&&await new Promise(i=>setTimeout(i,500))}finally{clearTimeout(n);try{Ze.rmSync(lC(),{force:!0})}catch{}}}async function a$(t=300){let e=Ar();if(e.ended)return!1;e.ended=!0,e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);let r=kN(e,process.exitCode);Hr("run.end",{exitCode:process.exitCode??0,durationMs:Date.now()-e.startedAt,...r}),DN(e,r),e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);let n=!1;if(e.flushing){let i=e.flushing,s;await Promise.race([i,new Promise(a=>{s=setTimeout(()=>{n=!0,e.abort?.abort(),a()},t)})]),clearTimeout(s),await i.catch(()=>{})}return NN(),n}function c$(){let t=globalThis.__linkai_oplog__;t?.flushTimer&&clearTimeout(t.flushTimer),globalThis.__linkai_oplog__=void 0}var xN,w6,Q6,IN,b6,D6,S6,k6,Md,BA,Od,hl,BN,lC,To,T6,R6,v6,O6,yN,G6,J6,$6,Z6,e$,t$,r$,i$,fl=re(()=>{"use strict";ei();fN();CN();xA();xN="linkai.oplog/1",w6=1800*1e3,Q6=20*1024*1024,IN=200,b6=300,D6=60*1e3,S6=60*1e3,k6=3600*1e3,Md=o(()=>Xt.join(is(),"upload-state.json"),"backoffFile");o(Ud,"inBackoff");o(N6,"recordUpload");o(is,"oplogDir");BA=o(()=>Xt.join(is(),"spool.jsonl"),"spoolFile"),Od=o(()=>!!process.env.VITEST,"testMode"),hl=o(()=>process.env.LINKAI_OPLOG_UPLOADER==="1","uploaderMode"),BN=30*1e3,lC=o(()=>Xt.join(is(),"uploader.lock"),"lockFile");o(hC,"ensureDir");o(fC,"readJson");o(_6,"installId");To=o(t=>B6("sha256").update(t).digest("hex"),"sha256"),T6=[[/WORKBUDDY/i,"workbuddy"],[/^CLAUDECODE$|^CLAUDE_CODE_/,"claude"],[/^CODEX_/,"codex"],[/CODEBUDDY/i,"codebuddy"],[/^CURSOR_/,"cursor"]],R6=[[/claude/i,"claude"],[/codex/i,"codex"],[/workbuddy/i,"workbuddy"],[/codebuddy/i,"codebuddy"],[/cursor/i,"cursor"],[/deepseek/i,"deepseek"]];o(wN,"detectCaller");v6=tC;o(F6,"nextSession");o(M6,"nextSessionUnlocked");o(Ar,"state");o(L6,"currentRunId");o(U6,"currentCaller");O6=/pass(word)?|token|secret|cookie|credential|authorization|api[-_]?key/i,yN=/^(file|spec|body|sql|input|output|dir|image|path|config)$/i;o(H6,"fileInfo");o(dC,"sanitizeArgs");o(gC,"describeArgv");o(P6,"appendSpool");o(QN,"buildEvent");o(Hr,"emit");o(V6,"setIdentity");o(q6,"setTransport");o(bN,"scheduleFlush");o(pC,"flush");o(Y6,"startRun");G6=o(()=>rC()*60*1e3,"flowIdleMs");o(W6,"attachFlow");o(DN,"flowRunEnded");J6=/^linkai\/(flow-|collab-|login$|logout$)/;o(mC,"flowNotice");$6=10*6e4;o(j6,"flowNoticeDue");o(SN,"currentModel");o(z6,"currentFlow");o(K6,"startFlow");o(X6,"endFlow");Z6=tN,e$=sC,t$=hN,r$=rC;o(n$,"mark");o(EC,"startupMarks");i$=new Set(["--help","-h","help"]);o(kN,"finalOutcome");o(Hd,"setOutcome");o(uC,"pendingUpload");o(s$,"uploaderRunning");o(NN,"spawnUploader");o(o$,"runUploader");o(a$,"endRun");o(c$,"_resetForTests")});var wA={};At(wA,{emitFailure:()=>ce,emitResult:()=>Ae,resultShape:()=>Vd});function _N(t){for(let e of t)Hr("notice",{kind:e.kind,text:e.text.slice(0,200),chars:e.text.length});return t.length?t.map(e=>e.text).join(" "):null}function l$(){let t=mC();return _N([...Fd(),...t?[{text:t,kind:"flow-start"}]:[]])}function Vd(t){return Array.isArray(t)?{kind:"array",rows:t.length}:t&&typeof t=="object"?{kind:"object",keys:Object.keys(t).slice(0,30)}:{kind:t==null?"null":typeof t}}function Ae(t){let e=l$(),r=JSON.stringify({ok:!0,data:t??null,...e?{notice:e}:{}});Hd({ok:!0,outputBytes:Buffer.byteLength(r),result:Vd(t)}),console.log(r)}function ce(t,e){let r=t,n=r?.ok===!1&&r.error&&typeof r.error=="object"?r:void 0;n&&(t=n.error);let i=JE(t);if(n?.trace&&(i.trace=n.trace),t&&typeof t=="object"){let l=t;for(let u of["code","message","hint","candidates","matches_n","details","expected","actual","available","available_keys","download"])["code","message","hint"].includes(u)&&typeof l[u]!="string"||l[u]!==void 0&&(i.error[u]=l[u])}["command_result_unknown","command_lost","result_evicted","outcome_unknown","detached_mid_command","cdp_timeout"].includes(i.error.code)&&(i.error.outcome="unknown");let s=i.error;i.error.help&&(s.hint??=i.error.help,delete i.error.help),(i.error.code==="usage_error"||i.error.code.startsWith("commander."))&&(i.error.exitCode=2),e!==void 0&&(i.error.exitCode=e),i.error.code==="UNKNOWN"&&i.error.exitCode===2&&(i.error.code="usage_error");let a=_N(Fd()),c=JSON.stringify(a?{...i,notice:a}:i);Hd({ok:!1,errorCode:i.error.code,errorExitCode:i.error.exitCode||1,message:String(i.error.message||"").slice(0,500),outcomeUnknown:s.outcome==="unknown"||void 0}),console.log(c),process.exitCode=i.error.exitCode||1}var fn=re(()=>{"use strict";Ct();fl();xA();o(_N,"deliver");o(l$,"notices");o(Vd,"resultShape");o(Ae,"emitResult");o(ce,"emitFailure")});var bA={};At(bA,{GATE_EXEMPT:()=>GN,PACKAGE_NAME:()=>yC,UpgradeRequiredError:()=>Gd,autoUpgradeEnabled:()=>HN,belowMinVersion:()=>p$,checkAndUpgrade:()=>qN,checkDue:()=>YN,compareVersions:()=>gl,enforceMinVersion:()=>B$,fetchLatest:()=>xC,installPrefix:()=>Wd,npmCommand:()=>VN,readState:()=>Hs,reexec:()=>E$,registryUrl:()=>QA,spawnBackgroundUpgrade:()=>g$,takeResult:()=>m$,verifyIntegrity:()=>PN});import{spawn as u$,spawnSync as FN}from"node:child_process";import{createHash as TN}from"node:crypto";import*as Zt from"node:fs";import*as MN from"node:os";import*as Pr from"node:path";function Hs(){try{return JSON.parse(Zt.readFileSync(CC(),"utf8"))||{}}catch{return{}}}function Ro(t){let e={...Hs(),...t};try{Zt.mkdirSync(dl(),{recursive:!0,mode:448});let r=`${CC()}.${process.pid}.tmp`;Zt.writeFileSync(r,JSON.stringify(e),{mode:384}),Zt.renameSync(r,CC())}catch{}return e}function gl(t,e){let r=o(s=>String(s).replace(/^v/,"").split("-")[0].split(".").map(a=>Number(a)||0),"p"),[n,i]=[r(t),r(e)];for(let s=0;s<3;s++)if((n[s]??0)!==(i[s]??0))return(n[s]??0)-(i[s]??0);return 0}function QA(t=process.env,e=mA){if(t.LINKAI_REGISTRY)return t.LINKAI_REGISTRY.replace(/\/+$/,"");try{let r=JSON.parse(Zt.readFileSync(Pr.join(e,"package.json"),"utf8"));return String(r.publishConfig?.registry||"").replace(/\/+$/,"")}catch{return""}}function Wd(t=mA,e=process.platform){let r=(e==="win32"?Pr.win32:Pr.posix).resolve(t).split(/[\\/]+/),n=r.length;if(n<4||r[n-1]!=="linkai-cli"||r[n-2]!=="@linkaicli"||r[n-3]!=="node_modules"||r.includes("_npx")||r.includes(".npm"))return null;let i=e==="win32"?"\\":"/",s=r.slice(0,n-3);if(e!=="win32"){if(s[s.length-1]!=="lib")return null;s.pop()}return s.join(i)||i}function HN(t=process.env){return t.LINKAI_NO_AUTO_UPGRADE==="1"||t.CI||t.CONTINUOUS_INTEGRATION||t.VITEST?!1:!!QA(t)&&Wd()!==null}async function xC(t=QA(),e=UN){let r={Accept:"application/json"};process.env.LINKAI_REGISTRY_TOKEN&&(r.Authorization=`Bearer ${process.env.LINKAI_REGISTRY_TOKEN}`);let n=await fetch(`${t}/${yC.replace("/","%2f")}`,{headers:r,signal:AbortSignal.timeout(e)});if(!n.ok)throw new Error(`\u7248\u672C\u67E5\u8BE2\u5931\u8D25\uFF1AHTTP ${n.status}`);let i=await n.json(),s=i["dist-tags"]?.latest,a=s?i.versions?.[s]:void 0;if(!s||!a?.dist?.tarball)throw new Error("\u6E90\u4E0A\u6CA1\u6709\u53EF\u7528\u7684\u7248\u672C\u4FE1\u606F");return{version:s,tarball:a.dist.tarball,integrity:a.dist.integrity,shasum:a.dist.shasum,minVersion:a.linkaiUpgrade?.minVersion||null}}function PN(t,e){let r=String(e.integrity||"").split(/\s+/).find(n=>n.startsWith("sha512-"));return r?`sha512-${TN("sha512").update(t).digest("base64")}`===r:e.shasum?TN("sha1").update(t).digest("hex")===e.shasum:!1}async function RN(t){let e={};process.env.LINKAI_REGISTRY_TOKEN&&(e.Authorization=`Bearer ${process.env.LINKAI_REGISTRY_TOKEN}`);let r=await fetch(t.tarball,{headers:e,signal:AbortSignal.timeout(ON)});if(!r.ok)throw new Error(`\u4E0B\u8F7D\u5931\u8D25\uFF1AHTTP ${r.status}`);let n=Buffer.from(await r.arrayBuffer());if(!PN(n,t))throw new Error("\u5B89\u88C5\u5305\u6821\u9A8C\u5931\u8D25\uFF08integrity \u4E0D\u5339\u914D\uFF09\uFF0C\u672A\u5B89\u88C5");Zt.mkdirSync(dl(),{recursive:!0,mode:448});let i=Pr.join(dl(),`linkai-cli-${t.version}.tgz`);return Zt.writeFileSync(i,n,{mode:384}),i}function VN(t=process.execPath,e=process.platform){let r=Pr.dirname(t),i=(e==="win32"?[Pr.join(r,"node_modules","npm","bin","npm-cli.js")]:[Pr.join(r,"..","lib","node_modules","npm","bin","npm-cli.js"),Pr.join(r,"node_modules","npm","bin","npm-cli.js")]).find(s=>Zt.existsSync(s));return i?{cmd:t,args:[i],shell:!1}:{cmd:e==="win32"?"npm.cmd":"npm",args:[],shell:e==="win32"}}function vN(t,e){let r=VN(),n=FN(r.cmd,[...r.args,"install","-g",t,"--prefix",e,"--no-audit","--no-fund","--loglevel=error"],{encoding:"utf8",timeout:ON,shell:r.shell,windowsHide:!0,env:{...process.env,npm_config_update_notifier:"false"}});if(n.status!==0)throw new Error(`npm \u5B89\u88C5\u5931\u8D25\uFF1A${String(n.stderr||n.error?.message||n.status).trim().slice(-300)}`)}function IC(t){try{return JSON.parse(Zt.readFileSync(Pr.join(t,"package.json"),"utf8")).version||null}catch{return null}}function d$(){try{Zt.mkdirSync(dl(),{recursive:!0,mode:448});try{let t=Zt.statSync(Yd());if(Date.now()-t.mtimeMs<f$)return!1;Zt.rmSync(Yd(),{force:!0})}catch{}return Zt.writeFileSync(Yd(),String(process.pid),{flag:"wx"}),!0}catch{return!1}}async function qN({force:t=!1,pkgDir:e=mA,current:r=st,registry:n=QA()}={}){let i=Wd(e);if(!n||!i||!d$())return null;let s=Date.now(),a;try{Ro({lastAttempt:s}),a=await xC(n);let u=Hs();if(Ro({lastCheck:Date.now(),failures:0,latest:a.version,minVersion:a.minVersion}),gl(a.version,r)<=0||!t&&u.skip?.version===a.version&&Date.now()<u.skip.until)return qd(),null}catch{let u=(Hs().failures||0)+1;return Ro({failures:u}),qd(),null}let c=r,l;try{let u=await RN(a);vN(u,i);let A=IC(e);if(A!==a.version)throw new Error(`\u5B89\u88C5\u540E\u7248\u672C\u662F ${A??"\u672A\u77E5"}\uFF0C\u4E0D\u662F ${a.version}`);l={ok:!0,from:c,to:a.version,at:Date.now(),ms:Date.now()-s,prefix:i};try{Zt.rmSync(u,{force:!0})}catch{}}catch(u){let A=!1;if(IC(e)!==c)try{let g=(await fetch(`${n}/${yC.replace("/","%2f")}`,{signal:AbortSignal.timeout(UN)}).then(d=>d.json())).versions?.[c]?.dist;g?.tarball&&(vN(await RN({version:c,tarball:g.tarball,integrity:g.integrity,shasum:g.shasum,minVersion:null}),i),A=!0)}catch{}l={ok:!1,from:c,to:a.version,at:Date.now(),ms:Date.now()-s,error:String(u.message).slice(0,300),rolledBack:A,prefix:i},Ro({skip:{version:a.version,until:Date.now()+LN}})}return Ro({result:l}),qd(),l}function YN(t=Hs(),e=Date.now()){if(e-(t.lastCheck||0)<A$)return!1;let r=Math.min(LN,h$*2**Math.max(0,(t.failures||0)-1));return!t.failures||e-(t.lastAttempt||0)>=r}function g$(t=process.argv[1]){if(!t||!HN()||process.env.LINKAI_UPGRADER==="1")return!1;let e=Hs();if(!(e.latest&&gl(e.latest,st)>0&&!(e.skip?.version===e.latest&&Date.now()<e.skip.until))&&!YN(e))return!1;try{return u$(process.execPath,[t,"__self-upgrade"],{detached:!0,stdio:"ignore",windowsHide:!0,env:{...process.env,LINKAI_UPGRADER:"1"}}).unref(),!0}catch{return!1}}function p$(t=Hs()){return!!(t.minVersion&&gl(t.minVersion,st)>0)}function m$(){let t=Hs();return t.result?(Ro({result:null}),t.result):null}function E$(t,e){return FN(process.execPath,[t,...e],{stdio:"inherit",env:{...process.env,LINKAI_REEXEC:"1"}}).status??1}async function B$(t,{pkgDir:e=mA,current:r=st,registry:n=QA(),env:i=process.env,now:s=Date.now()}={}){if(!t.length||t[0].startsWith("__")||GN.has(t[0])||i.LINKAI_SKIP_UPGRADE==="1"||i.LINKAI_REEXEC==="1"||i.LINKAI_NO_AUTO_UPGRADE==="1"||i.CI||i.CONTINUOUS_INTEGRATION||i.VITEST||!n||Wd(e)===null)return"run";let a=Hs(),c=s-(a.lastCheck||0)>=C$,l=!a.failures||s-(a.lastAttempt||0)>=I$;if(c&&l)try{let h=await xC(n,y$);a=Ro({lastCheck:Date.now(),failures:0,latest:h.version,minVersion:h.minVersion})}catch{return Ro({lastAttempt:Date.now(),failures:(a.failures||0)+1}),"run"}let u=a.minVersion;if(!u||gl(u,r)<=0)return"run";let A=await qN({force:!0,pkgDir:e,current:r,registry:n});if(A?.ok)return"reexec";if(!A){let h=Date.now()+x$;for(;Date.now()<h;){let g=IC(e);if(g&&gl(g,u)>=0)return"reexec";await new Promise(d=>setTimeout(d,1e3))}}throw new Gd(u,r,A?.error||"\u7B49\u5F85\u5176\u5B83\u8FDB\u7A0B\u5B8C\u6210\u5347\u7EA7\u8D85\u65F6")}var yC,A$,h$,LN,f$,UN,ON,dl,CC,Yd,qd,C$,I$,y$,x$,GN,Gd,DA=re(()=>{"use strict";ei();yC="@linkaicli/linkai-cli",A$=4*36e5,h$=30*6e4,LN=24*36e5,f$=10*6e4,UN=15e3,ON=18e4,dl=o(()=>Pr.join(MN.homedir(),".linkai","upgrade"),"dir"),CC=o(()=>Pr.join(dl(),"state.json"),"stateFile"),Yd=o(()=>Pr.join(dl(),"lock"),"lockFile");o(Hs,"readState");o(Ro,"writeState");o(gl,"compareVersions");o(QA,"registryUrl");o(Wd,"installPrefix");o(HN,"autoUpgradeEnabled");o(xC,"fetchLatest");o(PN,"verifyIntegrity");o(RN,"download");o(VN,"npmCommand");o(vN,"npmInstall");o(IC,"installedVersion");o(d$,"acquireLock");qd=o(()=>{try{Zt.rmSync(Yd(),{force:!0})}catch{}},"releaseLock");o(qN,"checkAndUpgrade");o(YN,"checkDue");o(g$,"spawnBackgroundUpgrade");o(p$,"belowMinVersion");o(m$,"takeResult");o(E$,"reexec");C$=10*6e4,I$=10*6e4,y$=5e3,x$=6e4,GN=new Set(["-V","--version","-h","--help","help","doctor","daemon"]),Gd=class extends Error{static{o(this,"UpgradeRequiredError")}minVersion;current;reason;constructor(e,r,n){super(`LinkAI-CLI ${r} \u4F4E\u4E8E\u8981\u6C42\u7684\u6700\u4F4E\u7248\u672C ${e}\uFF0C\u81EA\u52A8\u5347\u7EA7\u5931\u8D25\uFF1A${n}`),this.minVersion=e,this.current=r,this.reason=n,this.name="UpgradeRequiredError"}};o(B$,"enforceMinVersion")});function w$(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}function SA(){if(WN)return vo;WN=1;function t(a){return typeof a>"u"||a===null}o(t,"isNothing");function e(a){return typeof a=="object"&&a!==null}o(e,"isObject");function r(a){return Array.isArray(a)?a:t(a)?[]:[a]}o(r,"toArray");function n(a,c){if(c){let l=Object.keys(c);for(let u=0,A=l.length;u<A;u+=1){let h=l[u];a[h]=c[h]}}return a}o(n,"extend");function i(a,c){let l="";for(let u=0;u<c;u+=1)l+=a;return l}o(i,"repeat");function s(a){return a===0&&Number.NEGATIVE_INFINITY===1/a}return o(s,"isNegativeZero"),vo.isNothing=t,vo.isObject=e,vo.toArray=r,vo.repeat=i,vo.isNegativeZero=s,vo.extend=n,vo}function kA(){if(JN)return BC;JN=1;function t(r,n){let i="",s=r.reason||"(unknown reason)";return r.mark?(r.mark.name&&(i+='in "'+r.mark.name+'" '),i+="("+(r.mark.line+1)+":"+(r.mark.column+1)+")",!n&&r.mark.snippet&&(i+=`
41
41
 
42
42
  `+r.mark.snippet),s+" "+i):s}o(t,"formatError");function e(r,n){Error.call(this),this.name="YAMLException",this.reason=r,this.mark=n,this.message=t(this,!1),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack||""}return o(e,"YAMLException2"),e.prototype=Object.create(Error.prototype),e.prototype.constructor=e,e.prototype.toString=o(function(n){return this.name+": "+t(this,n)},"toString"),BC=e,BC}function Q$(){if($N)return wC;$N=1;let t=SA();function e(i,s,a,c,l){let u="",A="",h=Math.floor(l/2)-1;return c-s>h&&(u=" ... ",s=c-h+u.length),a-c>h&&(A=" ...",a=c+h-A.length),{str:u+i.slice(s,a).replace(/\t/g,"\u2192")+A,pos:c-s+u.length}}o(e,"getLine");function r(i,s){return t.repeat(" ",s-i.length)+i}o(r,"padStart");function n(i,s){if(s=Object.create(s||null),!i.buffer)return null;s.maxLength||(s.maxLength=79),typeof s.indent!="number"&&(s.indent=1),typeof s.linesBefore!="number"&&(s.linesBefore=3),typeof s.linesAfter!="number"&&(s.linesAfter=2);let a=/\r?\n|\r|\0/g,c=[0],l=[],u,A=-1;for(;u=a.exec(i.buffer);)l.push(u.index),c.push(u.index+u[0].length),i.position<=u.index&&A<0&&(A=c.length-2);A<0&&(A=c.length-1);let h="",g=Math.min(i.line+s.linesAfter,l.length).toString().length,d=s.maxLength-(s.indent+g+3);for(let p=1;p<=s.linesBefore&&!(A-p<0);p++){let y=e(i.buffer,c[A-p],l[A-p],i.position-(c[A]-c[A-p]),d);h=t.repeat(" ",s.indent)+r((i.line-p+1).toString(),g)+" | "+y.str+`
43
43
  `+h}let I=e(i.buffer,c[A],l[A],i.position,d);h+=t.repeat(" ",s.indent)+r((i.line+1).toString(),g)+" | "+I.str+`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkaicli/linkai-cli",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -111,6 +111,6 @@
111
111
  },
112
112
  "homepage": "https://bi.linkmore.com",
113
113
  "linkaiUpgrade": {
114
- "minVersion": "1.0.21"
114
+ "minVersion": "1.0.22"
115
115
  }
116
116
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: linkai-data-workflow
3
- version: 2026.11.01
3
+ version: 2026.11.06
4
4
  description: 处理领谋DI/BI的Excel或CSV源数据导入、预算与实际更新、填报数据维护,以及根据源文件新建或扩展分析方案。用户说“店铺预算导进去”“这月数据更新到经营看板”“这些底表做个分析”“重新上传看看数据对不对”时使用,无须提CLI。纯本地表格整理且未涉及领谋、租户或已有分析方案时不接管。
5
5
  ---
6
6
 
@@ -16,7 +16,7 @@ description: 处理领谋DI/BI的Excel或CSV源数据导入、预算与实际更
16
16
 
17
17
  ## 开始工作
18
18
 
19
- 0. 先开工作流:导入/更新数据 `linkai flow-start --goal "<用户目标>" --type import [--plan <planId>] --model "<你的模型与版本>"`,只读核对用 `--type query`,新建或扩展方案用 `create` / `modify`;完成后 `linkai flow-end --status delivered|abandoned|failed`。`status` 返回 `unfinishedFlows` 时先按其 `next` 续做或结束。
19
+ 0. **第一条命令先开工作流**:导入/更新数据 `linkai flow-start --goal "<用户目标,一句话>" --type import --plan <planId> --model "<你的模型与版本>"`(已有方案必须带 `--plan`),只读核对用 `--type query`,新建或扩展方案用 `create` / `modify`;**结束时一定** `linkai flow-end --status delivered|abandoned|failed`。不开工作流,领谋网页「CLI 记录」里看不出这个任务的目标和方案;中途想起来也要补,补开后此前的命令会一起归入。`status` 返回 `unfinishedFlows` 时先按其 `next` 续做或结束。
20
20
  1. 执行 `linkai status`,核对环境、租户及目标。检查当前 `--help`,不要猜命令参数。未登录时按 `linkai login --help` 引导用户完成授权,不读取登录文件或索要密码。CLI不可用时明确缺少哪项能力,不让客户挑选接入技术。
21
21
  - **租户必须与方案规范一致**:规范写明了目标租户(名称/编码)时,`linkai status` 的 `tenantName`/租户编码必须与之相同。不一致时先按 `linkai login --help` 切换到规范指定的租户(如 `linkai login --tenant <租户名>`),切换后再 `status` 确认;无法切换就停止并告诉用户。**不能因为“当前租户下也能读到目标卡片”就认定租户正确或文档过时**——卡片可以跨租户读取,但填报数据按登录租户写入,租户不对会把数据写进另一家客户名下。CLI 写卡片/填报/导入前会校验目标卡片属于当前租户,返回 `TENANT_MISMATCH` 时按提示切换租户,不要绕过。
22
22
  2. 执行 `linkai kb-sources`,从当前租户可读源中定位候选source。数据字典由服务器下发、会不定时更新:**切换租户或重新登录后必须重新读取新租户的数据字典**,不能沿用其它租户或旧的结论;服务端更新过(重新探库或口径文档更新)也要重读。CLI 在 `ds-create` / `ds-update` / `build --execute`(有新数据集)前强制校验,未读或已过期返回 `DICTIONARY_REQUIRED`;`build` 预览与 `sql-check` 会提前提示。 公共source下也可能有租户文档,按文档scope及目标对象核对,不能仅凭source名称“公共”排除。
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: linkai-report-builder
3
- version: 2026.11.04
3
+ version: 2026.11.06
4
4
  description: 领谋 BI(LMBI,bi.linkmore.com)自动搭建报表:数据集、卡片(74 种图表,含 AG 表格、甘特图、指标卡)、看板(分析方案)、数据报表、筛选器与排序,按搭建报表知识库和数据字典知识库执行;新建看板优先用 linkai build 声明式搭建,完成后交付完整方案知识库。用户要新建或修改领谋 BI 的报表、看板、分析方案、卡片、指标块、数据集、筛选器、表格样式,或提到 bi.linkmore.com 时使用。
5
5
  ---
6
6
 
@@ -12,9 +12,11 @@ description: 领谋 BI(LMBI,bi.linkmore.com)自动搭建报表:数据集
12
12
 
13
13
  > **多会话 / 子代理并行**:要开子代理或多个会话处理同一个方案时,先读 `linkai skills read linkai-collab -f plain`,用任务板分工、写共享对象前认领;收到 `COLLAB_CONFLICT` 不要绕过。
14
14
 
15
- > **工作流(每个业务任务都要)**:接到任务先 `linkai flow-start --goal "<用户目标>" --type <create|modify|import|query> [--plan <planId>] --model "<你的模型与版本>"`;
15
+ > **工作流(每个业务任务都要,第一条命令就做)**:接到任务的第一条命令是 `linkai flow-start --goal "<用户目标,一句话>" --type <create|modify|import|query> [--plan <planId>] --model "<你的模型与版本>"`;
16
+ > **修改已有方案时必须带 `--plan <planId>`**。不开工作流,领谋网页的「CLI 记录」里这个任务没有目标、关联不到方案,看不出在做什么
17
+ > (2026-11-05 正式数据:没开工作流的 93 个任务只有 3 个正常交付,88 个空闲 30 分钟后被自动关闭)。中途才想起来也要补:补开后此前的命令会一起归入。
16
18
  > `--model` 填你(AI)当前使用的具体模型与版本(如 `Claude Opus 5.5`、`gpt-5.6`),中途换了模型就在下一条命令上加全局参数 `--ai-model "<新模型>"`。
17
- > 交付后 `linkai flow-end --status delivered [--plan <planId>] [--knowledge-version vN]`,放弃或失败用 `--status abandoned|failed --note <原因>`。
19
+ > **结束时一定 `flow-end`**:交付后 `linkai flow-end --status delivered [--plan <planId>] [--knowledge-version vN]`,放弃或失败用 `--status abandoned|failed --note <原因>`。
18
20
  > `linkai status` 返回 `unfinishedFlows` 时先按其中的 `next` 处理:继续做就在命令上加 `--flow <id>`,不做了就 `flow-end --flow <id> --status abandoned`。
19
21
 
20
22
  所有操作只用 `linkai <命令>`(纯接口,浏览器只用于 `shot`)。动手前读 `knowledge/report-builder/SOP.md`:
@@ -43,7 +45,7 @@ description: 领谋 BI(LMBI,bi.linkmore.com)自动搭建报表:数据集
43
45
  4. **不得自由搭建**:公共 BI 知识库没有明确支持的结构、参数或步骤,不得自行猜测写入;先停止并说明缺少哪篇规则或哪项产品能力。
44
46
  5. **SQL 由 AI 编写**:根据用户需求、当前租户数据字典、`ds-tables`/`ds-fields` 和确认后的业务口径编写 SQL 文件。CLI 只校验、保存、编排和验收,不会自行完成 SQL;模版 SQL 仅为格式示例。
45
47
  数据字典由服务器下发、会不定时更新:**切换租户或重新登录后必须重新读取新租户的数据字典**,不能沿用其它租户或旧的结论;服务端更新过(重新探库或口径文档更新)也要重读。CLI 在 `ds-create` / `ds-update` / `build --execute`(有新数据集)前强制校验,未读或已过期返回 `DICTIONARY_REQUIRED`;`build` 预览与 `sql-check` 会提前提示。复制已有方案到另一个租户也一样:先读新租户的数据字典,核对视图、字段与口径是否一致。
46
- 6. **先确认新建或修改**:用户没有明确说明时先询问,不能凭同名方案猜测。新方案 `operation: create`、知识库从 v1 开始;修改原方案必须定位精确 planId,先 `plan-knowledge-get` 读取现有知识,修改完成后同步更新知识库和版本;历史方案无知识库时自动补建 v1。
48
+ 6. **先确认新建或修改**(修改前先 `plan-knowledge-get`:返回 `readOnly: true` 的是领谋下发的**系统分析方案**,只能读不能改,停下来告诉用户「如需按本租户调整,请在领谋网页上把它另存为本租户的分析方案,再对新方案操作」,不要重试、不要建副本):用户没有明确说明时先询问,不能凭同名方案猜测。新方案 `operation: create`、知识库从 v1 开始;修改原方案必须定位精确 planId,先 `plan-knowledge-get` 读取现有知识,修改完成后同步更新知识库和版本;历史方案无知识库时自动补建 v1。
47
49
 
48
50
  ## 登录与租户
49
51
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: linkai-usage
3
- version: 2026.11.07
3
+ version: 2026.11.08
4
4
  description: LinkAI-CLI 通用用法:查找可用命令、查看参数、理解 JSON 返回与错误码、处理登录与后台服务问题。不确定该用哪个 linkai 命令时使用;领谋 BI 报表搭建请用 linkai-report-builder,网页操作请用 linkai-browser。
5
5
  allowed-tools: Bash(linkai:*), Read
6
6
  ---
@@ -25,6 +25,7 @@ linkai build --help -f json # 查看某个命令的参数
25
25
  - 成功:`{"ok":true,"data":…}`;失败:退出码非 0,`{"ok":false,"error":{"code","message","hint"}}`。日志在 stderr。
26
26
  - 按 `error.code` 和 `hint` 处理问题。常见错误码:
27
27
  - `AUTH_REQUIRED`:未登录或登录失效,请用户执行 `linkai login` 并在领谋网页完成 OAuth 授权;不要索要密码。
28
+ - **工作流**:每个业务任务(搭建、修改、导入)的第一条命令是 `linkai flow-start --goal "<一句话目标>" --type … [--plan <planId>]`,结束时 `linkai flow-end --status delivered|abandoned|failed`;命令返回里提示「尚未开始工作流」时立刻补上。
28
29
  - `status` 返回 `unfinishedFlows`:有未完成或中断的工作流(`reason`:process-killed 进程被强制结束 / build-incomplete 搭建未完成 / outcome-unknown 写操作结果未知 / idle 长时间无活动)。按 `next` 处理:续做加 `--flow <id>`,不做了 `linkai flow-end --flow <id> --status abandoned`;`linkai flow-status` 查看全部。
29
30
  - `DICTIONARY_REQUIRED`:写 SQL 前没有读过当前租户的最新数据字典(切换了租户、重新登录,或服务端更新过数据字典)。按提示执行 `kb-sources`、`kb-search` / `kb-object` / `kb-doc` 核对后再重做,不要沿用旧结论。
30
31
  - `SQL_INVALID`:服务端解析 SQL 失败(字段 / 表不存在或语法错误)。用 `kb-object` / `ds-tables` 核对字段名后改 SQL,`sql-check` 通过前不要执行 `ds-create` / `ds-update`。
@@ -36,7 +37,8 @@ linkai build --help -f json # 查看某个命令的参数
36
37
  - `FILE_NOT_FOUND`:输入文件不存在(相对路径按当前目录解析)。确认文件已写出、路径正确,不要原样重复执行。
37
38
  - `SKILLS_INSTALL_FAILED`:技能目录写不进去(Windows 上常见于目录被占用或无权限)。关闭 AI 工具后重试,或改用 `linkai skills install <工具> --mode rules`。
38
39
  - `CONFIG_INVALID`:卡片或筛选条件违反领谋界面的保存规则(与在界面点保存时的校验一致)。按 message 逐条修正后再保存,规则见 references/card.md「保存前校验」、filters.md §8。
39
- - `READ_ONLY`:系统报表 / 系统方案(资源包下发到本租户)只读,本租户改不了。停下告诉用户到来源租户修改后重新下发,不要换接口反复尝试。
40
+ - `READ_ONLY` / `SYSTEM_PLAN_READONLY`:领谋下发的**系统分析方案** / 系统报表只读,CLI 不能改它的卡片、筛选器、排序、删除,也不能写它的方案知识(可以读)。`plan-knowledge-get` 返回 `readOnly: true` 时也是这种情况。停下告诉用户:如需按本租户调整,请在领谋网页上把它另存为本租户的分析方案,再对新方案操作;不要重试、不要换接口、不要自己建副本。
41
+ - `CLI_FORBIDDEN`:这个接口不允许 LinkAI CLI 调用(领谋平台维护的禁止列表)。把 message 原样告诉用户,不要重试、不要换路径再试;问数用 `linkai ask`,口径补充或纠正请用户在领谋网页上维护。
40
42
  - `BAD_REQUEST`:请求体结构不对(多见于 `api` 透传)。查 `references/api-catalog.md` 或回读一条现有数据照抄结构,优先用已封装的命令。
41
43
  - `KNOWLEDGE_INVALID`:方案知识文件不完整(缺卡片段落、缺 cardId、修改方案缺 changeSummary 等)。按 message 补齐后重新执行 `plan-knowledge`。
42
44
  - `UPGRADE_REQUIRED`:当前版本低于要求的最低版本且自动升级失败,命令没有执行。把 message 和 hint 里的手动升级步骤转告用户,等用户升级完成后再继续,不要尝试绕过。