@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 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
@@ -4,6 +4,7 @@ interface LarkRunnerLike {
4
4
  (args: string[], opts?: {
5
5
  timeoutMs?: number;
6
6
  stdin?: string;
7
+ cwd?: string;
7
8
  }): Promise<{
8
9
  exitCode: number;
9
10
  stdout: string;
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 不可路由)。请先 bindings add / adopt 或完成授权。`, parsed);
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 (user.refreshExpiresAt !== void 0) {
583
- const expiry = Date.parse(user.refreshExpiresAt);
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 (opts.health && s.status && s.status !== "ready" && s.status !== "incomplete") warnings.push(`绑定 ${b.profile} 状态 ${s.status}:请 larklink bindings auth ${b.profile} 修复`);
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},请 larklink bindings auth ${profile} 修复后重试(不自动换绑定,Q1)`
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) => 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: "Non-secret binding management: adopt existing lark-cli profiles as larklink bindings, list bindings with live status, run health sync (also cleans dead pins), manage document pins. Adding accounts / QR re-auth / removal involve secrets and must run in the user's terminal (larklink bindings add|auth|remove), never through this tool.",
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
- req.on("data", (c) => data += c.toString("utf8"));
1303
- req.on("end", () => resolve(data));
1304
- req.on("error", reject);
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 = "Multi-account Feishu/Lark document operations routed through larklink tools: resolve which bound account operates on a document URL/token, then execute guarded lark-cli document commands. Use whenever the user provides a feishu.cn / larksuite.com / larkoffice.com / doubao.com document link or a bare token (doxcn/shtcn/wikcn/bascn...) and the machine has more than one lark binding.";
1401
- const LARKLINK_SKILL_MARKDOWN = `# larklink —— 多账号飞书/Lark 文档操作路由
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
- - 本机有多个 lark 账号(绑定)时,一切文档域操作必须经 larklink 路由
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. registry 存在 **≥2 个可用绑定**时:文档域操作必须 \`larklink_resolve\` → \`larklink_exec\`,**禁止**直接调用 lark-cli 文档命令(会静默打到默认账号)。
1411
- 2. 单一绑定时:默认 profile 即唯一账号,可直接用 lark-* skills,无需 larklink。
1412
- 3. 身份选择优先级:用户显式指定 > 文档 pin > 唯一绑定 > 询问用户。**能力 ≠ 意图**:探测结果、ready 状态、tenantHint 永不构成自动换号理由。绑定失效 → 提示用户修复授权,绝不换绑定重试。
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
- - 新增账号、授权修复(扫码 split-flow)、删除绑定:**让用户在终端执行** \`larklink bindings add|auth|remove\` —— app secret 与授权码不经模型上下文
1440
- - 工具 \`larklink_bindings\` 仅做无敏感操作:adopt(导入既有 profile)/ list / health / pin 管理
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.0",
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": "^0.1.0-rc.6",
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.0-rc.6",
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
+ }