@lark-apaas/openclaw-scripts-diagnose-cli 0.1.7-alpha.1 → 0.1.7
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/index.cjs +427 -550
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -50,7 +50,7 @@ node_assert = __toESM(node_assert);
|
|
|
50
50
|
* it terse and parseable.
|
|
51
51
|
*/
|
|
52
52
|
function getVersion() {
|
|
53
|
-
return "0.1.7
|
|
53
|
+
return "0.1.7";
|
|
54
54
|
}
|
|
55
55
|
//#endregion
|
|
56
56
|
//#region src/rule-engine/base.ts
|
|
@@ -76,6 +76,19 @@ function Rule(meta) {
|
|
|
76
76
|
function getAllRules() {
|
|
77
77
|
return topoSort(ruleRegistry.map((Ctor) => new Ctor()));
|
|
78
78
|
}
|
|
79
|
+
/** Return serialisable metadata for all registered rules (topo-sorted). */
|
|
80
|
+
function getRuleMetas(keys) {
|
|
81
|
+
const rules = getAllRules();
|
|
82
|
+
return (keys && keys.length > 0 ? rules.filter((r) => keys.includes(r.meta.key)) : rules).map((r) => ({
|
|
83
|
+
key: r.meta.key,
|
|
84
|
+
description: r.meta.description ?? "(no description)",
|
|
85
|
+
repairMode: r.meta.repairMode,
|
|
86
|
+
profile: r.meta.profile ?? "standard",
|
|
87
|
+
dependsOn: r.meta.dependsOn ?? [],
|
|
88
|
+
hasSkipWhen: r.meta.skipWhen !== void 0,
|
|
89
|
+
hasRepair: r.repair !== DiagnoseRule.prototype.repair
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
79
92
|
/**
|
|
80
93
|
* 判断规则在当前 profile 下是否应执行。
|
|
81
94
|
* experimental profile 运行所有规则;standard 只运行 standard(或未标注)规则。
|
|
@@ -1284,6 +1297,7 @@ let OpenclawRuntimeMissingRule = class OpenclawRuntimeMissingRule extends Diagno
|
|
|
1284
1297
|
};
|
|
1285
1298
|
OpenclawRuntimeMissingRule = __decorate([Rule({
|
|
1286
1299
|
key: "openclaw_runtime_missing",
|
|
1300
|
+
description: "检查 openclaw 二进制是否在 PATH 中可用;缺失时提示重新安装",
|
|
1287
1301
|
repairMode: "user-confirm"
|
|
1288
1302
|
})], OpenclawRuntimeMissingRule);
|
|
1289
1303
|
//#endregion
|
|
@@ -1307,6 +1321,7 @@ let ProcessStatusRule = class ProcessStatusRule extends DiagnoseRule {
|
|
|
1307
1321
|
};
|
|
1308
1322
|
ProcessStatusRule = __decorate([Rule({
|
|
1309
1323
|
key: "multi_process_detect",
|
|
1324
|
+
description: "通过 pgrep 检测是否存在多个并发的 openclaw-gateway 进程;多进程通常表明上次会话未正常退出",
|
|
1310
1325
|
repairMode: "standard"
|
|
1311
1326
|
})], ProcessStatusRule);
|
|
1312
1327
|
//#endregion
|
|
@@ -1372,6 +1387,7 @@ let ConfigFileBackupRule = class ConfigFileBackupRule extends DiagnoseRule {
|
|
|
1372
1387
|
};
|
|
1373
1388
|
ConfigFileBackupRule = __decorate([Rule({
|
|
1374
1389
|
key: "config_file_recover",
|
|
1390
|
+
description: "扫描 .bak* 配置备份,当主配置 openclaw.json 缺失时恢复编号最高的备份",
|
|
1375
1391
|
repairMode: "standard"
|
|
1376
1392
|
})], ConfigFileBackupRule);
|
|
1377
1393
|
//#endregion
|
|
@@ -1405,6 +1421,7 @@ let ConfigFileMissingRule = class ConfigFileMissingRule extends DiagnoseRule {
|
|
|
1405
1421
|
};
|
|
1406
1422
|
ConfigFileMissingRule = __decorate([Rule({
|
|
1407
1423
|
key: "config_file_missing",
|
|
1424
|
+
description: "在执行恢复后检查 openclaw.json 是否存在;若仍缺失则触发全量重置",
|
|
1408
1425
|
dependsOn: ["config_file_recover"],
|
|
1409
1426
|
repairMode: "reset"
|
|
1410
1427
|
})], ConfigFileMissingRule);
|
|
@@ -1427,6 +1444,7 @@ let ConfigSyntaxRule = class ConfigSyntaxRule extends DiagnoseRule {
|
|
|
1427
1444
|
};
|
|
1428
1445
|
ConfigSyntaxRule = __decorate([Rule({
|
|
1429
1446
|
key: "config_syntax_check",
|
|
1447
|
+
description: "验证 openclaw.json 的 JSON5 语法;schema 校验失败或解析错误时需要 AI 辅助修复",
|
|
1430
1448
|
dependsOn: ["config_file_recover", "config_file_missing"],
|
|
1431
1449
|
repairMode: "ai"
|
|
1432
1450
|
})], ConfigSyntaxRule);
|
|
@@ -1456,6 +1474,7 @@ let TemplateVarsUnreplacedRule = class TemplateVarsUnreplacedRule extends Diagno
|
|
|
1456
1474
|
};
|
|
1457
1475
|
TemplateVarsUnreplacedRule = __decorate([Rule({
|
|
1458
1476
|
key: "template_vars_unreplaced",
|
|
1477
|
+
description: "检测 openclaw.json 中未替换的 $__XXX__ 占位符,并从注入的环境变量中填充",
|
|
1459
1478
|
dependsOn: ["config_syntax_check"],
|
|
1460
1479
|
repairMode: "standard",
|
|
1461
1480
|
usesVars: ["templateVars"]
|
|
@@ -1604,6 +1623,7 @@ let ModelProviderRule = class ModelProviderRule extends DiagnoseRule {
|
|
|
1604
1623
|
};
|
|
1605
1624
|
ModelProviderRule = __decorate([Rule({
|
|
1606
1625
|
key: "model_provider",
|
|
1626
|
+
description: "检查妙搭 model provider 配置项(baseUrl、apiKey、必要 headers)是否存在且格式正确;非妙搭沙箱时跳过",
|
|
1607
1627
|
dependsOn: ["config_syntax_check"],
|
|
1608
1628
|
repairMode: "standard",
|
|
1609
1629
|
usesVars: ["innerAPIKey", "baseURL"],
|
|
@@ -1671,6 +1691,7 @@ let SecretProviderRule = class SecretProviderRule extends DiagnoseRule {
|
|
|
1671
1691
|
};
|
|
1672
1692
|
SecretProviderRule = _SecretProviderRule = __decorate([Rule({
|
|
1673
1693
|
key: "secret_provider",
|
|
1694
|
+
description: "验证 miaoda-provider / miaoda-secret-provider 配置块是否存在且完整;未检测到妙搭 provider 时跳过",
|
|
1674
1695
|
dependsOn: ["config_syntax_check"],
|
|
1675
1696
|
repairMode: "standard",
|
|
1676
1697
|
skipWhen: ({ hasMiaoda, deps }) => !hasMiaoda || !deps.usesMiaodaProvider && !deps.usesMiaodaSecretProvider
|
|
@@ -1735,6 +1756,7 @@ let FeishuChannelRule = class FeishuChannelRule extends DiagnoseRule {
|
|
|
1735
1756
|
};
|
|
1736
1757
|
FeishuChannelRule = __decorate([Rule({
|
|
1737
1758
|
key: "feishu_channel",
|
|
1759
|
+
description: "确保 channels.feishu 已启用,且包含单 agent 所需的 appId 和 appSecret 字段",
|
|
1738
1760
|
dependsOn: ["config_syntax_check", "feishu_default_account"],
|
|
1739
1761
|
repairMode: "standard",
|
|
1740
1762
|
usesVars: ["feishuAppID", "feishuAppSecret"]
|
|
@@ -1851,6 +1873,7 @@ let FeishuDefaultAccountRule = class FeishuDefaultAccountRule extends DiagnoseRu
|
|
|
1851
1873
|
};
|
|
1852
1874
|
FeishuDefaultAccountRule = __decorate([Rule({
|
|
1853
1875
|
key: "feishu_default_account",
|
|
1876
|
+
description: "将旧版 v1/v2 飞书 channel 配置迁移为规范的 v3 bot-<appId> 账号结构",
|
|
1854
1877
|
dependsOn: ["config_syntax_check"],
|
|
1855
1878
|
repairMode: "standard",
|
|
1856
1879
|
usesVars: ["feishuAppID", "teamChatID"]
|
|
@@ -1979,6 +2002,7 @@ let GatewayRule = class GatewayRule extends DiagnoseRule {
|
|
|
1979
2002
|
};
|
|
1980
2003
|
GatewayRule = __decorate([Rule({
|
|
1981
2004
|
key: "gateway",
|
|
2005
|
+
description: "验证 gateway 必填字段(port、mode、bind、auth、trustedProxies)是否存在且有效;修复缺失或非法值",
|
|
1982
2006
|
dependsOn: ["config_syntax_check"],
|
|
1983
2007
|
repairMode: "standard",
|
|
1984
2008
|
usesVars: ["gatewayToken"]
|
|
@@ -2024,6 +2048,7 @@ let AllowedOriginsRule = class AllowedOriginsRule extends DiagnoseRule {
|
|
|
2024
2048
|
};
|
|
2025
2049
|
AllowedOriginsRule = __decorate([Rule({
|
|
2026
2050
|
key: "allowed_origins",
|
|
2051
|
+
description: "确保所有 expectedOrigins 条目都存在于 gateway.auth.allowedOrigins 中;自动追加缺失的条目",
|
|
2027
2052
|
dependsOn: ["config_syntax_check"],
|
|
2028
2053
|
repairMode: "standard",
|
|
2029
2054
|
usesVars: ["expectedOrigins"]
|
|
@@ -2103,6 +2128,7 @@ let JwtTokenRule = class JwtTokenRule extends DiagnoseRule {
|
|
|
2103
2128
|
};
|
|
2104
2129
|
JwtTokenRule = __decorate([Rule({
|
|
2105
2130
|
key: "jwt_token",
|
|
2131
|
+
description: "验证 miaoda provider 引用的 JWT token 文件存在且未过期;非妙搭沙箱时跳过",
|
|
2106
2132
|
dependsOn: ["config_syntax_check"],
|
|
2107
2133
|
repairMode: "standard",
|
|
2108
2134
|
usesVars: ["providerFilePath"],
|
|
@@ -2159,6 +2185,7 @@ let SecretsFileRule = class SecretsFileRule extends DiagnoseRule {
|
|
|
2159
2185
|
};
|
|
2160
2186
|
SecretsFileRule = __decorate([Rule({
|
|
2161
2187
|
key: "secrets_file",
|
|
2188
|
+
description: "验证 miaoda secret-provider 引用的 secrets.json 文件存在且可解析;非妙搭或非 secret-provider 沙箱时跳过",
|
|
2162
2189
|
dependsOn: ["config_syntax_check"],
|
|
2163
2190
|
repairMode: "standard",
|
|
2164
2191
|
usesVars: [
|
|
@@ -2214,6 +2241,7 @@ let CleanupInstallBackupDirsRule = class CleanupInstallBackupDirsRule extends Di
|
|
|
2214
2241
|
};
|
|
2215
2242
|
CleanupInstallBackupDirsRule = __decorate([Rule({
|
|
2216
2243
|
key: "cleanup_install_backup_dirs",
|
|
2244
|
+
description: "清理 extensions/ 目录中升级中断遗留的 .openclaw-install-stage-* 和 .openclaw-install-backups/ 脏目录",
|
|
2217
2245
|
repairMode: "standard"
|
|
2218
2246
|
})], CleanupInstallBackupDirsRule);
|
|
2219
2247
|
//#endregion
|
|
@@ -2269,6 +2297,7 @@ let MiaodaOfficialPluginsInstallSpecUnlockRule = class MiaodaOfficialPluginsInst
|
|
|
2269
2297
|
};
|
|
2270
2298
|
MiaodaOfficialPluginsInstallSpecUnlockRule = __decorate([Rule({
|
|
2271
2299
|
key: "miaoda_official_plugins_install_spec_unlock",
|
|
2300
|
+
description: "移除官方妙搭插件安装条目中的锁版本 npm spec,使其跟随最新 manifest 版本",
|
|
2272
2301
|
dependsOn: ["config_syntax_check"],
|
|
2273
2302
|
repairMode: "standard"
|
|
2274
2303
|
})], MiaodaOfficialPluginsInstallSpecUnlockRule);
|
|
@@ -2335,6 +2364,7 @@ let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends Diag
|
|
|
2335
2364
|
};
|
|
2336
2365
|
OldMiaodaPluginsCleanupRule = __decorate([Rule({
|
|
2337
2366
|
key: "old_miaoda_plugins_cleanup",
|
|
2367
|
+
description: "当新版 openclaw-extension-miaoda 已存在时,清理过时插件引用(openclaw-feishu-greeting、openclaw-miaoda-keepalive 等)",
|
|
2338
2368
|
dependsOn: ["config_syntax_check"],
|
|
2339
2369
|
repairMode: "standard"
|
|
2340
2370
|
})], OldMiaodaPluginsCleanupRule);
|
|
@@ -2372,6 +2402,7 @@ let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
|
2372
2402
|
};
|
|
2373
2403
|
LarkPluginAllowRule = __decorate([Rule({
|
|
2374
2404
|
key: "lark_plugin_allow",
|
|
2405
|
+
description: "当飞书插件(openclaw-lark 或旧版名)已在磁盘安装但未加入 plugins.allow 时,自动添加",
|
|
2375
2406
|
dependsOn: ["config_syntax_check"],
|
|
2376
2407
|
repairMode: "standard"
|
|
2377
2408
|
})], LarkPluginAllowRule);
|
|
@@ -2427,6 +2458,7 @@ let MiaodaPluginAllowRule = class MiaodaPluginAllowRule extends DiagnoseRule {
|
|
|
2427
2458
|
};
|
|
2428
2459
|
MiaodaPluginAllowRule = __decorate([Rule({
|
|
2429
2460
|
key: "miaoda_plugin_allow",
|
|
2461
|
+
description: "当 openclaw-extension-miaoda 已在磁盘安装但未在 allow 列表中时,将其添加到 plugins.allow(实验性)",
|
|
2430
2462
|
dependsOn: ["config_syntax_check"],
|
|
2431
2463
|
repairMode: "standard",
|
|
2432
2464
|
profile: "experimental"
|
|
@@ -2468,6 +2500,7 @@ let BuiltinPluginMissingRule = class BuiltinPluginMissingRule extends DiagnoseRu
|
|
|
2468
2500
|
};
|
|
2469
2501
|
BuiltinPluginMissingRule = __decorate([Rule({
|
|
2470
2502
|
key: "builtin_plugin_missing",
|
|
2503
|
+
description: "检查所有内置扩展插件(openclaw-lark、openclaw-extension-miaoda 等)是否已在磁盘安装;缺失时提示重新安装(实验性)",
|
|
2471
2504
|
dependsOn: ["config_syntax_check"],
|
|
2472
2505
|
repairMode: "user-confirm",
|
|
2473
2506
|
profile: "experimental"
|
|
@@ -2510,6 +2543,7 @@ let BuiltinPluginInstallsCleanupRule = class BuiltinPluginInstallsCleanupRule ex
|
|
|
2510
2543
|
};
|
|
2511
2544
|
BuiltinPluginInstallsCleanupRule = __decorate([Rule({
|
|
2512
2545
|
key: "builtin_plugin_installs_cleanup",
|
|
2546
|
+
description: "在 builtin_plugin_missing 通过后,清除 plugins.installs 中磁盘目录已不存在的条目(实验性)",
|
|
2513
2547
|
dependsOn: ["builtin_plugin_missing"],
|
|
2514
2548
|
repairMode: "standard",
|
|
2515
2549
|
profile: "experimental"
|
|
@@ -2792,6 +2826,7 @@ let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extend
|
|
|
2792
2826
|
};
|
|
2793
2827
|
FeishuPluginStateNormalizeRule = __decorate([Rule({
|
|
2794
2828
|
key: "feishu_plugin_state_normalize",
|
|
2829
|
+
description: "规范化磁盘上的飞书插件状态:删除旧版插件目录、清理内置 feishu 目录,并将 openclaw.json 条目对齐到 openclaw-lark 插件",
|
|
2795
2830
|
dependsOn: ["config_syntax_check"],
|
|
2796
2831
|
repairMode: "standard"
|
|
2797
2832
|
})], FeishuPluginStateNormalizeRule);
|
|
@@ -3025,6 +3060,7 @@ let FeishuPluginVersionCompatRule = class FeishuPluginVersionCompatRule extends
|
|
|
3025
3060
|
};
|
|
3026
3061
|
FeishuPluginVersionCompatRule = __decorate([Rule({
|
|
3027
3062
|
key: "feishu_plugin_version_compat",
|
|
3063
|
+
description: "检查飞书插件与 openclaw 版本兼容性;不兼容时推荐 upgrade_openclaw 或 upgrade_lark 操作",
|
|
3028
3064
|
dependsOn: ["config_syntax_check"],
|
|
3029
3065
|
repairMode: "user-confirm",
|
|
3030
3066
|
usesVars: ["recommendedOpenclawTag"]
|
|
@@ -3185,6 +3221,7 @@ let FeishuAccountsConsistencyRule = class FeishuAccountsConsistencyRule extends
|
|
|
3185
3221
|
};
|
|
3186
3222
|
FeishuAccountsConsistencyRule = __decorate([Rule({
|
|
3187
3223
|
key: "feishu_accounts_consistency",
|
|
3224
|
+
description: "检测多 agent 配置中 channels.feishu.accounts、agents.list 与 feishu bindings 之间的数量/ID 不一致(实验性)",
|
|
3188
3225
|
dependsOn: ["config_syntax_check"],
|
|
3189
3226
|
repairMode: "check-only",
|
|
3190
3227
|
usesVars: ["feishuAppID"],
|
|
@@ -4134,7 +4171,7 @@ async function installExtension(tag, ossFileMap, opts = {}) {
|
|
|
4134
4171
|
};
|
|
4135
4172
|
}));
|
|
4136
4173
|
for (const { pkg, tarball } of tarballs) {
|
|
4137
|
-
installOne
|
|
4174
|
+
installOne(pkg, tarball, homeBase);
|
|
4138
4175
|
console.error(`[install-extension] ${pkg.name}: installed`);
|
|
4139
4176
|
}
|
|
4140
4177
|
if (!opts.skipConfigUpdate) updatePluginInstalls(opts.configPath ?? node_path.default.join(homeBase, "workspace/agent/openclaw.json"), targets);
|
|
@@ -4187,7 +4224,7 @@ function updatePluginInstalls(configPath, installedPkgs) {
|
|
|
4187
4224
|
moveSafe(tmpPath, configPath);
|
|
4188
4225
|
console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
|
|
4189
4226
|
}
|
|
4190
|
-
function installOne
|
|
4227
|
+
function installOne(pkg, tarball, homeBase) {
|
|
4191
4228
|
const destDir = node_path.default.join(homeBase, pkg.installPath);
|
|
4192
4229
|
const stagingDir = destDir + ".new";
|
|
4193
4230
|
const oldDir = destDir + ".old";
|
|
@@ -4729,464 +4766,114 @@ function sleepSync(ms) {
|
|
|
4729
4766
|
Atomics.wait(arr, 0, 0, ms);
|
|
4730
4767
|
}
|
|
4731
4768
|
//#endregion
|
|
4732
|
-
//#region src/
|
|
4733
|
-
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
4734
|
-
const PE_XML_TAG = "lark-cli-pe";
|
|
4735
|
-
const PE_PLACEHOLDER = `
|
|
4736
|
-
<${PE_XML_TAG}>
|
|
4737
|
-
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
4738
|
-
</${PE_XML_TAG}>
|
|
4739
|
-
`;
|
|
4740
|
-
function isLarkPluginInstalled(configPath) {
|
|
4741
|
-
const extDir = getExtensionsDir(configPath);
|
|
4742
|
-
return LARK_PLUGIN_NAMES.some((name) => {
|
|
4743
|
-
try {
|
|
4744
|
-
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
4745
|
-
} catch {
|
|
4746
|
-
return false;
|
|
4747
|
-
}
|
|
4748
|
-
});
|
|
4749
|
-
}
|
|
4750
|
-
function isLarkCliAvailable() {
|
|
4751
|
-
try {
|
|
4752
|
-
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
4753
|
-
encoding: "utf-8",
|
|
4754
|
-
timeout: 5e3,
|
|
4755
|
-
stdio: [
|
|
4756
|
-
"ignore",
|
|
4757
|
-
"pipe",
|
|
4758
|
-
"ignore"
|
|
4759
|
-
]
|
|
4760
|
-
}).status === 0;
|
|
4761
|
-
} catch {
|
|
4762
|
-
return false;
|
|
4763
|
-
}
|
|
4764
|
-
}
|
|
4765
|
-
function readConfig(configPath) {
|
|
4766
|
-
try {
|
|
4767
|
-
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
4768
|
-
const parsed = loadJSON5().parse(raw);
|
|
4769
|
-
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4770
|
-
} catch {
|
|
4771
|
-
return null;
|
|
4772
|
-
}
|
|
4773
|
-
}
|
|
4769
|
+
//#region src/oss/resolveOssFileMap.ts
|
|
4774
4770
|
/**
|
|
4775
|
-
*
|
|
4776
|
-
*
|
|
4777
|
-
*
|
|
4778
|
-
*
|
|
4779
|
-
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
4780
|
-
*
|
|
4781
|
-
* Value interpretation:
|
|
4782
|
-
* - string → use directly
|
|
4783
|
-
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
4771
|
+
* Pick an OssFileMap in the order of decreasing specificity:
|
|
4772
|
+
* 1. `--oss_file_map=` flag — operator override (manual invocations, tests)
|
|
4773
|
+
* 2. `ctx.install.ossFileMap` — new shape (innerapi-driven DoctorCtx)
|
|
4774
|
+
* 3. `ctx.resetData.ossFileMap` — legacy shape (sandbox_console push path)
|
|
4784
4775
|
*
|
|
4785
|
-
*
|
|
4776
|
+
* Throws when none of the three yields a non-empty map. Empty maps are
|
|
4777
|
+
* treated as missing — an empty map is useless downstream and almost always
|
|
4778
|
+
* indicates a ctx wiring bug.
|
|
4786
4779
|
*/
|
|
4787
|
-
function
|
|
4788
|
-
|
|
4789
|
-
if (
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
else {
|
|
4793
|
-
const accounts = asRecord(feishu.accounts);
|
|
4794
|
-
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
4795
|
-
const account = asRecord(val);
|
|
4796
|
-
if (account?.appId === appId) {
|
|
4797
|
-
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
4798
|
-
break;
|
|
4799
|
-
}
|
|
4800
|
-
}
|
|
4801
|
-
}
|
|
4802
|
-
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
4803
|
-
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
4804
|
-
return null;
|
|
4780
|
+
function resolveOssFileMap(args) {
|
|
4781
|
+
if (args.ossFileMapFlag) return JSON.parse(Buffer.from(args.ossFileMapFlag, "base64").toString("utf-8"));
|
|
4782
|
+
if (args.installOssFileMap && Object.keys(args.installOssFileMap).length > 0) return args.installOssFileMap;
|
|
4783
|
+
if (args.resetDataOssFileMap && Object.keys(args.resetDataOssFileMap).length > 0) return args.resetDataOssFileMap;
|
|
4784
|
+
throw new Error("ossFileMap missing: provide --oss_file_map flag, ctx.install.ossFileMap, or resetData.ossFileMap");
|
|
4805
4785
|
}
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
}
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
}
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4786
|
+
//#endregion
|
|
4787
|
+
//#region src/innerapi/client.ts
|
|
4788
|
+
var import_dist = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
4789
|
+
var __defProp = Object.defineProperty;
|
|
4790
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4791
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4792
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4793
|
+
var __export = (target, all) => {
|
|
4794
|
+
for (var name in all) __defProp(target, name, {
|
|
4795
|
+
get: all[name],
|
|
4796
|
+
enumerable: true
|
|
4797
|
+
});
|
|
4798
|
+
};
|
|
4799
|
+
var __copyProps = (to, from, except, desc) => {
|
|
4800
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
4801
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
4802
|
+
get: () => from[key],
|
|
4803
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
4804
|
+
});
|
|
4805
|
+
}
|
|
4806
|
+
return to;
|
|
4807
|
+
};
|
|
4808
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
4809
|
+
var index_exports = {};
|
|
4810
|
+
__export(index_exports, {
|
|
4811
|
+
DEFAULT_CLOCK_TOLERANCE_SEC: () => DEFAULT_CLOCK_TOLERANCE_SEC,
|
|
4812
|
+
DEFAULT_JWT_EXPIRE_TIME_MS: () => DEFAULT_JWT_EXPIRE_TIME_MS,
|
|
4813
|
+
HttpClient: () => HttpClient,
|
|
4814
|
+
HttpError: () => HttpError,
|
|
4815
|
+
generateJWTToken: () => generateJWTToken,
|
|
4816
|
+
parseJWTTokenWithVerify: () => parseJWTTokenWithVerify,
|
|
4817
|
+
registerPlatformPlugin: () => registerPlatformPlugin,
|
|
4818
|
+
resolvePlatformBaseURL: () => resolvePlatformBaseURL
|
|
4819
|
+
});
|
|
4820
|
+
module.exports = __toCommonJS(index_exports);
|
|
4821
|
+
var import_crypto = require("crypto");
|
|
4822
|
+
var DEFAULT_JWT_EXPIRE_TIME_MS = 1800 * 1e3;
|
|
4823
|
+
var DEFAULT_CLOCK_TOLERANCE_SEC = 60;
|
|
4824
|
+
var JWT_HEADER = {
|
|
4825
|
+
alg: "HS256",
|
|
4826
|
+
typ: "JWT"
|
|
4827
|
+
};
|
|
4828
|
+
function generateJWTToken(customClaims, config) {
|
|
4829
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
4830
|
+
const payload = {
|
|
4831
|
+
...customClaims,
|
|
4832
|
+
iss: customClaims.access_key,
|
|
4833
|
+
iat: nowSeconds,
|
|
4834
|
+
nbf: nowSeconds,
|
|
4835
|
+
exp: nowSeconds + Math.floor(config.expireTimeMs / 1e3),
|
|
4836
|
+
jti: (0, import_crypto.randomUUID)()
|
|
4837
|
+
};
|
|
4838
|
+
const encodedHeader = base64UrlEncode(JSON.stringify(JWT_HEADER));
|
|
4839
|
+
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
4840
|
+
return `${encodedHeader}.${encodedPayload}.${sign(`${encodedHeader}.${encodedPayload}`, config.secretKey)}`;
|
|
4843
4841
|
}
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
const
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4842
|
+
function parseJWTTokenWithVerify(tokenString, config, options) {
|
|
4843
|
+
const segments = tokenString.split(".");
|
|
4844
|
+
if (segments.length !== 3) throw new Error("invalid JWT token format");
|
|
4845
|
+
const [encodedHeader, encodedPayload, signature] = segments;
|
|
4846
|
+
if (JSON.parse(base64UrlDecode(encodedHeader)).alg !== "HS256") throw new Error("unsupported JWT alg");
|
|
4847
|
+
const expectedSignature = sign(`${encodedHeader}.${encodedPayload}`, config.secretKey);
|
|
4848
|
+
const expectedBuffer = Buffer.from(expectedSignature);
|
|
4849
|
+
const signatureBuffer = Buffer.from(signature);
|
|
4850
|
+
if (expectedBuffer.length !== signatureBuffer.length || !(0, import_crypto.timingSafeEqual)(expectedBuffer, signatureBuffer)) throw new Error("JWT signature verification failed");
|
|
4851
|
+
const payload = JSON.parse(base64UrlDecode(encodedPayload));
|
|
4852
|
+
if (!options?.skipExpiration) {
|
|
4853
|
+
const clockTolerance = options?.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SEC;
|
|
4854
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
4855
|
+
if (payload.exp !== void 0) {
|
|
4856
|
+
if (payload.exp + clockTolerance < now) throw new Error(`JWT token expired at ${(/* @__PURE__ */ new Date(payload.exp * 1e3)).toISOString()}`);
|
|
4857
|
+
}
|
|
4858
|
+
if (payload.nbf !== void 0) {
|
|
4859
|
+
if (payload.nbf - clockTolerance > now) throw new Error(`JWT token not yet valid, will be valid at ${(/* @__PURE__ */ new Date(payload.nbf * 1e3)).toISOString()}`);
|
|
4860
|
+
}
|
|
4861
|
+
if (payload.iat !== void 0) {
|
|
4862
|
+
if (payload.iat - clockTolerance > now) throw new Error(`JWT token issued in the future at ${(/* @__PURE__ */ new Date(payload.iat * 1e3)).toISOString()}`);
|
|
4855
4863
|
}
|
|
4856
4864
|
}
|
|
4865
|
+
return payload;
|
|
4857
4866
|
}
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
return null;
|
|
4861
|
-
}
|
|
4862
|
-
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
4863
|
-
if (agentId === "main") {
|
|
4864
|
-
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
4865
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
4866
|
-
}
|
|
4867
|
-
const agentsObj = asRecord(config.agents);
|
|
4868
|
-
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
4869
|
-
for (const a of list) {
|
|
4870
|
-
const agent = asRecord(a);
|
|
4871
|
-
if (agent?.id === agentId) {
|
|
4872
|
-
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
4873
|
-
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
4874
|
-
return node_path.default.join(ws, "AGENTS.md");
|
|
4875
|
-
}
|
|
4867
|
+
function sign(input, secret) {
|
|
4868
|
+
return (0, import_crypto.createHmac)("sha256", secret).update(input).digest("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
4876
4869
|
}
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
}
|
|
4880
|
-
function appendPeToAgentsMd(agentsMdPath) {
|
|
4881
|
-
const dir = node_path.default.dirname(agentsMdPath);
|
|
4882
|
-
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
4883
|
-
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
4884
|
-
if (existing.includes(`<${PE_XML_TAG}>`)) {
|
|
4885
|
-
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
4886
|
-
return;
|
|
4870
|
+
function base64UrlEncode(input) {
|
|
4871
|
+
return Buffer.from(input).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
4887
4872
|
}
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
/**
|
|
4893
|
-
* Collect every Feishu bot appId declared in the openclaw config.
|
|
4894
|
-
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
4895
|
-
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
4896
|
-
*/
|
|
4897
|
-
function collectFeishuAppIds(configPath) {
|
|
4898
|
-
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
4899
|
-
if (!config) return [];
|
|
4900
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4901
|
-
if (!feishu) return [];
|
|
4902
|
-
const appIds = /* @__PURE__ */ new Set();
|
|
4903
|
-
if (typeof feishu.appId === "string" && feishu.appId) appIds.add(feishu.appId);
|
|
4904
|
-
const accounts = asRecord(feishu.accounts);
|
|
4905
|
-
if (accounts) for (const val of Object.values(accounts)) {
|
|
4906
|
-
const account = asRecord(val);
|
|
4907
|
-
if (typeof account?.appId === "string" && account.appId) appIds.add(account.appId);
|
|
4908
|
-
}
|
|
4909
|
-
return [...appIds];
|
|
4910
|
-
}
|
|
4911
|
-
function runLarkCliInit(opts) {
|
|
4912
|
-
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
4913
|
-
if (!isLarkPluginInstalled(configPath)) {
|
|
4914
|
-
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
4915
|
-
return {
|
|
4916
|
-
ok: true,
|
|
4917
|
-
skipped: true,
|
|
4918
|
-
skipReason: "openclaw-lark plugin not installed"
|
|
4919
|
-
};
|
|
4920
|
-
}
|
|
4921
|
-
if (!isLarkCliAvailable()) {
|
|
4922
|
-
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
4923
|
-
return {
|
|
4924
|
-
ok: true,
|
|
4925
|
-
skipped: true,
|
|
4926
|
-
skipReason: "lark-cli command not found"
|
|
4927
|
-
};
|
|
4928
|
-
}
|
|
4929
|
-
const config = readConfig(configPath);
|
|
4930
|
-
if (!config) return {
|
|
4931
|
-
ok: false,
|
|
4932
|
-
error: `could not read config at ${configPath}`
|
|
4933
|
-
};
|
|
4934
|
-
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
4935
|
-
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
4936
|
-
if (!agentsMdPath) return {
|
|
4937
|
-
ok: false,
|
|
4938
|
-
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
4939
|
-
};
|
|
4940
|
-
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
4941
|
-
if (!appSecret) return {
|
|
4942
|
-
ok: false,
|
|
4943
|
-
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
4944
|
-
};
|
|
4945
|
-
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
4946
|
-
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
4947
|
-
"config",
|
|
4948
|
-
"init",
|
|
4949
|
-
"--name",
|
|
4950
|
-
opts.appId,
|
|
4951
|
-
"--app-id",
|
|
4952
|
-
opts.appId,
|
|
4953
|
-
"--brand",
|
|
4954
|
-
"feishu",
|
|
4955
|
-
"--app-secret-stdin",
|
|
4956
|
-
"--force-init"
|
|
4957
|
-
], {
|
|
4958
|
-
stdio: [
|
|
4959
|
-
"pipe",
|
|
4960
|
-
"pipe",
|
|
4961
|
-
"pipe"
|
|
4962
|
-
],
|
|
4963
|
-
encoding: "utf-8",
|
|
4964
|
-
input: appSecret
|
|
4965
|
-
});
|
|
4966
|
-
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
4967
|
-
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
4968
|
-
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
4969
|
-
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
4970
|
-
if (initRes.error) return {
|
|
4971
|
-
ok: false,
|
|
4972
|
-
configInitStdout,
|
|
4973
|
-
configInitStderr,
|
|
4974
|
-
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
4975
|
-
};
|
|
4976
|
-
if (initRes.status !== 0) return {
|
|
4977
|
-
ok: false,
|
|
4978
|
-
configInitExitCode: initRes.status ?? void 0,
|
|
4979
|
-
configInitStdout,
|
|
4980
|
-
configInitStderr,
|
|
4981
|
-
error: `lark-cli config init exited with code ${initRes.status}`
|
|
4982
|
-
};
|
|
4983
|
-
appendPeToAgentsMd(agentsMdPath);
|
|
4984
|
-
return {
|
|
4985
|
-
ok: true,
|
|
4986
|
-
configInitExitCode: 0,
|
|
4987
|
-
agentsMdPath
|
|
4988
|
-
};
|
|
4989
|
-
}
|
|
4990
|
-
const LARK_CLI_NAME = "lark-cli";
|
|
4991
|
-
async function installClis(tag, ossFileMap, opts) {
|
|
4992
|
-
const homeBase = opts.homeBase ?? "/home/gem";
|
|
4993
|
-
if (opts.names.length === 0) throw new Error("install-clis: must provide at least one --cli=<name>");
|
|
4994
|
-
const manifest = await fetchManifest(ossFileMap, tag);
|
|
4995
|
-
console.error(`[install-clis] manifest=${JSON.stringify(manifest)}`);
|
|
4996
|
-
const allClis = manifest.packages.filter((p) => p.role === "cli" && p.name !== "openclaw");
|
|
4997
|
-
const wanted = new Set(opts.names);
|
|
4998
|
-
const targets = allClis.filter((p) => wanted.has(p.name) || p.packageName != null && wanted.has(p.packageName));
|
|
4999
|
-
const foundKeys = /* @__PURE__ */ new Set();
|
|
5000
|
-
for (const t of targets) {
|
|
5001
|
-
foundKeys.add(t.name);
|
|
5002
|
-
if (t.packageName) foundKeys.add(t.packageName);
|
|
5003
|
-
}
|
|
5004
|
-
const missing = opts.names.filter((n) => !foundKeys.has(n));
|
|
5005
|
-
if (missing.length > 0) throw new Error(`install-clis: not found in manifest: ${missing.join(", ")}`);
|
|
5006
|
-
console.error(`[install-clis] tag=${tag} targets=${targets.length}`);
|
|
5007
|
-
const t0 = Date.now();
|
|
5008
|
-
const tarballs = await Promise.all(targets.map(async (p) => {
|
|
5009
|
-
const tb = await downloadWithCache(p, ossFileMap, opts);
|
|
5010
|
-
console.error(`[install-clis] ${p.name}: downloaded`);
|
|
5011
|
-
return {
|
|
5012
|
-
pkg: p,
|
|
5013
|
-
tarball: tb
|
|
5014
|
-
};
|
|
5015
|
-
}));
|
|
5016
|
-
for (const { pkg, tarball } of tarballs) {
|
|
5017
|
-
installOne(pkg, tarball, homeBase, opts.tmpRoot);
|
|
5018
|
-
console.error(`[install-clis] ${pkg.name}: installed`);
|
|
5019
|
-
}
|
|
5020
|
-
if (targets.some((p) => p.name === LARK_CLI_NAME)) {
|
|
5021
|
-
const appIds = collectFeishuAppIds();
|
|
5022
|
-
console.error(`[install-clis] lark-cli installed — running lark-cli-init for ${appIds.length} appId(s): [${appIds.join(", ")}]`);
|
|
5023
|
-
for (const appId of appIds) {
|
|
5024
|
-
console.error(`[install-clis] lark-cli-init: appId=${appId}`);
|
|
5025
|
-
const result = runLarkCliInit({
|
|
5026
|
-
appId,
|
|
5027
|
-
feishuAppSecret: opts.feishuAppSecret
|
|
5028
|
-
});
|
|
5029
|
-
console.error(`[install-clis] lark-cli-init: appId=${appId} ok=${result.ok}` + (result.skipped ? ` skipped=true reason=${result.skipReason}` : "") + (result.error ? ` error=${result.error}` : ""));
|
|
5030
|
-
}
|
|
5031
|
-
}
|
|
5032
|
-
console.error(`[install-clis] done ${targets.length}/${targets.length} in ${Date.now() - t0}ms`);
|
|
5033
|
-
}
|
|
5034
|
-
function installOne(pkg, tarball, homeBase, tmpRoot) {
|
|
5035
|
-
const targetDir = node_path.default.join(homeBase, pkg.installPath);
|
|
5036
|
-
const bakDir = targetDir + ".bak";
|
|
5037
|
-
const newDir = targetDir + ".new";
|
|
5038
|
-
node_fs.default.mkdirSync(node_path.default.dirname(targetDir), { recursive: true });
|
|
5039
|
-
if (node_fs.default.existsSync(newDir)) node_fs.default.rmSync(newDir, {
|
|
5040
|
-
recursive: true,
|
|
5041
|
-
force: true
|
|
5042
|
-
});
|
|
5043
|
-
if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
5044
|
-
recursive: true,
|
|
5045
|
-
force: true
|
|
5046
|
-
});
|
|
5047
|
-
const tmpStage = node_fs.default.mkdtempSync(node_path.default.join(tmpRoot ?? node_os.default.tmpdir(), "cli-install-"));
|
|
5048
|
-
try {
|
|
5049
|
-
extractTarballTolerant(tarball, tmpStage, { stripComponents: 1 });
|
|
5050
|
-
if (!node_fs.default.existsSync(node_path.default.join(tmpStage, "package.json"))) throw new Error(`cli tarball missing package.json: ${pkg.name}`);
|
|
5051
|
-
moveSafe(tmpStage, newDir);
|
|
5052
|
-
const hadExisting = node_fs.default.existsSync(targetDir);
|
|
5053
|
-
try {
|
|
5054
|
-
if (hadExisting) moveSafe(targetDir, bakDir);
|
|
5055
|
-
moveSafe(newDir, targetDir);
|
|
5056
|
-
} catch (e) {
|
|
5057
|
-
if (hadExisting && !node_fs.default.existsSync(targetDir) && node_fs.default.existsSync(bakDir)) try {
|
|
5058
|
-
moveSafe(bakDir, targetDir);
|
|
5059
|
-
} catch {}
|
|
5060
|
-
try {
|
|
5061
|
-
node_fs.default.rmSync(newDir, {
|
|
5062
|
-
recursive: true,
|
|
5063
|
-
force: true
|
|
5064
|
-
});
|
|
5065
|
-
} catch {}
|
|
5066
|
-
throw e;
|
|
5067
|
-
}
|
|
5068
|
-
if (hadExisting && node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
5069
|
-
recursive: true,
|
|
5070
|
-
force: true
|
|
5071
|
-
});
|
|
5072
|
-
} finally {
|
|
5073
|
-
if (node_fs.default.existsSync(tmpStage)) try {
|
|
5074
|
-
node_fs.default.rmSync(tmpStage, {
|
|
5075
|
-
recursive: true,
|
|
5076
|
-
force: true
|
|
5077
|
-
});
|
|
5078
|
-
} catch {}
|
|
5079
|
-
}
|
|
5080
|
-
}
|
|
5081
|
-
//#endregion
|
|
5082
|
-
//#region src/oss/resolveOssFileMap.ts
|
|
5083
|
-
/**
|
|
5084
|
-
* Pick an OssFileMap in the order of decreasing specificity:
|
|
5085
|
-
* 1. `--oss_file_map=` flag — operator override (manual invocations, tests)
|
|
5086
|
-
* 2. `ctx.install.ossFileMap` — new shape (innerapi-driven DoctorCtx)
|
|
5087
|
-
* 3. `ctx.resetData.ossFileMap` — legacy shape (sandbox_console push path)
|
|
5088
|
-
*
|
|
5089
|
-
* Throws when none of the three yields a non-empty map. Empty maps are
|
|
5090
|
-
* treated as missing — an empty map is useless downstream and almost always
|
|
5091
|
-
* indicates a ctx wiring bug.
|
|
5092
|
-
*/
|
|
5093
|
-
function resolveOssFileMap(args) {
|
|
5094
|
-
if (args.ossFileMapFlag) return JSON.parse(Buffer.from(args.ossFileMapFlag, "base64").toString("utf-8"));
|
|
5095
|
-
if (args.installOssFileMap && Object.keys(args.installOssFileMap).length > 0) return args.installOssFileMap;
|
|
5096
|
-
if (args.resetDataOssFileMap && Object.keys(args.resetDataOssFileMap).length > 0) return args.resetDataOssFileMap;
|
|
5097
|
-
throw new Error("ossFileMap missing: provide --oss_file_map flag, ctx.install.ossFileMap, or resetData.ossFileMap");
|
|
5098
|
-
}
|
|
5099
|
-
//#endregion
|
|
5100
|
-
//#region src/innerapi/client.ts
|
|
5101
|
-
var import_dist = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
5102
|
-
var __defProp = Object.defineProperty;
|
|
5103
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5104
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5105
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5106
|
-
var __export = (target, all) => {
|
|
5107
|
-
for (var name in all) __defProp(target, name, {
|
|
5108
|
-
get: all[name],
|
|
5109
|
-
enumerable: true
|
|
5110
|
-
});
|
|
5111
|
-
};
|
|
5112
|
-
var __copyProps = (to, from, except, desc) => {
|
|
5113
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
5114
|
-
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
5115
|
-
get: () => from[key],
|
|
5116
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
5117
|
-
});
|
|
5118
|
-
}
|
|
5119
|
-
return to;
|
|
5120
|
-
};
|
|
5121
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
5122
|
-
var index_exports = {};
|
|
5123
|
-
__export(index_exports, {
|
|
5124
|
-
DEFAULT_CLOCK_TOLERANCE_SEC: () => DEFAULT_CLOCK_TOLERANCE_SEC,
|
|
5125
|
-
DEFAULT_JWT_EXPIRE_TIME_MS: () => DEFAULT_JWT_EXPIRE_TIME_MS,
|
|
5126
|
-
HttpClient: () => HttpClient,
|
|
5127
|
-
HttpError: () => HttpError,
|
|
5128
|
-
generateJWTToken: () => generateJWTToken,
|
|
5129
|
-
parseJWTTokenWithVerify: () => parseJWTTokenWithVerify,
|
|
5130
|
-
registerPlatformPlugin: () => registerPlatformPlugin,
|
|
5131
|
-
resolvePlatformBaseURL: () => resolvePlatformBaseURL
|
|
5132
|
-
});
|
|
5133
|
-
module.exports = __toCommonJS(index_exports);
|
|
5134
|
-
var import_crypto = require("crypto");
|
|
5135
|
-
var DEFAULT_JWT_EXPIRE_TIME_MS = 1800 * 1e3;
|
|
5136
|
-
var DEFAULT_CLOCK_TOLERANCE_SEC = 60;
|
|
5137
|
-
var JWT_HEADER = {
|
|
5138
|
-
alg: "HS256",
|
|
5139
|
-
typ: "JWT"
|
|
5140
|
-
};
|
|
5141
|
-
function generateJWTToken(customClaims, config) {
|
|
5142
|
-
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
5143
|
-
const payload = {
|
|
5144
|
-
...customClaims,
|
|
5145
|
-
iss: customClaims.access_key,
|
|
5146
|
-
iat: nowSeconds,
|
|
5147
|
-
nbf: nowSeconds,
|
|
5148
|
-
exp: nowSeconds + Math.floor(config.expireTimeMs / 1e3),
|
|
5149
|
-
jti: (0, import_crypto.randomUUID)()
|
|
5150
|
-
};
|
|
5151
|
-
const encodedHeader = base64UrlEncode(JSON.stringify(JWT_HEADER));
|
|
5152
|
-
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
5153
|
-
return `${encodedHeader}.${encodedPayload}.${sign(`${encodedHeader}.${encodedPayload}`, config.secretKey)}`;
|
|
5154
|
-
}
|
|
5155
|
-
function parseJWTTokenWithVerify(tokenString, config, options) {
|
|
5156
|
-
const segments = tokenString.split(".");
|
|
5157
|
-
if (segments.length !== 3) throw new Error("invalid JWT token format");
|
|
5158
|
-
const [encodedHeader, encodedPayload, signature] = segments;
|
|
5159
|
-
if (JSON.parse(base64UrlDecode(encodedHeader)).alg !== "HS256") throw new Error("unsupported JWT alg");
|
|
5160
|
-
const expectedSignature = sign(`${encodedHeader}.${encodedPayload}`, config.secretKey);
|
|
5161
|
-
const expectedBuffer = Buffer.from(expectedSignature);
|
|
5162
|
-
const signatureBuffer = Buffer.from(signature);
|
|
5163
|
-
if (expectedBuffer.length !== signatureBuffer.length || !(0, import_crypto.timingSafeEqual)(expectedBuffer, signatureBuffer)) throw new Error("JWT signature verification failed");
|
|
5164
|
-
const payload = JSON.parse(base64UrlDecode(encodedPayload));
|
|
5165
|
-
if (!options?.skipExpiration) {
|
|
5166
|
-
const clockTolerance = options?.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SEC;
|
|
5167
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
5168
|
-
if (payload.exp !== void 0) {
|
|
5169
|
-
if (payload.exp + clockTolerance < now) throw new Error(`JWT token expired at ${(/* @__PURE__ */ new Date(payload.exp * 1e3)).toISOString()}`);
|
|
5170
|
-
}
|
|
5171
|
-
if (payload.nbf !== void 0) {
|
|
5172
|
-
if (payload.nbf - clockTolerance > now) throw new Error(`JWT token not yet valid, will be valid at ${(/* @__PURE__ */ new Date(payload.nbf * 1e3)).toISOString()}`);
|
|
5173
|
-
}
|
|
5174
|
-
if (payload.iat !== void 0) {
|
|
5175
|
-
if (payload.iat - clockTolerance > now) throw new Error(`JWT token issued in the future at ${(/* @__PURE__ */ new Date(payload.iat * 1e3)).toISOString()}`);
|
|
5176
|
-
}
|
|
5177
|
-
}
|
|
5178
|
-
return payload;
|
|
5179
|
-
}
|
|
5180
|
-
function sign(input, secret) {
|
|
5181
|
-
return (0, import_crypto.createHmac)("sha256", secret).update(input).digest("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
5182
|
-
}
|
|
5183
|
-
function base64UrlEncode(input) {
|
|
5184
|
-
return Buffer.from(input).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
5185
|
-
}
|
|
5186
|
-
function base64UrlDecode(input) {
|
|
5187
|
-
const padLength = (4 - (input.length % 4 || 4)) % 4;
|
|
5188
|
-
const padded = `${input}${"=".repeat(padLength)}`.replace(/-/g, "+").replace(/_/g, "/");
|
|
5189
|
-
return Buffer.from(padded, "base64").toString("utf8");
|
|
4873
|
+
function base64UrlDecode(input) {
|
|
4874
|
+
const padLength = (4 - (input.length % 4 || 4)) % 4;
|
|
4875
|
+
const padded = `${input}${"=".repeat(padLength)}`.replace(/-/g, "+").replace(/_/g, "/");
|
|
4876
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
5190
4877
|
}
|
|
5191
4878
|
var JWTTokenManager = class {
|
|
5192
4879
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -8180,14 +7867,254 @@ function finalize(results, aborted) {
|
|
|
8180
7867
|
};
|
|
8181
7868
|
}
|
|
8182
7869
|
//#endregion
|
|
8183
|
-
//#region src/
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
7870
|
+
//#region src/lark-cli-init.ts
|
|
7871
|
+
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
7872
|
+
const PE_XML_TAG = "lark-cli-pe";
|
|
7873
|
+
const PE_PLACEHOLDER = `
|
|
7874
|
+
<${PE_XML_TAG}>
|
|
7875
|
+
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
7876
|
+
</${PE_XML_TAG}>
|
|
7877
|
+
`;
|
|
7878
|
+
function isLarkPluginInstalled(configPath) {
|
|
7879
|
+
const extDir = getExtensionsDir(configPath);
|
|
7880
|
+
return LARK_PLUGIN_NAMES.some((name) => {
|
|
7881
|
+
try {
|
|
7882
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
7883
|
+
} catch {
|
|
7884
|
+
return false;
|
|
7885
|
+
}
|
|
7886
|
+
});
|
|
7887
|
+
}
|
|
7888
|
+
function isLarkCliAvailable() {
|
|
7889
|
+
try {
|
|
7890
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
7891
|
+
encoding: "utf-8",
|
|
7892
|
+
timeout: 5e3,
|
|
7893
|
+
stdio: [
|
|
7894
|
+
"ignore",
|
|
7895
|
+
"pipe",
|
|
7896
|
+
"ignore"
|
|
7897
|
+
]
|
|
7898
|
+
}).status === 0;
|
|
7899
|
+
} catch {
|
|
7900
|
+
return false;
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
function readConfig(configPath) {
|
|
7904
|
+
try {
|
|
7905
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
7906
|
+
const parsed = loadJSON5().parse(raw);
|
|
7907
|
+
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
7908
|
+
} catch {
|
|
7909
|
+
return null;
|
|
7910
|
+
}
|
|
7911
|
+
}
|
|
7912
|
+
/**
|
|
7913
|
+
* Resolve the feishu app secret for the given appId.
|
|
7914
|
+
*
|
|
7915
|
+
* Lookup order:
|
|
7916
|
+
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
7917
|
+
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
7918
|
+
*
|
|
7919
|
+
* Value interpretation:
|
|
7920
|
+
* - string → use directly
|
|
7921
|
+
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
7922
|
+
*
|
|
7923
|
+
* Returns null when the secret cannot be determined.
|
|
7924
|
+
*/
|
|
7925
|
+
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
7926
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
7927
|
+
if (!feishu) return null;
|
|
7928
|
+
let rawSecret;
|
|
7929
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
7930
|
+
else {
|
|
7931
|
+
const accounts = asRecord(feishu.accounts);
|
|
7932
|
+
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
7933
|
+
const account = asRecord(val);
|
|
7934
|
+
if (account?.appId === appId) {
|
|
7935
|
+
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
7936
|
+
break;
|
|
7937
|
+
}
|
|
7938
|
+
}
|
|
7939
|
+
}
|
|
7940
|
+
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
7941
|
+
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
7942
|
+
return null;
|
|
7943
|
+
}
|
|
7944
|
+
/**
|
|
7945
|
+
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
7946
|
+
*
|
|
7947
|
+
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
7948
|
+
* → WORKSPACE_DIR/AGENTS.md
|
|
7949
|
+
*
|
|
7950
|
+
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
7951
|
+
* → find account key where account.appId === appId
|
|
7952
|
+
* → find binding where match.channel=feishu && match.accountId=that key
|
|
7953
|
+
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
7954
|
+
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
7955
|
+
*
|
|
7956
|
+
* Returns null when the path cannot be determined.
|
|
7957
|
+
*/
|
|
7958
|
+
function resolveAgentsMdPath(appId, config) {
|
|
7959
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
7960
|
+
if (!feishu) {
|
|
7961
|
+
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
7962
|
+
return null;
|
|
7963
|
+
}
|
|
7964
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
7965
|
+
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
7966
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
7967
|
+
}
|
|
7968
|
+
const accounts = asRecord(feishu.accounts);
|
|
7969
|
+
if (!accounts) {
|
|
7970
|
+
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
7971
|
+
return null;
|
|
7972
|
+
}
|
|
7973
|
+
let accountId;
|
|
7974
|
+
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
7975
|
+
accountId = key;
|
|
7976
|
+
break;
|
|
7977
|
+
}
|
|
7978
|
+
if (!accountId) {
|
|
7979
|
+
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
7980
|
+
return null;
|
|
7981
|
+
}
|
|
7982
|
+
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
7983
|
+
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
7984
|
+
let agentId;
|
|
7985
|
+
for (const b of bindings) {
|
|
7986
|
+
const binding = asRecord(b);
|
|
7987
|
+
if (!binding) continue;
|
|
7988
|
+
const match = asRecord(binding.match);
|
|
7989
|
+
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
7990
|
+
if (typeof binding.agentId === "string") {
|
|
7991
|
+
agentId = binding.agentId;
|
|
7992
|
+
break;
|
|
7993
|
+
}
|
|
7994
|
+
}
|
|
7995
|
+
}
|
|
7996
|
+
if (!agentId) {
|
|
7997
|
+
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
7998
|
+
return null;
|
|
7999
|
+
}
|
|
8000
|
+
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
8001
|
+
if (agentId === "main") {
|
|
8002
|
+
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
8003
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
8004
|
+
}
|
|
8005
|
+
const agentsObj = asRecord(config.agents);
|
|
8006
|
+
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
8007
|
+
for (const a of list) {
|
|
8008
|
+
const agent = asRecord(a);
|
|
8009
|
+
if (agent?.id === agentId) {
|
|
8010
|
+
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
8011
|
+
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
8012
|
+
return node_path.default.join(ws, "AGENTS.md");
|
|
8013
|
+
}
|
|
8014
|
+
}
|
|
8015
|
+
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
8016
|
+
return null;
|
|
8017
|
+
}
|
|
8018
|
+
function appendPeToAgentsMd(agentsMdPath) {
|
|
8019
|
+
const dir = node_path.default.dirname(agentsMdPath);
|
|
8020
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
8021
|
+
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
8022
|
+
if (existing.includes(`<${PE_XML_TAG}>`)) {
|
|
8023
|
+
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
8024
|
+
return;
|
|
8025
|
+
}
|
|
8026
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
8027
|
+
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
8028
|
+
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
8029
|
+
}
|
|
8030
|
+
function runLarkCliInit(opts) {
|
|
8031
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
8032
|
+
if (!isLarkPluginInstalled(configPath)) {
|
|
8033
|
+
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
8034
|
+
return {
|
|
8035
|
+
ok: true,
|
|
8036
|
+
skipped: true,
|
|
8037
|
+
skipReason: "openclaw-lark plugin not installed"
|
|
8038
|
+
};
|
|
8039
|
+
}
|
|
8040
|
+
if (!isLarkCliAvailable()) {
|
|
8041
|
+
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
8042
|
+
return {
|
|
8043
|
+
ok: true,
|
|
8044
|
+
skipped: true,
|
|
8045
|
+
skipReason: "lark-cli command not found"
|
|
8046
|
+
};
|
|
8047
|
+
}
|
|
8048
|
+
const config = readConfig(configPath);
|
|
8049
|
+
if (!config) return {
|
|
8050
|
+
ok: false,
|
|
8051
|
+
error: `could not read config at ${configPath}`
|
|
8052
|
+
};
|
|
8053
|
+
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
8054
|
+
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
8055
|
+
if (!agentsMdPath) return {
|
|
8056
|
+
ok: false,
|
|
8057
|
+
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
8058
|
+
};
|
|
8059
|
+
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
8060
|
+
if (!appSecret) return {
|
|
8061
|
+
ok: false,
|
|
8062
|
+
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
8063
|
+
};
|
|
8064
|
+
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
8065
|
+
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
8066
|
+
"config",
|
|
8067
|
+
"init",
|
|
8068
|
+
"--name",
|
|
8069
|
+
opts.appId,
|
|
8070
|
+
"--app-id",
|
|
8071
|
+
opts.appId,
|
|
8072
|
+
"--brand",
|
|
8073
|
+
"feishu",
|
|
8074
|
+
"--app-secret-stdin",
|
|
8075
|
+
"--force-init"
|
|
8076
|
+
], {
|
|
8077
|
+
stdio: [
|
|
8078
|
+
"pipe",
|
|
8079
|
+
"pipe",
|
|
8080
|
+
"pipe"
|
|
8081
|
+
],
|
|
8082
|
+
encoding: "utf-8",
|
|
8083
|
+
input: appSecret
|
|
8084
|
+
});
|
|
8085
|
+
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
8086
|
+
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
8087
|
+
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
8088
|
+
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
8089
|
+
if (initRes.error) return {
|
|
8090
|
+
ok: false,
|
|
8091
|
+
configInitStdout,
|
|
8092
|
+
configInitStderr,
|
|
8093
|
+
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
8094
|
+
};
|
|
8095
|
+
if (initRes.status !== 0) return {
|
|
8096
|
+
ok: false,
|
|
8097
|
+
configInitExitCode: initRes.status ?? void 0,
|
|
8098
|
+
configInitStdout,
|
|
8099
|
+
configInitStderr,
|
|
8100
|
+
error: `lark-cli config init exited with code ${initRes.status}`
|
|
8101
|
+
};
|
|
8102
|
+
appendPeToAgentsMd(agentsMdPath);
|
|
8103
|
+
return {
|
|
8104
|
+
ok: true,
|
|
8105
|
+
configInitExitCode: 0,
|
|
8106
|
+
agentsMdPath
|
|
8107
|
+
};
|
|
8108
|
+
}
|
|
8109
|
+
//#endregion
|
|
8110
|
+
//#region src/innerapi/reportCliRun.ts
|
|
8111
|
+
/**
|
|
8112
|
+
* CLI-side client for studio_server's `openclaw.report_cli_run` inner
|
|
8113
|
+
* API — the unified telemetry sink for every CLI command.
|
|
8114
|
+
*
|
|
8115
|
+
* Best-effort: gated by `DoctorCtx.telemetry.reportCliRun` returned by
|
|
8116
|
+
* `get_doctor_ctx`. When the server hasn't rolled out the API yet the
|
|
8117
|
+
* flag is absent and the CLI never invokes this. When enabled, the CLI
|
|
8191
8118
|
* fires-and-forgets one report per command after work is done.
|
|
8192
8119
|
*
|
|
8193
8120
|
* Failures here MUST NOT change exit code or otherwise affect the
|
|
@@ -8259,7 +8186,7 @@ async function reportCliRun(opts) {
|
|
|
8259
8186
|
//#region src/help.ts
|
|
8260
8187
|
const BIN = "mclaw-diagnose";
|
|
8261
8188
|
function versionBanner() {
|
|
8262
|
-
return `v0.1.7
|
|
8189
|
+
return `v0.1.7`;
|
|
8263
8190
|
}
|
|
8264
8191
|
const COMMANDS = [
|
|
8265
8192
|
{
|
|
@@ -8477,37 +8404,6 @@ OPTIONS
|
|
|
8477
8404
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
8478
8405
|
--ctx=<base64> Opaque ctx; see install-openclaw for semantics.
|
|
8479
8406
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
8480
|
-
`
|
|
8481
|
-
},
|
|
8482
|
-
{
|
|
8483
|
-
name: "install-clis",
|
|
8484
|
-
hidden: true,
|
|
8485
|
-
summary: "Install CLI package(s) (role=cli) from the manifest",
|
|
8486
|
-
help: `USAGE
|
|
8487
|
-
${BIN} install-clis <tag> --cli=<name>... [options]
|
|
8488
|
-
|
|
8489
|
-
DESCRIPTION
|
|
8490
|
-
Downloads + installs one or more CLI tarballs (role=cli in the manifest)
|
|
8491
|
-
into <home_base>/<pkg.installPath>. Currently the only CLI in this
|
|
8492
|
-
category is lark-cli (@larksuite/cli); openclaw itself is handled by the
|
|
8493
|
-
dedicated install-openclaw command.
|
|
8494
|
-
|
|
8495
|
-
Each package is first extracted into a temporary directory (os.tmpdir(),
|
|
8496
|
-
typically tmpfs) to avoid overlayfs race conditions, then ferried to a
|
|
8497
|
-
sibling .new directory and atomically swapped into the final target path.
|
|
8498
|
-
|
|
8499
|
-
ARGUMENTS
|
|
8500
|
-
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
8501
|
-
|
|
8502
|
-
OPTIONS
|
|
8503
|
-
--cli=<name> CLI package to install by short name or scoped
|
|
8504
|
-
packageName (repeatable, at least one required).
|
|
8505
|
-
--home_base=<dir> Override the /home/gem base (tests).
|
|
8506
|
-
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
8507
|
-
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
8508
|
-
|
|
8509
|
-
EXAMPLES
|
|
8510
|
-
${BIN} install-clis 2026.4.11 --cli=lark-cli
|
|
8511
8407
|
`
|
|
8512
8408
|
},
|
|
8513
8409
|
{
|
|
@@ -8557,6 +8453,39 @@ OPTIONS
|
|
|
8557
8453
|
EXIT CODES
|
|
8558
8454
|
0 Success or skipped (prerequisites not met).
|
|
8559
8455
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
8456
|
+
`
|
|
8457
|
+
},
|
|
8458
|
+
{
|
|
8459
|
+
name: "rules",
|
|
8460
|
+
hidden: true,
|
|
8461
|
+
summary: "List all registered rule metadata as JSON",
|
|
8462
|
+
help: `USAGE
|
|
8463
|
+
${BIN} rules [--rule=<key>]...
|
|
8464
|
+
|
|
8465
|
+
DESCRIPTION
|
|
8466
|
+
Prints a JSON array of serialisable metadata for every registered rule,
|
|
8467
|
+
topologically sorted by dependsOn. Useful for tooling, dashboards, and
|
|
8468
|
+
analysis scripts that need a stable machine-readable view of the rule
|
|
8469
|
+
registry.
|
|
8470
|
+
|
|
8471
|
+
Each element has the shape:
|
|
8472
|
+
{
|
|
8473
|
+
"key": "gateway",
|
|
8474
|
+
"description": "...",
|
|
8475
|
+
"repairMode": "standard" | "user-confirm" | "ai" | "reset" | "check-only",
|
|
8476
|
+
"profile": "standard" | "experimental",
|
|
8477
|
+
"dependsOn": ["config_syntax_check"],
|
|
8478
|
+
"hasSkipWhen": false
|
|
8479
|
+
}
|
|
8480
|
+
|
|
8481
|
+
OPTIONS
|
|
8482
|
+
--rule=<key> Filter output to specific rule keys (repeatable). Returns
|
|
8483
|
+
all rules when omitted.
|
|
8484
|
+
|
|
8485
|
+
EXAMPLES
|
|
8486
|
+
${BIN} rules # all rules
|
|
8487
|
+
${BIN} rules --rule=gateway # single rule
|
|
8488
|
+
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
8560
8489
|
`
|
|
8561
8490
|
},
|
|
8562
8491
|
{
|
|
@@ -10003,7 +9932,7 @@ function parseCtxFlag(args) {
|
|
|
10003
9932
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
10004
9933
|
*/
|
|
10005
9934
|
function getPositionalTag(args, modeName) {
|
|
10006
|
-
return args.find((a, i) => i > 0 && !a.startsWith("
|
|
9935
|
+
return args.find((a, i) => i > 0 && !a.startsWith("--") && a !== modeName);
|
|
10007
9936
|
}
|
|
10008
9937
|
function getFlag(args, name) {
|
|
10009
9938
|
const prefix = `--${name}=`;
|
|
@@ -10344,64 +10273,6 @@ async function main() {
|
|
|
10344
10273
|
if (error) throw error;
|
|
10345
10274
|
break;
|
|
10346
10275
|
}
|
|
10347
|
-
case "install-clis": {
|
|
10348
|
-
const tag = getPositionalTag(args, "install-clis");
|
|
10349
|
-
if (!tag) {
|
|
10350
|
-
console.error("Usage: install-clis <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
10351
|
-
node_process.default.exit(1);
|
|
10352
|
-
}
|
|
10353
|
-
const names = getMultiFlag(args, "cli");
|
|
10354
|
-
const homeBase = getFlag(args, "home_base");
|
|
10355
|
-
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
10356
|
-
let installOssFileMap;
|
|
10357
|
-
let rawForTelemetry;
|
|
10358
|
-
if (!ossFileMapFlag) {
|
|
10359
|
-
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10360
|
-
populate: planCtxPopulate({ command: "install" }),
|
|
10361
|
-
caller,
|
|
10362
|
-
traceId
|
|
10363
|
-
});
|
|
10364
|
-
installOssFileMap = normalizeCtx(rawForTelemetry).install.ossFileMap;
|
|
10365
|
-
}
|
|
10366
|
-
const ossFileMap = resolveOssFileMap({
|
|
10367
|
-
ossFileMapFlag,
|
|
10368
|
-
installOssFileMap
|
|
10369
|
-
});
|
|
10370
|
-
console.error(`[install-clis] ossFileMap=${JSON.stringify(ossFileMap)}`);
|
|
10371
|
-
let feishuAppSecret;
|
|
10372
|
-
if (names.includes("lark-cli")) feishuAppSecret = normalizeCtx(await fetchCtxViaInnerApi({
|
|
10373
|
-
populate: { app: ["feishuAppSecret"] },
|
|
10374
|
-
caller,
|
|
10375
|
-
traceId
|
|
10376
|
-
})).app.feishuAppSecret || void 0;
|
|
10377
|
-
let success = true;
|
|
10378
|
-
let error;
|
|
10379
|
-
try {
|
|
10380
|
-
await installClis(tag, ossFileMap, {
|
|
10381
|
-
names,
|
|
10382
|
-
homeBase,
|
|
10383
|
-
feishuAppSecret
|
|
10384
|
-
});
|
|
10385
|
-
} catch (e) {
|
|
10386
|
-
success = false;
|
|
10387
|
-
error = e;
|
|
10388
|
-
}
|
|
10389
|
-
if (success) console.log(JSON.stringify({ ok: true }));
|
|
10390
|
-
await reportRun("install-clis", rc, rawForTelemetry, args.join(" "), Date.now() - t0, {
|
|
10391
|
-
success,
|
|
10392
|
-
result: {
|
|
10393
|
-
tag,
|
|
10394
|
-
names
|
|
10395
|
-
},
|
|
10396
|
-
error
|
|
10397
|
-
}, {
|
|
10398
|
-
scene,
|
|
10399
|
-
profile,
|
|
10400
|
-
fix: false
|
|
10401
|
-
});
|
|
10402
|
-
if (error) throw error;
|
|
10403
|
-
break;
|
|
10404
|
-
}
|
|
10405
10276
|
case "download-resource": {
|
|
10406
10277
|
const tag = getPositionalTag(args, "download-resource");
|
|
10407
10278
|
if (!tag) {
|
|
@@ -10460,6 +10331,12 @@ async function main() {
|
|
|
10460
10331
|
if (error) throw error;
|
|
10461
10332
|
break;
|
|
10462
10333
|
}
|
|
10334
|
+
case "rules": {
|
|
10335
|
+
const keys = getMultiFlag(args, "rule");
|
|
10336
|
+
const metas = getRuleMetas(keys.length > 0 ? keys : void 0);
|
|
10337
|
+
console.log(JSON.stringify(metas));
|
|
10338
|
+
break;
|
|
10339
|
+
}
|
|
10463
10340
|
case "lark-cli-init": {
|
|
10464
10341
|
const appId = getFlag(args, "app-id");
|
|
10465
10342
|
if (!appId) {
|