@linkaicli/linkai-cli 1.0.19 → 1.0.20
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 +1 -1
- package/GUIDE.md +10 -0
- package/README.md +41 -48
- package/THIRD_PARTY_NOTICES.txt +1 -1
- package/cli-manifest.json +51 -0
- package/clis/linkai/commands.js +3 -3
- package/clis/linkai/core.js +6 -6
- package/clis/linkai/dictionary.js +1 -1
- package/dist/bundle/core.mjs +180 -180
- package/dist/bundle/oplog.mjs +1 -1
- package/knowledge/report-builder/references/pitfalls.md +6 -7
- package/llms.txt +15 -16
- package/package.json +12 -6
- package/skills/agent-prompt.md +1 -0
- package/skills/linkai-ask/SKILL.md +78 -0
- package/skills/linkai-report-builder/SKILL.md +2 -1
- package/skills/linkai-usage/SKILL.md +3 -1
package/clis/linkai/core.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
var ze=Object.defineProperty;var o=(t,e)=>ze(t,"name",{value:e,configurable:!0});import h from"node:fs";import x from"node:path";import le from"node:os";import U from"node:crypto";import{authorizeTenantSession as Ge,loginWithOauth as Ke,refreshOauthSession as Te,revokeOauthSession as H}from"./oauth.js";import{validateFillCardPayload as We}from"./fill-card.js";import{checkCardPayload as ke,checkFilterPayload as Ne}from"./card-rules.js";import*as k from"@linkaicli/linkai-cli/oplog";import*as Oe from"./collab.js";import _e from"./templates.js";const j="linkai",b="00000000-0000-0000-0000-000000000000",He="https://bi.linkmore.com/",Je="https://bi-api.linkmore.com",qe=/UND_ERR_CONNECT_TIMEOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|Connect Timeout/,Ve=/EPIPE|ECONNRESET|socket hang up|UND_ERR_SOCKET|other side closed/i,Qe=new Set(["GET","HEAD"]);class Ye extends Error{static{o(this,"NetworkError")}constructor(e,n){super(e,n),this.name="NetworkError"}}async function Ze(t,e,n=3){for(let r=1;;r++)try{return await fetch(t,e)}catch(a){if(e?.signal?.aborted)throw a;const i=`${a?.cause?.code||""} ${a?.cause?.message||""} ${a?.message||""}`,l=qe.test(i)||Qe.has(String(e?.method||"GET").toUpperCase())&&Ve.test(i);if(r>=n||!l)throw new Ye(`无法连接领谋 BI 服务器,请检查网络后重试(${i.trim().replace(/\s+/g," ").slice(0,120)})`,{cause:a});await new Promise(s=>setTimeout(s,1500*r))}}o(Ze,"fetchRetry");function ce(){const t=process.env.LINKAI_SITE_CONFIG_DIR||x.join(le.homedir(),".linkai","sites");return h.mkdirSync(t,{recursive:!0,mode:448}),Xe(t),t}o(ce,"configDir");let Ce=!1;function Xe(t){if(!Ce){Ce=!0;for(const[e,n]of[["lmbi.json",`${j}.json`],["lmbi.session.json",`${j}.session.json`]]){const r=x.join(t,e),a=x.join(t,n);try{h.existsSync(r)&&!h.existsSync(a)&&h.renameSync(r,a)}catch{}}if(!process.env.LINKAI_ARTIFACTS_DIR){const e=x.join(le.homedir(),".linkai","artifacts","lmbi"),n=et();try{h.existsSync(e)&&!h.existsSync(n)&&h.renameSync(e,n)}catch{}}}}o(Xe,"migrateLegacyFiles");const de=o(()=>x.join(ce(),`${j}.json`),"configFile");function et(...t){return x.join(process.env.LINKAI_ARTIFACTS_DIR||x.join(le.homedir(),".linkai","artifacts",j),...t)}o(et,"artifactsDir");function I(){const t=pe();if("username"in t||"password"in t||"tenantConfirmed"in t){const{username:e,password:n,tenantConfirmed:r,...a}=t;return he(a),$e(a)}return $e(t)}o(I,"readConfig");function $e(t){const e=new URL(t.url||He);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||Je).replace(/\/$/,"")}}o($e,"configFrom");const Y=o(()=>x.join(ce(),`${j}.session.json`),"sessionFile"),tt="请用户执行 linkai login,在浏览器完成领谋授权";function L(){try{const t=JSON.parse(h.readFileSync(Y(),"utf8"));return t.authMode==="oauth"&&t.token&&t.refreshToken&&t.tenantId?t:null}catch{return null}}o(L,"readSession");const J=o(()=>x.join(ce(),`${j}.cache.json`),"shortCacheFile");async function ue(t,e,n){const r=L()?.tenantId;if(!r||process.env.LINKAI_NO_CACHE==="1")return n();let a={};try{a=JSON.parse(h.readFileSync(J(),"utf8"))||{}}catch{}a.tenantId!==r&&(a={tenantId:r,entries:{}});const i=a.entries?.[t];if(i&&Date.now()-i.at>=0&&Date.now()-i.at<e)return i.value;const l=await n();try{try{a=JSON.parse(h.readFileSync(J(),"utf8"))||{}}catch{a={}}a.tenantId!==r&&(a={tenantId:r,entries:{}}),a.entries??={},a.entries[t]={at:Date.now(),value:l};const s=`${J()}.${process.pid}.tmp`;h.writeFileSync(s,JSON.stringify(a),{mode:384}),h.renameSync(s,J())}catch{}return l}o(ue,"shortCache");function Ht(){try{h.rmSync(J(),{force:!0})}catch{}}o(Ht,"clearShortCache");function Z(t){if(t?.authMode!=="oauth"||!t.token||!t.refreshToken||!t.tenantId||!t.agentId)throw new Error("只允许保存完整的 OAuth 会话");const e=Y(),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 h.writeFileSync(n,JSON.stringify(r),{mode:384}),h.renameSync(n,e),O=r,z=null,G=null,r}o(Z,"saveSession");function nt(){try{h.rmSync(Y())}catch{}O=null,z=null,G=null}o(nt,"clearSession");function D(t){try{return JSON.parse(Buffer.from(String(t).split(".")[1],"base64").toString())}catch{return{}}}o(D,"tokenPayload");function q(t){const e=D(t);return e.exp?(e.exp*1e3-Date.now())/36e5:-1}o(q,"tokenHoursLeft");function fe(t){const e=D(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(fe,"spaceOf");class E extends Error{static{o(this,"LoginRequiredError")}constructor(e){super(`${e}。${tt}`),this.name="LoginRequiredError"}}function pe(){try{return JSON.parse(h.readFileSync(de(),"utf8"))||{}}catch{return{}}}o(pe,"readConfigFile");function he(t){const e=de(),n=`${e}.${process.pid}.tmp`;h.writeFileSync(n,JSON.stringify(t,null,2),{mode:384}),h.renameSync(n,e)}o(he,"writeConfigFile");function me(){const{username:t,password:e,tenantId:n,tenantConfirmed:r,...a}=pe();if(Object.keys(a).length)he(a);else try{h.rmSync(de())}catch{}}o(me,"forgetCredentials");function rt(){me();const t=pe();if(typeof t.oauthAgentId=="string"&&/^[A-Za-z0-9._:-]{8,128}$/.test(t.oauthAgentId))return t.oauthAgentId;const e=U.randomUUID().replace(/-/g,"");return he({...t,oauthAgentId:e}),e}o(rt,"oauthAgentId");async function Jt({tenant:t,switchAccount:e=!1,timeoutSeconds:n=300,browser:r}={}){const a=L();return Ke(I(),{agentId:rt(),tenant:t,switchAccount:e,timeoutSeconds:n,browser:r,onAuthorized:o(async i=>{if(e&&a){const s=D(a.token).UserId,u=D(i.token).UserId;if(s&&u&&s===u)throw await H(I(),i).catch(()=>{}),new Error("授权后仍是原领谋账号;请在授权页切换账号后重试")}if(t)try{const s=await Fe(i),u=String(t).trim(),c=s.filter(f=>f.tenantId===u||f.code===u||f.name===u),d=c.length?c:s.filter(f=>f.name.includes(u));if(d.length!==1||d[0].tenantId!==i.tenantId)throw new Error(`授权的租户不是「${u}」,请在领谋授权页选择目标租户后重试`)}catch(s){throw await H(I(),i).catch(()=>{}),s}me();const l=Z(i);return a?.refreshToken&&await H(I(),a).catch(()=>{}),{session:l,tenantName:fe(l).tenantName||l.tenantId}},"onAuthorized")})}o(Jt,"oauthLogin");async function qt(){const t=L();if(!t?.refreshToken)return!1;try{return await H(I(),t)}catch{return!1}}o(qt,"oauthLogout");let O=null,X=null;async function Ae(t){const e=`${Y()}.refresh.lock`,n=Date.now()+3e4;let r;for(;r===void 0;)try{r=h.openSync(e,"wx",384),h.writeFileSync(r,String(process.pid))}catch(a){if(a.code!=="EEXIST")throw a;try{const i=Number(h.readFileSync(e,"utf8"));if(Number.isInteger(i)&&i>0){let l=!0;try{process.kill(i,0)}catch(s){s.code==="ESRCH"&&(l=!1)}if(!l){h.rmSync(e);continue}}}catch{}if(Date.now()>=n)throw new Error("等待 OAuth 会话操作超时,请稍后重试");await new Promise(i=>setTimeout(i,150))}try{return await t()}finally{h.closeSync(r),h.rmSync(e)}}o(Ae,"withSessionLock");async function at(t){return Ae(async()=>{const e=L();if(!e?.refreshToken)throw new E("OAuth 会话已在本机退出");return e.refreshToken!==t.refreshToken&&q(e.token)>.05?(O=e,e):Z(await Te(I(),e))})}o(at,"refreshOauthLocked");async function M({refresh:t=!1}={}){if(me(),O&&!t&&q(O.token)>.05)return O;const e=O||L();if(e&&!t&&q(e.token)>.05)return O=e,e;if(e?.authMode==="oauth"&&e.refreshToken)return X||(X=(async()=>{try{return await at(e)}catch(n){if(["invalid_grant","invalid_client","oauth_exchange_unknown"].includes(n.code)){const r=L();if(r&&r.refreshToken!==e.refreshToken&&q(r.token)>.05)return O=r,r;throw nt(),new E(n.code==="oauth_exchange_unknown"?"OAuth 续期结果无法确认":"OAuth 授权已失效")}throw n}})().finally(()=>{X=null})),X;throw new E(e?"登录已过期":"尚未登录领谋 BI")}o(M,"ensureSession");async function Fe(t){t=t||await M();const e=D(t.token),n=await V(I(),t,"GET",`/api/app/account/user-bind-tenant-list/${e.UserId||e.sub||e.userId}`);if(n.code!==200)throw new E(`读取租户列表失败(${n.message||n.code})`);return(n.data||[]).map(r=>({tenantId:r.tenantId,name:r.spaceName||r.tenantName||"",code:r.tenantCode||""})).filter(r=>!ot(r))}o(Fe,"tenantList");function ot(t){return t.tenantId===b||t.code==="0000"||t.name==="全域租户"||t.name==="全域"}o(ot,"isPlatformTenant");async function Vt(t,e){if(t?.authMode!=="oauth")throw new E("当前没有 OAuth 授权");return Ae(async()=>{let n=L();if(!n)throw new E("当前没有 OAuth 授权");if(D(n.token).UserId!==D(t.token).UserId)throw new Error("领谋账号已切换,请重新读取当前租户");q(n.token)<=.05&&(n=Z(await Te(I(),n)));const r=await Fe(n),a=String(e).trim(),i=r.filter(c=>c.tenantId===a||c.code===a||c.name===a),l=i.length?i: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 s=l[0];if(s.tenantId===n.tenantId)return{session:n,tenant:s,switched:!1};const u=await Ge(I(),n,s.tenantId);return Z(u),Q=null,await H(I(),n).catch(()=>{}),{session:u,tenant:s,switched:!0}})}o(Vt,"switchTenant");let we="";function Qt(t){we=String(t||"")}o(Qt,"setClientVersion");function Le(t){const[e,n=""]=String(t).split("?"),r=n?[...new URLSearchParams(n).keys()]:[];return{path:e,query:r.length?r:void 0}}o(Le,"logPath");function it(t){if(!t)return null;const e=D(t.token);return{userId:String(e.UserId||e.sub||e.userId||""),tenantId:t.tenantId,tenantCode:fe(t).tenantCode}}o(it,"identityOf");function B(t,e,n={}){k.emit("guard",{check:t,pass:e,...n})}o(B,"logGuard");async function V(t,e,n,r,a,{log:i=!0,signal:l}={}){const s=Date.now(),u=a!==void 0?JSON.stringify(a):void 0;i&&k.setIdentity(it(e));let c;try{c=await Ze(t.apiBase+r,{method:n,headers:{"X-LinkAI-Run-Id":k.currentRunId(),...we?{"X-LinkAI-Version":we}:{},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 i&&k.emit("api.call",{method:n,...Le(r),ok:!1,network:!0,ms:Date.now()-s,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(i){const m=c.status<400&&f.code===200;k.emit("api.call",{method:n,...Le(r),status:c.status,code:f.code??null,ok:m,ms:Date.now()-s,reqBytes:u?Buffer.byteLength(u):0,resBytes:Buffer.byteLength(d),...m?{}:{message:String(f.message||"").slice(0,300)}})}return f}o(V,"rawFetch");const De="/api/app/linkai-oplog/events";k.setTransport(async(t,e)=>{if(!L())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 V(I(),r,"POST",De,{events:t},{log:!1,signal:e});return n(a)&&(r=await M({refresh:!0}),a=await V(I(),r,"POST",De,{events:t},{log:!1,signal:e})),a.__status<400&&a.code===200?(k.saveServerConfig(a.data||{}),!0):!1}catch{return!1}});const st=[/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 Yt(t){let e=String(t);try{for(let r=0;r<3;r++)e=decodeURIComponent(e)}catch{}const n=st.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(Yt,"assertApiAllowed");async function _(t,e,n,{raw:r=!1}={}){const a=I();e=String(e).trim(),e.startsWith("/")||(e="/"+e),e.startsWith("/api/")||(e="/api/app"+e);try{We(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=ke(t,e,n)}catch(f){const m=/update-new/i.test(e)&&n?.id&&n.id!==b?n.id:null,g=m&&f.errors?await Se(m).catch(()=>null):null;if(!g)throw f;c=ke(t,e,n,{previous:g})}for(const f of c)k.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,g=m&&f.errors?(await Ct(m).catch(()=>null))?.filters:null;if(!g)throw f;d=Ne(t,e,n,{previous:g})}for(const f of d)k.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 P("CONFIG_INVALID",c.message)}const i=kt(t,e,n);if(i){Oe.assertWritable([Oe.normalizeObject(`card:${i}`)]);try{await Nt(i),B("tenant-ownership",!0,{target:i,path:e.split("?")[0]})}catch(c){throw c instanceof Ue&&B("tenant-ownership",!1,{target:i,path:e.split("?")[0]}),c}}let l=await M(),s=await V(a,l,t,e,n);const u=o(c=>c.__status===401||c.code===401||c.__status===406||c.code===406,"expired");if(u(s)){if(!l.refreshToken)throw new E("OAuth 授权已失效");if(l=await M({refresh:!0}),s=await V(a,l,t,e,n),u(s))throw new E("自动续登后仍被拒绝")}if(delete s.__status,r)return s;if(s.code!==200)throw new ve(t,e,s);return s.data}o(_,"api");class P extends Error{static{o(this,"KindError")}constructor(e,n){super(n),this.name="KindError",this.kind=e}}const lt=[["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 Pe(t){return String(t||"").split(/\s*STACK TRACE:/)[0].replace(/\s*\w*Exception:\s*/g," ").replace(/\s+/g," ").trim()}o(Pe,"cleanApiMessage");class ve extends Error{static{o(this,"ApiError")}constructor(e,n,r={}){const a=Pe(r.message),i=Pe(r.error&&r.error.details||""),l=i&&!a.includes(i)&&!i.includes(a)?`${a} ${i}`:i.length>a.length?i: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=lt.find(([,s])=>s.test(l))?.[0]||null}}const S=o((t,e)=>_("POST",t,e),"post"),v=o(t=>_("GET",t),"get");let z=null;async function ee(){if(z)return z;const t=I();let e=process.env.LM_DATASOURCE||t.dataSourceId||"",n=[];if(!e){if(n=await ue("dataSources",30*6e4,async()=>{const a=await v("/index-block/index-block-data-source");return(Array.isArray(a)?a:a.items||[]).map(i=>({id:i.id,name:i.name}))}),!n.length)throw new Error("当前租户没有可用的数据源,请联系领谋开通");e=n[0].id}const r=await ue("rootFolders",30*6e4,async()=>{const[a,i,l]=await Promise.all([v("/index-block/folders?type=0"),v("/index-block/folders?type=1"),v("/index-block/folders?type=3")]);return{planFolder:a[0]?.id||b,cardFolder:i[0]?.id||b,dsFolder:l[0]?.id||b}});return z={dataSourceId:e,dataSources:n,...r},z}o(ee,"ctx");function ct(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*)?",i=[];for(const l of e.matchAll(new RegExp(`\\b(?:FROM|JOIN)\\s+${a}((?:\\s*,\\s*${a})*)`,"gi"))){i.push(l[1]);for(const s of(l[2]||"").matchAll(new RegExp(`,\\s*${a}`,"gi")))i.push(s[1])}return[...new Set(i)].filter(l=>!n.has(l.toLowerCase()))}o(ct,"sqlTables");let G=null;async function dt(){if(G)return G;const t=await ee(),e=await ue(`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(dt,"allowedTables");function ut(t){const e=t.split(`
|
|
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
|
|
3
|
-
${
|
|
1
|
+
var We=Object.defineProperty;var o=(t,e)=>We(t,"name",{value:e,configurable:!0});import p from"node:fs";import x from"node:path";import ue from"node:os";import j from"node:crypto";import{authorizeTenantSession as He,loginWithOauth as Je,refreshOauthSession as Ce,revokeOauthSession as H}from"./oauth.js";import{validateFillCardPayload as Ve}from"./fill-card.js";import{checkCardPayload as Ne,checkFilterPayload as Oe}from"./card-rules.js";import*as C from"@linkaicli/linkai-cli/oplog";import*as _e from"./collab.js";import $e from"./templates.js";const P="linkai",b="00000000-0000-0000-0000-000000000000",qe="https://bi.linkmore.com/",Qe="https://bi-api.linkmore.com",Ye=/UND_ERR_CONNECT_TIMEOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|Connect Timeout/,Ze=/EPIPE|ECONNRESET|socket hang up|UND_ERR_SOCKET|other side closed/i,Xe=new Set(["GET","HEAD"]);class et extends Error{static{o(this,"NetworkError")}constructor(e,n){super(e,n),this.name="NetworkError"}}async function tt(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=Ye.test(s)||Xe.has(String(e?.method||"GET").toUpperCase())&&Ze.test(s);if(r>=n||!l)throw new et(`无法连接领谋 BI 服务器,请检查网络后重试(${s.trim().replace(/\s+/g," ").slice(0,120)})`,{cause:a});await new Promise(i=>setTimeout(i,1500*r))}}o(tt,"fetchRetry");function X(){const t=process.env.LINKAI_SITE_CONFIG_DIR||x.join(ue.homedir(),".linkai","sites");return p.mkdirSync(t,{recursive:!0,mode:448}),nt(t),t}o(X,"configDir");let Ae=!1;function nt(t){if(!Ae){Ae=!0;for(const[e,n]of[["lmbi.json",`${P}.json`],["lmbi.session.json",`${P}.session.json`]]){const r=x.join(t,e),a=x.join(t,n);try{p.existsSync(r)&&!p.existsSync(a)&&p.renameSync(r,a)}catch{}}if(!process.env.LINKAI_ARTIFACTS_DIR){const e=x.join(ue.homedir(),".linkai","artifacts","lmbi"),n=rt();try{p.existsSync(e)&&!p.existsSync(n)&&p.renameSync(e,n)}catch{}}}}o(nt,"migrateLegacyFiles");const fe=o(()=>x.join(X(),`${P}.json`),"configFile");function rt(...t){return x.join(process.env.LINKAI_ARTIFACTS_DIR||x.join(ue.homedir(),".linkai","artifacts",P),...t)}o(rt,"artifactsDir");function I(){const t=he();if("username"in t||"password"in t||"tenantConfirmed"in t){const{username:e,password:n,tenantConfirmed:r,...a}=t;return me(a),Le(a)}return Le(t)}o(I,"readConfig");function Le(t){const e=new URL(t.url||qe);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||Qe).replace(/\/$/,"")}}o(Le,"configFrom");const ee=o(()=>x.join(X(),`${P}.session.json`),"sessionFile"),at="请用户执行 linkai login,在浏览器完成领谋授权";function _(){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(_,"readSession");const J=o(()=>x.join(X(),`${P}.cache.json`),"shortCacheFile");async function V(t,e,n){const r=_()?.tenantId;if(!r||process.env.LINKAI_NO_CACHE==="1")return n();let a={};try{a=JSON.parse(p.readFileSync(J(),"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(J(),"utf8"))||{}}catch{a={}}a.tenantId!==r&&(a={tenantId:r,entries:{}}),a.entries??={},a.entries[t]={at:Date.now(),value:l};const i=`${J()}.${process.pid}.tmp`;p.writeFileSync(i,JSON.stringify(a),{mode:384}),p.renameSync(i,J())}catch{}return l}o(V,"shortCache");function nn(){try{p.rmSync(J(),{force:!0})}catch{}}o(nn,"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 ot(){try{p.rmSync(ee())}catch{}$=null,z=null,G=null}o(ot,"clearSession");function v(t){try{return JSON.parse(Buffer.from(String(t).split(".")[1],"base64").toString())}catch{return{}}}o(v,"tokenPayload");function q(t){const e=v(t);return e.exp?(e.exp*1e3-Date.now())/36e5:-1}o(q,"tokenHoursLeft");function pe(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(pe,"spaceOf");class k extends Error{static{o(this,"LoginRequiredError")}constructor(e){super(`${e}。${at}`),this.name="LoginRequiredError"}}function he(){try{return JSON.parse(p.readFileSync(fe(),"utf8"))||{}}catch{return{}}}o(he,"readConfigFile");function me(t){const e=fe(),n=`${e}.${process.pid}.tmp`;p.writeFileSync(n,JSON.stringify(t,null,2),{mode:384}),p.renameSync(n,e)}o(me,"writeConfigFile");function we(){const{username:t,password:e,tenantId:n,tenantConfirmed:r,...a}=he();if(Object.keys(a).length)me(a);else try{p.rmSync(fe())}catch{}}o(we,"forgetCredentials");function it(){we();const t=he();if(typeof t.oauthAgentId=="string"&&/^[A-Za-z0-9._:-]{8,128}$/.test(t.oauthAgentId))return t.oauthAgentId;const e=j.randomUUID().replace(/-/g,"");return me({...t,oauthAgentId:e}),e}o(it,"oauthAgentId");async function rn({tenant:t,switchAccount:e=!1,timeoutSeconds:n=300,browser:r}={}){const a=_();return Je(I(),{agentId:it(),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 H(I(),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 H(I(),s).catch(()=>{}),i}we();const l=te(s);return a?.refreshToken&&await H(I(),a).catch(()=>{}),{session:l,tenantName:pe(l).tenantName||l.tenantId}},"onAuthorized")})}o(rn,"oauthLogin");async function an(){const t=_();if(!t?.refreshToken)return!1;try{return await H(I(),t)}catch{return!1}}o(an,"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 st(t){return Fe(async()=>{const e=_();if(!e?.refreshToken)throw new k("OAuth 会话已在本机退出");return e.refreshToken!==t.refreshToken&&q(e.token)>.05?($=e,e):te(await Ce(I(),e))})}o(st,"refreshOauthLocked");async function M({refresh:t=!1}={}){if(we(),$&&!t&&q($.token)>.05)return $;const e=$||_();if(e&&!t&&q(e.token)>.05)return $=e,e;if(e?.authMode==="oauth"&&e.refreshToken)return ne||(ne=(async()=>{try{return await st(e)}catch(n){if(["invalid_grant","invalid_client","oauth_exchange_unknown"].includes(n.code)){const r=_();if(r&&r.refreshToken!==e.refreshToken&&q(r.token)>.05)return $=r,r;throw ot(),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=v(t.token),n=await Q(I(),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=>!lt(r))}o(De,"tenantList");function lt(t){return t.tenantId===b||t.code==="0000"||t.name==="全域租户"||t.name==="全域"}o(lt,"isPlatformTenant");async function on(t,e){if(t?.authMode!=="oauth")throw new k("当前没有 OAuth 授权");return Fe(async()=>{let n=_();if(!n)throw new k("当前没有 OAuth 授权");if(v(n.token).UserId!==v(t.token).UserId)throw new Error("领谋账号已切换,请重新读取当前租户");q(n.token)<=.05&&(n=te(await Ce(I(),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 He(I(),n,i.tenantId);return te(u),Y=null,await H(I(),n).catch(()=>{}),{session:u,tenant:i,switched:!0}})}o(on,"switchTenant");let ge="";function sn(t){ge=String(t||"")}o(sn,"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 ct(t){if(!t)return null;const e=v(t.token);return{userId:String(e.UserId||e.sub||e.userId||""),tenantId:t.tenantId,tenantCode:pe(t).tenantCode}}o(ct,"identityOf");function B(t,e,n={}){C.emit("guard",{check:t,pass:e,...n})}o(B,"logGuard");async function Q(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(ct(e));let c;try{c=await tt(t.apiBase+r,{method:n,headers:{"X-LinkAI-Run-Id":C.currentRunId(),...ge?{"X-LinkAI-Version":ge}:{},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(Q,"rawFetch");const Pe="/api/app/linkai-oplog/events";C.setTransport(async(t,e)=>{if(!_())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 Q(I(),r,"POST",Pe,{events:t},{log:!1,signal:e});return n(a)&&(r=await M({refresh:!0}),a=await Q(I(),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 dt=[/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 ln(t){let e=String(t);try{for(let r=0;r<3;r++)e=decodeURIComponent(e)}catch{}const n=dt.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(ln,"assertApiAllowed");async function N(t,e,n,{raw:r=!1}={}){const a=I();e=String(e).trim(),e.startsWith("/")||(e="/"+e),e.startsWith("/api/")||(e="/api/app"+e);try{Ve(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=Ne(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=Ne(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 Ft(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 A("CONFIG_INVALID",c.message)}const s=_t(t,e,n);if(s){_e.assertWritable([_e.normalizeObject(`card:${s}`)]);try{await $t(s),B("tenant-ownership",!0,{target:s,path:e.split("?")[0]})}catch(c){throw c instanceof je&&B("tenant-ownership",!1,{target:s,path:e.split("?")[0]}),c}}let l=await M(),i=await Q(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 Q(a,l,t,e,n),u(i))throw new k("自动续登后仍被拒绝")}if(delete i.__status,r)return i;if(i.code!==200)throw new ye(t,e,i);return i.data}o(N,"api");class A extends Error{static{o(this,"KindError")}constructor(e,n){super(n),this.name="KindError",this.kind=e}}const ut=[["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 ye 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=ut.find(([,i])=>i.test(l))?.[0]||null}}const S=o((t,e)=>N("POST",t,e),"post"),R=o(t=>N("GET",t),"get");let z=null;async function re(){if(z)return z;const t=I();let e=process.env.LM_DATASOURCE||t.dataSourceId||"",n=[];if(!e){if(n=await V("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 V("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(re,"ctx");function ft(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(ft,"sqlTables");let G=null;async function pt(){if(G)return G;const t=await re(),e=await V(`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(pt,"allowedTables");function ht(t){const e=t.split(`
|
|
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
|
+
${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
|
-
`+u.trim()}),a=a.replace(/\u0001(\d+)\u0001/g,(l,
|
|
7
|
-
`}o(ut,"formatSql");function ge(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(ge,"stripTrailingSemicolon");const ft=/^(information_schema|mysql|performance_schema|sys)\./i;function pt(t){const e=String(t).replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ").trim();if(!/^(SELECT|WITH)\b/i.test(e))throw new P("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 P("SQL_READONLY","Rejected: SELECT … INTO OUTFILE/DUMPFILE is not allowed");ge(t)}o(pt,"assertReadOnlySql");function ht(t){try{pt(t)}catch(e){throw B("sql-readonly",!1,{sqlSha256:k.sha256(String(t)),chars:String(t).length}),e}}o(ht,"precheckSql");async function mt(t){ht(t),t=ge(t);const e=ct(t),n=t.replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/`/g,""),r=[...new Set([...e.filter(i=>ft.test(i)),...[...n.matchAll(/\b(information_schema|mysql|performance_schema|sys)\s*\.\s*\w+/gi)].map(i=>i[0])])];if(r.length)throw new P("SQL_TABLE_NOT_ALLOWED",`Rejected: system schema access is not allowed (${r.join(", ")})`);if(e.length){const i=await dt(),l=e.filter(s=>!i.has(s.replace(/^[\w]+\./,"").toLowerCase()));if(l.length)throw new P("SQL_TABLE_NOT_ALLOWED",`Rejected: not in the space's table list (${l.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).`)}const a=await ee();return S("/index-block/get-field-info-by-data-set-sql",{dataSourceId:a.dataSourceId,sql:t,mode:2})}o(mt,"checkSql");const wt=99,gt=2,yt=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:U.randomUUID(),tableFieldName:e.field_name,aliasCnName:e.field_name,fieldComment:"",argumentType:r?0:2,calculationMode:r?gt:wt,tableFieldType:r?"decimal":e.field_type,tableFieldTypeName:e.type_name,sortNum:n,sqlType:0,isCustomize:!1}}),"fieldColumns");async function Zt({id:t,name:e,sql:n,remark:r,noFormat:a=!1,current:i=null}){const l=await ee(),s=t&&t!==b?i||await bt(t):null;!a&&process.env.LM_NO_FMT!=="1"&&(n=ut(n)),n=ge(n);const u=await mt(n);if(!u?.length)throw new P("SQL_INVALID","运行 SQL 没有返回任何字段,无法生成映射字段");const c=Date.now(),d=[{id:U.randomUUID(),tableName:`sql_name_${c}`,tableComment:`sql_comment_${c}`,sortNum:0,fieldColumns:yt(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 ve?new P("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:s?.parentId||l.dsFolder,tag:s?.tag??"",remark:r??s?.remark??"",isPrivate:s?.isPrivate??!1,joinOns:[],tables:d})}o(Zt,"dsSave");async function St({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}}),i=a.items||[];n.push(...i.map(s=>({name:s.name,dsId:s.objId,folderId:s.id,...t?{editTime:s.editTime||null,addUser:s.addUser||null}:{}})));const l=Number(a.totalCount??a.total??0);if(i.length<500||l&&n.length>=l)break}return n}o(St,"dsList");const bt=o(t=>v(`/index-block-data-argument/data-set-detail/${t}`),"dsDetail"),It=o(t=>v(`/index-block-data-argument/data-set-fields/${t}`),"dsFields");async function Xt(t){return(await v(`/index-block/data-set-quoted-infos/${t}`)||[]).map(n=>({name:n.name,cardId:n.indexBlockId}))}o(Xt,"dsRefs");function en(){return Object.entries(_e).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(en,"kinds");const xt={dim:{prefix:"waxis",axis:"",argumentType:2},meas:{prefix:"yaxis",axis:"y1",argumentType:1},series:{prefix:"waxis_type",axis:"",argumentType:3}};function te({field:t,col:e,axis:n="",argumentType:r=2,tableFieldType:a="string",title:i,orderByType:l=0,i:s=0}){return{id:U.randomUUID(),format:"default",axis:n,field:t,title:i??e,sortNum:s,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:U.randomUUID(),isCustomizeFill:!1,isInnerFill:!1,fieldDesc:""}}o(te,"baseDto");const Et=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 ne({dsId:t,name:e,remark:n,chartParentType:r,chartType:a,dtos:i,chartSetting:l,limitCount:s=500,id:u}){const c=await ee(),d=u&&u!==b?await Se(u):null,f={indexBlockDataId:t,isMulData:!1,indexBlockShowOrderFields:i.map(Et),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:s,indexBlockDataSettingDtos:i,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(ne,"saveCard");async function ye(t){const e=await It(t);return{fields:e,meta:Object.fromEntries(e.map(n=>[n.tableFieldComment,n]))}}o(ye,"datasetMeta");async function tn({dsId:t,name:e,kind:n,dim:r,meas:a,series:i,id:l}){const s=_e[n];if(!s)throw new Error(`Chart kind "${n}" is not in the v2 chart registry; run: linkai kinds`);if(s._disabled)throw new Error(`Chart kind "${n}" (${s._name}) is disabled: ${s._disabled}`);const{fields:u,meta:c}=await ye(t),d=o((C,$,W,p)=>{const w=c[W];if(!w)throw new Error(`Dataset has no column "${W}". Available: ${u.map(A=>A.tableFieldComment).join(" / ")}`);const y=xt[C];return te({field:y.prefix+$,col:W,axis:y.axis,argumentType:y.argumentType,tableFieldType:w.tableFieldType,i:p})},"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 g=f.map((C,$)=>d("dim",$+1,C,$));return m.forEach((C,$)=>g.push(d("meas",$+1,C,g.length))),i&&g.push(d("series",1,i,g.length)),ne({dsId:t,name:e,chartParentType:s.chartParentType,chartType:s.chartType,dtos:g,chartSetting:s.chartSetting,limitCount:0,id:l})}o(tn,"cardChart");async function nn({dsId:t,name:e,col:n,subtitle:r,color:a="#E8611C",id:i}){const l=[te({field:"yaxis1",col:n,axis:"y1",argumentType:1,tableFieldType:"decimal",title:r||n})];return ne({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:i})}o(nn,"cardMeter");async function rn({dsId:t,spec:e,id:n}){const{meta:r}=await ye(t),a={},i=[];let l=0;const s=o(p=>{if(a[p])return a[p];const w=r[p];if(!w)throw new Error(`Dataset has no column "${p}". Available: ${Object.keys(r).join(" / ")}`);const y=`waxis${++l}`;return i.push(te({field:y,col:p,argumentType:w.argumentType,tableFieldType:w.tableFieldType,i:l-1})),a[p]=y,y},"reg"),u=o(()=>U.randomUUID(),"u"),c=o(p=>({fontSize:12,fill:"#1F2329",...p}),"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((p,w,y)=>({id:"column_"+u(),field:[p],fixed:!1,enableCustomRender:!1,areas:[{id:u(),width:null,rows:[{id:u(),itemGap:10,items:[f()]}]}],name:w,contentAlign:"left",spanRows:!1,hidden:y,isPivot:!1,childPivotFields:[],topValueField:!1}),"plainColumn"),g=[];for(const p of e.columns||[]){if(p.plain){g.push(m(s(p.plain),p.header,!1));continue}const w=[],y=[],A=o(T=>{const[N,F=""]=T.split(":"),Ie=s(N);w.push(Ie);const se=F==="image",xe=d[se?"":F]||d[""],Ee={id:u(),type:se?"image":"labelValue",field:Ie,layout:"left",arrangement:"horizontal",fieldStyle:xe,valueStyle:xe,showLabel:!1,iconPosition:"left",labelValueGap:4,showColon:!1};return se&&(Ee.imgWidth=p.imgWidth||112),Ee},"mkItem");p.image&&y.push({id:u(),width:(p.imgWidth||112)+16,rows:[{id:u(),itemGap:10,items:[A(`${p.image}:image`)]}]}),y.push({id:u(),width:null,rows:(p.rows||[]).map(T=>({id:u(),itemGap:10,items:(Array.isArray(T)?T:[T]).map(A)}))}),g.push({id:"column_"+u(),field:w,fixed:!1,enableCustomRender:!0,areas:y,name:p.header,contentAlign:"left",spanRows:!1,hidden:!1,isPivot:!1,childPivotFields:[],topValueField:!1})}for(const p of e.hidden||[])g.push(m(s(p),p,!0));const C=o(p=>(e[p]||[]).flatMap(w=>{const y=a[w.dep]||s(w.dep),A=a[w.col]||s(w.col),T=[];for(const[N,F]of Object.entries(w.eq||{}))T.push({field:A,color:F,depField:y,operator:"==",value:isNaN(+N)?N:+N});for(const[N,F]of Object.entries(w.gte||{}))T.push({field:A,color:F,depField:y,operator:">=",value:+N});for(const[N,F]of Object.entries(w.ne||{}))T.push({field:A,color:F,depField:y,operator:"!=",value:N});return T}),"conds"),$=e.rowGrey?[{field:"",color:"#F7F8FA",depField:a[e.rowGrey.dep]||s(e.rowGrey.dep),operator:">=",value:e.rowGrey.gte}]:[];e.order&&s(e.order);const W={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(p=>({key:g.find(w=>w.name===p.header).field[0],width:p.width||200})),conditionStyle:{value:{conditions:C("pills")},arrow:{conditions:[]},fontColor:{conditions:C("fontColor")},rowMark:{conditions:$},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:g}}};return ne({dsId:t,name:e.name,remark:e.remark,chartParentType:"table",chartType:"agtable",dtos:i,chartSetting:W,limitCount:e.limitCount||2e3,id:n})}o(rn,"cardTable");async function an({dsId:t,spec:e,id:n}){const{meta:r}=await ye(t),a={},i=[];let l=0;const s=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 i.push(te({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:s(e.image),main:s(e.main),assist:s(e.assist),status:s(e.status),tags:(e.tags||[]).map(s),desc:(e.desc||[]).map(s),list:(e.list||[]).map(s),progress:s(e.progress),taskBar:{textFields:(e.taskBarText||[]).map(s)},fieldMap:{startDate:s(e.start),endDate:s(e.end),plannedStart:s(e.plannedStart),plannedEnd:s(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&&s(e.order),ne({dsId:t,name:e.name,remark:e.remark,chartParentType:"table",chartType:"gantt",dtos:i,chartSetting:u,id:n})}o(an,"cardGantt");async function Re(){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 i=Number(r.totalCount??r.total??0);if(a.length<500||i&&e.length>=i)break}return e}o(Re,"cardList");class Ue extends Error{static{o(this,"TenantMismatchError")}constructor(e,n){super(`卡片 ${e} 不属于当前租户${n?`「${n}」`:""},已拒绝写入(写进去的数据会落在当前租户名下)`),this.name="TenantMismatchError",this.cardId=e}}const Tt=/\/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 Q=null;function kt(t,e,n){if(String(t).toUpperCase()==="GET"||!Tt.test(String(e))||!n||typeof n!="object"||Array.isArray(n))return"";const r=o((...a)=>{for(const i of a){const l=Object.keys(n).find(s=>s.toLowerCase()===i);if(l&&n[l])return String(n[l])}return""},"pick");return/update-new/i.test(e)?r("id"):r("usedindexblockid")}o(kt,"tenantGuardTarget");async function Nt(t){if(!t)return;const e=String(t).toLowerCase(),n=o(async()=>{Q=new Set((await Re()).map(r=>String(r.cardId).toLowerCase()))},"load");if(Q?Q.has(e)||await n():await n(),!Q.has(e))throw new Ue(t,fe(await M()).tenantName)}o(Nt,"assertCardInTenant");const Se=o(t=>S("/index-block/get-new-index-block-detail",{id:t}),"cardDetail");async function on(t,e){let n;if(e==="clear")n=[];else if(Array.isArray(e))n=e;else{const i=(await Se(t)).indexBlockDataSettingDtos||[];n=String(e).split(",").map(l=>l.trim()).filter(Boolean).map(l=>{const[s,u="asc"]=l.split(":"),c=i.find(d=>d.field===s)||i.find(d=>d.comment===s);if(!c)throw new Error(`Card has no field "${s}". Available: ${i.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((i,l)=>({type:1,sortFields:[],...i,sortNum:l}));await _("PUT","/page-setting/page-card-index-block-sort-field",{usedIndexBlockId:t,sortField:r});const a=await Ot({cardId:t,pageSize:1});return{cardId:t,requested:r.map(i=>`${i.field} ${i.type===2?"manual":i.sort}`),sqlHasOrderBy:/\bORDER\s+BY\s+(?:W\.)?(?:waxis|yaxis|waxis_type|order)\w*/i.test(a.sql||""),queryCode:a.code}}o(on,"cardSort");async function Ot({cardId:t,pageSize:e=20,filters:n=[]}){const r=await _("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(Ot,"queryCard");async function _t(){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(_t,"planList");const Ct=o((t,e=!0)=>S(`/page-setting/get-new-page-plan/${t}?isEdit=${e}`,{}),"planDetail");async function sn({planId:t,cardId:e,x:n=0,y:r=0,w:a=24,h:i=20}){const l=await S("/page-setting/new-page-card",{indexBlockId:e,sourceType:0,x:+n,y:+r,width:+a,height:+i,mobileX:0,mobileY:+r,mobileWidth:4,mobileHeight:+i,cardType:1,bindFilterIds:[],userPageSettingId:t});return{pageCardId:l.id,x:l.x,y:l.y,width:l.width,height:l.height}}o(sn,"planAdd");async function ln(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(ln,"planFilter");async function $t(){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($t,"reportList");const cn=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 $t()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("plans",(await _t()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("cards",(await Re()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("datasets",(await St()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),{prefix:t,removed:e}}o(dn,"cleanPrefix");function un(t){return`(() => { const s = ${JSON.stringify({t:t.token,c:t.tenantId,a:t.agentId})};
|
|
6
|
+
`+u.trim()}),a=a.replace(/\u0001(\d+)\u0001/g,(l,i)=>r[+i]),a.trim()+`
|
|
7
|
+
`}o(ht,"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 mt=/^(information_schema|mysql|performance_schema|sys)\./i;function wt(t){const e=String(t).replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ").trim();if(!/^(SELECT|WITH)\b/i.test(e))throw new A("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 A("SQL_READONLY","Rejected: SELECT … INTO OUTFILE/DUMPFILE is not allowed");Se(t)}o(wt,"assertReadOnlySql");function gt(t){try{wt(t)}catch(e){throw B("sql-readonly",!1,{sqlSha256:C.sha256(String(t)),chars:String(t).length}),e}}o(gt,"precheckSql");async function yt(t){gt(t),t=Se(t);const e=ft(t),n=t.replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/`/g,""),r=[...new Set([...e.filter(a=>mt.test(a)),...[...n.matchAll(/\b(information_schema|mysql|performance_schema|sys)\s*\.\s*\w+/gi)].map(a=>a[0])])];if(r.length)throw new A("SQL_TABLE_NOT_ALLOWED",`Rejected: system schema access is not allowed (${r.join(", ")})`);if(e.length){const a=await pt(),s=e.filter(l=>!a.has(l.replace(/^[\w]+\./,"").toLowerCase()));if(s.length)throw new A("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(yt,"localCheckSql");async function St(t){t=await yt(t);const e=await re();return S("/index-block/get-field-info-by-data-set-sql",{dataSourceId:e.dataSourceId,sql:t,mode:2})}o(St,"checkSql");function cn(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(cn,"hasOffset");async function dn(t){const e=await N("POST","/linkai-ask/query",t,{raw:!0});if(Z(e))throw new A("ASK_UNAVAILABLE","问数服务暂未开通(服务端接口尚未上线)");if(e.code!==200)throw new ye("POST","/api/app/linkai-ask/query",e);return e.data}o(dn,"askQuery");const bt=99,It=2,xt=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?It:bt,tableFieldType:r?"decimal":e.field_type,tableFieldTypeName:e.type_name,sortNum:n,sqlType:0,isCustomize:!1}}),"fieldColumns");async function un({id:t,name:e,sql:n,remark:r,noFormat:a=!1,current:s=null}){const l=await re(),i=t&&t!==b?s||await Et(t):null;!a&&process.env.LM_NO_FMT!=="1"&&(n=ht(n)),n=Se(n);const u=await St(n);if(!u?.length)throw new A("SQL_INVALID","运行 SQL 没有返回任何字段,无法生成映射字段");const c=Date.now(),d=[{id:j.randomUUID(),tableName:`sql_name_${c}`,tableComment:`sql_comment_${c}`,sortNum:0,fieldColumns:xt(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 ye?new A("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(un,"dsSave");async function kt({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(kt,"dsList");const Et=o(t=>R(`/index-block-data-argument/data-set-detail/${t}`),"dsDetail"),Tt=o(t=>R(`/index-block-data-argument/data-set-fields/${t}`),"dsFields");async function fn(t){return(await R(`/index-block/data-set-quoted-infos/${t}`)||[]).map(n=>({name:n.name,cardId:n.indexBlockId}))}o(fn,"dsRefs");function pn(){return Object.entries($e).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(pn,"kinds");const Ct={dim:{prefix:"waxis",axis:"",argumentType:2},meas:{prefix:"yaxis",axis:"y1",argumentType:1},series:{prefix:"waxis_type",axis:"",argumentType:3}};function ae({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(ae,"baseDto");const Nt=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 oe({dsId:t,name:e,remark:n,chartParentType:r,chartType:a,dtos:s,chartSetting:l,limitCount:i=500,id:u}){const c=await re(),d=u&&u!==b?await Ie(u):null,f={indexBlockDataId:t,isMulData:!1,indexBlockShowOrderFields:s.map(Nt),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(oe,"saveCard");async function be(t){const e=await Tt(t);return{fields:e,meta:Object.fromEntries(e.map(n=>[n.tableFieldComment,n]))}}o(be,"datasetMeta");async function hn({dsId:t,name:e,kind:n,dim:r,meas:a,series:s,id:l}){const i=$e[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((E,L,W,h)=>{const g=c[W];if(!g)throw new Error(`Dataset has no column "${W}". Available: ${u.map(F=>F.tableFieldComment).join(" / ")}`);const y=Ct[E];return ae({field:y.prefix+L,col:W,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((E,L)=>d("dim",L+1,E,L));return m.forEach((E,L)=>w.push(d("meas",L+1,E,w.length))),s&&w.push(d("series",1,s,w.length)),oe({dsId:t,name:e,chartParentType:i.chartParentType,chartType:i.chartType,dtos:w,chartSetting:i.chartSetting,limitCount:0,id:l})}o(hn,"cardChart");async function mn({dsId:t,name:e,col:n,subtitle:r,color:a="#E8611C",id:s}){const l=[ae({field:"yaxis1",col:n,axis:"y1",argumentType:1,tableFieldType:"decimal",title:r||n})];return oe({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(mn,"cardMeter");async function wn({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(ae({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(T=>{const[O,D=""]=T.split(":"),ke=i(O);g.push(ke);const de=D==="image",Ee=d[de?"":D]||d[""],Te={id:u(),type:de?"image":"labelValue",field:ke,layout:"left",arrangement:"horizontal",fieldStyle:Ee,valueStyle:Ee,showLabel:!1,iconPosition:"left",labelValueGap:4,showColon:!1};return de&&(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(T=>({id:u(),itemGap:10,items:(Array.isArray(T)?T:[T]).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 E=o(h=>(e[h]||[]).flatMap(g=>{const y=a[g.dep]||i(g.dep),F=a[g.col]||i(g.col),T=[];for(const[O,D]of Object.entries(g.eq||{}))T.push({field:F,color:D,depField:y,operator:"==",value:isNaN(+O)?O:+O});for(const[O,D]of Object.entries(g.gte||{}))T.push({field:F,color:D,depField:y,operator:">=",value:+O});for(const[O,D]of Object.entries(g.ne||{}))T.push({field:F,color:D,depField:y,operator:"!=",value:O});return T}),"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 W={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:E("pills")},arrow:{conditions:[]},fontColor:{conditions:E("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 oe({dsId:t,name:e.name,remark:e.remark,chartParentType:"table",chartType:"agtable",dtos:s,chartSetting:W,limitCount:e.limitCount||2e3,id:n})}o(wn,"cardTable");async function gn({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(ae({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),oe({dsId:t,name:e.name,remark:e.remark,chartParentType:"table",chartType:"gantt",dtos:s,chartSetting:u,id:n})}o(gn,"cardGantt");async function Ue(){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(Ue,"cardList");class je extends Error{static{o(this,"TenantMismatchError")}constructor(e,n){super(`卡片 ${e} 不属于当前租户${n?`「${n}」`:""},已拒绝写入(写进去的数据会落在当前租户名下)`),this.name="TenantMismatchError",this.cardId=e}}const Ot=/\/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 Y=null;function _t(t,e,n){if(String(t).toUpperCase()==="GET"||!Ot.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(_t,"tenantGuardTarget");async function $t(t){if(!t)return;const e=String(t).toLowerCase(),n=o(async()=>{Y=new Set((await Ue()).map(r=>String(r.cardId).toLowerCase()))},"load");if(Y?Y.has(e)||await n():await n(),!Y.has(e))throw new je(t,pe(await M()).tenantName)}o($t,"assertCardInTenant");const Ie=o(t=>S("/index-block/get-new-index-block-detail",{id:t}),"cardDetail");async function yn(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 N("PUT","/page-setting/page-card-index-block-sort-field",{usedIndexBlockId:t,sortField:r});const a=await At({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(yn,"cardSort");async function At({cardId:t,pageSize:e=20,filters:n=[]}){const r=await N("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(At,"queryCard");async function Lt(){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(Lt,"planList");const Ft=o((t,e=!0)=>S(`/page-setting/get-new-page-plan/${t}?isEdit=${e}`,{}),"planDetail");async function Sn({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(Sn,"planAdd");async function bn(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(bn,"planFilter");async function Dt(){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(Dt,"reportList");const In=o(t=>S("/page-setting/get-new-page-card-report",{indexBlockId:t,isSubWarn:!1}),"reportDetail");async function xn(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 Dt()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("plans",(await Lt()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("cards",(await Ue()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),await n("datasets",(await kt()).filter(r=>r.name.startsWith(t)).map(r=>r.folderId)),{prefix:t,removed:e}}o(xn,"cleanPrefix");function kn(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(
|
|
12
|
+
return true; })()`}o(kn,"browserSessionScript");function En(t,e){return t=x.resolve(String(t)),p.mkdirSync(x.dirname(t),{recursive:!0}),p.writeFileSync(t,typeof e=="string"?e:JSON.stringify(e,null,1)),t}o(En,"writeJsonFile");function Tn(t){const e=x.resolve(String(t).trim());if(!p.existsSync(e))throw new A("FILE_NOT_FOUND",`文件不存在:${e}(相对路径按当前目录 ${process.cwd()} 解析)`);return p.readFileSync(e,"utf8")}o(Tn,"readInputFile");const xe="/linkai-kb",ie=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 Me extends Error{static{o(this,"KbUnavailableError")}constructor(){super("知识库服务暂不可用"),this.name="KbUnavailableError"}}class vt extends Error{static{o(this,"KbDocumentNotFoundError")}constructor(e,n){super(`知识库文档不存在:${e}/${n}`),this.name="KbDocumentNotFoundError"}}const Z=o(t=>(t.code===404||t.__status===404)&&/non-JSON/.test(String(t.message||"")),"endpointMissing");function Pt(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(Pt,"normalizeKbPath");function Rt(t,e){const n=Pt(t);let r=e?String(e):"",a=n;const s=Object.entries(ie).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=ie[r];if(!l)throw new Error(`未知知识库:${r}(支持:${Object.keys(ie).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(Rt,"kbRequest");async function Cn(t="bi-builder"){const e=ie[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 N("GET",`${xe}/catalog?library=${encodeURIComponent(t)}`,void 0,{raw:!0});if(Z(n))throw new Me;if(n.code!==200)throw new Error(`读取知识库目录失败:${n.message||n.code}`);return{...n.data||{docs:[]},library:t,scope:e.scope}}o(Cn,"kbCatalog");async function Nn(t,e){const n=Rt(t,e);if(n.library==="data-dictionary")throw new Error("数据字典不再按 Markdown 文件路径读取;请使用 kb-search / kb-object / kb-docs / kb-doc");const r=await N("GET",`${xe}/doc?library=${encodeURIComponent(n.library)}&path=${encodeURIComponent(n.path)}`,void 0,{raw:!0});if(Z(r))throw new Me;if(r.code===404||r.__status===404)throw new vt(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(Nn,"kbDoc");const Ut="/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 se(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(se,"query");async function K(t,e,n){let r;try{r=await N(t,`${Ut}/${e}`,n,{raw:!0})}catch(a){throw a instanceof k?a:new U(503,`数据字典服务暂不可用:${a.message||a}`)}if(Z(r))throw new U(503,"数据字典接口尚未部署或当前网关不可用");if(r.code!==200)throw new U(r.code,r.message);return r.data}o(K,"aiKbRequest");const jt=o(()=>K("GET","sources"),"aiKbSources");function On({source:t,keyword:e,pageIndex:n=1,pageSize:r=30}){return K("GET",`catalog?${se({source:t,keyword:e,pageIndex:n,pageSize:r})}`)}o(On,"aiKbCatalog");function _n({source:t,db:e,object:n}){return K("GET",`object?${se({source:t,db:e,object:n})}`)}o(_n,"aiKbObject");function Mt({source:t,category:e,keyword:n,pageIndex:r=1,pageSize:a=30}){return K("GET",`docs?${se({source:t,category:e,keyword:n,pageIndex:r,pageSize:a})}`)}o(Mt,"aiKbDocs");function Bt({source:t,doc:e,offset:n=0,limit:r=2e4,expectedVersion:a}){return K("GET",`doc?${se({source:t,doc:e,offset:n,limit:r,expectedVersion:a})}`)}o(Bt,"aiKbDocPage");async function $n({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 Bt({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($n,"aiKbDoc");const Be=2*6e4,ze=20*1024*1024;let le=null;const An=o(()=>le,"kbCacheHit"),zt=o(()=>V("dict:sources",Be,async()=>(await jt()).sources||[]),"dictSourcesCached"),Gt=o(t=>V(`dict:docs:${String(t).toLowerCase()}`,Be,async()=>{const e=await Mt({source:t,pageIndex:1,pageSize:20});return{items:(e?.items||[]).map(n=>({updatedAt:n.updatedAt})),total:e?.totalCount??null}}),"dictDocsHead");async function Kt(t){const[e,n]=await Promise.all([zt(),Gt(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(Kt,"dictVersion");const Wt=o(t=>x.join(X(),`${P}.kbcache-${String(t).replace(/[^\w-]/g,"")}.json`),"kbCacheFile");async function Ln(t,e,n){const r=_()?.tenantId;let a=null;if(r&&process.env.LINKAI_NO_CACHE!=="1")try{a=await Kt(t)}catch{a=null}if(!a)return le=!1,n();const s=Wt(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 le=!0,u.value;const c=await n();le=!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>ze){const m=Object.keys(i).sort((w,E)=>(i[w].at||0)-(i[E].at||0));for(;m.length>1&&d.length>ze;)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(Ln,"kbCached");const Fn=o(t=>String(t||"").trim().replace(/\s+/g," ").toLowerCase(),"normalizeQuestion");function Dn({source:t,question:e,topTables:n=5,topDocs:r=3}){return K("POST","search",{source:t,question:e,topTables:n,topDocs:r})}o(Dn,"aiKbSearch");const Ge=`${xe}/plan-knowledge`;class ce extends Error{static{o(this,"PlanKnowledgeUnavailableError")}constructor(){super("方案知识库服务暂不可用"),this.name="PlanKnowledgeUnavailableError"}}class Ht extends Error{static{o(this,"PlanKnowledgeConflictError")}constructor(e="方案知识库版本冲突,请重新读取当前版本后再更新"){super(e),this.name="PlanKnowledgeConflictError"}}const Ke=o(t=>Z(t)||t.code===429||Number(t.code)>=500,"planKnowledgeUnavailable");async function vn(t){let e;try{e=await N("GET",`${Ge}?planId=${encodeURIComponent(t)}`,void 0,{raw:!0})}catch(n){throw n instanceof k?n:new ce}if(Ke(e))throw new ce;if(e.code===404)return null;if(e.code!==200)throw new Error(e.message||`读取方案知识库失败:${t}`);return e.data||null}o(vn,"planKnowledgeGet");async function Pn(t){let e;try{e=await N("POST",Ge,t,{raw:!0})}catch(r){throw r instanceof k?r:new ce}if(Ke(e))throw new ce;if(e.code===409)throw new Ht(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(Pn,"planKnowledgePut");export{U as AiKbError,ye as ApiError,Be as DICT_VERSION_TTL_MS,ie as KB_LIBRARIES,vt as KbDocumentNotFoundError,Me as KbUnavailableError,A as KindError,k as LoginRequiredError,et as NetworkError,Ht as PlanKnowledgeConflictError,ce as PlanKnowledgeUnavailableError,P as SITE_ID,je as TenantMismatchError,b as Z,On as aiKbCatalog,$n as aiKbDoc,Bt as aiKbDocPage,Mt as aiKbDocs,_n as aiKbObject,Dn as aiKbSearch,jt as aiKbSources,pt as allowedTables,N as api,rt as artifactsDir,dn as askQuery,ln as assertApiAllowed,$t as assertCardInTenant,wt as assertReadOnlySql,kn as browserSessionScript,hn as cardChart,Ie as cardDetail,gn as cardGantt,Ue as cardList,mn as cardMeter,yn as cardSort,wn as cardTable,St as checkSql,Re as cleanApiMessage,xn as cleanPrefix,ot as clearSession,nn as clearShortCache,X as configDir,fe as configFile,re as ctx,Gt as dictDocsHead,zt as dictSourcesCached,Kt as dictVersion,Et as dsDetail,Tt as dsFields,kt as dsList,fn as dsRefs,un as dsSave,M as ensureSession,xt as fieldColumns,we as forgetCredentials,ht as formatSql,R as get,cn as hasOffset,lt as isPlatformTenant,An as kbCacheHit,Ln as kbCached,Cn as kbCatalog,Nn as kbDoc,Rt as kbRequest,pn as kinds,yt as localCheckSql,B as logGuard,Pt as normalizeKbPath,Fn as normalizeQuestion,rn as oauthLogin,an as oauthLogout,Sn as planAdd,Ft as planDetail,bn as planFilter,vn as planKnowledgeGet,Pn as planKnowledgePut,Lt as planList,S as post,gt as precheckSql,At as queryCard,I as readConfig,Tn as readInputFile,_ as readSession,In as reportDetail,Dt as reportList,te as saveSession,sn as setClientVersion,V as shortCache,pe as spaceOf,ft as sqlTables,Se as stripTrailingSemicolon,on as switchTenant,_t as tenantGuardTarget,De as tenantList,v as tokenPayload,En as writeJsonFile};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
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};
|