@vibe-lark/larkpal 0.1.89 → 0.1.91-issue68.0

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.
Files changed (112) hide show
  1. package/dist/cli.mjs +92 -7
  2. package/dist/interactive-init-DVk_q9Yz.mjs +362 -0
  3. package/dist/lark-cli-provider-7LvtOIF_.mjs +554 -0
  4. package/dist/lark-logger-D7_pEVQc.mjs +143 -0
  5. package/dist/live-smoke-DsR13pI9.mjs +7104 -0
  6. package/dist/main.mjs +18746 -480
  7. package/dist/mcp-server.mjs +520 -4
  8. package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
  9. package/package.json +2 -4
  10. package/dist/account-id-BM1T6029-BvcZ99G0.mjs +0 -1
  11. package/dist/activation-context--5yu1dPF-DHM2x52-.mjs +0 -1
  12. package/dist/anthropic-gnoZ_QRy.mjs +0 -39
  13. package/dist/assistant-visible-text-DuFWokAt-GbRm46te.mjs +0 -9
  14. package/dist/azure-openai-responses-DbHzq7dt.mjs +0 -1
  15. package/dist/boundary-file-read-BSd9MXPy-Tpd4W7aA.mjs +0 -1
  16. package/dist/browser-lifecycle-cleanup-BdNsMhMD-Bjf5GI3L.mjs +0 -1
  17. package/dist/build-DSSm_dj2.mjs +0 -3
  18. package/dist/bundled-compat-BpaDaAAu-DElY7pj-.mjs +0 -27
  19. package/dist/bundled-dir-U738ay4K-DxHVp4u1.mjs +0 -1
  20. package/dist/call-DimWbpgJ-BnglLl-j.mjs +0 -20
  21. package/dist/channel-bootstrap.runtime-BVclcGv1-BH-3XviM.mjs +0 -1
  22. package/dist/channel-bootstrap.runtime-Cwcr38m2-K7QN0kxA.mjs +0 -1
  23. package/dist/chunk-DHbaKqs-.mjs +0 -1
  24. package/dist/command-format-LSnUCVVF-Ck0yuZRk.mjs +0 -16
  25. package/dist/config-Bim2pi8a-xbw-saVR.mjs +0 -1
  26. package/dist/config-CtxZK_ZE.mjs +0 -2
  27. package/dist/delivery-context.shared-j5hC5qb1-DIY5v1oC.mjs +0 -1
  28. package/dist/diagnostic-ClMwNnUA-BLmizo4w.mjs +0 -1
  29. package/dist/dist-BUE_cxIa.mjs +0 -5
  30. package/dist/dist-Drhg3yZT.mjs +0 -601
  31. package/dist/env-api-keys-qlOAYu08.mjs +0 -1
  32. package/dist/event-stream-JP51qlT7.mjs +0 -1
  33. package/dist/facade-loader-DFzIYYt_--SN2-DQM.mjs +0 -1
  34. package/dist/from-BmSefSMV.mjs +0 -14
  35. package/dist/gateway-method-policy-C7rf0SeV-DXbYlbpG.mjs +0 -1
  36. package/dist/github-copilot-headers-BQ2T2OVX.mjs +0 -1
  37. package/dist/google-BdoIr_xq.mjs +0 -1
  38. package/dist/google-gemini-cli-H9jO3Mvn.mjs +0 -2
  39. package/dist/google-shared-DDK4NMAJ.mjs +0 -301
  40. package/dist/google-vertex-B_OTGKnl.mjs +0 -1
  41. package/dist/hash-DtceYjXb.mjs +0 -1
  42. package/dist/headers-KsIWdB9S.mjs +0 -1
  43. package/dist/home-dir-C44RaPME-BqOMy1pu.mjs +0 -1
  44. package/dist/ids-BPsM98Uu-DGJB5aAM.mjs +0 -1
  45. package/dist/image-2w3cApg7-B_-Jsk_m.mjs +0 -3
  46. package/dist/interactive-init-CzbT0dDB.mjs +0 -8
  47. package/dist/io-CH2g3vsv-BevbMdi6.mjs +0 -10
  48. package/dist/jiti-loader-cache-Bat69wam-Cq2EONsH.mjs +0 -9
  49. package/dist/json-file-BQRNuxmK-DQHpAzI5.mjs +0 -1
  50. package/dist/json-parse-BJPqxncf.mjs +0 -3
  51. package/dist/jws-EKtyH_je.mjs +0 -12
  52. package/dist/lark-cli-provider-DrGmcwiB.mjs +0 -4
  53. package/dist/lark-logger-xpMKNNtY.mjs +0 -1
  54. package/dist/live-smoke-Be7Fzqj0.mjs +0 -33
  55. package/dist/load-context-mb7uvLSn-C6DFwEWN.mjs +0 -1
  56. package/dist/loader-708jrx4Y-eFvaGnCt.mjs +0 -4
  57. package/dist/manifest-registry-_hxKCS2K-ezh_x2Cu.mjs +0 -15
  58. package/dist/message-channel-Bk_YHfQg-BGBJVvRK.mjs +0 -1
  59. package/dist/message-channel-core-DTA--Gjh-DeWCDZu1.mjs +0 -1
  60. package/dist/message.config.runtime-K-k4XOyq-JWPtRES_.mjs +0 -1
  61. package/dist/message.gateway.runtime-DqHOHRTQ-BeYUdgSg.mjs +0 -1
  62. package/dist/mistral-zWGv0zHS.mjs +0 -7
  63. package/dist/model-auth-env-oyg2Hexc-B5WQGZqf.mjs +0 -1
  64. package/dist/models-config-BmpSQJQF-Bwnt6IgA.mjs +0 -1
  65. package/dist/models-config.runtime-DkQfNKqN.mjs +0 -1
  66. package/dist/multipart-parser-D_0C82Yv.mjs +0 -2
  67. package/dist/oauth-TYJ7I-Cs.mjs +0 -75
  68. package/dist/openai-D-MIrxrj.mjs +0 -16
  69. package/dist/openai-codex-responses-CJA9360v.mjs +0 -7
  70. package/dist/openai-completions-BfXo7VuS.mjs +0 -5
  71. package/dist/openai-responses-BH-GaE-g.mjs +0 -1
  72. package/dist/openai-responses-shared-DHm7pNQU.mjs +0 -10
  73. package/dist/paths-D6msg0S1-C3GtW9t2.mjs +0 -1
  74. package/dist/paths-D92DjaZ--CKAvIWoZ.mjs +0 -1
  75. package/dist/pi-model-discovery-Bd0TWzo8-DcalEcic.mjs +0 -1
  76. package/dist/pi-model-discovery-runtime-DnW_CaR2.mjs +0 -1
  77. package/dist/pi-tools.before-tool-call.runtime-Bn7uqKie-CitpH_Lv.mjs +0 -2
  78. package/dist/plugin-auto-enable-DdH3n9V8-Dd9H4luQ.mjs +0 -1
  79. package/dist/process-scoped-map-B2cWkYET-Bc7DvrUZ.mjs +0 -1
  80. package/dist/provider-discovery.runtime-CVft4TR0.mjs +0 -1
  81. package/dist/provider-model-compat-CFqcq0it-W6mE-T-j.mjs +0 -1
  82. package/dist/provider-runtime-C8rljsNs-BGpzSHUk.mjs +0 -1
  83. package/dist/provider-runtime-CT50mNIX-CC3WVtfL.mjs +0 -79
  84. package/dist/provider-stream-COLujAAo-BtNcIBP4.mjs +0 -248
  85. package/dist/providers.runtime-Ba7tOlq7-CX4GoaJL.mjs +0 -1
  86. package/dist/public-surface-loader-BVl0NQAE-CTXfgfHE.mjs +0 -1
  87. package/dist/resolve-C33B7eeu-DhMVJCKm.mjs +0 -1
  88. package/dist/runtime-Cym-fqI8-BuiEWXF-.mjs +0 -1
  89. package/dist/runtime-channel-state-CohMybAh-DfCjeyJq.mjs +0 -1
  90. package/dist/runtime-snapshot-C54O1-gI-0r2omPvJ.mjs +0 -1
  91. package/dist/runtime-state-IBOIXKOx-Cr97WVOr.mjs +0 -1
  92. package/dist/runtime-web-tools-fallback.runtime-Ca-qn1mj-C5hD_aI-.mjs +0 -1
  93. package/dist/runtime-web-tools-manifest.runtime-BgxQTjmq-BTi3YQxK.mjs +0 -1
  94. package/dist/runtime-web-tools-public-artifacts.runtime-D9qcoWIX-DNaDY4zG.mjs +0 -1
  95. package/dist/session-archive.runtime-B52_5vs4-CUKEEsvr.mjs +0 -1
  96. package/dist/session-key-Cn2QoOyX-BIh-9Y77.mjs +0 -1
  97. package/dist/session-transcript-files.fs-DEeFXruQ-Dh_OJado.mjs +0 -1
  98. package/dist/src-5lm1WxWm.mjs +0 -5
  99. package/dist/src-C9CsRdwI.mjs +0 -3
  100. package/dist/subagent-announce-L5WsQatW-DyZFJvpK.mjs +0 -2
  101. package/dist/subagent-announce.registry.runtime-DiqZyeTs-WCL-itg8.mjs +0 -1
  102. package/dist/subagent-registry-steer-runtime-DFgZFC0j-BlxUMPcM.mjs +0 -1
  103. package/dist/subagent-system-prompt-C1qnN--K-bhNPmt0u.mjs +0 -23
  104. package/dist/target-parsing-loaded-DKkb6jJY-Bx7-SykE.mjs +0 -12
  105. package/dist/task-registry-delivery-runtime-Dd1CUkZF-xNioBd-k.mjs +0 -16
  106. package/dist/task-registry-delivery-runtime-ragbeVx5-BXczubuw.mjs +0 -1
  107. package/dist/thinking-Bh-7MqXz-BB7ZJReA.mjs +0 -1
  108. package/dist/transcript-E_-ISOkD-STFNoSGx.mjs +0 -5
  109. package/dist/transcript.runtime-BqhvZZdl-BLeKq_RO.mjs +0 -1
  110. package/dist/transform-messages-B6RF0Czv.mjs +0 -1
  111. package/dist/web-provider-public-artifacts-BlpRwIUS-CjacPoGF.mjs +0 -1
  112. package/dist/web-search-providers.runtime--0aUtC3b-EcJfEKJo.mjs +0 -1
