dsh-schedule-ilife 0.3.7 → 0.3.9
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/dist/client.js +237 -81
- package/dist/client.js.map +1 -1
- package/dist/contract.d.ts +16 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/settings.d.ts +20 -3
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +22 -23
- package/dist/settings.js.map +1 -1
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -33,7 +33,7 @@ window.__ModuleLoader__.load({
|
|
|
33
33
|
const PLUGIN = "dsh-schedule-ilife";
|
|
34
34
|
//#endregion
|
|
35
35
|
//#region src/settings.ts
|
|
36
|
-
/**
|
|
36
|
+
/** 常用项:#764 起是「可改 1 + 只读 2」(高级组已空,见下)。 */
|
|
37
37
|
const COMMON = [
|
|
38
38
|
{
|
|
39
39
|
key: "db.dir",
|
|
@@ -48,30 +48,23 @@ window.__ModuleLoader__.load({
|
|
|
48
48
|
title: "库文件名",
|
|
49
49
|
tier: "common",
|
|
50
50
|
control: "text",
|
|
51
|
-
|
|
51
|
+
readonly: true,
|
|
52
|
+
resolveFrom: "dbFile",
|
|
53
|
+
hint: "数据目录下的库文件。只读,要改请编辑配置文件。"
|
|
52
54
|
},
|
|
53
55
|
{
|
|
54
56
|
key: "html.dir",
|
|
55
|
-
title: "
|
|
57
|
+
title: "HELP 产物目录",
|
|
56
58
|
tier: "common",
|
|
57
59
|
control: "text",
|
|
58
|
-
|
|
60
|
+
readonly: true,
|
|
61
|
+
resolveFrom: "htmlDir",
|
|
62
|
+
hint: "HELP 页面的存放目录。只读,要改请编辑配置文件。"
|
|
59
63
|
}
|
|
60
64
|
];
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
title: "产物文件名主体 · HELP",
|
|
65
|
-
tier: "advanced",
|
|
66
|
-
control: "text",
|
|
67
|
-
hint: "HELP 文件名前缀,后面自动加时间戳。"
|
|
68
|
-
}, {
|
|
69
|
-
key: "lark.cliPath",
|
|
70
|
-
title: "飞书 CLI 路径",
|
|
71
|
-
tier: "advanced",
|
|
72
|
-
control: "text",
|
|
73
|
-
hint: "飞书 CLI 的完整路径。留空=自动探测。"
|
|
74
|
-
}];
|
|
65
|
+
/** 高级项:#764 起已空(`files.help`/`lark.cliPath` 出配置表)——组还在,行没有。
|
|
66
|
+
* 空组不渲染(见 client.ts),行表长度即 3。 */
|
|
67
|
+
const ADVANCED = [];
|
|
75
68
|
/** 设置页的行表,顺序即页面顺序。 */
|
|
76
69
|
const CONFIG_ITEMS = [...COMMON, ...ADVANCED];
|
|
77
70
|
/** 常用项行数(页面上直接画出来的那些)。 */
|
|
@@ -116,6 +109,11 @@ window.__ModuleLoader__.load({
|
|
|
116
109
|
const RPC_ENDPOINT_CONFIG_GET = "config.get";
|
|
117
110
|
const RPC_ENDPOINT_CONFIG_SAVE = "config.save";
|
|
118
111
|
const RPC_ENDPOINT_CONFIG_RESET = "config.reset";
|
|
112
|
+
/** #706 配置体检端点:只读一次,回一份报告(判据由技能侧出,本包只透传,不重写一个字)。
|
|
113
|
+
*
|
|
114
|
+
* 端点名与技能侧那条只读命令同名(`<技能>.config.check`);报告形状的唯一真相在技能侧
|
|
115
|
+
* `packages/skill-<技能>/src/health.ts`,面板侧镜像在 `packages/plugin-manager/src/health-contract.ts`。 */
|
|
116
|
+
const RPC_ENDPOINT_CONFIG_CHECK = "config.check";
|
|
119
117
|
/** 信封守卫:供 client 拆包前校验(port 拥有信封定义,守卫住 port)。 */
|
|
120
118
|
function isRpcResult(raw) {
|
|
121
119
|
if (typeof raw !== "object" || raw === null) return false;
|
|
@@ -300,15 +298,6 @@ window.__ModuleLoader__.load({
|
|
|
300
298
|
if (needle === "") return entries;
|
|
301
299
|
return entries.filter((entry) => entry.name.toLowerCase().includes(needle));
|
|
302
300
|
}
|
|
303
|
-
/** 给界面用的一句话位置说明:优先显示相对 home 的说法,省得整条绝对路径占满一行。 */
|
|
304
|
-
function locationLabel(listing) {
|
|
305
|
-
const home = listing.home.replace(/[\\/]+$/, "");
|
|
306
|
-
if (home !== "" && listing.path !== home && listing.path.startsWith(home)) {
|
|
307
|
-
const tail = listing.path.slice(home.length).replace(/^[\\/]+/, "");
|
|
308
|
-
if (tail !== "") return "~" + (listing.path.includes("\\") ? "\\" : "/") + tail;
|
|
309
|
-
}
|
|
310
|
-
return listing.path;
|
|
311
|
-
}
|
|
312
301
|
/** 新建文件夹的名字合不合法(单个路径段:不许分隔符、不许空、不许 `.`/`..`)。
|
|
313
302
|
*
|
|
314
303
|
* 与宿主 browse 后端的校验同口径——这里先拦一道,是为了在图上给得出人话,不是替代宿主校验。 */
|
|
@@ -687,14 +676,6 @@ window.__ModuleLoader__.load({
|
|
|
687
676
|
fontSize: 14,
|
|
688
677
|
fontWeight: 700
|
|
689
678
|
},
|
|
690
|
-
headTail: {
|
|
691
|
-
marginLeft: "auto",
|
|
692
|
-
fontSize: 12,
|
|
693
|
-
opacity: .72,
|
|
694
|
-
overflow: "hidden",
|
|
695
|
-
textOverflow: "ellipsis",
|
|
696
|
-
whiteSpace: "nowrap"
|
|
697
|
-
},
|
|
698
679
|
crumbs: {
|
|
699
680
|
display: "flex",
|
|
700
681
|
flexWrap: "wrap",
|
|
@@ -724,10 +705,6 @@ window.__ModuleLoader__.load({
|
|
|
724
705
|
padding: "0 12px 8px",
|
|
725
706
|
fontSize: 12
|
|
726
707
|
},
|
|
727
|
-
rootsLabel: {
|
|
728
|
-
opacity: .72,
|
|
729
|
-
marginRight: 2
|
|
730
|
-
},
|
|
731
708
|
rootButton: {
|
|
732
709
|
border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35))",
|
|
733
710
|
borderRadius: 6,
|
|
@@ -836,14 +813,11 @@ window.__ModuleLoader__.load({
|
|
|
836
813
|
});
|
|
837
814
|
return react.createElement("div", { style: S$1.crumbs }, parts);
|
|
838
815
|
}
|
|
839
|
-
/** 可跳转的根那一行(Windows
|
|
840
|
-
|
|
816
|
+
/** 可跳转的根那一行(Windows 上的盘符等):**就是本机全部磁盘**,故不加行首说明。
|
|
817
|
+
* **少于两个根就不画**:一个根没有「跳到别处」可言。 */
|
|
818
|
+
function rootsRow(state, onEnter) {
|
|
841
819
|
if (state.roots.length < 2) return null;
|
|
842
|
-
const parts =
|
|
843
|
-
key: "roots-label",
|
|
844
|
-
style: S$1.rootsLabel
|
|
845
|
-
}, labels.roots)];
|
|
846
|
-
for (const root of state.roots) parts.push(react.createElement("button", {
|
|
820
|
+
const parts = state.roots.map((root) => react.createElement("button", {
|
|
847
821
|
key: root.path,
|
|
848
822
|
type: "button",
|
|
849
823
|
style: {
|
|
@@ -853,11 +827,7 @@ window.__ModuleLoader__.load({
|
|
|
853
827
|
title: ROOT_KIND_TEXT[root.kind] ?? ROOT_KIND_TEXT.other,
|
|
854
828
|
onClick: () => onEnter(root.path)
|
|
855
829
|
}, root.path));
|
|
856
|
-
return react.createElement("div", {
|
|
857
|
-
style: S$1.roots,
|
|
858
|
-
role: "group",
|
|
859
|
-
"aria-label": labels.roots
|
|
860
|
-
}, parts);
|
|
830
|
+
return react.createElement("div", { style: S$1.roots }, parts);
|
|
861
831
|
}
|
|
862
832
|
/** 一行子目录:点名字进它,点「选」把它当目标(「选」再点一次取消)。 */
|
|
863
833
|
function entryRow(entry, selected, labels, onToggleSelect, onEnter) {
|
|
@@ -935,11 +905,10 @@ window.__ModuleLoader__.load({
|
|
|
935
905
|
if (!open) return null;
|
|
936
906
|
const rows = rowsOf(state);
|
|
937
907
|
const target = targetOf(state);
|
|
938
|
-
const
|
|
939
|
-
const hidden = hiddenCount(listing);
|
|
908
|
+
const hidden = hiddenCount(state.listing ?? EMPTY_LISTING);
|
|
940
909
|
const body = [];
|
|
941
910
|
body.push(crumbRow(state, props.onEnter));
|
|
942
|
-
body.push(rootsRow(state,
|
|
911
|
+
body.push(rootsRow(state, props.onEnter));
|
|
943
912
|
body.push(react.createElement("div", { style: S$1.pathRow }, react.createElement("input", {
|
|
944
913
|
style: S$1.input,
|
|
945
914
|
value: state.draft,
|
|
@@ -1006,12 +975,12 @@ window.__ModuleLoader__.load({
|
|
|
1006
975
|
role: "dialog",
|
|
1007
976
|
"aria-modal": true,
|
|
1008
977
|
"aria-label": labels.title
|
|
1009
|
-
}, react.createElement("div", { style: S$1.head }, react.createElement("span", { style: S$1.title }, labels.title), react.createElement("
|
|
1010
|
-
style: S$1.headTail,
|
|
1011
|
-
title: listing.path
|
|
1012
|
-
}, state.listing === null ? "" : locationLabel(listing)), react.createElement("button", {
|
|
978
|
+
}, react.createElement("div", { style: S$1.head }, react.createElement("span", { style: S$1.title }, labels.title), react.createElement("button", {
|
|
1013
979
|
type: "button",
|
|
1014
|
-
style:
|
|
980
|
+
style: {
|
|
981
|
+
...S$1.button,
|
|
982
|
+
marginLeft: "auto"
|
|
983
|
+
},
|
|
1015
984
|
"aria-label": labels.close,
|
|
1016
985
|
title: labels.close,
|
|
1017
986
|
onClick: props.onClose
|
|
@@ -1214,24 +1183,121 @@ window.__ModuleLoader__.load({
|
|
|
1214
1183
|
function resetConfigSurface(call) {
|
|
1215
1184
|
return configRpc(call, RPC_ENDPOINT_CONFIG_RESET, {});
|
|
1216
1185
|
}
|
|
1217
|
-
/**
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1186
|
+
/** 读一次配置体检(只读;判据由技能侧出,本包只透传,不重写一个字)。 */
|
|
1187
|
+
function fetchHealthSurface(call) {
|
|
1188
|
+
return configRpc(call, RPC_ENDPOINT_CONFIG_CHECK, {});
|
|
1189
|
+
}
|
|
1190
|
+
/** 从体检报告里挑出 `lark.cli` 那一项;形状不对回 null(不抛)。 */
|
|
1191
|
+
function larkItemOf(report) {
|
|
1192
|
+
if (typeof report !== "object" || report === null) return null;
|
|
1193
|
+
const items = report.items;
|
|
1194
|
+
if (!Array.isArray(items)) return null;
|
|
1195
|
+
for (const it of items) {
|
|
1196
|
+
if (typeof it !== "object" || it === null) continue;
|
|
1197
|
+
const rec = it;
|
|
1198
|
+
if (rec["id"] !== "lark.cli") continue;
|
|
1199
|
+
if (typeof rec["id"] !== "string" || typeof rec["status"] !== "string" || typeof rec["message"] !== "string") return null;
|
|
1200
|
+
return {
|
|
1201
|
+
id: rec["id"],
|
|
1202
|
+
status: rec["status"],
|
|
1203
|
+
message: rec["message"],
|
|
1204
|
+
action: typeof rec["action"] === "string" ? rec["action"] : ""
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
/** 飞书 CLI 官网(面板状态行与安装 prompt 里的同一地址,唯一定义地是这里)。 */
|
|
1210
|
+
const LARK_OFFICIAL_URL = "https://www.feishu.cn/feishu-cli";
|
|
1211
|
+
/** 状态行上逐字显示的那一行(三档都在、显示成文字、可点击跳转,不改写、不加话)。 */
|
|
1212
|
+
const LARK_OFFICIAL_LINE = "飞书CLI官网为:https://www.feishu.cn/feishu-cli";
|
|
1213
|
+
/** 「复制 prompt」按钮复制的内容(定稿住 #759 最新一条评论 v5 的逐字全文,两家的实施票都引它,
|
|
1214
|
+
* 不在本票里另写一份;官网指南 shape 变化时改 #759 那一条,这里跟着换)。 */
|
|
1215
|
+
const LARK_PROMPT_V5 = `在这台机器上安装并配置飞书 CLI(lark-cli)。你来推进,做完为止。
|
|
1216
|
+
|
|
1217
|
+
必须满足的终态(每条都要真跑,把命令原文和输出贴回来):
|
|
1218
|
+
1) lark-cli --version >= 1.0.82(更低的版本没有我们要用的 --no-wait)
|
|
1219
|
+
2) where lark-cli(Windows)或 which lark-cli(macOS/Linux)能命中;Windows 上还要确认 %APPDATA%\\npm\\lark-cli.cmd 这个文件存在
|
|
1220
|
+
3) lark-cli auth status 的输出里有 identities.user.openId
|
|
1221
|
+
4) lark-cli auth check --scope task 退出码 0(连退出码一起贴)—— 备忘录要用任务域
|
|
1222
|
+
5) lark-cli calendar +agenda 退出码 0(连退出码一起贴)—— 作息管家要读你的主日历
|
|
1223
|
+
6) 技能侧也能用:跑一次作息管家的「飞书探测」(=一条不落地的同步探测),不报「缺 CLI/未登录/日历不可达」
|
|
1224
|
+
|
|
1225
|
+
怎么做:
|
|
1226
|
+
7) Node.js/npm 不可用就先装。
|
|
1227
|
+
8) 安装:npm install -g @larksuite/cli。只装这个官方包(bin 名 lark-cli);不要装 npm 上的 lark-cli。
|
|
1228
|
+
9) 官方指南里的第二件事也照做:npx -y skills add https://open.feishu.cn --skill -y
|
|
1229
|
+
10) 配置与登录照这两条走(都是 CLI 自己在 --help 里写给 AI agent 的写法,别改写成别的):
|
|
1230
|
+
先初始化:lark-cli config init --new
|
|
1231
|
+
它会阻塞等你完成浏览器授权,所以起在后台、从它的输出里把验证链接捞出来
|
|
1232
|
+
(CLI 原文:Run it in the background and retrieve the verification URL from its output);
|
|
1233
|
+
链接给我,我批准后你继续。
|
|
1234
|
+
如果它报 Inside an Agent context 之类被拒,照它的提示改用 lark-cli config bind。
|
|
1235
|
+
再授权:lark-cli auth login --no-wait --json --domain calendar,task
|
|
1236
|
+
它会立刻返回一个验证链接(或二维码);把链接给我,我批准后你再用
|
|
1237
|
+
lark-cli auth login --device-code <拿到的 device code> 收尾。
|
|
1238
|
+
官方指南:https://open.feishu.cn/document/no_class/mcp-archive/feishu-cli-installation-guide.md
|
|
1239
|
+
子命令以 lark-cli auth --help / config --help 和官方文档为准,不要凭猜;做完要能过第 3、4、5 条。
|
|
1240
|
+
11) npm 全局目录如果不在系统 PATH 上,把它加进系统 PATH(不是只在当前终端 export)。
|
|
1241
|
+
|
|
1242
|
+
怎么推进:
|
|
1243
|
+
12) 除第 13 条那三类,其余你自己一口气做完,不要逐步问我确认:安装、改 PATH、重试、修报错都自己做完。
|
|
1244
|
+
13) 只有这三类必须我介入;遇到就用 /wizard 生成脚本带我走,走完你接着自动往下做:
|
|
1245
|
+
① 浏览器里的初始化授权 / 登录(把验证链接或二维码给我);
|
|
1246
|
+
② 系统权限提示(管理员/sudo);
|
|
1247
|
+
③ Node.js 需要我先定怎么装。
|
|
1248
|
+
14) 任何一步失败:自己按报错修;修不动再一次性告诉我「卡在哪、报错原文、需要我做什么」。
|
|
1249
|
+
|
|
1250
|
+
参考官网:https://www.feishu.cn/feishu-cli`;
|
|
1251
|
+
/** 复制一段文本:先剪贴板,不成再走兜底(永不抛,回是否成功)。 */
|
|
1252
|
+
async function copyText(text, deps = {}) {
|
|
1253
|
+
if (deps.clipboard !== void 0) try {
|
|
1254
|
+
await deps.clipboard.writeText(text);
|
|
1255
|
+
return true;
|
|
1256
|
+
} catch {}
|
|
1257
|
+
if (deps.execCopy !== void 0) try {
|
|
1258
|
+
return deps.execCopy(text);
|
|
1259
|
+
} catch {
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
return false;
|
|
1263
|
+
}
|
|
1264
|
+
/** 当刻环境的剪贴板(经 `globalThis` 懒取:顶层不碰 DOM,loader 沙箱与 node 单测都不炸;拿不到回 null)。 */
|
|
1265
|
+
function clipboardOf() {
|
|
1266
|
+
try {
|
|
1267
|
+
return globalThis.navigator?.clipboard ?? null;
|
|
1268
|
+
} catch {
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
/** 只读行显示什么:标了 `resolveFrom` ⇒ 回执 `resolved` 组那一格;没标 ⇒ 配置文件里那个值本身
|
|
1273
|
+
* (数字类只读项走这一档:显示的是生效数字,见 #749 补注二的甲档)。 */
|
|
1274
|
+
function readonlyTextOf(item, raw, prefill) {
|
|
1275
|
+
if (item.resolveFrom === void 0) return raw;
|
|
1276
|
+
const shown = prefill.resolved?.[item.resolveFrom];
|
|
1277
|
+
return typeof shown === "string" ? shown : "";
|
|
1278
|
+
}
|
|
1221
1279
|
function toDraft(values, prefill = {}) {
|
|
1222
1280
|
const draft = {};
|
|
1223
1281
|
for (const item of CONFIG_ITEMS) {
|
|
1224
1282
|
const v = readPath(values, item.key);
|
|
1225
1283
|
const raw = v === void 0 || v === null ? "" : String(v);
|
|
1284
|
+
if (item.readonly === true) {
|
|
1285
|
+
draft[item.key] = readonlyTextOf(item, raw, prefill);
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1226
1288
|
const fallback = item.prefillFrom === void 0 ? void 0 : prefill[item.prefillFrom];
|
|
1227
1289
|
draft[item.key] = raw === "" && typeof fallback === "string" && fallback !== "" ? fallback : raw;
|
|
1228
1290
|
}
|
|
1229
1291
|
return draft;
|
|
1230
1292
|
}
|
|
1231
|
-
/** 表单态 →
|
|
1293
|
+
/** 表单态 → 配置取值(按控件种类还原类型;空串对文本项照收,语义由「按默认落点」承担)。
|
|
1294
|
+
*
|
|
1295
|
+
* **只读行不收**(#764,照 #749 样板):它们显示的是技能算好的绝对路径,写回配置就是把「显示的路径」
|
|
1296
|
+
* 当成「配置值」——保存只提交真能改的那些行(本家=`db.dir` 一行),其余键由技能侧做组内合并保留现值。 */
|
|
1232
1297
|
function fromDraft(draft) {
|
|
1233
1298
|
const values = {};
|
|
1234
1299
|
for (const item of CONFIG_ITEMS) {
|
|
1300
|
+
if (item.readonly === true) continue;
|
|
1235
1301
|
const raw = draft[item.key] ?? "";
|
|
1236
1302
|
const value = item.control === "number" ? Number(raw) : item.control === "switch" ? raw === "true" : raw;
|
|
1237
1303
|
writePath(values, item.key, item.control === "number" && !Number.isFinite(value) ? 0 : value);
|
|
@@ -1290,31 +1356,38 @@ window.__ModuleLoader__.load({
|
|
|
1290
1356
|
/** 一行输入(四种控件对齐受限 YAML 子集:文本/数字/布尔/目录)。
|
|
1291
1357
|
*
|
|
1292
1358
|
* 目录档=文本框 + 一枚唤起系统文件夹选择器的按钮;`onBrowse` 缺席(命名空间拿不到、
|
|
1293
|
-
* 或这条路已被拒)时不画按钮,文本框照旧——那是该缝自己的契约(供不了就收起入口,不是失败)。
|
|
1359
|
+
* 或这条路已被拒)时不画按钮,文本框照旧——那是该缝自己的契约(供不了就收起入口,不是失败)。
|
|
1360
|
+
*
|
|
1361
|
+
* **只读行(#764,照 #749 样板)**:`item.readonly` 为真时控件一律 `disabled`(值只经技能侧解析后显示、
|
|
1362
|
+
* 不给改),目录行的浏览按钮**保留但不可点击**(#747 定稿:入口不作废,只是不能改)。控件文案**不出现省略号**
|
|
1363
|
+
* (#746 处置 9)——按钮就写「选择文件夹」/「浏览」两个字面。 */
|
|
1294
1364
|
function Row(props) {
|
|
1295
1365
|
const { item } = props;
|
|
1296
|
-
const
|
|
1366
|
+
const readonly = item.readonly === true;
|
|
1367
|
+
const disabled = props.disabled || readonly;
|
|
1368
|
+
const onChange = readonly ? void 0 : (e) => props.onChange(item.key, e.target.value);
|
|
1297
1369
|
const control = item.control === "switch" ? react.createElement("input", {
|
|
1298
1370
|
type: "checkbox",
|
|
1299
1371
|
checked: props.value === "true",
|
|
1300
|
-
disabled
|
|
1301
|
-
onChange: (e) => props.onChange(item.key, e.target.checked ? "true" : "false")
|
|
1372
|
+
disabled,
|
|
1373
|
+
onChange: readonly ? void 0 : (e) => props.onChange(item.key, e.target.checked ? "true" : "false")
|
|
1302
1374
|
}) : react.createElement("input", {
|
|
1303
1375
|
style: S.input,
|
|
1304
1376
|
type: item.control === "number" ? "number" : "text",
|
|
1305
1377
|
value: props.value,
|
|
1306
|
-
disabled
|
|
1378
|
+
disabled,
|
|
1307
1379
|
spellCheck: false,
|
|
1308
1380
|
onChange
|
|
1309
1381
|
});
|
|
1310
|
-
/** 三态入口:供不了(`none`/缺席)就不画,不摆一个点了没反应的死按钮。
|
|
1382
|
+
/** 三态入口:供不了(`none`/缺席)就不画,不摆一个点了没反应的死按钮。
|
|
1383
|
+
* 只读行照画(按钮保留),但 `disabled` ⇒ 点不动。 */
|
|
1311
1384
|
const entry = props.browser ?? null;
|
|
1312
1385
|
const browse = item.control === "directory" && entry !== null && entry.mode !== "none" ? react.createElement("button", {
|
|
1313
1386
|
style: S.btnPick,
|
|
1314
1387
|
type: "button",
|
|
1315
|
-
disabled
|
|
1388
|
+
disabled,
|
|
1316
1389
|
onClick: () => entry.onOpen(item.key)
|
|
1317
|
-
}, entry.mode === "native" ? "
|
|
1390
|
+
}, entry.mode === "native" ? "选择文件夹" : "浏览") : null;
|
|
1318
1391
|
return react.createElement("div", { style: S.row }, react.createElement("div", { style: S.label }, item.title), react.createElement("div", { style: S.hint }, item.hint), browse === null ? control : react.createElement("div", { style: S.pickRow }, control, browse));
|
|
1319
1392
|
}
|
|
1320
1393
|
/** 技能设置页:承载作息管家自己的全部可配置项(只配置,不干活)。 */
|
|
@@ -1326,6 +1399,10 @@ window.__ModuleLoader__.load({
|
|
|
1326
1399
|
const [busy, setBusy] = react.useState(false);
|
|
1327
1400
|
const [notice, setNotice] = react.useState(null);
|
|
1328
1401
|
const [error, setError] = react.useState(null);
|
|
1402
|
+
/** 飞书 CLI 状态行(三档读数由技能侧体检出,面板只显示;`null` 以外的三态见下)。 */
|
|
1403
|
+
const [lark, setLark] = react.useState({ kind: "loading" });
|
|
1404
|
+
/** 复制 prompt 那枚按钮的回执(idle=没点过)。 */
|
|
1405
|
+
const [copied, setCopied] = react.useState("idle");
|
|
1329
1406
|
const apply = react.useCallback((surface) => {
|
|
1330
1407
|
setState({
|
|
1331
1408
|
kind: "ready",
|
|
@@ -1333,6 +1410,25 @@ window.__ModuleLoader__.load({
|
|
|
1333
1410
|
});
|
|
1334
1411
|
setDraft(toDraft(surface.values, surface));
|
|
1335
1412
|
}, []);
|
|
1413
|
+
const loadHealth = react.useCallback(async () => {
|
|
1414
|
+
setLark({ kind: "loading" });
|
|
1415
|
+
const r = await fetchHealthSurface(props.getCall());
|
|
1416
|
+
if (!r.ok) {
|
|
1417
|
+
setLark({
|
|
1418
|
+
kind: "failed",
|
|
1419
|
+
message: r.message
|
|
1420
|
+
});
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
const item = larkItemOf(r.surface);
|
|
1424
|
+
setLark(item === null ? {
|
|
1425
|
+
kind: "failed",
|
|
1426
|
+
message: "体检回执里没有飞书 CLI 那一项"
|
|
1427
|
+
} : {
|
|
1428
|
+
kind: "ready",
|
|
1429
|
+
item
|
|
1430
|
+
});
|
|
1431
|
+
}, [props.getCall]);
|
|
1336
1432
|
const load = react.useCallback(async () => {
|
|
1337
1433
|
setNotice(null);
|
|
1338
1434
|
setError(null);
|
|
@@ -1342,7 +1438,12 @@ window.__ModuleLoader__.load({
|
|
|
1342
1438
|
kind: "failed",
|
|
1343
1439
|
message: r.message
|
|
1344
1440
|
});
|
|
1345
|
-
|
|
1441
|
+
await loadHealth();
|
|
1442
|
+
}, [
|
|
1443
|
+
apply,
|
|
1444
|
+
loadHealth,
|
|
1445
|
+
props.getCall
|
|
1446
|
+
]);
|
|
1346
1447
|
react.useEffect(() => {
|
|
1347
1448
|
let alive = true;
|
|
1348
1449
|
(async () => {
|
|
@@ -1353,6 +1454,22 @@ window.__ModuleLoader__.load({
|
|
|
1353
1454
|
kind: "failed",
|
|
1354
1455
|
message: r.message
|
|
1355
1456
|
});
|
|
1457
|
+
const h = await fetchHealthSurface(props.getCall());
|
|
1458
|
+
if (!alive) return;
|
|
1459
|
+
if (!h.ok) setLark({
|
|
1460
|
+
kind: "failed",
|
|
1461
|
+
message: h.message
|
|
1462
|
+
});
|
|
1463
|
+
else {
|
|
1464
|
+
const item = larkItemOf(h.surface);
|
|
1465
|
+
setLark(item === null ? {
|
|
1466
|
+
kind: "failed",
|
|
1467
|
+
message: "体检回执里没有飞书 CLI 那一项"
|
|
1468
|
+
} : {
|
|
1469
|
+
kind: "ready",
|
|
1470
|
+
item
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1356
1473
|
})();
|
|
1357
1474
|
return () => {
|
|
1358
1475
|
alive = false;
|
|
@@ -1417,7 +1534,10 @@ window.__ModuleLoader__.load({
|
|
|
1417
1534
|
onOpen: onOpenRow
|
|
1418
1535
|
};
|
|
1419
1536
|
const surface = state.kind === "ready" ? state.surface : null;
|
|
1420
|
-
|
|
1537
|
+
/** 脏值只看**可改行**(#764,照 #749 样板):只读行显示的是技能算好的绝对路径,
|
|
1538
|
+
* 拿它当「改动」会让打开面板就变「未保存」。 */
|
|
1539
|
+
const editableItems = CONFIG_ITEMS.filter((i) => i.readonly !== true);
|
|
1540
|
+
const dirty = surface !== null && editableItems.some((i) => (draft[i.key] ?? "") !== (toDraft(surface.values, surface)[i.key] ?? ""));
|
|
1421
1541
|
/** 写完之后重新读一份:写回执只有 {path, values},头部那两行要的是读整面。 */
|
|
1422
1542
|
const writeThenReload = react.useCallback(async (done) => {
|
|
1423
1543
|
setBusy(true);
|
|
@@ -1455,8 +1575,8 @@ window.__ModuleLoader__.load({
|
|
|
1455
1575
|
if (r.ok) await writeThenReload("已重置为默认(原配置已另存一份 .bak)");
|
|
1456
1576
|
else setError(r.message);
|
|
1457
1577
|
}, [props.getCall, writeThenReload]);
|
|
1458
|
-
const head = react.createElement("div", null, react.createElement("div", { style: S.title }, `${SLOT_TITLE} · 配置`), surface !== null ? react.createElement("div", null, react.createElement("div", { style: S.info }, `配置文件 ${surface.path}`), react.createElement("div", { style: S.info }, `数据目录 ${surface.dataDir}`), surface.created ? react.createElement("div", { style: S.muted }, "(配置文件刚按默认值生成)") : null) : null);
|
|
1459
|
-
if (state.kind === "loading") return react.createElement("div", { style: S.card }, head, react.createElement("div", { style: S.muted }, "
|
|
1578
|
+
const head = react.createElement("div", null, react.createElement("div", { style: S.title }, `${SLOT_TITLE} · 配置`), surface !== null ? react.createElement("div", null, react.createElement("div", { style: S.info }, `配置文件 ${surface.path}`), react.createElement("div", { style: S.info }, `数据目录 ${surface.resolved?.dbDir ?? surface.dataDir}`), surface.created ? react.createElement("div", { style: S.muted }, "(配置文件刚按默认值生成)") : null) : null);
|
|
1579
|
+
if (state.kind === "loading") return react.createElement("div", { style: S.card }, head, react.createElement("div", { style: S.muted }, "配置读取中"));
|
|
1460
1580
|
if (state.kind === "failed") return react.createElement("div", { style: S.card }, react.createElement("div", { style: S.title }, `${SLOT_TITLE} · 配置`), react.createElement("div", { style: S.error }, state.message), react.createElement("div", { style: S.bar }, react.createElement("button", {
|
|
1461
1581
|
style: S.btn,
|
|
1462
1582
|
type: "button",
|
|
@@ -1476,12 +1596,42 @@ window.__ModuleLoader__.load({
|
|
|
1476
1596
|
onChange,
|
|
1477
1597
|
browser: rowEntry
|
|
1478
1598
|
});
|
|
1479
|
-
|
|
1599
|
+
/** 「复制 prompt」:剪贴板不在就报失败,不抛(按钮三档都在,见 #761 定稿)。 */
|
|
1600
|
+
const onCopyPrompt = react.useCallback(async () => {
|
|
1601
|
+
const ok = await copyText(LARK_PROMPT_V5, { clipboard: clipboardOf() ?? void 0 });
|
|
1602
|
+
setCopied(ok ? "done" : "failed");
|
|
1603
|
+
}, []);
|
|
1604
|
+
/** 「飞书 CLI」状态行(#764,定稿 #761):不是配置项——三档读数由技能侧体检出,面板只显示;
|
|
1605
|
+
* 三档都有「复制 prompt」按钮与官网那一行(逐字、可点击)。 */
|
|
1606
|
+
const renderLark = () => {
|
|
1607
|
+
if (lark.kind === "loading") return react.createElement("div", { style: S.rows }, react.createElement("div", { style: S.label }, "飞书 CLI"), react.createElement("div", { style: S.muted }, "飞书 CLI 检测中"));
|
|
1608
|
+
if (lark.kind === "failed") return react.createElement("div", { style: S.rows }, react.createElement("div", { style: S.label }, "飞书 CLI"), react.createElement("div", { style: S.error }, lark.message), react.createElement("div", { style: S.bar }, react.createElement("button", {
|
|
1609
|
+
style: S.btn,
|
|
1610
|
+
type: "button",
|
|
1611
|
+
onClick: () => void loadHealth()
|
|
1612
|
+
}, "重新检测")));
|
|
1613
|
+
const item = lark.item;
|
|
1614
|
+
const tone = item.status === "green" ? S.okText : item.status === "red" ? S.error : S.muted;
|
|
1615
|
+
return react.createElement("div", { style: S.rows }, react.createElement("div", { style: S.label }, "飞书 CLI"), react.createElement("div", { style: tone }, item.message), item.action === "" ? null : react.createElement("div", { style: S.hint }, item.action), react.createElement("div", { style: S.bar }, react.createElement("button", {
|
|
1616
|
+
style: S.btn,
|
|
1617
|
+
type: "button",
|
|
1618
|
+
onClick: () => void onCopyPrompt()
|
|
1619
|
+
}, "复制 prompt"), react.createElement("button", {
|
|
1620
|
+
style: S.btn,
|
|
1621
|
+
type: "button",
|
|
1622
|
+
onClick: () => void loadHealth()
|
|
1623
|
+
}, "重新检测")), copied === "done" ? react.createElement("div", { style: S.okText }, "已复制安装指引") : copied === "failed" ? react.createElement("div", { style: S.error }, "复制失败:剪贴板不可用,请手动复制对话框里的安装指引") : null, react.createElement("div", { style: S.info }, react.createElement("a", {
|
|
1624
|
+
href: LARK_OFFICIAL_URL,
|
|
1625
|
+
target: "_blank",
|
|
1626
|
+
rel: "noreferrer"
|
|
1627
|
+
}, LARK_OFFICIAL_LINE)));
|
|
1628
|
+
};
|
|
1629
|
+
return react.createElement("div", { style: S.card }, head, react.createElement("div", { style: S.rows }, common.map(renderRow)), advanced.length === 0 ? null : react.createElement("details", { style: S.advanced }, react.createElement("summary", { style: S.summary }, ADVANCED_GROUP_TITLE), react.createElement("div", { style: S.muted }, ADVANCED_GROUP_NOTE), advanced.map(renderRow)), renderLark(), react.createElement("div", { style: S.bar }, react.createElement("button", {
|
|
1480
1630
|
style: dirty ? S.btnPrimary : S.btn,
|
|
1481
1631
|
type: "button",
|
|
1482
1632
|
disabled: busy || !dirty,
|
|
1483
1633
|
onClick: () => void onSave()
|
|
1484
|
-
}, busy ? "
|
|
1634
|
+
}, busy ? "处理中" : "保存"), react.createElement("button", {
|
|
1485
1635
|
style: S.btn,
|
|
1486
1636
|
type: "button",
|
|
1487
1637
|
disabled: busy,
|
|
@@ -1502,7 +1652,7 @@ window.__ModuleLoader__.load({
|
|
|
1502
1652
|
go: "转到",
|
|
1503
1653
|
showHidden: (n) => "显示隐藏目录(" + n + ")",
|
|
1504
1654
|
empty: "这个目录里没有子目录。",
|
|
1505
|
-
loading: "
|
|
1655
|
+
loading: "正在读取",
|
|
1506
1656
|
newFolder: "新建文件夹",
|
|
1507
1657
|
createConfirm: "创建",
|
|
1508
1658
|
createCancel: "取消",
|
|
@@ -1510,8 +1660,7 @@ window.__ModuleLoader__.load({
|
|
|
1510
1660
|
selected: "已选",
|
|
1511
1661
|
open: "选定这个目录",
|
|
1512
1662
|
cancel: "取消",
|
|
1513
|
-
willPick: "将选定:"
|
|
1514
|
-
roots: "其他磁盘:"
|
|
1663
|
+
willPick: "将选定:"
|
|
1515
1664
|
}
|
|
1516
1665
|
}) : null, notice !== null ? react.createElement("div", { style: S.okText }, notice) : null, error !== null ? react.createElement("div", { style: S.error }, error) : null);
|
|
1517
1666
|
}
|
|
@@ -1536,16 +1685,23 @@ window.__ModuleLoader__.load({
|
|
|
1536
1685
|
})));
|
|
1537
1686
|
}
|
|
1538
1687
|
//#endregion
|
|
1688
|
+
exports.LARK_OFFICIAL_LINE = LARK_OFFICIAL_LINE;
|
|
1689
|
+
exports.LARK_OFFICIAL_URL = LARK_OFFICIAL_URL;
|
|
1690
|
+
exports.LARK_PROMPT_V5 = LARK_PROMPT_V5;
|
|
1539
1691
|
exports.READ_TIMEOUT_MS = READ_TIMEOUT_MS;
|
|
1540
1692
|
exports.Row = Row;
|
|
1541
1693
|
exports.apply = apply;
|
|
1694
|
+
exports.clipboardOf = clipboardOf;
|
|
1695
|
+
exports.copyText = copyText;
|
|
1542
1696
|
exports.createBrowseHandler = createBrowseHandler;
|
|
1543
1697
|
exports.directoryEntryMode = directoryEntryMode;
|
|
1544
1698
|
exports.fetchConfigSurface = fetchConfigSurface;
|
|
1699
|
+
exports.fetchHealthSurface = fetchHealthSurface;
|
|
1545
1700
|
exports.fromDraft = fromDraft;
|
|
1546
1701
|
exports.humanizeConfigFailure = humanizeConfigFailure;
|
|
1547
1702
|
exports.inject = inject;
|
|
1548
1703
|
exports.isDirectoryPicker = isDirectoryPicker;
|
|
1704
|
+
exports.larkItemOf = larkItemOf;
|
|
1549
1705
|
exports.openRowBrowser = openRowBrowser;
|
|
1550
1706
|
exports.pickDirectory = pickDirectory;
|
|
1551
1707
|
exports.pickerModeOf = pickerModeOf;
|