@vibe-lark/larkpal 0.1.28 → 0.1.29

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/cli.mjs ADDED
@@ -0,0 +1,87 @@
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 服务
33
+
34
+ ${BOLD}用法:${RESET}
35
+ larkpal 启动服务
36
+ larkpal init 交互式初始化向导
37
+ larkpal --help 显示帮助信息
38
+ larkpal --version 显示版本号
39
+
40
+ ${BOLD}环境变量:${RESET}
41
+ LARK_APP_ID 飞书应用 App ID(可选,覆盖 lark-cli 配置)
42
+ LARK_APP_SECRET 飞书应用 App Secret(可选,覆盖 lark-cli 配置)
43
+ LARKPAL_WORKSPACE 工作目录根路径(默认 /workspace)
44
+ CLAUDE_MODEL Claude 模型名称(如 ark-code-latest)
45
+
46
+ ${DIM}首次使用?运行 ${CYAN}larkpal init${DIM} 开始配置。${RESET}
47
+ `);
48
+ }
49
+ async function cli() {
50
+ const command = process.argv.slice(2)[0];
51
+ if (command === "--version" || command === "-v") {
52
+ console.log(getVersion());
53
+ return;
54
+ }
55
+ if (command === "--help" || command === "-h") {
56
+ printHelp();
57
+ return;
58
+ }
59
+ if (command === "init") {
60
+ const { runInteractiveInit } = await import("./interactive-init-BYF2-otj.mjs");
61
+ const exitCode = await runInteractiveInit();
62
+ process.exit(exitCode);
63
+ }
64
+ if (command && !command.startsWith("-")) {
65
+ console.log(`${YELLOW}未知命令: ${command}${RESET}\n`);
66
+ printHelp();
67
+ process.exit(1);
68
+ }
69
+ if (!(process.env.LARKPAL_HEADLESS === "1" || process.env.LARKPAL_HEADLESS === "true")) {
70
+ const { LarkCliCredentialProvider } = await import("./lark-cli-provider-CdgwmqSz.mjs").then((n) => n.n);
71
+ if (!LarkCliCredentialProvider.isConfigured()) {
72
+ console.log(`\n${YELLOW}⚠️ 飞书应用凭证未配置${RESET}\n`);
73
+ console.log(` LarkPal 需要飞书应用凭证才能启动。正在进入初始化向导...\n`);
74
+ const { runInteractiveInit } = await import("./interactive-init-BYF2-otj.mjs");
75
+ const exitCode = await runInteractiveInit();
76
+ if (exitCode !== 0) process.exit(exitCode);
77
+ console.log(`${DIM}凭证配置完成,正在启动服务...${RESET}\n`);
78
+ }
79
+ }
80
+ await import("./main.mjs");
81
+ }
82
+ cli().catch((err) => {
83
+ console.error("LarkPal CLI 启动失败:", err);
84
+ process.exit(1);
85
+ });
86
+ //#endregion
87
+ export {};
@@ -0,0 +1,292 @@
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
+ //#region src/init/interactive-init.ts
8
+ /**
9
+ * 交互式初始化流程
10
+ *
11
+ * 引导用户完成 LarkPal 首次配置:
12
+ * Step 1: 检查前置依赖(claude、lark-cli)
13
+ * Step 2: 配置飞书应用凭证(lark-cli config init + 补充 App Secret)
14
+ * Step 3: 打印成功信息
15
+ *
16
+ * 仅使用 Node.js 内置模块,不依赖第三方包。
17
+ */
18
+ const log = larkLogger("init");
19
+ const CYAN = "\x1B[36m";
20
+ const GREEN = "\x1B[32m";
21
+ const YELLOW = "\x1B[33m";
22
+ const RED = "\x1B[31m";
23
+ const BOLD = "\x1B[1m";
24
+ const DIM = "\x1B[2m";
25
+ const RESET = "\x1B[0m";
26
+ /** LarkPal 自身配置目录 */
27
+ const LARKPAL_CONFIG_DIR = join(homedir(), ".larkpal");
28
+ /** LarkPal 凭证文件(存储 appId + appSecret) */
29
+ const LARKPAL_CREDENTIALS_PATH = join(LARKPAL_CONFIG_DIR, "credentials.json");
30
+ /** readline 问答封装 */
31
+ function ask(rl, question) {
32
+ return new Promise((resolve) => {
33
+ rl.question(question, (answer) => resolve(answer.trim()));
34
+ });
35
+ }
36
+ /** 检测 CLI 工具是否已安装 */
37
+ function isCliInstalled(name) {
38
+ try {
39
+ return !!execSync(`which ${name}`, {
40
+ encoding: "utf-8",
41
+ timeout: 5e3
42
+ }).trim();
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+ /** 通过 lark-cli config show 获取当前配置的 appId */
48
+ function getAppIdFromLarkCli() {
49
+ try {
50
+ const jsonMatch = execSync("lark-cli config show", {
51
+ encoding: "utf-8",
52
+ timeout: 1e4,
53
+ stdio: [
54
+ "pipe",
55
+ "pipe",
56
+ "pipe"
57
+ ]
58
+ }).match(/\{[\s\S]*\}/);
59
+ if (jsonMatch) return JSON.parse(jsonMatch[0]).appId || null;
60
+ } catch {}
61
+ return null;
62
+ }
63
+ /** 写入凭证到 LarkPal 配置文件 */
64
+ function writeCredentials(appId, appSecret) {
65
+ try {
66
+ if (!existsSync(LARKPAL_CONFIG_DIR)) {
67
+ mkdirSync(LARKPAL_CONFIG_DIR, { recursive: true });
68
+ log.info("创建 LarkPal 配置目录", { path: LARKPAL_CONFIG_DIR });
69
+ }
70
+ writeFileSync(LARKPAL_CREDENTIALS_PATH, JSON.stringify({
71
+ app_id: appId,
72
+ app_secret: appSecret
73
+ }, null, 2) + "\n", {
74
+ encoding: "utf-8",
75
+ mode: 384
76
+ });
77
+ log.info("凭证已写入", {
78
+ path: LARKPAL_CREDENTIALS_PATH,
79
+ appId
80
+ });
81
+ return true;
82
+ } catch (err) {
83
+ const msg = err instanceof Error ? err.message : String(err);
84
+ log.error("写入凭证文件失败", {
85
+ path: LARKPAL_CREDENTIALS_PATH,
86
+ error: msg
87
+ });
88
+ return false;
89
+ }
90
+ }
91
+ /** 检查 LarkPal 凭证文件是否已有有效凭证 */
92
+ function hasLarkPalCredentials() {
93
+ if (!existsSync(LARKPAL_CREDENTIALS_PATH)) return null;
94
+ try {
95
+ const raw = readFileSync(LARKPAL_CREDENTIALS_PATH, "utf-8");
96
+ const creds = JSON.parse(raw);
97
+ if (creds.app_id && creds.app_secret) return {
98
+ appId: creds.app_id,
99
+ appSecret: creds.app_secret
100
+ };
101
+ } catch {}
102
+ return null;
103
+ }
104
+ function checkPrerequisites() {
105
+ console.log(`\n${BOLD}🔍 检查前置依赖${RESET}\n`);
106
+ if (isCliInstalled("claude")) console.log(` ${GREEN}✅ claude 已安装${RESET}`);
107
+ else {
108
+ console.log(` ${RED}❌ claude 未安装${RESET}`);
109
+ console.log(`\n Claude Code 是 LarkPal 的 AI 推理引擎,必须安装。`);
110
+ console.log(` ${DIM}安装命令:${RESET} npm install -g @anthropic-ai/claude-code`);
111
+ console.log(` ${DIM}文档:${RESET} https://docs.anthropic.com/en/docs/claude-code\n`);
112
+ log.error("前置依赖检查失败: claude 未安装");
113
+ return false;
114
+ }
115
+ if (isCliInstalled("lark-cli")) console.log(` ${GREEN}✅ lark-cli 已安装${RESET}`);
116
+ else {
117
+ console.log(` ${RED}❌ lark-cli 未安装${RESET}`);
118
+ console.log(`\n lark-cli 提供飞书原生能力和凭证管理,必须安装。`);
119
+ console.log(` ${DIM}安装命令:${RESET}`);
120
+ console.log(` npm install -g @larksuite/cli`);
121
+ console.log(` npx skills add larksuite/cli -y -g`);
122
+ console.log(` ${DIM}文档:${RESET} https://github.com/larksuite/cli\n`);
123
+ log.error("前置依赖检查失败: lark-cli 未安装");
124
+ return false;
125
+ }
126
+ console.log();
127
+ return true;
128
+ }
129
+ /**
130
+ * 通过 lark-cli config init 子进程配置凭证。
131
+ * 返回解析到的 appId(成功)或 null(失败)。
132
+ */
133
+ function runLarkCliConfigInit() {
134
+ return new Promise((resolve) => {
135
+ console.log(`\n${DIM}正在启动 lark-cli config init ...${RESET}\n`);
136
+ log.info("启动子进程: lark-cli config init");
137
+ let capturedOutput = "";
138
+ const child = spawn("lark-cli", ["config", "init"], { stdio: [
139
+ "inherit",
140
+ "pipe",
141
+ "pipe"
142
+ ] });
143
+ child.stdout?.on("data", (chunk) => {
144
+ const text = chunk.toString();
145
+ capturedOutput += text;
146
+ process.stdout.write(text);
147
+ });
148
+ child.stderr?.on("data", (chunk) => {
149
+ const text = chunk.toString();
150
+ capturedOutput += text;
151
+ process.stderr.write(text);
152
+ });
153
+ child.on("error", (err) => {
154
+ log.error("lark-cli config init 启动失败", { error: err.message });
155
+ console.log(`\n${RED}启动 lark-cli config init 失败: ${err.message}${RESET}`);
156
+ resolve(null);
157
+ });
158
+ child.on("close", (code) => {
159
+ log.info("lark-cli config init 执行完毕", {
160
+ exitCode: code,
161
+ output: capturedOutput.substring(0, 500)
162
+ });
163
+ if (code === 0) {
164
+ let appId = capturedOutput.match(/App\s*ID:\s*(\S+)/i)?.[1] || null;
165
+ if (!appId) appId = getAppIdFromLarkCli();
166
+ if (appId) {
167
+ log.info("lark-cli config init 成功", { appId });
168
+ console.log(`\n${GREEN}✅ 飞书应用创建成功${RESET} ${DIM}(App ID: ${appId})${RESET}`);
169
+ } else console.log(`\n${GREEN}✅ lark-cli config init 完成${RESET}`);
170
+ resolve(appId);
171
+ } else {
172
+ console.log(`\n${YELLOW}⚠️ lark-cli config init 退出码: ${code}${RESET}`);
173
+ resolve(null);
174
+ }
175
+ });
176
+ });
177
+ }
178
+ /**
179
+ * 提示用户输入 App Secret 并保存到 LarkPal 凭证文件。
180
+ *
181
+ * lark-cli 新版使用 master key 加密存储 secret,LarkPal 无法直接读取。
182
+ * 需要用户从飞书开放平台复制 App Secret 并输入。
183
+ */
184
+ async function promptAppSecret(rl, appId) {
185
+ console.log(`\n${BOLD}🔑 输入 App Secret${RESET}\n`);
186
+ console.log(` ${DIM}lark-cli 已完成应用配置,但 LarkPal 还需要 App Secret 来启动服务。${RESET}`);
187
+ console.log(` ${DIM}请在飞书开放平台的应用管理页面(凭证与基础信息)中复制 App Secret:${RESET}\n`);
188
+ console.log(` ${CYAN}https://open.feishu.cn/app/${appId}${RESET}\n`);
189
+ const appSecret = await ask(rl, ` App Secret: `);
190
+ if (!appSecret) {
191
+ console.log(`\n${RED}App Secret 不能为空${RESET}`);
192
+ return false;
193
+ }
194
+ if (writeCredentials(appId, appSecret)) {
195
+ console.log(`\n${GREEN}✅ 凭证已保存${RESET} ${DIM}(${LARKPAL_CREDENTIALS_PATH})${RESET}`);
196
+ return true;
197
+ }
198
+ console.log(`\n${RED}凭证保存失败${RESET}`);
199
+ return false;
200
+ }
201
+ /** 手动输入完整凭证(App ID + App Secret) */
202
+ async function manualCredentialInput(rl) {
203
+ console.log(`\n${BOLD}📝 手动输入飞书应用凭证${RESET}\n`);
204
+ console.log(` ${DIM}你可以在飞书开放平台 (https://open.feishu.cn) 的应用管理页面找到这些信息。${RESET}\n`);
205
+ const appId = await ask(rl, ` App ID: `);
206
+ if (!appId) {
207
+ console.log(`\n${RED}App ID 不能为空${RESET}`);
208
+ return false;
209
+ }
210
+ const appSecret = await ask(rl, ` App Secret: `);
211
+ if (!appSecret) {
212
+ console.log(`\n${RED}App Secret 不能为空${RESET}`);
213
+ return false;
214
+ }
215
+ if (writeCredentials(appId, appSecret)) {
216
+ console.log(`\n${GREEN}✅ 凭证已保存${RESET} ${DIM}(${LARKPAL_CREDENTIALS_PATH})${RESET}`);
217
+ return true;
218
+ }
219
+ console.log(`\n${RED}凭证保存失败${RESET}`);
220
+ return false;
221
+ }
222
+ async function configureCredentials(rl) {
223
+ if (process.env.LARK_APP_ID && process.env.LARK_APP_SECRET) {
224
+ console.log(`${GREEN}✅ 飞书应用凭证已通过环境变量配置${RESET}`);
225
+ log.info("凭证已通过环境变量配置");
226
+ return true;
227
+ }
228
+ const existing = hasLarkPalCredentials();
229
+ if (existing) {
230
+ console.log(`${GREEN}✅ 飞书应用凭证已配置${RESET} ${DIM}(App ID: ${existing.appId})${RESET}`);
231
+ log.info("LarkPal 凭证文件已存在", { appId: existing.appId });
232
+ return true;
233
+ }
234
+ const existingAppId = getAppIdFromLarkCli();
235
+ if (existingAppId) {
236
+ console.log(`${GREEN}✅ 飞书应用已配置${RESET} ${DIM}(App ID: ${existingAppId})${RESET}`);
237
+ console.log(`\n ${YELLOW}但 LarkPal 还需要 App Secret 才能启动。${RESET}`);
238
+ return promptAppSecret(rl, existingAppId);
239
+ }
240
+ console.log(`${BOLD}📋 飞书应用配置${RESET}\n`);
241
+ console.log(` LarkPal 需要飞书应用凭证才能运行。推荐通过 lark-cli 一键配置:\n`);
242
+ console.log(` ${CYAN}👉 运行: lark-cli config init${RESET}\n`);
243
+ console.log(` 这会打开浏览器,引导你在飞书开放平台完成应用配置。\n`);
244
+ console.log(` ${DIM}[1] 运行 lark-cli config init(推荐)${RESET}`);
245
+ console.log(` ${DIM}[2] 手动输入 App ID 和 App Secret${RESET}\n`);
246
+ const option = await ask(rl, ` 请选择 [1/2](默认 1): `) === "2" ? 2 : 1;
247
+ log.info("用户选择凭证配置方式", { option });
248
+ if (option === 1) {
249
+ const appId = await runLarkCliConfigInit();
250
+ if (appId) return promptAppSecret(rl, appId);
251
+ console.log(`\n ${YELLOW}lark-cli 配置未成功,是否手动输入凭证?${RESET}`);
252
+ if ((await ask(rl, ` 手动输入? [y/N]: `)).toLowerCase() === "y") return manualCredentialInput(rl);
253
+ return false;
254
+ }
255
+ return manualCredentialInput(rl);
256
+ }
257
+ /**
258
+ * 执行交互式初始化流程。
259
+ *
260
+ * @returns 0 成功,1 失败
261
+ */
262
+ async function runInteractiveInit() {
263
+ console.log(`\n${BOLD}${CYAN}╔══════════════════════════════════════╗${RESET}`);
264
+ console.log(`${BOLD}${CYAN}║ LarkPal 初始化向导 ║${RESET}`);
265
+ console.log(`${BOLD}${CYAN}╚══════════════════════════════════════╝${RESET}\n`);
266
+ log.info("开始交互式初始化流程");
267
+ if (!checkPrerequisites()) {
268
+ console.log(`${RED}请安装缺失的依赖后重新运行 ${CYAN}larkpal init${RESET}\n`);
269
+ return 1;
270
+ }
271
+ const rl = createInterface({
272
+ input: process.stdin,
273
+ output: process.stdout
274
+ });
275
+ let credentialsOk = false;
276
+ try {
277
+ credentialsOk = await configureCredentials(rl);
278
+ } finally {
279
+ rl.close();
280
+ }
281
+ if (!credentialsOk) {
282
+ console.log(`\n${RED}凭证配置未完成。你可以稍后运行 ${CYAN}larkpal init${RED} 重新配置。${RESET}\n`);
283
+ return 1;
284
+ }
285
+ console.log(`\n${BOLD}${GREEN}🎉 初始化完成!${RESET}\n`);
286
+ console.log(` ${DIM}下一步:${RESET}`);
287
+ console.log(` 运行 ${CYAN}larkpal${RESET} 启动服务\n`);
288
+ log.info("交互式初始化流程完成");
289
+ return 0;
290
+ }
291
+ //#endregion
292
+ export { runInteractiveInit };