@timeryu/dsh-larklink 0.1.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/lib/client.cjs +1 -1
- package/lib/index.d.mts +1 -0
- package/lib/index.mjs +312 -25
- package/package.json +9 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 timeryu288
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/lib/client.cjs
CHANGED
|
@@ -4,7 +4,7 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
-
"use strict";var b=Object.create;var p=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var x=Object.getPrototypeOf,S=Object.prototype.hasOwnProperty;var T=(e,n)=>{for(var i in n)p(e,i,{get:n[i],enumerable:!0})},m=(e,n,i,d)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of R(n))!S.call(e,r)&&r!==i&&p(e,r,{get:()=>n[r],enumerable:!(d=w(n,r))||d.enumerable});return e};var B=(e,n,i)=>(i=e!=null?b(x(e)):{},m(n||!e||!e.__esModule?p(i,"default",{value:e,enumerable:!0}):i,e)),P=e=>m(p({},"__esModule",{value:!0}),e);var C={};T(C,{apply:()=>O,inject:()=>I});module.exports=P(C);var t=B(require("react"),1),I=["slots"],f="@timeryu/dsh-larklink/bindings",D="larklink-bindings",g="Lark \u7ED1\u5B9A",h="sidebar.right.pane.tab",y="sidebar.right.pane.tab.title",v="/larklink",z={ready:"#22c55e",needs_refresh:"#f59e0b",unauthorized:"#ef4444",incomplete:"#9ca3af"};function k({size:e=16}){return t.default.createElement("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"},t.default.createElement("path",{d:"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"}),t.default.createElement("path",{d:"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"}))}function A(){let[e,n]=(0,t.useState)(null),[i,d]=(0,t.useState)(!1),[r,o]=(0,t.useState)(null),a=(0,t.useRef)(!0),l=(u=!1)=>{d(!0),fetch(`${v}/bindings${u?"?live=1":""}`).then(c=>c.json()).then(c=>{a.current&&(n(c),o(null))}).catch(c=>{a.current&&o(c.message)}).finally(()=>{a.current&&d(!1)})},s=u=>fetch(`${v}/${u}`,{method:"POST"}).then(c=>c.json()).then(c=>{a.current&&(n(c),o(null))}).catch(c=>{a.current&&o(c.message)});return(0,t.useEffect)(()=>{a.current=!0,l();let u=setInterval(()=>l(),5e3);return()=>{a.current=!1,clearInterval(u)}},[]),{data:e,loading:i,error:r,refresh:l,act:s}}function $({b:e}){let n=z[e.status]??"#9ca3af";return t.default.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,padding:"6px 0",borderBottom:"1px solid rgba(128,128,128,.2)"}},t.default.createElement("span",{title:e.status,style:{width:8,height:8,borderRadius:999,background:n,flexShrink:0}}),t.default.createElement("div",{style:{minWidth:0,flex:1}},t.default.createElement("div",{style:{display:"flex",gap:6,alignItems:"baseline"}},t.default.createElement("strong",{style:{fontSize:12,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},e.profile),t.default.createElement("span",{style:{fontSize:10,opacity:.7}},e.brand),e.adopted?t.default.createElement("span",{style:{fontSize:10,opacity:.6}},"\u5BFC\u5165"):null),t.default.createElement("div",{style:{fontSize:10,opacity:.65,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},`${e.userName??"\uFF08\u672A\u767B\u5F55\u7528\u6237\uFF09"} \xB7 ${e.status}`)))}function j(){let{data:e,loading:n,error:i,refresh:d,act:r}=A(),[o,a]=(0,t.useState)(null),l=(s,u)=>{a(s),r(u).finally(()=>a(null))};return t.default.createElement("div",{style:{padding:10,fontSize:12,display:"flex",flexDirection:"column",gap:8}},t.default.createElement("div",{style:{display:"flex",gap:6}},E("\u540C\u6B65\u72B6\u6001(health)",()=>l("health","health"),o==="health"),E("\u5BFC\u5165 profile(adopt)",()=>l("adopt","adopt"),o==="adopt"),n?t.default.createElement("span",{style:{opacity:.6,alignSelf:"center"}},"\u2026"):null),i!==null?t.default.createElement("div",{style:{color:"#ef4444"}},`\u52A0\u8F7D\u5931\u8D25\uFF1A${i}\uFF08\u63D2\u4EF6\u540E\u7AEF\u672A\u8FD0\u884C\uFF1F\uFF09`):null,e?.warnings?.map((s,u)=>t.default.createElement("div",{key:u,style:{color:"#f59e0b",fontSize:11}},s)),e!==null&&e.bindings.length===0?t.default.createElement("div",{style:{opacity:.7}},"\u5C1A\u65E0\u7ED1\u5B9A\u3002\u70B9\u300C\u5BFC\u5165 profile(adopt)\u300D\u5BFC\u5165\u672C\u673A lark-cli profile\uFF0C\u6216\u5728\u7EC8\u7AEF\u8FD0\u884C larklink bindings add \u65B0\u589E\u8D26\u53F7\u3002"):null,e?.bindings.map(s=>t.default.createElement($,{key:s.profile,b:s})),e!==null&&e.pins.length>0?t.default.createElement("div",{style:{marginTop:4}},t.default.createElement("div",{style:{fontWeight:600,marginBottom:4}},`\u6587\u6863\u504F\u597D\uFF08pin\uFF0C${e.pins.length}\uFF09`),e.pins.map(s=>t.default.createElement("div",{key:s.token,style:{fontSize:10,opacity:.7,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},`${s.token.slice(0,14)}\u2026 \u2192 ${s.profile}`))):null,t.default.createElement("div",{style:{fontSize:10,opacity:.55,marginTop:4}},"add / auth\uFF08\u626B\u7801\uFF09/ remove \u9700\u5728\u7EC8\u7AEF\u6267\u884C\u2014\u2014\u5BC6\u94A5\u4E0D\u7ECF\u9762\u677F\u4E0E\u6A21\u578B\u3002"))}function E(e,n,i){return t.default.createElement("button",{onClick:n,disabled:i,style:{fontSize:11,padding:"4px 8px",borderRadius:6,cursor:i?"default":"pointer",border:"1px solid rgba(128,128,128,.4)",background:"transparent",opacity:i?.5:1}},e)}function L(){return t.default.createElement("span",{style:{display:"inline-flex",alignItems:"center",gap:6}},t.default.createElement(k,null),g)}function O(e){let n=[],i=r=>{typeof r=="function"&&n.push(r)},d=(r,o)=>{typeof o=="function"&&r.push(o)};return i(e.inject(["sidebarRight","sidebarRightTabs"],r=>{let o=r.get("sidebarRightTabs");if(o===null||typeof o.register!="function")return()=>{};let a=[];return d(a,r.slots.inject(h,()=>r.slots.inject(y,()=>(a.push((()=>{let l=o.register({id:f,kind:D,title:()=>g,guide:[{order:70,title:()=>g,description:()=>"lark \u591A\u8D26\u53F7\u7ED1\u5B9A\u72B6\u6001\u4E0E\u6587\u6863\u8DEF\u7531\u504F\u597D\uFF08adopt / health / pins\uFF09",icon:k}]});return typeof l=="function"?l:()=>{}})()),d(a,r.slots.register({name:h,key:f},()=>t.default.createElement(j))),d(a,r.slots.register({name:y,key:f},()=>t.default.createElement(L))),()=>{for(let l of[...a].reverse())l()})))),()=>{for(let l of[...a].reverse())l()}})),()=>{for(let r of[...n].reverse())r()}}
|
|
7
|
+
"use strict";var D=Object.create;var v=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var _=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var P=(e,l)=>{for(var n in l)v(e,n,{get:l[n],enumerable:!0})},S=(e,l,n,s)=>{if(l&&typeof l=="object"||typeof l=="function")for(let i of O(l))!C.call(e,i)&&i!==n&&v(e,i,{get:()=>l[i],enumerable:!(s=B(l,i))||s.enumerable});return e};var F=(e,l,n)=>(n=e!=null?D(_(e)):{},S(l||!e||!e.__esModule?v(n,"default",{value:e,enumerable:!0}):n,e)),N=e=>S(v({},"__esModule",{value:!0}),e);var Q={};P(Q,{apply:()=>J,inject:()=>U});module.exports=N(Q);var t=F(require("react"),1),U=["slots"],w="@timeryu/dsh-larklink/bindings",j="larklink-bindings",b="Lark \u7ED1\u5B9A",x="sidebar.right.pane.tab",R="sidebar.right.pane.tab.title",q="larklink",A="/larklink",$={ready:"#22c55e",needs_refresh:"#f59e0b",unauthorized:"#ef4444",incomplete:"#9ca3af"};function T({size:e=16}){return t.default.createElement("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"},t.default.createElement("path",{d:"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"}),t.default.createElement("path",{d:"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"}))}function z(e){let[l,n]=(0,t.useState)(null),[s,i]=(0,t.useState)(!1),[a,o]=(0,t.useState)(null),c=(0,t.useRef)(!0),m=(p=!1)=>{i(!0),fetch(`${A}/bindings${p?"?live=1":""}`).then(f=>f.json()).then(f=>{c.current&&(n(f),o(null))}).catch(f=>{c.current&&o(f.message)}).finally(()=>{c.current&&i(!1)})},y=(p,f)=>fetch(`${A}/${p}`,{method:"POST",headers:f!==void 0?{"content-type":"application/json"}:void 0,body:f!==void 0?JSON.stringify(f):void 0}).then(g=>g.json()).then(g=>(c.current&&g.bindings!==void 0&&(n(g),o(null)),g)).catch(g=>(c.current&&o(g.message),{ok:!1,error:g.message}));return(0,t.useEffect)(()=>{c.current=!0,m(e?.initialLive===!0);let p=setInterval(()=>m(),5e3);return()=>{c.current=!1,clearInterval(p)}},[]),{data:l,loading:s,error:a,refresh:m,act:y}}function V({b:e,onStartAuth:l,busy:n}){let s=$[e.status]??"#9ca3af",i=e.status!=="ready",a=`auth:${e.profile}`;return t.default.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,padding:"6px 0",borderBottom:"1px solid rgba(128,128,128,.2)"}},t.default.createElement("span",{title:e.status,style:{width:8,height:8,borderRadius:999,background:s,flexShrink:0}}),t.default.createElement("div",{style:{minWidth:0,flex:1}},t.default.createElement("div",{style:{display:"flex",gap:6,alignItems:"baseline"}},t.default.createElement("strong",{style:{fontSize:12,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},e.profile),t.default.createElement("span",{style:{fontSize:10,opacity:.7}},e.brand),e.adopted?t.default.createElement("span",{style:{fontSize:10,opacity:.6}},"\u5BFC\u5165"):null,i?h(n===a?"\u53D1\u8D77\u4E2D\u2026":"\u626B\u7801\u767B\u5F55",()=>l(e.profile),n===a):null),t.default.createElement("div",{style:{fontSize:10,opacity:.65,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},`${e.userName??"\uFF08\u672A\u767B\u5F55\u7528\u6237\uFF09"} \xB7 ${e.status}`)))}function I({flow:e,onCompleteAuth:l,busy:n}){return t.default.createElement("div",{style:{marginTop:6,padding:8,borderRadius:8,fontSize:11,border:"1px solid rgba(128,128,128,.35)",display:"flex",flexDirection:"column",gap:6}},t.default.createElement("div",{style:{fontWeight:600}},`\u767B\u5F55\u6388\u6743\u8FDB\u884C\u4E2D\uFF08${e.profile}\uFF09`),e.verificationUrl!==void 0?t.default.createElement("a",{href:e.verificationUrl,target:"_blank",rel:"noreferrer",style:{wordBreak:"break-all",color:"#3b82f6"}},e.browserOpened?"\u5DF2\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u6388\u6743\u9875\uFF08\u70B9\u6B64\u91CD\u5F00\uFF09":"\u70B9\u6B64\u6253\u5F00\u6388\u6743\u9875"):null,e.qrcode!==void 0?t.default.createElement("div",{style:{opacity:.7}},`\u4E8C\u7EF4\u7801\u6587\u4EF6\uFF1A${e.qrcode}`):null,t.default.createElement("div",{style:{opacity:.75}},"\u5728\u6D4F\u89C8\u5668\u786E\u8BA4\u6388\u6743\u540E\uFF0C\u70B9\u300C\u6211\u5DF2\u6388\u6743\u300D\u5B8C\u6210\u767B\u5F55\uFF0810 \u5206\u949F\u5185\u6709\u6548\uFF09\u3002"),h(n==="auth-complete"?"\u5B8C\u6210\u4E2D\u2026":"\u6211\u5DF2\u6388\u6743\uFF0C\u5B8C\u6210\u767B\u5F55",()=>l(e.profile),n==="auth-complete"))}function W(){let{data:e,loading:l,error:n,refresh:s,act:i}=z(),[a,o]=(0,t.useState)(null),[c,m]=(0,t.useState)(null),[y,p]=(0,t.useState)(null),f=(d,r)=>{o(d),i(r).finally(()=>o(null))},g=d=>{o(`auth:${d}`),p(null),i("auth-start",{profile:d}).then(r=>{r.ok===!0?m({profile:d,verificationUrl:typeof r.verificationUrl=="string"?r.verificationUrl:void 0,qrcode:typeof r.qrcode=="string"?r.qrcode:void 0,browserOpened:r.browserOpened===!0}):p(typeof r.message=="string"?r.message:typeof r.error=="string"?r.error:"auth-start \u5931\u8D25")}).finally(()=>o(null))},k=d=>{o("auth-complete"),p(null),i("auth-complete",{profile:d}).then(r=>{r.ok===!0?(m(null),s(!0)):p(typeof r.message=="string"?r.message:typeof r.error=="string"?r.error:"auth-complete \u5931\u8D25\uFF08\u7528\u6237\u662F\u5426\u5DF2\u5728\u6D4F\u89C8\u5668\u786E\u8BA4\uFF1F\uFF09")}).finally(()=>o(null))};return t.default.createElement("div",{style:{padding:10,fontSize:12,display:"flex",flexDirection:"column",gap:8}},t.default.createElement("div",{style:{display:"flex",gap:6}},h("\u540C\u6B65\u72B6\u6001(health)",()=>f("health","health"),a==="health"),h("\u5BFC\u5165 profile(adopt)",()=>f("adopt","adopt"),a==="adopt"),l?t.default.createElement("span",{style:{opacity:.6,alignSelf:"center"}},"\u2026"):null),n!==null?t.default.createElement("div",{style:{color:"#ef4444"}},`\u52A0\u8F7D\u5931\u8D25\uFF1A${n}\uFF08\u63D2\u4EF6\u540E\u7AEF\u672A\u8FD0\u884C\uFF1F\uFF09`):null,y!==null?t.default.createElement("div",{style:{color:"#ef4444"}},y):null,c!==null?t.default.createElement(I,{flow:c,onCompleteAuth:k,busy:a}):null,e?.warnings?.map((d,r)=>t.default.createElement("div",{key:r,style:{color:"#f59e0b",fontSize:11}},d)),e!==null&&e.bindings.length===0?t.default.createElement("div",{style:{opacity:.7}},"\u5C1A\u65E0\u7ED1\u5B9A\u3002\u70B9\u300C\u5BFC\u5165 profile(adopt)\u300D\u5BFC\u5165\u672C\u673A lark-cli profile\uFF0C\u6216\u5728\u7EC8\u7AEF\u8FD0\u884C larklink bindings add \u65B0\u589E\u8D26\u53F7\u3002"):null,e?.bindings.map(d=>t.default.createElement(V,{key:d.profile,b:d,onStartAuth:g,busy:a})),e!==null&&e.pins.length>0?t.default.createElement("div",{style:{marginTop:4}},t.default.createElement("div",{style:{fontWeight:600,marginBottom:4}},`\u6587\u6863\u504F\u597D\uFF08pin\uFF0C${e.pins.length}\uFF09`),e.pins.map(d=>t.default.createElement("div",{key:d.token,style:{fontSize:10,opacity:.7,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},`${d.token.slice(0,14)}\u2026 \u2192 ${d.profile}`))):null,t.default.createElement("div",{style:{fontSize:10,opacity:.55,marginTop:4}},"add / remove \u6D89\u53CA app secret \u6216\u7834\u574F\u6027\u64CD\u4F5C\uFF0C\u9700\u5728\u7EC8\u7AEF\u6267\u884C\uFF1B\u767B\u5F55\u6388\u6743\uFF08\u626B\u7801\uFF09\u53EF\u76F4\u63A5\u5728\u9762\u677F\u6216\u8BA9 Agent \u89E6\u53D1\u3002"))}function h(e,l,n){return t.default.createElement("button",{onClick:l,disabled:n,style:{fontSize:11,padding:"4px 8px",borderRadius:6,cursor:n?"default":"pointer",border:"1px solid rgba(128,128,128,.4)",background:"transparent",opacity:n?.5:1}},e)}var M={ready:"\u53EF\u7528",needs_refresh:"\u9700\u5237\u65B0\u6388\u6743",unauthorized:"\u6388\u6743\u5931\u6548",incomplete:"\u672A\u5B8C\u6210\u6388\u6743"};function H(e){if(e===void 0||e==="")return"\u672A\u77E5\uFF08\u70B9\u300C\u5B9E\u65F6\u6838\u9A8C\u300D\u83B7\u53D6\uFF09";let l=Date.parse(e);if(Number.isNaN(l))return e;let n=new Date(l),s=c=>String(c).padStart(2,"0"),i=`${n.getFullYear()}-${s(n.getMonth()+1)}-${s(n.getDate())} ${s(n.getHours())}:${s(n.getMinutes())}`,a=(l-Date.now())/36e5,o=a<0?"\u5DF2\u8FC7\u671F\uFF0C\u9700\u91CD\u65B0\u6388\u6743":a<48?`\u5269\u4F59 ${a.toFixed(1)} \u5C0F\u65F6`:`\u5269\u4F59 ${(a/24).toFixed(1)} \u5929`;return`${i}\uFF08${o}\uFF09`}function E(e,l,n=!1){return t.default.createElement("div",{style:{display:"flex",gap:8,fontSize:11,lineHeight:1.6}},t.default.createElement("span",{style:{opacity:.55,width:88,flexShrink:0}},e),t.default.createElement("span",{style:{opacity:.9,wordBreak:"break-all",fontFamily:n?"var(--dsh-mono, ui-monospace, monospace)":void 0}},l===void 0||l===""?"\u2014":l))}function K({b:e,onStartAuth:l,busy:n}){let s=$[e.status]??"#9ca3af",i=e.status==="ready";return t.default.createElement("div",{style:{border:"1px solid rgba(128,128,128,.3)",borderRadius:10,padding:"10px 12px",display:"flex",flexDirection:"column",gap:6}},t.default.createElement("div",{style:{display:"flex",alignItems:"center",gap:8}},t.default.createElement("span",{style:{width:9,height:9,borderRadius:999,background:s,flexShrink:0},title:e.status}),t.default.createElement("strong",{style:{fontSize:13,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},e.profile),t.default.createElement("span",{style:{fontSize:10,opacity:.65,border:"1px solid rgba(128,128,128,.4)",borderRadius:4,padding:"0 4px"}},e.brand==="feishu"?"\u98DE\u4E66":"Lark"),e.adopted?t.default.createElement("span",{style:{fontSize:10,opacity:.55}},"\u5BFC\u5165"):null,t.default.createElement("span",{style:{marginLeft:"auto",fontSize:11,color:s,flexShrink:0}},M[e.status]??e.status),h(i?"\u91CD\u65B0\u6388\u6743":"\u626B\u7801\u767B\u5F55/\u4FEE\u590D",()=>l(e.profile),n===`auth:${e.profile}`)),E("\u767B\u5F55\u7528\u6237",e.userName??"\uFF08\u672A\u767B\u5F55\u7528\u6237\uFF09"),E("open_id",e.openId,!0),E("\u5E94\u7528 App ID",e.appId,!0),E("\u6388\u6743\u6709\u6548\u671F\u81F3",H(e.refreshExpiresAt)))}function Y(){let{data:e,loading:l,error:n,refresh:s,act:i}=z({initialLive:!0}),[a,o]=(0,t.useState)(null),[c,m]=(0,t.useState)(null),[y,p]=(0,t.useState)(null),f=(r,u,L=!1)=>{o(r),i(u).then(()=>{L&&s(!0)}).finally(()=>o(null))},g=r=>{o(`auth:${r}`),p(null),i("auth-start",{profile:r}).then(u=>{u.ok===!0?m({profile:r,verificationUrl:typeof u.verificationUrl=="string"?u.verificationUrl:void 0,qrcode:typeof u.qrcode=="string"?u.qrcode:void 0,browserOpened:u.browserOpened===!0}):p(typeof u.message=="string"?u.message:typeof u.error=="string"?u.error:"auth-start \u5931\u8D25")}).finally(()=>o(null))},k=r=>{o("auth-complete"),p(null),i("auth-complete",{profile:r}).then(u=>{u.ok===!0?(m(null),s(!0)):p(typeof u.message=="string"?u.message:typeof u.error=="string"?u.error:"auth-complete \u5931\u8D25\uFF08\u7528\u6237\u662F\u5426\u5DF2\u5728\u6D4F\u89C8\u5668\u786E\u8BA4\uFF1F\uFF09")}).finally(()=>o(null))},d=e?.bindings.filter(r=>r.status==="ready").length??0;return t.default.createElement("div",{style:{display:"flex",flexDirection:"column",gap:12,fontSize:12,maxWidth:640}},t.default.createElement("div",{style:{display:"flex",alignItems:"baseline",gap:10}},t.default.createElement("h3",{style:{margin:0,fontSize:14}},"LarkLink \u8D26\u53F7\u7BA1\u7406"),t.default.createElement("span",{style:{opacity:.65,fontSize:11}},e!==null?`${e.bindings.length} \u4E2A\u7ED1\u5B9A \xB7 ${d} \u4E2A\u53EF\u7528`:l?"\u52A0\u8F7D\u4E2D\u2026":""),t.default.createElement("span",{style:{marginLeft:"auto",display:"flex",gap:6}},h(a==="live"?"\u6838\u9A8C\u4E2D\u2026":"\u5B9E\u65F6\u6838\u9A8C",()=>{o("live"),s(!0),setTimeout(()=>o(null),400)},a==="live"),h("\u5BFC\u5165 profile(adopt)",()=>f("adopt","adopt",!0),a==="adopt"))),n!==null?t.default.createElement("div",{style:{color:"#ef4444"}},`\u52A0\u8F7D\u5931\u8D25\uFF1A${n}\uFF08\u63D2\u4EF6\u540E\u7AEF\u672A\u8FD0\u884C\uFF1F\uFF09`):null,y!==null?t.default.createElement("div",{style:{color:"#ef4444"}},y):null,c!==null?t.default.createElement(I,{flow:c,onCompleteAuth:k,busy:a}):null,e?.warnings?.map((r,u)=>t.default.createElement("div",{key:u,style:{color:"#f59e0b",fontSize:11}},r))??null,e!==null&&e.bindings.length===0?t.default.createElement("div",{style:{opacity:.7,border:"1px dashed rgba(128,128,128,.4)",borderRadius:10,padding:14}},"\u5C1A\u65E0\u7ED1\u5B9A\u3002\u70B9\u300C\u5BFC\u5165 profile(adopt)\u300D\u5BFC\u5165\u672C\u673A lark-cli profile\uFF1B\u5168\u65B0\u673A\u5668\u9700\u5728\u7EC8\u7AEF\u8FD0\u884C larklink bindings add \u65B0\u589E\u8D26\u53F7\uFF08app secret \u4E0D\u7ECF\u754C\u9762\uFF09\u3002"):null,e?.bindings.map(r=>t.default.createElement(K,{key:r.profile,b:r,onStartAuth:g,busy:a})),e!==null&&e.pins.length>0?t.default.createElement("div",{style:{display:"flex",flexDirection:"column",gap:4}},t.default.createElement("div",{style:{fontWeight:600}},`\u6587\u6863\u8D26\u53F7\u504F\u597D\uFF08pin\uFF0C${e.pins.length}\uFF09`),e.pins.map(r=>t.default.createElement("div",{key:r.token,style:{fontSize:11,opacity:.7,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},`${r.token.slice(0,16)}\u2026 \u2192 ${r.profile}`))):null,t.default.createElement("div",{style:{fontSize:10,opacity:.55}},"\u4F1A\u8BDD\u4E2D\u7C98\u8D34\u98DE\u4E66/Lark \u6587\u6863\u94FE\u63A5\u5373\u81EA\u52A8\u8DEF\u7531\uFF1BAgent \u9047\u672A\u767B\u5F55\u4F1A\u81EA\u52A8\u53D1\u8D77\u626B\u7801\u767B\u5F55\u3002\u65B0\u589E\u5E94\u7528\u4E0E\u5220\u9664\u7ED1\u5B9A\u6D89\u53CA app secret / \u7834\u574F\u6027\u64CD\u4F5C\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u6267\u884C larklink bindings add / remove\u3002"))}function G(){return t.default.createElement("span",{style:{display:"inline-flex",alignItems:"center",gap:6}},t.default.createElement(T,null),b)}function J(e){let l=[],n=i=>{typeof i=="function"&&l.push(i)},s=(i,a)=>{typeof a=="function"&&i.push(a)};if(n(e.inject(["sidebarRight","sidebarRightTabs"],i=>{let a=i.get("sidebarRightTabs");if(a===null||typeof a.register!="function")return()=>{};let o=[];return s(o,i.slots.inject(x,()=>i.slots.inject(R,()=>(o.push((()=>{let c=a.register({id:w,kind:j,title:()=>b,guide:[{order:70,title:()=>b,description:()=>"lark \u591A\u8D26\u53F7\u7ED1\u5B9A\u72B6\u6001\u4E0E\u6587\u6863\u8DEF\u7531\u504F\u597D\uFF08adopt / health / pins\uFF09",icon:T}]});return typeof c=="function"?c:()=>{}})()),s(o,i.slots.register({name:x,key:w},()=>t.default.createElement(W))),s(o,i.slots.register({name:R,key:w},()=>t.default.createElement(G))),()=>{for(let c of[...o].reverse())c()})))),()=>{for(let c of[...o].reverse())c()}})),e.slots!=null&&typeof e.slots.register=="function")try{n(e.slots.register({name:"settings.section",id:q,order:50,label:()=>"LarkLink \u8D26\u53F7\u7BA1\u7406"},()=>t.default.createElement(Y)))}catch(i){console.warn("[dsh-larklink] settings.section \u69FD\u4E0D\u53EF\u7528\uFF08\u5BBF\u4E3B\u672A\u58F0\u660E\uFF1F\uFF09\uFF0C\u8BBE\u7F6E\u9875\u5DF2\u8DF3\u8FC7\uFF0C\u4FA7\u680F\u9762\u677F\u4E0D\u53D7\u5F71\u54CD",i)}return()=>{for(let i of[...l].reverse())i()}}
|
|
8
8
|
|
|
9
9
|
return module.exports;
|
|
10
10
|
}
|
package/lib/index.d.mts
CHANGED
package/lib/index.mjs
CHANGED
|
@@ -46,6 +46,7 @@ const defaultLarkRunner = (args, opts = {}) => {
|
|
|
46
46
|
const child = spawn(bin, args, {
|
|
47
47
|
shell: false,
|
|
48
48
|
windowsHide: true,
|
|
49
|
+
cwd: opts.cwd,
|
|
49
50
|
stdio: [
|
|
50
51
|
"pipe",
|
|
51
52
|
"pipe",
|
|
@@ -326,7 +327,7 @@ function resolve(registry, input, opts = {}) {
|
|
|
326
327
|
};
|
|
327
328
|
}
|
|
328
329
|
const candidates = routableBindings(registry, parsed.brand);
|
|
329
|
-
if (candidates.length === 0) throw new ResolveError("no-routable-binding", `无可路由绑定(${parsed.brand === "none" ? "全部绑定" : `${parsed.brand} 品牌绑定`} 中没有可用绑定;incomplete
|
|
330
|
+
if (candidates.length === 0) throw new ResolveError("no-routable-binding", `无可路由绑定(${parsed.brand === "none" ? "全部绑定" : `${parsed.brand} 品牌绑定`} 中没有可用绑定;incomplete 不可路由)。若本机已有 lark-cli profile,先 larklink_bindings(action=adopt) 导入为绑定;全新机器需先在终端 larklink bindings add(app secret 不经模型)。`, parsed);
|
|
330
331
|
if (candidates.length === 1) {
|
|
331
332
|
const sole = candidates[0];
|
|
332
333
|
const repair = sole.status !== "ready" ? `;该绑定状态 ${sole.status},需修复授权而非换绑定` : "";
|
|
@@ -557,6 +558,199 @@ async function cmdAdopt(ctx, opts, emit = stdEmit) {
|
|
|
557
558
|
}, null, 2);
|
|
558
559
|
return 0;
|
|
559
560
|
}
|
|
561
|
+
function pendingAuthPath(home) {
|
|
562
|
+
return join(home, "pending-auth.json");
|
|
563
|
+
}
|
|
564
|
+
async function readPendingAuth(home) {
|
|
565
|
+
try {
|
|
566
|
+
const parsed = safeJson(await readFile(pendingAuthPath(home), "utf8"));
|
|
567
|
+
if (parsed === null || typeof parsed !== "object" || !parsed.profile || !parsed.deviceCode) return null;
|
|
568
|
+
return parsed;
|
|
569
|
+
} catch {
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
async function writePendingAuth(home, flow) {
|
|
574
|
+
await mkdir(home, { recursive: true });
|
|
575
|
+
await writeFile(pendingAuthPath(home), JSON.stringify(flow, null, 2) + "\n", "utf8");
|
|
576
|
+
}
|
|
577
|
+
async function clearPendingAuth(home) {
|
|
578
|
+
await rm(pendingAuthPath(home), { force: true });
|
|
579
|
+
}
|
|
580
|
+
/** 尽力打开系统浏览器(DSH 宿主运行在用户本机;失败不阻塞,envelope 里报告)。 */
|
|
581
|
+
function openInBrowser(url) {
|
|
582
|
+
try {
|
|
583
|
+
const cmd = process.platform === "win32" ? "rundll32" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
584
|
+
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
585
|
+
const child = spawn(cmd, args, {
|
|
586
|
+
shell: false,
|
|
587
|
+
stdio: "ignore",
|
|
588
|
+
detached: true
|
|
589
|
+
});
|
|
590
|
+
child.on("error", () => {});
|
|
591
|
+
child.unref();
|
|
592
|
+
return true;
|
|
593
|
+
} catch {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* auth:split-flow 授权/恢复(Q8-②:中断后每次都发起新流程,绝不缓存授权码复用)。
|
|
599
|
+
* 全程无密钥经手——app secret 留在 lark-cli profile 里,lark-cli 官方 agent 契约本身就要求
|
|
600
|
+
* agent 展示 verification_url/二维码并自行执行 --device-code 收尾。
|
|
601
|
+
* - 发起:`auth login --domain <域> --no-wait --json` → verification_url + device_code + 二维码 →(默认)自动开浏览器 → 暂存待收尾
|
|
602
|
+
* - 收尾:`auth login --device-code <code>`(码取自参数或暂存)→ 同步绑定状态
|
|
603
|
+
*/
|
|
604
|
+
async function cmdAuth(ctx, opts, emit = stdEmit) {
|
|
605
|
+
const home = ctx.home ?? larklinkHome();
|
|
606
|
+
if (opts.complete === true || opts.deviceCode !== void 0) {
|
|
607
|
+
let deviceCode = opts.deviceCode;
|
|
608
|
+
let fromStash = false;
|
|
609
|
+
if (deviceCode === void 0) {
|
|
610
|
+
const pending = await readPendingAuth(home);
|
|
611
|
+
if (pending === null) {
|
|
612
|
+
emit({
|
|
613
|
+
ok: false,
|
|
614
|
+
code: "no-pending-auth",
|
|
615
|
+
message: `没有进行中的授权流程(${pendingAuthPath(home)} 不存在)。先执行 auth-start 发起新流程。`
|
|
616
|
+
});
|
|
617
|
+
return 2;
|
|
618
|
+
}
|
|
619
|
+
if (pending.profile !== opts.profile) {
|
|
620
|
+
emit({
|
|
621
|
+
ok: false,
|
|
622
|
+
code: "pending-auth-mismatch",
|
|
623
|
+
message: `进行中的授权流程属于绑定 ${pending.profile},与请求的 ${opts.profile} 不符。请先对 ${pending.profile} 收尾或重新发起。`
|
|
624
|
+
});
|
|
625
|
+
return 2;
|
|
626
|
+
}
|
|
627
|
+
if (Date.parse(pending.expiresAt) <= Date.now()) {
|
|
628
|
+
await clearPendingAuth(home);
|
|
629
|
+
emit({
|
|
630
|
+
ok: false,
|
|
631
|
+
code: "pending-auth-expired",
|
|
632
|
+
message: `授权流程已过期(发起于 ${pending.startedAt})。请重新 auth-start 发起全新流程(Q8-②:不缓存旧授权码)。`
|
|
633
|
+
});
|
|
634
|
+
return 2;
|
|
635
|
+
}
|
|
636
|
+
deviceCode = pending.deviceCode;
|
|
637
|
+
fromStash = true;
|
|
638
|
+
}
|
|
639
|
+
const res = await ctx.runner([
|
|
640
|
+
"--profile",
|
|
641
|
+
opts.profile,
|
|
642
|
+
"auth",
|
|
643
|
+
"login",
|
|
644
|
+
"--device-code",
|
|
645
|
+
deviceCode,
|
|
646
|
+
"--json"
|
|
647
|
+
], { timeoutMs: 6e4 });
|
|
648
|
+
if (res.exitCode !== 0) {
|
|
649
|
+
emit({
|
|
650
|
+
ok: false,
|
|
651
|
+
code: "lark-cli-error",
|
|
652
|
+
exitCode: res.exitCode,
|
|
653
|
+
error: safeJson(res.stderr) ?? res.stderr.slice(0, 400),
|
|
654
|
+
hint: "若用户尚未在浏览器完成授权,请等用户确认后重新 auth-complete(轮询重试)"
|
|
655
|
+
});
|
|
656
|
+
return 1;
|
|
657
|
+
}
|
|
658
|
+
if (fromStash) await clearPendingAuth(home);
|
|
659
|
+
const health = await syncBindingStatus(ctx, home, opts.profile);
|
|
660
|
+
await appendAudit({
|
|
661
|
+
kind: "bindings",
|
|
662
|
+
profile: opts.profile,
|
|
663
|
+
message: `auth complete(status=${health.status ?? "?"})`
|
|
664
|
+
}, home);
|
|
665
|
+
emit({
|
|
666
|
+
ok: true,
|
|
667
|
+
profile: opts.profile,
|
|
668
|
+
status: health.status,
|
|
669
|
+
userName: health.userName,
|
|
670
|
+
warning: health.warning,
|
|
671
|
+
next: health.status === "ready" ? "授权恢复,可继续原操作" : `状态仍为 ${health.status},可能需要检查应用配置后重新授权`
|
|
672
|
+
}, null, 2);
|
|
673
|
+
return 0;
|
|
674
|
+
}
|
|
675
|
+
const domain = opts.domain ?? "docs";
|
|
676
|
+
const res = await ctx.runner([
|
|
677
|
+
"--profile",
|
|
678
|
+
opts.profile,
|
|
679
|
+
"auth",
|
|
680
|
+
"login",
|
|
681
|
+
"--domain",
|
|
682
|
+
domain,
|
|
683
|
+
"--no-wait",
|
|
684
|
+
"--json"
|
|
685
|
+
], { timeoutMs: 3e4 });
|
|
686
|
+
const payload = safeJson(res.stdout);
|
|
687
|
+
const errPayload = safeJson(res.stderr);
|
|
688
|
+
if (res.exitCode !== 0 || payload === null) {
|
|
689
|
+
emit({
|
|
690
|
+
ok: false,
|
|
691
|
+
code: "lark-cli-error",
|
|
692
|
+
exitCode: res.exitCode,
|
|
693
|
+
error: errPayload ?? res.stderr.slice(0, 400)
|
|
694
|
+
});
|
|
695
|
+
return 1;
|
|
696
|
+
}
|
|
697
|
+
const url = payload["verification_url"] ?? payload["url"];
|
|
698
|
+
const deviceCode = payload["device_code"] ?? payload["deviceCode"];
|
|
699
|
+
if (!url) {
|
|
700
|
+
emit({
|
|
701
|
+
ok: false,
|
|
702
|
+
code: "no-verification-url",
|
|
703
|
+
message: "lark-cli 未返回 verification_url",
|
|
704
|
+
payload
|
|
705
|
+
});
|
|
706
|
+
return 1;
|
|
707
|
+
}
|
|
708
|
+
const expiresInSec = typeof payload["expires_in"] === "number" ? payload["expires_in"] : 600;
|
|
709
|
+
let qrcode;
|
|
710
|
+
try {
|
|
711
|
+
await mkdir(home, { recursive: true });
|
|
712
|
+
const qrName = `qrcode-${opts.profile}.png`;
|
|
713
|
+
if ((await ctx.runner([
|
|
714
|
+
"auth",
|
|
715
|
+
"qrcode",
|
|
716
|
+
url,
|
|
717
|
+
"--output",
|
|
718
|
+
qrName
|
|
719
|
+
], {
|
|
720
|
+
timeoutMs: 15e3,
|
|
721
|
+
cwd: home
|
|
722
|
+
})).exitCode === 0) qrcode = join(home, qrName);
|
|
723
|
+
} catch {}
|
|
724
|
+
const flow = {
|
|
725
|
+
profile: opts.profile,
|
|
726
|
+
deviceCode: deviceCode ?? "",
|
|
727
|
+
verificationUrl: url,
|
|
728
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
729
|
+
expiresAt: new Date(Date.now() + expiresInSec * 1e3).toISOString()
|
|
730
|
+
};
|
|
731
|
+
if (deviceCode !== void 0) await writePendingAuth(home, flow);
|
|
732
|
+
const browserOpened = opts.open !== false ? openInBrowser(url) : false;
|
|
733
|
+
await appendAudit({
|
|
734
|
+
kind: "bindings",
|
|
735
|
+
profile: opts.profile,
|
|
736
|
+
message: `auth start(domain=${domain},expiresIn=${expiresInSec}s)`
|
|
737
|
+
}, home);
|
|
738
|
+
emit({
|
|
739
|
+
ok: true,
|
|
740
|
+
profile: opts.profile,
|
|
741
|
+
status: "auth-started",
|
|
742
|
+
verificationUrl: url,
|
|
743
|
+
qrcode,
|
|
744
|
+
expiresInSeconds: expiresInSec,
|
|
745
|
+
browserOpened,
|
|
746
|
+
next: [
|
|
747
|
+
"1. 让用户在浏览器(已自动打开;未打开则手动访问 verificationUrl 或扫码二维码)完成授权确认",
|
|
748
|
+
"2. 用户确认后调用 auth-complete(device_code 已暂存,无需携带)",
|
|
749
|
+
"3. 授权 URL 10 分钟内有效,过期须重新 auth-start(Q8-②)"
|
|
750
|
+
].join("\n")
|
|
751
|
+
}, null, 2);
|
|
752
|
+
return 0;
|
|
753
|
+
}
|
|
560
754
|
/**
|
|
561
755
|
* 用真实 `auth status --json --verify` 结构同步(identities.user.{userName,tokenStatus,refreshExpiresAt})。
|
|
562
756
|
* 无 user 身份 → incomplete(绑定=app+user,bot-only 不可作用户身份路由)。
|
|
@@ -578,9 +772,10 @@ async function syncBindingStatus(ctx, home, profile) {
|
|
|
578
772
|
};
|
|
579
773
|
}
|
|
580
774
|
const status = mapStatus(user.tokenStatus ?? user.status, true);
|
|
775
|
+
const refreshExpiresAt = user.refreshExpiresAt;
|
|
581
776
|
let warning;
|
|
582
|
-
if (
|
|
583
|
-
const expiry = Date.parse(
|
|
777
|
+
if (refreshExpiresAt !== void 0) {
|
|
778
|
+
const expiry = Date.parse(refreshExpiresAt);
|
|
584
779
|
if (!Number.isNaN(expiry)) {
|
|
585
780
|
const hoursLeft = (expiry - Date.now()) / 36e5;
|
|
586
781
|
if (hoursLeft < 0) warning = `绑定 ${profile} refresh token 已过期,需重新授权`;
|
|
@@ -602,6 +797,7 @@ async function syncBindingStatus(ctx, home, profile) {
|
|
|
602
797
|
status,
|
|
603
798
|
userName: user.userName,
|
|
604
799
|
openId: user.openId,
|
|
800
|
+
refreshExpiresAt,
|
|
605
801
|
warning
|
|
606
802
|
};
|
|
607
803
|
}
|
|
@@ -621,11 +817,17 @@ async function cmdList(ctx, opts, emit = stdEmit) {
|
|
|
621
817
|
const home = ctx.home ?? larklinkHome();
|
|
622
818
|
let registry = await loadRegistry(home);
|
|
623
819
|
const warnings = [];
|
|
820
|
+
/** 实时核验结果(openId / refreshExpiresAt 不落 registry,仅随本次信封透出)。 */
|
|
821
|
+
const liveInfo = /* @__PURE__ */ new Map();
|
|
624
822
|
if (!opts.fast) {
|
|
625
823
|
for (const b of registry.bindings) try {
|
|
626
824
|
const s = await syncBindingStatus(ctx, home, b.profile);
|
|
627
825
|
if (s.warning) warnings.push(s.warning);
|
|
628
|
-
if (
|
|
826
|
+
if (s.openId !== void 0 || s.refreshExpiresAt !== void 0) liveInfo.set(b.profile, {
|
|
827
|
+
openId: s.openId,
|
|
828
|
+
refreshExpiresAt: s.refreshExpiresAt
|
|
829
|
+
});
|
|
830
|
+
if (opts.health && s.status && s.status !== "ready" && s.status !== "incomplete") warnings.push(`绑定 ${b.profile} 状态 ${s.status}:调用 larklink_bindings(action=auth-start, profile=${b.profile}) 发起登录修复`);
|
|
629
831
|
} catch (e) {
|
|
630
832
|
warnings.push(`绑定 ${b.profile} 状态同步失败:${e.message}`);
|
|
631
833
|
}
|
|
@@ -650,6 +852,8 @@ async function cmdList(ctx, opts, emit = stdEmit) {
|
|
|
650
852
|
brand: b.brand,
|
|
651
853
|
appId: b.appId,
|
|
652
854
|
userName: b.userName,
|
|
855
|
+
openId: liveInfo.get(b.profile)?.openId ?? b.openId,
|
|
856
|
+
refreshExpiresAt: liveInfo.get(b.profile)?.refreshExpiresAt,
|
|
653
857
|
status: b.status,
|
|
654
858
|
adopted: b.adopted ?? false
|
|
655
859
|
})),
|
|
@@ -960,7 +1164,8 @@ async function cmdExec(runner, opts, home = larklinkHome(), emit = stdEmit) {
|
|
|
960
1164
|
code: "binding-needs-repair",
|
|
961
1165
|
profile,
|
|
962
1166
|
bindingStatus: result.bindingStatus,
|
|
963
|
-
message: `绑定 ${profile} 状态 ${result.bindingStatus}
|
|
1167
|
+
message: `绑定 ${profile} 状态 ${result.bindingStatus},需修复授权(不自动换绑定,Q1)`,
|
|
1168
|
+
fix: `立即调用 larklink_bindings(action=auth-start, profile=${profile}) 发起登录:自动打开浏览器并生成二维码;用户确认后 auth-complete,然后重试本命令`
|
|
964
1169
|
}, null, 2);
|
|
965
1170
|
return 2;
|
|
966
1171
|
}
|
|
@@ -1109,7 +1314,10 @@ function registerLarklinkTools(deps) {
|
|
|
1109
1314
|
additionalProperties: true,
|
|
1110
1315
|
description: "larklink JSON envelope"
|
|
1111
1316
|
},
|
|
1112
|
-
render: (_args, value) =>
|
|
1317
|
+
render: (_args, value) => [{
|
|
1318
|
+
type: "text",
|
|
1319
|
+
text: JSON.stringify(value, null, 2)
|
|
1320
|
+
}]
|
|
1113
1321
|
},
|
|
1114
1322
|
...spec
|
|
1115
1323
|
}));
|
|
@@ -1117,7 +1325,7 @@ function registerLarklinkTools(deps) {
|
|
|
1117
1325
|
};
|
|
1118
1326
|
register({
|
|
1119
1327
|
name: "larklink_resolve",
|
|
1120
|
-
description: "Resolve which lark/feishu account (binding) should operate on a document URL or bare token. Returns the routing decision (profile, confidence, brand) or an ambiguous candidate list with read-only probe annotations (accessible / no-permission / auth-failed / timeout). Never auto-picks among multiple candidates — ask the user.",
|
|
1328
|
+
description: "Resolve which lark/feishu account (binding) should operate on a document URL or bare token, and manage lark-cli login lifecycle. Use PROACTIVELY whenever the user mentions a feishu.cn / larksuite.com / larkoffice.com / doubao.com document link or a bare token (doxcn/shtcn/wikcn/bascn/...), or when any lark-cli/larklink operation reports auth failure, token expiry, or not-logged-in — regardless of how many bindings exist. Returns the routing decision (profile, confidence, brand) or an ambiguous candidate list with read-only probe annotations (accessible / no-permission / auth-failed / timeout). Never auto-picks among multiple candidates — ask the user.",
|
|
1121
1329
|
parameters: {
|
|
1122
1330
|
input: {
|
|
1123
1331
|
type: "string",
|
|
@@ -1156,7 +1364,7 @@ function registerLarklinkTools(deps) {
|
|
|
1156
1364
|
});
|
|
1157
1365
|
register({
|
|
1158
1366
|
name: "larklink_exec",
|
|
1159
|
-
description: "Execute a guarded lark-cli document-domain command (docs/drive/sheets/markdown/slides/base/whiteboard/wiki/mindnotes) as the resolved or explicitly named binding. Enforces: target consistency vs routing, no --as smuggling (use the as param), exit-10 confirmation fingerprint binding, timeout-unknown reporting. Prefer larklink_resolve first.",
|
|
1367
|
+
description: "Execute a guarded lark-cli document-domain command (docs/drive/sheets/markdown/slides/base/whiteboard/wiki/mindnotes) as the resolved or explicitly named binding. Use for every feishu/lark document read/write operation (single OR multiple bindings). Enforces: target consistency vs routing, no --as smuggling (use the as param), exit-10 confirmation fingerprint binding, timeout-unknown reporting. Prefer larklink_resolve first. On binding-needs-repair, run larklink_bindings auth-start immediately instead of asking the user to use a terminal.",
|
|
1160
1368
|
parameters: {
|
|
1161
1369
|
args: {
|
|
1162
1370
|
type: "array",
|
|
@@ -1207,7 +1415,7 @@ function registerLarklinkTools(deps) {
|
|
|
1207
1415
|
});
|
|
1208
1416
|
register({
|
|
1209
1417
|
name: "larklink_bindings",
|
|
1210
|
-
description: "
|
|
1418
|
+
description: "Binding management and lark-cli login lifecycle. Actions: adopt existing lark-cli profiles as larklink bindings; list bindings with live status; health sync (also cleans dead pins); manage document pins; auth-start initiates the QR login flow for a binding (auto-opens the browser, returns verification URL + QR code path; run it IMMEDIATELY whenever resolve/exec reports binding-needs-repair, auth-failed, needs_refresh, unauthorized, or incomplete — do not send the user to a terminal); auth-complete finishes login after the user confirms in the browser (device code is stashed server-side; no code needed). Only adding new apps (app secret via stdin) and removing bindings stay in the user's terminal (larklink bindings add|remove).",
|
|
1211
1419
|
parameters: {
|
|
1212
1420
|
action: {
|
|
1213
1421
|
type: "string",
|
|
@@ -1218,13 +1426,15 @@ function registerLarklinkTools(deps) {
|
|
|
1218
1426
|
"health",
|
|
1219
1427
|
"pin-add",
|
|
1220
1428
|
"pin-remove",
|
|
1221
|
-
"pin-list"
|
|
1429
|
+
"pin-list",
|
|
1430
|
+
"auth-start",
|
|
1431
|
+
"auth-complete"
|
|
1222
1432
|
],
|
|
1223
1433
|
description: "Management action."
|
|
1224
1434
|
},
|
|
1225
1435
|
profile: {
|
|
1226
1436
|
type: "string",
|
|
1227
|
-
description: "Binding profile name (adopt filter / pin target)."
|
|
1437
|
+
description: "Binding profile name (adopt filter / pin target / auth-start & auth-complete target)."
|
|
1228
1438
|
},
|
|
1229
1439
|
token: {
|
|
1230
1440
|
type: "string",
|
|
@@ -1233,6 +1443,14 @@ function registerLarklinkTools(deps) {
|
|
|
1233
1443
|
fast: {
|
|
1234
1444
|
type: "boolean",
|
|
1235
1445
|
description: "list: skip live status verification."
|
|
1446
|
+
},
|
|
1447
|
+
domain: {
|
|
1448
|
+
type: "string",
|
|
1449
|
+
description: "auth-start: lark-cli scope domains, comma-separated (default docs)."
|
|
1450
|
+
},
|
|
1451
|
+
open: {
|
|
1452
|
+
type: "boolean",
|
|
1453
|
+
description: "auth-start: open the browser at the verification URL (default true)."
|
|
1236
1454
|
}
|
|
1237
1455
|
},
|
|
1238
1456
|
async execute(args) {
|
|
@@ -1270,6 +1488,19 @@ function registerLarklinkTools(deps) {
|
|
|
1270
1488
|
case "pin-list":
|
|
1271
1489
|
await cmdPin({ op: "list" }, home, cap.emit);
|
|
1272
1490
|
break;
|
|
1491
|
+
case "auth-start":
|
|
1492
|
+
case "auth-complete":
|
|
1493
|
+
if (profile === void 0) throw new Error(`${action} 需要 profile`);
|
|
1494
|
+
await cmdAuth({
|
|
1495
|
+
runner,
|
|
1496
|
+
home
|
|
1497
|
+
}, {
|
|
1498
|
+
profile,
|
|
1499
|
+
domain: args["domain"] !== void 0 ? String(args["domain"]) : void 0,
|
|
1500
|
+
complete: action === "auth-complete",
|
|
1501
|
+
open: args["open"] !== false
|
|
1502
|
+
}, cap.emit);
|
|
1503
|
+
break;
|
|
1273
1504
|
default: throw new Error(`未知 action: ${action}`);
|
|
1274
1505
|
}
|
|
1275
1506
|
return cap.payload();
|
|
@@ -1299,9 +1530,21 @@ function fenceViolation(req) {
|
|
|
1299
1530
|
function readBody(req) {
|
|
1300
1531
|
return new Promise((resolve, reject) => {
|
|
1301
1532
|
let data = "";
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1533
|
+
let done = false;
|
|
1534
|
+
/** 单次收尾:防 end/error/超时重复触发;清掉超时定时器。 */
|
|
1535
|
+
const finish = (settle) => {
|
|
1536
|
+
if (done) return;
|
|
1537
|
+
done = true;
|
|
1538
|
+
clearTimeout(timer);
|
|
1539
|
+
settle();
|
|
1540
|
+
};
|
|
1541
|
+
const timer = setTimeout(() => finish(() => reject(/* @__PURE__ */ new Error("body timeout"))), 5e3);
|
|
1542
|
+
req.on("data", (c) => {
|
|
1543
|
+
data += c.toString("utf8");
|
|
1544
|
+
if (data.length > 65536) finish(() => reject(/* @__PURE__ */ new Error("body too large")));
|
|
1545
|
+
});
|
|
1546
|
+
req.on("end", () => finish(() => resolve(data)));
|
|
1547
|
+
req.on("error", (e) => finish(() => reject(e)));
|
|
1305
1548
|
});
|
|
1306
1549
|
}
|
|
1307
1550
|
function captureEmit() {
|
|
@@ -1314,6 +1557,14 @@ function captureEmit() {
|
|
|
1314
1557
|
payload: () => value
|
|
1315
1558
|
};
|
|
1316
1559
|
}
|
|
1560
|
+
function safeParseBody(text) {
|
|
1561
|
+
try {
|
|
1562
|
+
const parsed = JSON.parse(text);
|
|
1563
|
+
return parsed !== null && typeof parsed === "object" ? parsed : {};
|
|
1564
|
+
} catch {
|
|
1565
|
+
return {};
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1317
1568
|
function registerLarklinkRoutes(deps) {
|
|
1318
1569
|
const { ctx, runner, home } = deps;
|
|
1319
1570
|
let disposed = false;
|
|
@@ -1383,6 +1634,29 @@ function registerLarklinkRoutes(deps) {
|
|
|
1383
1634
|
}, { health: true }, cap.emit);
|
|
1384
1635
|
sendJson(res, 200, cap.payload());
|
|
1385
1636
|
});
|
|
1637
|
+
const authRoute = (complete) => async (req, res) => {
|
|
1638
|
+
const body = safeParseBody(await readBody(req).catch(() => ""));
|
|
1639
|
+
const profile = typeof body["profile"] === "string" ? body["profile"] : void 0;
|
|
1640
|
+
if (profile === void 0 || profile === "") {
|
|
1641
|
+
sendJson(res, 400, {
|
|
1642
|
+
ok: false,
|
|
1643
|
+
error: "需要 JSON body { profile }"
|
|
1644
|
+
});
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
const cap = captureEmit();
|
|
1648
|
+
await cmdAuth({
|
|
1649
|
+
runner,
|
|
1650
|
+
home
|
|
1651
|
+
}, {
|
|
1652
|
+
profile,
|
|
1653
|
+
complete,
|
|
1654
|
+
open: !complete
|
|
1655
|
+
}, cap.emit);
|
|
1656
|
+
sendJson(res, 200, cap.payload());
|
|
1657
|
+
};
|
|
1658
|
+
route("POST", `${ROUTE_BASE}/auth-start`, authRoute(false));
|
|
1659
|
+
route("POST", `${ROUTE_BASE}/auth-complete`, authRoute(true));
|
|
1386
1660
|
return () => {
|
|
1387
1661
|
for (const d of [...disposers].reverse()) d();
|
|
1388
1662
|
disposers.length = 0;
|
|
@@ -1397,24 +1671,28 @@ function registerLarklinkRoutes(deps) {
|
|
|
1397
1671
|
// src/skill-content.ts
|
|
1398
1672
|
/** 内嵌 SKILL 内容(随插件注册到 DSH skills 服务,source: bundled)。 */
|
|
1399
1673
|
const LARKLINK_SKILL_NAME = "larklink";
|
|
1400
|
-
const LARKLINK_SKILL_DESCRIPTION = "
|
|
1401
|
-
const LARKLINK_SKILL_MARKDOWN = `# larklink ——
|
|
1674
|
+
const LARKLINK_SKILL_DESCRIPTION = "Feishu/Lark document operations + login lifecycle, routed through larklink tools. Use PROACTIVELY, regardless of how many accounts are bound, whenever: (1) the user provides a feishu.cn / larksuite.com / larkoffice.com / doubao.com document link or a bare token (doxcn/shtcn/wikcn/bascn/doccn/fldcn/obcn...), or asks to read/edit/create any Feishu/Lark document, sheet, base, wiki or slides; (2) any lark-cli or lark-* skill operation fails with auth errors, token expiry, needs_refresh, unauthorized, or not-logged-in — larklink triggers the QR login flow automatically (opens the browser; no terminal needed).";
|
|
1675
|
+
const LARKLINK_SKILL_MARKDOWN = `# larklink —— 飞书/Lark 文档操作路由与登录管理
|
|
1402
1676
|
|
|
1403
|
-
##
|
|
1677
|
+
## 何时使用(无条件触发,不看绑定数量)
|
|
1404
1678
|
|
|
1405
1679
|
- 用户给出 feishu.cn / larksuite.com / larkoffice.com / doubao.com 的文档链接,或 doxcn / doccn / wikcn / shtcn / bascn / fldcn / obcn 裸 token,要求读取或修改
|
|
1406
|
-
-
|
|
1680
|
+
- 用户要求创建/编辑飞书文档、表格、Base、知识库、幻灯片
|
|
1681
|
+
- 任何 lark-cli / lark-* skill 操作报授权错误(未登录 / token 过期 / needs_refresh / unauthorized / incomplete)——立即走本 skill 的登录修复流程
|
|
1682
|
+
|
|
1683
|
+
**单一绑定也要走 larklink**:larklink 统一提供路由、授权生命周期管理(自动触发扫码登录)、目标守卫与审计。不要因为"只有一个账号"就绕过。
|
|
1407
1684
|
|
|
1408
1685
|
## 硬纪律(多账号防串号)
|
|
1409
1686
|
|
|
1410
|
-
1.
|
|
1411
|
-
2.
|
|
1412
|
-
3.
|
|
1413
|
-
4. 执行身份默认 user;以应用身份(bot)执行必须用户明确要求(\`as: "bot"\`),绝不静默回退。
|
|
1687
|
+
1. 一切文档域操作必须 \`larklink_resolve\` → \`larklink_exec\`,**禁止**直接调用 lark-cli 文档命令(多账号时会静默打到默认账号,单账号时绕过授权管理与审计)。
|
|
1688
|
+
2. 身份选择优先级:用户显式指定 > 文档 pin > 唯一绑定 > 询问用户。**能力 ≠ 意图**:探测结果、ready 状态、tenantHint 永不构成自动换号理由。绑定失效 → 走登录修复流程,绝不换绑定重试。
|
|
1689
|
+
3. 执行身份默认 user;以应用身份(bot)执行必须用户明确要求(\`as: "bot"\`),绝不静默回退。
|
|
1414
1690
|
|
|
1415
1691
|
## 标准流程
|
|
1416
1692
|
|
|
1417
1693
|
1. \`larklink_resolve(input)\` → \`resolved\`(profile / confidence / wikiUnwrapNeeded)或 \`ambiguous\`(candidates 含 probe 标注:accessible / no-permission / auth-failed / timeout)
|
|
1694
|
+
- 报 \`no-routable-binding\` 且本机有 lark-cli profile:先 \`larklink_bindings(action=adopt)\` 导入再重试 resolve
|
|
1695
|
+
- 全新机器(无任何 profile):提示用户在终端执行 \`larklink bindings add\`(app secret 不经模型)
|
|
1418
1696
|
2. ambiguous → 把候选与标注呈现给用户选择;选择后 \`larklink_resolve(input, askedProfile, pin=true)\` 记住偏好
|
|
1419
1697
|
3. \`larklink_exec(args, url 或 profile)\` 执行 lark-cli 文档命令,返回信封:
|
|
1420
1698
|
- \`needs-confirmation\`:向用户展示 action 与参数预览;用户明确同意后,**原样命令** + \`yes=true\` + 返回的 \`confirmationId\`(指纹不符会被拒绝——确认不可挪用)
|
|
@@ -1422,6 +1700,16 @@ const LARKLINK_SKILL_MARKDOWN = `# larklink —— 多账号飞书/Lark 文档
|
|
|
1422
1700
|
- \`error\`:读 error 字段修复参数或授权后重试
|
|
1423
1701
|
4. wiki 链接(wikcn…)先 \`drive +inspect\` 解包出真实 token/type 再路由到对应命令
|
|
1424
1702
|
|
|
1703
|
+
## 登录修复(未登录/授权失效时自动触发,不请用户去终端)
|
|
1704
|
+
|
|
1705
|
+
只要 resolve / exec / 探测出现 \`binding-needs-repair\`、\`auth-failed\`、\`needs_refresh\`、\`unauthorized\`、\`incomplete\` 任一信号,立即:
|
|
1706
|
+
|
|
1707
|
+
1. \`larklink_bindings(action=auth-start, profile=<绑定>)\` —— 自动打开系统浏览器并生成二维码(envelope 含 verificationUrl、qrcode 路径、expiresInSeconds)。把 verificationUrl 作为可点链接给用户;若环境支持读图,用 read_image 展示 qrcode PNG;浏览器没自动打开时提醒用户手动点链接或扫码
|
|
1708
|
+
2. 等用户在浏览器完成授权确认(10 分钟内有效)。用户回复完成后:\`larklink_bindings(action=auth-complete, profile=<绑定>)\`(device_code 已在插件侧暂存,无需携带;过期会报 pending-auth-expired,届时重新 auth-start)
|
|
1709
|
+
3. auth-complete 返回 ready 后**原样重试**之前的操作
|
|
1710
|
+
|
|
1711
|
+
注意:auth-complete 失败通常意味着用户还没在浏览器确认——提示用户确认后再次 auth-complete,不要立刻重新发起。绝不静默换绑定。
|
|
1712
|
+
|
|
1425
1713
|
## 写操作纪律
|
|
1426
1714
|
|
|
1427
1715
|
- \`overwrite\` / 整段替换前**必须先 fetch 最新内容**做增量合并;优先 str_replace 类局部修改(目标文本不存在会失败而非误覆盖)
|
|
@@ -1436,9 +1724,8 @@ docs · drive · sheets · markdown · slides · base · whiteboard · wiki · m
|
|
|
1436
1724
|
|
|
1437
1725
|
## 绑定管理
|
|
1438
1726
|
|
|
1439
|
-
-
|
|
1440
|
-
-
|
|
1441
|
-
- 绑定授权过期(needs_refresh/unauthorized):报告用户执行 \`larklink bindings auth <profile>\`,不要反复重试
|
|
1727
|
+
- \`larklink_bindings\` 工具动作:adopt(导入既有 profile)/ list / health / pin 管理 / **auth-start / auth-complete(扫码登录与授权修复,无密钥经手)**
|
|
1728
|
+
- 仅以下操作让用户在终端执行:\`larklink bindings add\`(新建应用,app secret 走 stdin)、\`larklink bindings remove\`(破坏性删除)
|
|
1442
1729
|
`;
|
|
1443
1730
|
// src/index.ts
|
|
1444
1731
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timeryu/dsh-larklink",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "DeepSeek Harness plugin: multi-account lark-cli routing with guarded document exec (larklink_resolve / larklink_exec / larklink_bindings).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,11 +38,6 @@
|
|
|
38
38
|
]
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
|
-
"scripts": {
|
|
42
|
-
"build": "tsdown && node scripts/build-client.mjs",
|
|
43
|
-
"build:client": "node scripts/build-client.mjs",
|
|
44
|
-
"typecheck": "tsc --noEmit"
|
|
45
|
-
},
|
|
46
41
|
"keywords": [
|
|
47
42
|
"dsh-plugin",
|
|
48
43
|
"deepseek-harness",
|
|
@@ -52,12 +47,12 @@
|
|
|
52
47
|
],
|
|
53
48
|
"peerDependencies": {
|
|
54
49
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
55
|
-
"@deepseek-ai/dsh-tools": "
|
|
50
|
+
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.6 <0.3.0",
|
|
56
51
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
57
52
|
},
|
|
58
53
|
"devDependencies": {
|
|
59
54
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
60
|
-
"@deepseek-ai/dsh-tools": "^0.1
|
|
55
|
+
"@deepseek-ai/dsh-tools": "^0.2.1-rc.2",
|
|
61
56
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
62
57
|
"@types/node": "^24.0.0",
|
|
63
58
|
"@types/react": "^18.3.0",
|
|
@@ -66,5 +61,10 @@
|
|
|
66
61
|
"react-dom": "^18.2.0",
|
|
67
62
|
"tsdown": "^0.22.14",
|
|
68
63
|
"typescript": "^5.8.0"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tsdown && node scripts/build-client.mjs",
|
|
67
|
+
"build:client": "node scripts/build-client.mjs",
|
|
68
|
+
"typecheck": "tsc --noEmit"
|
|
69
69
|
}
|
|
70
|
-
}
|
|
70
|
+
}
|