package/dist/cli.mjs CHANGED
@@ -1,7 +1,37 @@
1
- import{readFileSync as e}from"node:fs";import{dirname as t,join as n}from"node:path";import{fileURLToPath as r}from"node:url";const i=`\x1B[2m`,a=`\x1B[1m`,o=`\x1B[33m`,s=`\x1B[0m`;function c(){try{let i=n(t(r(import.meta.url)),`..`,`package.json`);return JSON.parse(e(i,`utf-8`)).version??`unknown`}catch{return`unknown`}}function l(){let e=c();console.log(`
2
- ${a}LarkPal${s} ${i}v${e}${s} 飞书 AI Bot 服务
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ //#region src/cli.ts
5
+ /**
6
+ * LarkPal CLI 入口 — 子命令路由
7
+ *
8
+ * 用法:
9
+ * larkpal 启动服务(凭证未配置时自动进入初始化流程)
10
+ * larkpal init 交互式初始化向导
11
+ * larkpal --help 显示帮助信息
12
+ * larkpal --version 显示版本号
13
+ *
14
+ * 不依赖 commander / yargs 等外部包,仅使用 process.argv 解析。
15
+ */
16
+ const CYAN = "\x1B[36m";
17
+ const DIM = "\x1B[2m";
18
+ const BOLD = "\x1B[1m";
19
+ const YELLOW = "\x1B[33m";
20
+ const RESET = "\x1B[0m";
21
+ function getVersion() {
22
+ try {
23
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
24
+ return JSON.parse(readFileSync(pkgPath, "utf-8")).version ?? "unknown";
25
+ } catch {
26
+ return "unknown";
27
+ }
28
+ }
29
+ function printHelp() {
30
+ const version = getVersion();
31
+ console.log(`
32
+ ${BOLD}LarkPal${RESET} ${DIM}v${version}${RESET} — 飞书 AI Bot 服务
3
33
 
4
- ${a}用法:${s}
34
+ ${BOLD}用法:${RESET}
5
35
  larkpal 启动服务
6
36
  larkpal init 交互式初始化向导
7
37
  larkpal meeting-live-smoke --meeting-number <number>
@@ -16,13 +46,68 @@ ${a}用法:${s}
16
46
  larkpal --help 显示帮助信息
17
47
  larkpal --version 显示版本号
18
48
 
19
- ${a}环境变量:${s}
49
+ ${BOLD}环境变量:${RESET}
20
50
  LARK_APP_ID 飞书应用 App ID(可选,覆盖 lark-cli 配置)
21
51
  LARK_APP_SECRET 飞书应用 App Secret(可选,覆盖 lark-cli 配置)
22
52
  LARK_USER_ACCESS_TOKEN 飞书用户访问令牌(会议实验能力需要)
23
53
  LARKPAL_WORKSPACE 工作目录根路径(默认 ~/.larkpal/workspace)
24
54
  CLAUDE_MODEL Claude 模型名称(如 ark-code-latest)
25
55
 
26
- ${i}首次使用?运行 larkpal init${i} 开始配置。${s}
27
- `)}async function u(){let e=process.argv.slice(2),t=e[0];if(t===`--version`||t===`-v`){console.log(c());return}if(t===`--help`||t===`-h`){l();return}if(t===`init`){let{runInteractiveInit:e}=await import(`./interactive-init-CzbT0dDB.mjs`),t=await e();process.exit(t)}if(t===`meeting-live-smoke`){let{runMeetingLiveSmokeCli:t}=await import(`./live-smoke-Be7Fzqj0.mjs`).then(e=>e.t);await t(e.slice(1));return}if(t===`meeting-live-teammate`){let{runMeetingLiveTeammateCli:t}=await import(`./live-smoke-Be7Fzqj0.mjs`).then(e=>e.t);await t(e.slice(1));return}if(t===`meeting-live-check`){let{runMeetingLiveCheckCli:t}=await import(`./live-smoke-Be7Fzqj0.mjs`).then(e=>e.t),n=await t(e.slice(1));process.exit(n)}if(t&&!t.startsWith(`-`)&&(console.log(`${o}未知命令: ${t}${s}\n`),l(),process.exit(1)),!(process.env.LARKPAL_HEADLESS===`1`||process.env.LARKPAL_HEADLESS===`true`)){let{LarkCliCredentialProvider:e}=await import(`./lark-cli-provider-DrGmcwiB.mjs`).then(e=>e.n);if(!e.isConfigured()){console.log(`\n${o}⚠️ 飞书应用凭证未配置${s}\n`),console.log(` LarkPal 需要飞书应用凭证才能启动。正在进入初始化向导...
28
- `);let{runInteractiveInit:e}=await import(`./interactive-init-CzbT0dDB.mjs`),t=await e();t!==0&&process.exit(t),console.log(`${i}凭证配置完成,正在启动服务...${s}\n`)}}await import(`./main.mjs`)}u().catch(e=>{console.error(`LarkPal CLI 启动失败:`,e),process.exit(1)});export{};
56
+ ${DIM}首次使用?运行 ${CYAN}larkpal init${DIM} 开始配置。${RESET}
57
+ `);
58
+ }
59
+ async function cli() {
60
+ const args = process.argv.slice(2);
61
+ const command = args[0];
62
+ if (command === "--version" || command === "-v") {
63
+ console.log(getVersion());
64
+ return;
65
+ }
66
+ if (command === "--help" || command === "-h") {
67
+ printHelp();
68
+ return;
69
+ }
70
+ if (command === "init") {
71
+ const { runInteractiveInit } = await import("./interactive-init-DVk_q9Yz.mjs");
72
+ const exitCode = await runInteractiveInit();
73
+ process.exit(exitCode);
74
+ }
75
+ if (command === "meeting-live-smoke") {
76
+ const { runMeetingLiveSmokeCli } = await import("./live-smoke-DsR13pI9.mjs").then((n) => n.t);
77
+ await runMeetingLiveSmokeCli(args.slice(1));
78
+ return;
79
+ }
80
+ if (command === "meeting-live-teammate") {
81
+ const { runMeetingLiveTeammateCli } = await import("./live-smoke-DsR13pI9.mjs").then((n) => n.t);
82
+ await runMeetingLiveTeammateCli(args.slice(1));
83
+ return;
84
+ }
85
+ if (command === "meeting-live-check") {
86
+ const { runMeetingLiveCheckCli } = await import("./live-smoke-DsR13pI9.mjs").then((n) => n.t);
87
+ const exitCode = await runMeetingLiveCheckCli(args.slice(1));
88
+ process.exit(exitCode);
89
+ }
90
+ if (command && !command.startsWith("-")) {
91
+ console.log(`${YELLOW}未知命令: ${command}${RESET}\n`);
92
+ printHelp();
93
+ process.exit(1);
94
+ }
95
+ if (!(process.env.LARKPAL_HEADLESS === "1" || process.env.LARKPAL_HEADLESS === "true")) {
96
+ const { LarkCliCredentialProvider } = await import("./lark-cli-provider-7LvtOIF_.mjs").then((n) => n.n);
97
+ if (!LarkCliCredentialProvider.isConfigured()) {
98
+ console.log(`\n${YELLOW}⚠️ 飞书应用凭证未配置${RESET}\n`);
99
+ console.log(` LarkPal 需要飞书应用凭证才能启动。正在进入初始化向导...\n`);
100
+ const { runInteractiveInit } = await import("./interactive-init-DVk_q9Yz.mjs");
101
+ const exitCode = await runInteractiveInit();
102
+ if (exitCode !== 0) process.exit(exitCode);
103
+ console.log(`${DIM}凭证配置完成,正在启动服务...${RESET}\n`);
104
+ }
105
+ }
106
+ await import("./main.mjs");
107
+ }
108
+ cli().catch((err) => {
109
+ console.error("LarkPal CLI 启动失败:", err);
110
+ process.exit(1);
111
+ });
112
+ //#endregion
113
+ export {};
@@ -0,0 +1,362 @@
1
+ import { t as larkLogger } from "./lark-logger-D7_pEVQc.mjs";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import { execSync, spawn } from "node:child_process";
6
+ import { createInterface } from "node:readline";
7
+ import * as Lark from "@larksuiteoapi/node-sdk";
8
+ //#region src/credentials/app-registration.ts
9
+ async function registerFeishuAppCredentials(options) {
10
+ const result = await (options.registerApp ?? defaultRegisterApp)({
11
+ source: options.source ?? "larkpal",
12
+ signal: options.signal,
13
+ onQRCodeReady: options.onQRCodeReady,
14
+ onStatusChange: options.onStatusChange
15
+ });
16
+ if (!result.client_id || !result.client_secret) throw new Error("飞书应用创建完成但未返回 App ID 或 App Secret");
17
+ await options.saveCredentials({
18
+ appId: result.client_id,
19
+ appSecret: result.client_secret
20
+ });
21
+ return {
22
+ appId: result.client_id,
23
+ appSecret: result.client_secret,
24
+ creatorOpenId: result.user_info?.open_id,
25
+ tenantBrand: result.user_info?.tenant_brand
26
+ };
27
+ }
28
+ async function defaultRegisterApp(options) {
29
+ return Lark.registerApp(options);
30
+ }
31
+ //#endregion
32
+ //#region src/init/prerequisites.ts
33
+ function assessInitPrerequisites(deps) {
34
+ const larkCli = deps.detectCli("lark-cli");
35
+ const runtimeCliName = deps.runtimeName === "codex-app-server" ? "codex" : "claude";
36
+ const runtimeCli = deps.detectCli(runtimeCliName);
37
+ const warnings = [];
38
+ if (!runtimeCli.installed) warnings.push(runtimeCliName === "codex" ? "OpenAI Codex CLI is not installed. Feishu app credentials can still be configured now, but service startup with LARKPAL_RUNTIME=codex-app-server will require codex later." : "Claude Code is not installed. Feishu app credentials can still be configured now, but service startup with the default runtime will require Claude Code later.");
39
+ if (!larkCli.installed) warnings.push("lark-cli is not installed. Basic Feishu bot chat can be configured with app credentials, but execution-layer tools for documents, calendars, mail, tasks, and personal data will require lark-cli later.");
40
+ return {
41
+ canConfigureFeishuCredentials: true,
42
+ larkCli: {
43
+ ...larkCli,
44
+ ready: larkCli.installed,
45
+ requiredForBasicChat: false
46
+ },
47
+ runtime: {
48
+ ...runtimeCli,
49
+ ready: runtimeCli.installed,
50
+ name: deps.runtimeName,
51
+ cliName: runtimeCliName
52
+ },
53
+ warnings
54
+ };
55
+ }
56
+ //#endregion
57
+ //#region src/init/interactive-init.ts
58
+ /**
59
+ * 交互式初始化流程
60
+ *
61
+ * 引导用户完成 LarkPal 首次配置:
62
+ * Step 1: 检查前置依赖(claude、lark-cli)
63
+ * Step 2: 配置飞书应用凭证(lark-cli config init + 补充 App Secret)
64
+ * Step 3: 打印成功信息
65
+ *
66
+ * 仅使用 Node.js 内置模块,不依赖第三方包。
67
+ */
68
+ const log = larkLogger("init");
69
+ const CYAN = "\x1B[36m";
70
+ const GREEN = "\x1B[32m";
71
+ const YELLOW = "\x1B[33m";
72
+ const RED = "\x1B[31m";
73
+ const BOLD = "\x1B[1m";
74
+ const DIM = "\x1B[2m";
75
+ const RESET = "\x1B[0m";
76
+ /** LarkPal 自身配置目录 */
77
+ const LARKPAL_CONFIG_DIR = join(homedir(), ".larkpal");
78
+ /** LarkPal 凭证文件(存储 appId + appSecret) */
79
+ const LARKPAL_CREDENTIALS_PATH = join(LARKPAL_CONFIG_DIR, "credentials.json");
80
+ /** readline 问答封装 */
81
+ function ask(rl, question) {
82
+ return new Promise((resolve) => {
83
+ rl.question(question, (answer) => resolve(answer.trim()));
84
+ });
85
+ }
86
+ /** 检测 CLI 工具是否已安装 */
87
+ function isCliInstalled(name) {
88
+ try {
89
+ return !!execSync(`which ${name}`, {
90
+ encoding: "utf-8",
91
+ timeout: 5e3
92
+ }).trim();
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+ /** 通过 lark-cli config show 获取当前配置的 appId */
98
+ function getAppIdFromLarkCli() {
99
+ try {
100
+ const jsonMatch = execSync("lark-cli config show", {
101
+ encoding: "utf-8",
102
+ timeout: 1e4,
103
+ stdio: [
104
+ "pipe",
105
+ "pipe",
106
+ "pipe"
107
+ ]
108
+ }).match(/\{[\s\S]*\}/);
109
+ if (jsonMatch) return JSON.parse(jsonMatch[0]).appId || null;
110
+ } catch {}
111
+ return null;
112
+ }
113
+ /** 写入凭证到 LarkPal 配置文件 */
114
+ function writeCredentials(appId, appSecret) {
115
+ try {
116
+ if (!existsSync(LARKPAL_CONFIG_DIR)) {
117
+ mkdirSync(LARKPAL_CONFIG_DIR, { recursive: true });
118
+ log.info("创建 LarkPal 配置目录", { path: LARKPAL_CONFIG_DIR });
119
+ }
120
+ writeFileSync(LARKPAL_CREDENTIALS_PATH, JSON.stringify({
121
+ app_id: appId,
122
+ app_secret: appSecret
123
+ }, null, 2) + "\n", {
124
+ encoding: "utf-8",
125
+ mode: 384
126
+ });
127
+ log.info("凭证已写入", {
128
+ path: LARKPAL_CREDENTIALS_PATH,
129
+ appId
130
+ });
131
+ return true;
132
+ } catch (err) {
133
+ const msg = err instanceof Error ? err.message : String(err);
134
+ log.error("写入凭证文件失败", {
135
+ path: LARKPAL_CREDENTIALS_PATH,
136
+ error: msg
137
+ });
138
+ return false;
139
+ }
140
+ }
141
+ async function saveCredentialsOrThrow(credentials) {
142
+ if (!writeCredentials(credentials.appId, credentials.appSecret)) throw new Error("凭证保存失败");
143
+ }
144
+ /** 检查 LarkPal 凭证文件是否已有有效凭证 */
145
+ function hasLarkPalCredentials() {
146
+ if (!existsSync(LARKPAL_CREDENTIALS_PATH)) return null;
147
+ try {
148
+ const raw = readFileSync(LARKPAL_CREDENTIALS_PATH, "utf-8");
149
+ const creds = JSON.parse(raw);
150
+ if (creds.app_id && creds.app_secret) return {
151
+ appId: creds.app_id,
152
+ appSecret: creds.app_secret
153
+ };
154
+ } catch {}
155
+ return null;
156
+ }
157
+ function checkPrerequisites() {
158
+ console.log(`\n${BOLD}🔍 检查前置依赖${RESET}\n`);
159
+ const assessment = assessInitPrerequisites({
160
+ runtimeName: process.env.LARKPAL_RUNTIME || "claude-code",
161
+ detectCli: (name) => ({ installed: isCliInstalled(name) })
162
+ });
163
+ if (assessment.runtime.ready) console.log(` ${GREEN}✅ ${assessment.runtime.cliName} 已安装${RESET}`);
164
+ else console.log(` ${YELLOW}⚠️ ${assessment.runtime.cliName} 未安装${RESET}`);
165
+ if (assessment.larkCli.ready) console.log(` ${GREEN}✅ lark-cli 已安装${RESET}`);
166
+ else console.log(` ${YELLOW}⚠️ lark-cli 未安装(基础飞书连接不阻塞)${RESET}`);
167
+ for (const warning of assessment.warnings) console.log(` ${DIM}${warning}${RESET}`);
168
+ console.log();
169
+ }
170
+ /**
171
+ * 通过 lark-cli config init 子进程配置凭证。
172
+ * 返回解析到的 appId(成功)或 null(失败)。
173
+ */
174
+ function runLarkCliConfigInit() {
175
+ return new Promise((resolve) => {
176
+ console.log(`\n${DIM}正在启动 lark-cli config init ...${RESET}\n`);
177
+ log.info("启动子进程: lark-cli config init");
178
+ let capturedOutput = "";
179
+ const child = spawn("lark-cli", ["config", "init"], { stdio: [
180
+ "inherit",
181
+ "pipe",
182
+ "pipe"
183
+ ] });
184
+ child.stdout?.on("data", (chunk) => {
185
+ const text = chunk.toString();
186
+ capturedOutput += text;
187
+ process.stdout.write(text);
188
+ });
189
+ child.stderr?.on("data", (chunk) => {
190
+ const text = chunk.toString();
191
+ capturedOutput += text;
192
+ process.stderr.write(text);
193
+ });
194
+ child.on("error", (err) => {
195
+ log.error("lark-cli config init 启动失败", { error: err.message });
196
+ console.log(`\n${RED}启动 lark-cli config init 失败: ${err.message}${RESET}`);
197
+ resolve(null);
198
+ });
199
+ child.on("close", (code) => {
200
+ log.info("lark-cli config init 执行完毕", {
201
+ exitCode: code,
202
+ output: capturedOutput.substring(0, 500)
203
+ });
204
+ if (code === 0) {
205
+ let appId = capturedOutput.match(/App\s*ID:\s*(\S+)/i)?.[1] || null;
206
+ if (!appId) appId = getAppIdFromLarkCli();
207
+ if (appId) {
208
+ log.info("lark-cli config init 成功", { appId });
209
+ console.log(`\n${GREEN}✅ 飞书应用创建成功${RESET} ${DIM}(App ID: ${appId})${RESET}`);
210
+ } else console.log(`\n${GREEN}✅ lark-cli config init 完成${RESET}`);
211
+ resolve(appId);
212
+ } else {
213
+ console.log(`\n${YELLOW}⚠️ lark-cli config init 退出码: ${code}${RESET}`);
214
+ resolve(null);
215
+ }
216
+ });
217
+ });
218
+ }
219
+ /**
220
+ * 提示用户输入 App Secret 并保存到 LarkPal 凭证文件。
221
+ *
222
+ * lark-cli 新版使用 master key 加密存储 secret,LarkPal 无法直接读取。
223
+ * 需要用户从飞书开放平台复制 App Secret 并输入。
224
+ */
225
+ async function promptAppSecret(rl, appId) {
226
+ console.log(`\n${BOLD}🔑 输入 App Secret${RESET}\n`);
227
+ console.log(` ${DIM}lark-cli 已完成应用配置,但 LarkPal 还需要 App Secret 来启动服务。${RESET}`);
228
+ console.log(` ${DIM}请在飞书开放平台的应用管理页面(凭证与基础信息)中复制 App Secret:${RESET}\n`);
229
+ console.log(` ${CYAN}https://open.feishu.cn/app/${appId}${RESET}\n`);
230
+ const appSecret = await ask(rl, ` App Secret: `);
231
+ if (!appSecret) {
232
+ console.log(`\n${RED}App Secret 不能为空${RESET}`);
233
+ return false;
234
+ }
235
+ if (writeCredentials(appId, appSecret)) {
236
+ console.log(`\n${GREEN}✅ 凭证已保存${RESET} ${DIM}(${LARKPAL_CREDENTIALS_PATH})${RESET}`);
237
+ return true;
238
+ }
239
+ console.log(`\n${RED}凭证保存失败${RESET}`);
240
+ return false;
241
+ }
242
+ /** 手动输入完整凭证(App ID + App Secret) */
243
+ async function manualCredentialInput(rl) {
244
+ console.log(`\n${BOLD}📝 手动输入飞书应用凭证${RESET}\n`);
245
+ console.log(` ${DIM}你可以在飞书开放平台 (https://open.feishu.cn) 的应用管理页面找到这些信息。${RESET}\n`);
246
+ const appId = await ask(rl, ` App ID: `);
247
+ if (!appId) {
248
+ console.log(`\n${RED}App ID 不能为空${RESET}`);
249
+ return false;
250
+ }
251
+ const appSecret = await ask(rl, ` App Secret: `);
252
+ if (!appSecret) {
253
+ console.log(`\n${RED}App Secret 不能为空${RESET}`);
254
+ return false;
255
+ }
256
+ if (writeCredentials(appId, appSecret)) {
257
+ console.log(`\n${GREEN}✅ 凭证已保存${RESET} ${DIM}(${LARKPAL_CREDENTIALS_PATH})${RESET}`);
258
+ return true;
259
+ }
260
+ console.log(`\n${RED}凭证保存失败${RESET}`);
261
+ return false;
262
+ }
263
+ async function scanCreateAppCredentials() {
264
+ console.log(`\n${BOLD}📱 扫码创建飞书智能体应用${RESET}\n`);
265
+ console.log(` LarkPal 会通过飞书官方一键创建应用能力获取 App ID 和 App Secret。`);
266
+ console.log(` ${DIM}这一步只配置应用凭据;个人日历、文档、邮件等能力后续通过 lark-cli auth login 授权。${RESET}\n`);
267
+ try {
268
+ const result = await registerFeishuAppCredentials({
269
+ saveCredentials: saveCredentialsOrThrow,
270
+ onQRCodeReady: ({ url, expireIn }) => {
271
+ console.log(` ${CYAN}请打开或扫码以下链接完成创建:${RESET}`);
272
+ console.log(` ${url}`);
273
+ console.log(` ${DIM}链接有效期 ${expireIn} 秒。${RESET}\n`);
274
+ },
275
+ onStatusChange: ({ status }) => {
276
+ if (status === "polling") console.log(` ${DIM}等待飞书确认中...${RESET}`);
277
+ else if (status === "slow_down") console.log(` ${DIM}飞书要求降低轮询频率,继续等待...${RESET}`);
278
+ else if (status === "domain_switched") console.log(` ${DIM}已根据租户切换认证域名,继续等待...${RESET}`);
279
+ },
280
+ source: "larkpal-init"
281
+ });
282
+ console.log(`\n${GREEN}✅ 飞书应用创建并保存成功${RESET} ${DIM}(App ID: ${result.appId})${RESET}`);
283
+ return true;
284
+ } catch (err) {
285
+ console.log(`\n${RED}扫码创建飞书应用失败: ${err instanceof Error ? err.message : String(err)}${RESET}`);
286
+ return false;
287
+ }
288
+ }
289
+ async function configureCredentials(rl) {
290
+ if (process.env.LARK_APP_ID && process.env.LARK_APP_SECRET) {
291
+ console.log(`${GREEN}✅ 飞书应用凭证已通过环境变量配置${RESET}`);
292
+ log.info("凭证已通过环境变量配置");
293
+ return true;
294
+ }
295
+ const existing = hasLarkPalCredentials();
296
+ if (existing) {
297
+ console.log(`${GREEN}✅ 飞书应用凭证已配置${RESET} ${DIM}(App ID: ${existing.appId})${RESET}`);
298
+ log.info("LarkPal 凭证文件已存在", { appId: existing.appId });
299
+ return true;
300
+ }
301
+ const existingAppId = getAppIdFromLarkCli();
302
+ if (existingAppId) {
303
+ console.log(`${GREEN}✅ 飞书应用已配置${RESET} ${DIM}(App ID: ${existingAppId})${RESET}`);
304
+ console.log(`\n ${YELLOW}但 LarkPal 还需要 App Secret 才能启动。${RESET}`);
305
+ return promptAppSecret(rl, existingAppId);
306
+ }
307
+ console.log(`${BOLD}📋 飞书应用配置${RESET}\n`);
308
+ console.log(` LarkPal 需要飞书应用凭据才能进入飞书会话。推荐使用飞书官方扫码创建应用能力:\n`);
309
+ console.log(` ${DIM}[1] 扫码创建飞书智能体应用(推荐)${RESET}`);
310
+ console.log(` ${DIM}[2] 手动输入 App ID 和 App Secret${RESET}`);
311
+ console.log(` ${DIM}[3] 使用 lark-cli config init(兼容旧流程)${RESET}\n`);
312
+ const choice = await ask(rl, ` 请选择 [1/2/3](默认 1): `);
313
+ const option = choice === "2" ? 2 : choice === "3" ? 3 : 1;
314
+ log.info("用户选择凭证配置方式", { option });
315
+ if (option === 1) {
316
+ if (await scanCreateAppCredentials()) return true;
317
+ console.log(`\n ${YELLOW}扫码创建未完成,是否手动输入凭证?${RESET}`);
318
+ if ((await ask(rl, ` 手动输入? [y/N]: `)).toLowerCase() === "y") return manualCredentialInput(rl);
319
+ return false;
320
+ }
321
+ if (option === 3) {
322
+ const appId = await runLarkCliConfigInit();
323
+ if (appId) return promptAppSecret(rl, appId);
324
+ console.log(`\n ${YELLOW}lark-cli 配置未成功,是否手动输入凭证?${RESET}`);
325
+ if ((await ask(rl, ` 手动输入? [y/N]: `)).toLowerCase() === "y") return manualCredentialInput(rl);
326
+ return false;
327
+ }
328
+ return manualCredentialInput(rl);
329
+ }
330
+ /**
331
+ * 执行交互式初始化流程。
332
+ *
333
+ * @returns 0 成功,1 失败
334
+ */
335
+ async function runInteractiveInit() {
336
+ console.log(`\n${BOLD}${CYAN}╔══════════════════════════════════════╗${RESET}`);
337
+ console.log(`${BOLD}${CYAN}║ LarkPal 初始化向导 ║${RESET}`);
338
+ console.log(`${BOLD}${CYAN}╚══════════════════════════════════════╝${RESET}\n`);
339
+ log.info("开始交互式初始化流程");
340
+ checkPrerequisites();
341
+ const rl = createInterface({
342
+ input: process.stdin,
343
+ output: process.stdout
344
+ });
345
+ let credentialsOk;
346
+ try {
347
+ credentialsOk = await configureCredentials(rl);
348
+ } finally {
349
+ rl.close();
350
+ }
351
+ if (!credentialsOk) {
352
+ console.log(`\n${RED}凭证配置未完成。你可以稍后运行 ${CYAN}larkpal init${RED} 重新配置。${RESET}\n`);
353
+ return 1;
354
+ }
355
+ console.log(`\n${BOLD}${GREEN}🎉 初始化完成!${RESET}\n`);
356
+ console.log(` ${DIM}下一步:${RESET}`);
357
+ console.log(` 运行 ${CYAN}larkpal${RESET} 启动服务\n`);
358
+ log.info("交互式初始化流程完成");
359
+ return 0;
360
+ }
361
+ //#endregion
362
+ export { runInteractiveInit };