@jesonliu/lark-claudecode-bridge 1.0.4 → 1.0.6
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/assets/web/js/pages/larkcli-device.js +178 -44
- package/assets/web/js/pages/overview.js +45 -18
- package/assets/web/js/ui.js +5 -2
- package/dist/bin/lcb.js +5 -4
- package/dist/bin/lcb.js.map +1 -1
- package/dist/lark-cli-manager.d.ts +132 -15
- package/dist/lark-cli-manager.js +404 -60
- package/dist/lark-cli-manager.js.map +1 -1
- package/dist/web/server.js +20 -24
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
// ============
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// ============ 飞书配置应用 / 扫码授权:页面内二维码弹窗 ============
|
|
2
|
+
// 与「拉终端」并列的第二条路径——把 lark-cli 的 config init 与 device flow 都搬到配置页里,
|
|
3
|
+
// 用户直接扫码完成,不用切窗口。它不经过终端探测,因此无桌面 / SSH 环境同样可用
|
|
4
|
+
// (那条路上「去终端」原本是无出口的死路)。
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// 同一个弹窗里两个阶段(phase),因为它们是**一条连续的路**:
|
|
7
|
+
// config:应用还没配置时,先扫码创建/绑定飞书应用(后端 spawn `config init --new` 并
|
|
8
|
+
// 从其输出里流式解析授权链接——官方 README 与内置 skill 规定的用法)
|
|
9
|
+
// auth :再扫码授权(设备流,三段式见 src/lark-cli-manager.ts 的设备流一节)
|
|
10
|
+
// 配置完成后**自动接续**授权,用户不需要关掉弹窗再来一次(后端配置会话置 done 前
|
|
11
|
+
// 已用 checkAuth() 复检过「确实配上了」,所以接续时设备流的前置守卫必然放行)。
|
|
10
12
|
//
|
|
11
13
|
// **device_code 全程只存后端内存,从不下发到浏览器。**
|
|
12
14
|
//
|
|
@@ -23,7 +25,12 @@ export function fmtRemain(sec) {
|
|
|
23
25
|
return `${String(Math.floor(s / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
/**
|
|
28
|
+
/** 纯函数(可测):与后端 needsLarkCliConfig 同源——detail 含 not_configured 即「未配置应用」 */
|
|
29
|
+
export function larkNotConfigured(r) {
|
|
30
|
+
return r?.auth?.state === 'unauthorized' && /not_configured/i.test(r?.auth?.detail || '');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 纯函数(可测):授权阶段终态 → 给用户看的一句话 */
|
|
27
34
|
export function deviceStateHint(state, error) {
|
|
28
35
|
if (state === 'done') return '✅ 授权完成,可以关闭本弹窗了。';
|
|
29
36
|
if (state === 'expired') return error || '二维码已过期,请重新生成。';
|
|
@@ -33,19 +40,39 @@ export function deviceStateHint(state, error) {
|
|
|
33
40
|
return '';
|
|
34
41
|
}
|
|
35
42
|
|
|
43
|
+
/** 纯函数(可测):配置阶段终态 → 给用户看的一句话 */
|
|
44
|
+
export function configStateHint(state, error) {
|
|
45
|
+
if (state === 'done') return '✅ 飞书应用已配置,接着申请授权二维码。';
|
|
46
|
+
if (state === 'expired') return error || '配置链接已失效,请重新生成二维码。';
|
|
47
|
+
// 配置这条路每一步都在浏览器里,失败原因多半是「没走完」——把两个出口都点出来
|
|
48
|
+
if (state === 'failed') return error || '配置未完成,可重新生成二维码再试,或改用终端窗口。';
|
|
49
|
+
if (state === 'none') return error || '配置会话已失效(桥接器可能已重启),请重新生成二维码。';
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const STATE_TAG = { done: 'ok', expired: 'warn', failed: 'warn', none: 'warn' };
|
|
54
|
+
const STATE_LABEL = {
|
|
55
|
+
config: { done: '已配置', expired: '已过期', failed: '未完成', none: '未完成' },
|
|
56
|
+
auth: { done: '已授权', expired: '已过期', failed: '未完成', none: '未完成' },
|
|
57
|
+
};
|
|
58
|
+
|
|
36
59
|
/**
|
|
37
|
-
*
|
|
60
|
+
* 打开飞书配置 / 扫码授权弹窗。未配置应用时自动进入 config 阶段,配完接续 auth 阶段。
|
|
38
61
|
* @param {object} opts
|
|
39
62
|
* @param {(r:any)=>void} [opts.onChange] 收到最新 /api/lark-cli 结果时回调,用于同步刷新概览行
|
|
40
|
-
* @param {()=>void} [opts.onTerminal]
|
|
63
|
+
* @param {()=>void} [opts.onTerminal] 授权阶段选「改用终端窗口」时回调
|
|
64
|
+
* @param {()=>void} [opts.onConfigTerminal] 配置阶段选「改用终端窗口」时回调
|
|
41
65
|
* @param {boolean} [opts.wasAuthorized] 打开时的授权态快照:区分「本来就好」与「本次扫码完成」
|
|
66
|
+
* @param {boolean} [opts.notConfigured] 应用未配置(not_configured):先走 config 阶段
|
|
42
67
|
*/
|
|
43
|
-
export function openLarkCliDeviceDialog({
|
|
68
|
+
export function openLarkCliDeviceDialog({
|
|
69
|
+
onChange, onTerminal, onConfigTerminal, wasAuthorized = false, notConfigured = false,
|
|
70
|
+
} = {}) {
|
|
44
71
|
const { mask, unmount } = mountDialog(`
|
|
45
72
|
<div class="modal dlg" role="dialog" aria-modal="true" style="width:min(520px,94vw)">
|
|
46
|
-
<h3 class="dlg-title">飞书授权(扫码完成)</h3>
|
|
47
|
-
<div class="dlg-msg"
|
|
48
|
-
<div id="lqdBody"><div class="hint"
|
|
73
|
+
<h3 class="dlg-title" id="lqdTitle">飞书授权(扫码完成)</h3>
|
|
74
|
+
<div class="dlg-msg" id="lqdLead"></div>
|
|
75
|
+
<div id="lqdBody"><div class="hint">正在向飞书申请二维码…</div></div>
|
|
49
76
|
<div class="dlg-foot">
|
|
50
77
|
<button class="btn" data-lqd="terminal">改用终端窗口</button>
|
|
51
78
|
<button class="btn" data-lqd="copy" hidden>复制链接</button>
|
|
@@ -54,10 +81,19 @@ export function openLarkCliDeviceDialog({ onChange, onTerminal, wasAuthorized =
|
|
|
54
81
|
</div>
|
|
55
82
|
</div>`);
|
|
56
83
|
|
|
84
|
+
const titleEl = mask.querySelector('#lqdTitle');
|
|
85
|
+
const leadEl = mask.querySelector('#lqdLead');
|
|
57
86
|
const body = mask.querySelector('#lqdBody');
|
|
58
87
|
const btnCopy = mask.querySelector('[data-lqd="copy"]');
|
|
59
88
|
const btnRegen = mask.querySelector('[data-lqd="regen"]');
|
|
89
|
+
const btnTerminal = mask.querySelector('[data-lqd="terminal"]');
|
|
90
|
+
const btnClose = mask.querySelector('[data-lqd="close"]');
|
|
91
|
+
|
|
92
|
+
// needConfig 必须是可变绑定:配置完成后就地翻掉,doStart 才能接着走授权
|
|
93
|
+
let needConfig = !!notConfigured;
|
|
94
|
+
let phase = needConfig ? 'config' : 'auth';
|
|
60
95
|
let session = null; // 最近一次 start 的响应(存链接与二维码)
|
|
96
|
+
let painted = {}; // 已画出去的内容:相同内容不重画,免得二维码每隔 2s 闪一下
|
|
61
97
|
let baseLeft = 0; // 服务端给的剩余秒数
|
|
62
98
|
let baseAt = 0; // 收到该秒数时的本地时刻
|
|
63
99
|
let poll = null;
|
|
@@ -73,53 +109,90 @@ export function openLarkCliDeviceDialog({ onChange, onTerminal, wasAuthorized =
|
|
|
73
109
|
// 一个一打开就已过期的二维码
|
|
74
110
|
const remainNow = () => Math.max(0, baseLeft - (Date.now() - baseAt) / 1000);
|
|
75
111
|
|
|
76
|
-
const
|
|
112
|
+
const setLead = (text) => { leadEl.textContent = text; };
|
|
113
|
+
const setTitle = (text) => { titleEl.textContent = text; };
|
|
114
|
+
|
|
115
|
+
const hideActions = () => { btnCopy.hidden = true; btnRegen.hidden = true; };
|
|
116
|
+
|
|
117
|
+
const paintQr = (s) => {
|
|
77
118
|
const qr = s.qrDataUrl
|
|
78
|
-
? `<div class="qr-box"><img src="${esc(s.qrDataUrl)}" alt="
|
|
119
|
+
? `<div class="qr-box"><img src="${esc(s.qrDataUrl)}" alt="飞书二维码" width="220" height="220"></div>`
|
|
79
120
|
: '<div class="hint" style="margin-top:8px">二维码生成失败,请复制下方链接在飞书中打开。</div>';
|
|
121
|
+
// 倒计时只在服务端给了有效期时才显示:配置流拿不到有效期,编一个数字只会误导用户
|
|
122
|
+
const life = typeof s.expiresInSec === 'number'
|
|
123
|
+
? `二维码 <b id="lqdLeft">${fmtRemain(remainNow())}</b> 后失效`
|
|
124
|
+
: '若二维码失效,点「重新生成二维码」即可';
|
|
80
125
|
body.innerHTML = qr
|
|
81
|
-
+ `<div class="hint" style="margin-top:8px"
|
|
126
|
+
+ `<div class="hint" style="margin-top:8px">${life}`
|
|
82
127
|
+ (s.userCode ? ` · 授权码 <span class="chip">${esc(s.userCode)}</span>` : '')
|
|
83
128
|
+ '</div>'
|
|
84
129
|
+ `<div class="hint qr-url">${esc(s.verificationUrl || '')}</div>`;
|
|
85
130
|
btnCopy.hidden = false;
|
|
86
131
|
btnRegen.hidden = false;
|
|
132
|
+
painted = { url: s.verificationUrl, qr: s.qrDataUrl };
|
|
87
133
|
};
|
|
88
134
|
|
|
89
135
|
const paintDone = (state, { identity, error } = {}) => {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
body.innerHTML = `<div><span class="tag ${
|
|
136
|
+
const label = (STATE_LABEL[phase] || STATE_LABEL.auth)[state] || '未完成';
|
|
137
|
+
const hint = phase === 'config' ? configStateHint(state, error) : deviceStateHint(state, error);
|
|
138
|
+
body.innerHTML = `<div><span class="tag ${STATE_TAG[state] || 'warn'}">${label}</span>`
|
|
93
139
|
+ (identity ? ` <span class="chip">${esc(identity)}</span>` : '')
|
|
94
140
|
+ '</div>'
|
|
95
|
-
+ `<div class="hint" style="margin-top:8px">${esc(
|
|
141
|
+
+ `<div class="hint" style="margin-top:8px">${esc(hint)}</div>`;
|
|
96
142
|
btnCopy.hidden = true;
|
|
97
143
|
btnRegen.hidden = state === 'done';
|
|
98
144
|
};
|
|
99
145
|
|
|
146
|
+
/** 后端把前置问题写成了可照做的文案(未安装 / 探测不出 / 已配置),直接透出 */
|
|
147
|
+
const paintFail = (msg) => {
|
|
148
|
+
body.innerHTML = '<div><span class="tag warn">无法发起</span></div>'
|
|
149
|
+
+ `<div class="hint" style="margin-top:8px">${esc(msg)}</div>`;
|
|
150
|
+
btnCopy.hidden = true;
|
|
151
|
+
btnRegen.hidden = false; // 给出路:失败也要能重来,不留死胡同
|
|
152
|
+
};
|
|
153
|
+
|
|
100
154
|
/** 拉一次概览数据同步行内状态(拿不到就算了,别让弹窗崩) */
|
|
101
155
|
const syncRow = async () => {
|
|
102
|
-
if (!onChange) return;
|
|
156
|
+
if (!onChange) return null;
|
|
103
157
|
const cur = await api('GET', '/api/lark-cli').catch(() => null);
|
|
104
158
|
if (cur) onChange(cur);
|
|
105
159
|
return cur;
|
|
106
160
|
};
|
|
107
161
|
|
|
162
|
+
const startPolling = () => {
|
|
163
|
+
poll = setInterval(doPoll, POLL_MS);
|
|
164
|
+
tick = setInterval(() => {
|
|
165
|
+
const el = body.querySelector('#lqdLeft');
|
|
166
|
+
if (el) el.textContent = fmtRemain(remainNow());
|
|
167
|
+
}, TICK_MS);
|
|
168
|
+
};
|
|
169
|
+
|
|
108
170
|
const doPoll = async () => {
|
|
109
171
|
if (closed) return;
|
|
172
|
+
const endpoint = phase === 'config' ? '/api/lark-cli/config/status' : '/api/lark-cli/device/status';
|
|
110
173
|
let r;
|
|
111
174
|
try {
|
|
112
|
-
r = await api('GET',
|
|
175
|
+
r = await api('GET', endpoint);
|
|
113
176
|
} catch {
|
|
114
177
|
return; // 静默重试:桥接器重启时会短暂失联,弹错反而吓人
|
|
115
178
|
}
|
|
116
179
|
if (closed) return;
|
|
117
180
|
if (r.state === 'pending') {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
181
|
+
if (phase === 'auth') {
|
|
182
|
+
// 以服务端时间为准校正倒计时基准(每次轮询都对一次表)
|
|
183
|
+
baseLeft = r.expiresInSec ?? remainNow();
|
|
184
|
+
baseAt = Date.now();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// 配置流:链接与二维码要等子进程吐出来。start 若没等到就返回了,这里补画
|
|
188
|
+
// (内容没变就不重画——每 2s 重建一次 <img> 会让二维码闪)
|
|
189
|
+
if (r.verificationUrl && (painted.url !== r.verificationUrl || painted.qr !== r.qrDataUrl)) {
|
|
190
|
+
session = { ...(session || {}), ...r };
|
|
191
|
+
paintQr(session);
|
|
192
|
+
}
|
|
121
193
|
return;
|
|
122
194
|
}
|
|
195
|
+
if (phase === 'config') return finishConfigPhase(r);
|
|
123
196
|
stopTimers();
|
|
124
197
|
if (r.state === 'none') {
|
|
125
198
|
// 内存态被冲(桥接器重启),但用户可能已经扫完了:回查授权态兜底,
|
|
@@ -133,31 +206,88 @@ export function openLarkCliDeviceDialog({ onChange, onTerminal, wasAuthorized =
|
|
|
133
206
|
paintDone(r.state, { identity: r.identity, error: r.error });
|
|
134
207
|
};
|
|
135
208
|
|
|
136
|
-
|
|
209
|
+
/**
|
|
210
|
+
* 配置完成 → 同一个弹窗里就地接续授权,兑现「完成后自动接着走授权登录」的承诺。
|
|
211
|
+
* 后端置 done 之前已用 checkAuth() 复检确认应用真的配上了,所以设备流的前置守卫必然放行。
|
|
212
|
+
*/
|
|
213
|
+
const continueToAuth = async () => {
|
|
214
|
+
phase = 'auth';
|
|
215
|
+
needConfig = false;
|
|
216
|
+
await syncRow(); // 概览行先翻成「已配置」
|
|
217
|
+
if (closed) return;
|
|
218
|
+
toast('✅ 飞书应用已配置,接着申请授权二维码');
|
|
219
|
+
await startAuth(false);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const finishConfigPhase = async (r) => {
|
|
137
223
|
stopTimers();
|
|
224
|
+
if (r.state === 'done') return continueToAuth();
|
|
225
|
+
if (r.state === 'none') {
|
|
226
|
+
// 内存态被冲(桥接器重启),但用户可能已经在浏览器里配完了:回查兜底
|
|
227
|
+
const cur = await syncRow();
|
|
228
|
+
if (cur && !larkNotConfigured(cur)) return continueToAuth();
|
|
229
|
+
}
|
|
230
|
+
return paintDone(r.state, { error: r.error });
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const startAuth = async (regenerate) => {
|
|
234
|
+
stopTimers();
|
|
235
|
+
setTitle('飞书授权(扫码完成)');
|
|
236
|
+
setLead('用飞书 App 扫描下方二维码,按提示确认授权。授权成功后本页会自动更新。');
|
|
138
237
|
body.innerHTML = '<div class="hint">正在向飞书申请授权二维码…</div>';
|
|
139
|
-
|
|
140
|
-
btnRegen.hidden = true;
|
|
238
|
+
hideActions();
|
|
141
239
|
try {
|
|
142
240
|
session = await api('POST', '/api/lark-cli/device/start', { regenerate: !!regenerate });
|
|
143
241
|
} catch (e) {
|
|
144
242
|
// 后端把「未安装」「应用未配置」等前置问题写成了可照做的文案,直接透出
|
|
145
|
-
|
|
146
|
-
+ `<div class="hint" style="margin-top:8px">${esc(e.message)}</div>`;
|
|
147
|
-
toast(e.message, true);
|
|
243
|
+
paintFail(e.message);
|
|
148
244
|
return;
|
|
149
245
|
}
|
|
150
246
|
if (closed) return;
|
|
151
247
|
baseLeft = session.expiresInSec ?? 0;
|
|
152
248
|
baseAt = Date.now();
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const el = body.querySelector('#lqdLeft');
|
|
157
|
-
if (el) el.textContent = fmtRemain(remainNow());
|
|
158
|
-
}, TICK_MS);
|
|
249
|
+
painted = {};
|
|
250
|
+
paintQr(session);
|
|
251
|
+
startPolling();
|
|
159
252
|
};
|
|
160
253
|
|
|
254
|
+
const startConfig = async (regenerate) => {
|
|
255
|
+
stopTimers();
|
|
256
|
+
setTitle('飞书配置(扫码完成)');
|
|
257
|
+
setLead('用飞书 App 扫描下方二维码,按提示创建或绑定飞书应用。'
|
|
258
|
+
+ '完成后会自动接着申请授权二维码,不用回到本页重来。');
|
|
259
|
+
body.innerHTML = '<div class="hint">正在向飞书申请配置二维码…</div>';
|
|
260
|
+
hideActions();
|
|
261
|
+
try {
|
|
262
|
+
session = await api('POST', '/api/lark-cli/config/start', { regenerate: !!regenerate });
|
|
263
|
+
} catch (e) {
|
|
264
|
+
// 打开弹窗时的 notConfigured 快照可能已经过期(用户刚在别处配好了,后端据此拒绝)。
|
|
265
|
+
// 回查一次,确实已配置就直接接续授权,而不是把一个已经过时的错误甩给用户。
|
|
266
|
+
const cur = await syncRow();
|
|
267
|
+
if (cur && !larkNotConfigured(cur)) return continueToAuth();
|
|
268
|
+
paintFail(e.message);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (closed) return;
|
|
272
|
+
painted = {};
|
|
273
|
+
if (session.state === 'pending') {
|
|
274
|
+
if (session.verificationUrl) paintQr(session);
|
|
275
|
+
else paintWait(); // 链接要等子进程输出,交给轮询补画
|
|
276
|
+
startPolling();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
return paintDone(session.state, { error: session.error });
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/** 配置链接还没吐出来时的过渡态(仍然给「重新生成」这条出路,不让人干等) */
|
|
283
|
+
const paintWait = () => {
|
|
284
|
+
body.innerHTML = '<div class="hint">正在向飞书申请配置二维码…(通常几秒内出现)</div>';
|
|
285
|
+
btnCopy.hidden = true;
|
|
286
|
+
btnRegen.hidden = false;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const doStart = (regenerate) => (phase === 'config' ? startConfig(regenerate) : startAuth(regenerate));
|
|
290
|
+
|
|
161
291
|
const close = () => {
|
|
162
292
|
closed = true;
|
|
163
293
|
stopTimers();
|
|
@@ -166,17 +296,21 @@ export function openLarkCliDeviceDialog({ onChange, onTerminal, wasAuthorized =
|
|
|
166
296
|
};
|
|
167
297
|
const unbind = bindDialogKeys((k) => { if (k === 'Escape') close(); });
|
|
168
298
|
|
|
169
|
-
|
|
170
|
-
|
|
299
|
+
btnClose.onclick = close;
|
|
300
|
+
btnRegen.onclick = () => void doStart(true);
|
|
171
301
|
// 关弹窗**不动后端会话**(沿用终端弹窗「关弹窗不中断流程」的既有理念):
|
|
172
|
-
// 重新打开时 start 不带 regenerate →
|
|
173
|
-
|
|
302
|
+
// 重新打开时 start 不带 regenerate → 后端幂等复用同一个会话,二维码原样回来
|
|
303
|
+
btnTerminal.onclick = () => {
|
|
304
|
+
const cb = phase === 'config' ? onConfigTerminal : onTerminal;
|
|
305
|
+
close();
|
|
306
|
+
cb?.();
|
|
307
|
+
};
|
|
174
308
|
btnCopy.onclick = async () => {
|
|
175
309
|
const url = session?.verificationUrl;
|
|
176
310
|
if (!url) return;
|
|
177
311
|
try {
|
|
178
312
|
await navigator.clipboard.writeText(url);
|
|
179
|
-
toast('
|
|
313
|
+
toast('链接已复制');
|
|
180
314
|
} catch {
|
|
181
315
|
toast('复制失败,请手动选中链接复制', true);
|
|
182
316
|
}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// 桥接器启停 / 版本更新(宿主进程管理:embedded 页面随进程生死,独立页面经 PID 跨进程操作)
|
|
3
3
|
import { S, $, esc, toast, api, refresh } from '../core.js';
|
|
4
4
|
import { bindDialogKeys, confirmDialog, mountDialog, progressDialog } from '../ui.js';
|
|
5
|
-
|
|
5
|
+
// larkNotConfigured 与后端 needsLarkCliConfig 同源(detail 含 not_configured 即「未配置应用」),
|
|
6
|
+
// 判定只有一份、放在弹窗模块里,避免两处漂移
|
|
7
|
+
import { larkNotConfigured, openLarkCliDeviceDialog } from './larkcli-device.js';
|
|
6
8
|
|
|
7
9
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
8
10
|
const UPD = { checkedAt: 0, data: null };
|
|
@@ -122,11 +124,12 @@ async function doCheckUpdate() {
|
|
|
122
124
|
async function runUpdateFlow() {
|
|
123
125
|
const running = S.status?.embedded || S.status?.bridge?.running;
|
|
124
126
|
const extra = running ? ',完成后自动重启桥接器(页面将短暂失联后自动恢复)' : '(桥接器未在运行,下次启动生效)';
|
|
125
|
-
if (!(await confirmDialog({ title: '一键更新', message: `将通过 npm
|
|
127
|
+
if (!(await confirmDialog({ title: '一键更新', message: `将通过 npm 安装桥接器最新版本${extra},可能需要 1-2 分钟。`, confirmText: '开始更新' }))) return;
|
|
126
128
|
const btn = $('#btnUpdRun');
|
|
127
129
|
if (btn) { btn.disabled = true; btn.textContent = '更新中…'; }
|
|
128
130
|
try {
|
|
129
|
-
|
|
131
|
+
// 只更新桥接器自身:飞书 CLI 的更新在它自己那一行点「更新」,两条更新各自独立
|
|
132
|
+
await api('POST', '/api/update/run');
|
|
130
133
|
UPD.checkedAt = 0; LARKCLI.checkedAt = 0;
|
|
131
134
|
if (running) await restartFlow();
|
|
132
135
|
else toast('✅ 更新完成,下次启动生效');
|
|
@@ -137,11 +140,6 @@ async function runUpdateFlow() {
|
|
|
137
140
|
}
|
|
138
141
|
}
|
|
139
142
|
|
|
140
|
-
/** 纯函数(可测):与后端 needsLarkCliConfig 同源——detail 含 not_configured 即「未配置应用」 */
|
|
141
|
-
function larkCliNotConfigured(r) {
|
|
142
|
-
return r?.auth?.state === 'unauthorized' && /not_configured/i.test(r?.auth?.detail || '');
|
|
143
|
-
}
|
|
144
|
-
|
|
145
143
|
/** 纯函数(可测):概览表格行里「飞书 CLI」的状态 HTML(不含操作按钮,按钮由 render 按需挂) */
|
|
146
144
|
export function larkCliStatHtml(r) {
|
|
147
145
|
if (!r) return '<span class="hint">未检查</span>';
|
|
@@ -162,14 +160,29 @@ export function larkCliStatHtml(r) {
|
|
|
162
160
|
}
|
|
163
161
|
|
|
164
162
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
163
|
+
* 纯函数(可测):概览行主入口按钮的文案——按「点开后会先走哪一步」命名,
|
|
164
|
+
* 不出现「按钮写扫码授权、点开弹窗却是配置二维码」的错位。
|
|
165
|
+
* 未配置应用时弹窗从 config 阶段起步(见 openDeviceDialog 与 larkcli-device.js),故叫「配置应用」。
|
|
166
|
+
* 探测不出(unknown)**不**算未配置:误判会把已配好应用的用户反复推去重配,按「扫码授权」处理。
|
|
167
|
+
*/
|
|
168
|
+
export function larkCliPrimaryLabel(r) {
|
|
169
|
+
if (r?.auth?.state === 'authorized') return '重新授权';
|
|
170
|
+
return larkNotConfigured(r) ? '配置应用' : '扫码授权';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 扫码弹窗(配置 + 授权的主路径)。未配置应用时它从**配置阶段**起步:页面内直接出示
|
|
175
|
+
* 配置二维码,用户扫完在浏览器里建好应用,弹窗自动接续出示授权二维码——全程不切窗口,
|
|
176
|
+
* 无桌面 / SSH 环境下同样走得通(那条路上「去终端向导」原本是无出口的死路)。
|
|
177
|
+
* 两个阶段的「改用终端窗口」都接回既有终端链路:那条路一行没改,保留补跑命令的能力。
|
|
167
178
|
*/
|
|
168
179
|
function openDeviceDialog() {
|
|
169
180
|
openLarkCliDeviceDialog({
|
|
170
181
|
wasAuthorized: LARKCLI.data?.auth?.state === 'authorized',
|
|
182
|
+
notConfigured: larkNotConfigured(LARKCLI.data),
|
|
171
183
|
onChange: (r) => { LARKCLI.data = r; LARKCLI.checkedAt = Date.now(); renderLarkCliResult(); },
|
|
172
184
|
onTerminal: () => void larkCliActionFlow('auth'),
|
|
185
|
+
onConfigTerminal: () => void larkCliActionFlow('config'),
|
|
173
186
|
});
|
|
174
187
|
}
|
|
175
188
|
|
|
@@ -190,12 +203,13 @@ function renderLarkCliResult() {
|
|
|
190
203
|
};
|
|
191
204
|
if (!r.installed) { add('安装', 'install'); return; }
|
|
192
205
|
if (r.hasUpdate) add('更新', 'update');
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
// 入口常驻,且已授权也给(token 会过期,随时可重走一遍 device flow 刷新)。
|
|
207
|
+
// 走**页面内二维码**(主路径,无桌面环境同样可用);终端窗口降级为弹窗里的备选。
|
|
208
|
+
// 文案跟着「弹窗会先走哪一步」走(见 larkCliPrimaryLabel):未配置应用时弹窗从配置阶段
|
|
209
|
+
// 起步、先出配置二维码,按钮就叫「配置应用」;已配置才是「扫码授权」。
|
|
210
|
+
// **入口数量不变**——三种状态都是同一个弹窗(op 一律 auth-device),只是弹窗按前置条件
|
|
211
|
+
// 决定先出哪种二维码(见 openDeviceDialog 与 larkcli-device.js)。
|
|
212
|
+
add(larkCliPrimaryLabel(r), 'auth-device');
|
|
199
213
|
if (r.skill && !r.skill.installed) add('装 SKILL', 'skill');
|
|
200
214
|
}
|
|
201
215
|
|
|
@@ -331,6 +345,8 @@ async function larkCliActionFlow(op = 'install') {
|
|
|
331
345
|
},
|
|
332
346
|
config: {
|
|
333
347
|
what: '配置应用',
|
|
348
|
+
// 这是**备选**路径:主路径已改成弹窗里的配置二维码(见 larkcli-device.js 的 config 阶段),
|
|
349
|
+
// 只有用户在弹窗里显式点「改用终端窗口」才会走到这里
|
|
334
350
|
msg: '将打开一个终端窗口执行 <code>lark-cli config init --new</code>:按窗口打印的链接在浏览器完成飞书应用创建,'
|
|
335
351
|
+ '完成后会自动进入授权登录。'
|
|
336
352
|
+ '<br><br>若本机没有可用终端(无桌面 / SSH 会话),需在服务器上手动执行该命令。',
|
|
@@ -346,7 +362,11 @@ async function larkCliActionFlow(op = 'install') {
|
|
|
346
362
|
const rowBtn = $('#larkCliArea button');
|
|
347
363
|
if (rowBtn) rowBtn.disabled = true;
|
|
348
364
|
// 先弹进度框再发请求:静默降级那条路最长 5 分钟,没有它页面会静默无反馈
|
|
349
|
-
const dlg = progressDialog({
|
|
365
|
+
const dlg = progressDialog({
|
|
366
|
+
title: `飞书 CLI ${what}`,
|
|
367
|
+
note: op === 'skill' ? '正在安装 SKILL…' : '正在探测可用终端…',
|
|
368
|
+
reportMaxHeight: op === 'skill' ? 360 : 220, // SKILL 要列 28 条名单,默认 220px 只够十来行
|
|
369
|
+
});
|
|
350
370
|
try {
|
|
351
371
|
const r = await api('POST', '/api/lark-cli/action', { op });
|
|
352
372
|
if (r.mode === 'terminal') {
|
|
@@ -354,7 +374,14 @@ async function larkCliActionFlow(op = 'install') {
|
|
|
354
374
|
openLarkCliTerminalDialog(op);
|
|
355
375
|
toast('已拉起终端窗口,请在终端里完成操作');
|
|
356
376
|
} else {
|
|
357
|
-
|
|
377
|
+
// SKILL 装完展示扫盘得到的名单,而不是 npx 那段带 ANSI 的进度表原文
|
|
378
|
+
// (名单由后端 listLarkCliSkills 扫 ~/.claude/skills 得出,比解析输出可靠)
|
|
379
|
+
if (op === 'skill' && Array.isArray(r.skills) && r.skills.length) {
|
|
380
|
+
dlg.setNote(`已同步 ${r.skills.length} 个 SKILL\n\n`
|
|
381
|
+
+ r.skills.map((s) => `• ${s}`).join('\n'));
|
|
382
|
+
} else {
|
|
383
|
+
dlg.setNote(String(r.output || '').trim().slice(-600) || '完成');
|
|
384
|
+
}
|
|
358
385
|
if (r.reason) dlg.appendLog(`⚠️ 未使用终端:${r.reason}`);
|
|
359
386
|
const done = op === 'skill' ? '✅ SKILL 安装完成,重启桥接器后飞书会话可见' : `✅ 飞书 CLI ${what}完成`;
|
|
360
387
|
dlg.finish(true, done);
|
package/assets/web/js/ui.js
CHANGED
|
@@ -200,8 +200,11 @@ export function initDrawer() {
|
|
|
200
200
|
* 观感与 plugins.js 的 runOpDialog 一致,但那个与插件页的 busyText / report / update-all 的
|
|
201
201
|
* 真实 i/N 推进深度耦合,泛化它等于改一个正在工作的界面(`main.css` 明确要求向后兼容)。
|
|
202
202
|
* 这里单独实现语义单一的版本,重复记为可接受代价。
|
|
203
|
+
*
|
|
204
|
+
* @param {number} [opts.reportMaxHeight] 输出区最大高度(px)。默认 220 适合几行总结;
|
|
205
|
+
* 要展示成片清单(如 SKILL 名单)的调用方传更大的值。
|
|
203
206
|
*/
|
|
204
|
-
export function progressDialog({ title = '执行中', note = '请稍候…' } = {}) {
|
|
207
|
+
export function progressDialog({ title = '执行中', note = '请稍候…', reportMaxHeight = 220 } = {}) {
|
|
205
208
|
const { mask, unmount } = mountDialog(`
|
|
206
209
|
<div class="modal" role="dialog" aria-modal="true" style="width:min(640px,94vw)">
|
|
207
210
|
<div style="display:flex;align-items:center;gap:10px">
|
|
@@ -209,7 +212,7 @@ export function progressDialog({ title = '执行中', note = '请稍候…' } =
|
|
|
209
212
|
<b class="pd-title">${esc(title)}</b>
|
|
210
213
|
</div>
|
|
211
214
|
<div class="prog"><div class="prog-bar"></div></div>
|
|
212
|
-
<pre class="report" style="margin-top:12px;max-height
|
|
215
|
+
<pre class="report" style="margin-top:12px;max-height:${Number(reportMaxHeight) || 220}px"></pre>
|
|
213
216
|
<div class="savebar" style="justify-content:flex-end">
|
|
214
217
|
<button class="btn primary" hidden>关闭</button>
|
|
215
218
|
</div>
|
package/dist/bin/lcb.js
CHANGED
|
@@ -16,7 +16,7 @@ import { VERSION } from '../version.js';
|
|
|
16
16
|
import { join } from 'node:path';
|
|
17
17
|
import { ensureRuntimeDirs } from '../util/runtime-dirs.js';
|
|
18
18
|
import { installLogTee, cleanupOldLogs } from '../util/log-tee.js';
|
|
19
|
-
import {
|
|
19
|
+
import { checkLarkCliAtStartup, AGENT_CONTEXT_ENV_KEYS } from '../lark-cli-manager.js';
|
|
20
20
|
function showPending(access) {
|
|
21
21
|
const pending = access.listPending();
|
|
22
22
|
if (pending.length)
|
|
@@ -64,9 +64,10 @@ async function main() {
|
|
|
64
64
|
// claude 会话(飞书里跑 lark-cli 命令)、web API 探测等所有下游子进程一并干净
|
|
65
65
|
for (const k of AGENT_CONTEXT_ENV_KEYS)
|
|
66
66
|
delete process.env[k];
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
67
|
+
// 深度拥抱飞书:后台**只检测**官方 Lark CLI(@larksuite/cli)并提示去处,不 await
|
|
68
|
+
// 不阻塞启动。安装/更新/授权统一下沉到 Web 配置页——启动时静默装会让用户跳过
|
|
69
|
+
// 官方流程的 config/auth 两步,打开配置页时 CLI 就绪却无从授权(见 checkLarkCliAtStartup)
|
|
70
|
+
void checkLarkCliAtStartup().catch((e) => console.warn('[lark-cli] 检测流程异常(不影响启动):', e instanceof Error ? e.message : e));
|
|
70
71
|
await startBridge(CONFIG_PATH);
|
|
71
72
|
// 前台监听 stdin:管理员可直接在运行终端输入配对码批准。
|
|
72
73
|
// 现读现批(每次 load 新实例再 approve):桥运行中 beginPairing 写入的新 pending
|
package/dist/bin/lcb.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lcb.js","sourceRoot":"","sources":["../../src/bin/lcb.ts"],"names":[],"mappings":";AACA,sDAAsD;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"lcb.js","sourceRoot":"","sources":["../../src/bin/lcb.ts"],"names":[],"mappings":"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"}
|