Skip to content

Commit 5f3fa3d

Browse files
committed
feat: 一键安装并配置
1 parent 2a30124 commit 5f3fa3d

4 files changed

Lines changed: 270 additions & 18 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@larksuite/cli",
3-
"version": "1.0.10",
3+
"version": "1.0.11",
44
"description": "The official CLI for Lark/Feishu open platform",
55
"bin": {
66
"lark-cli": "scripts/run.js"
@@ -27,6 +27,7 @@
2727
"license": "MIT",
2828
"files": [
2929
"scripts/install.js",
30+
"scripts/install-wizard.js",
3031
"scripts/run.js",
3132
"CHANGELOG.md"
3233
]

scripts/install-wizard.js

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
#!/usr/bin/env node
2+
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
3+
// SPDX-License-Identifier: MIT
4+
5+
const { execFileSync, execSync } = require("child_process");
6+
const readline = require("readline");
7+
8+
const PKG = "@larksuite/cli";
9+
const SKILLS_REPO = "https://github.com/larksuite/cli";
10+
const isWindows = process.platform === "win32";
11+
12+
// ---------------------------------------------------------------------------
13+
// Helpers
14+
// ---------------------------------------------------------------------------
15+
16+
function ask(question) {
17+
return new Promise((resolve) => {
18+
const rl = readline.createInterface({
19+
input: process.stdin,
20+
output: process.stdout,
21+
});
22+
rl.question(question, (answer) => {
23+
rl.close();
24+
resolve(answer.trim());
25+
});
26+
});
27+
}
28+
29+
async function confirm(question) {
30+
const answer = await ask(`${question} (Y/n) `);
31+
return answer === "" || answer.toLowerCase() !== "n";
32+
}
33+
34+
function run(cmd, args, opts = {}) {
35+
execFileSync(cmd, args, { stdio: "inherit", ...opts });
36+
}
37+
38+
function runSilent(cmd, args, opts = {}) {
39+
return execFileSync(cmd, args, {
40+
stdio: ["ignore", "pipe", "pipe"],
41+
...opts,
42+
});
43+
}
44+
45+
/** Resolve the path of globally installed lark-cli. */
46+
function whichLarkCli() {
47+
try {
48+
const cmd = isWindows ? "where" : "which";
49+
return execSync(`${cmd} lark-cli`, { stdio: ["ignore", "pipe", "pipe"] })
50+
.toString()
51+
.split("\n")[0]
52+
.trim();
53+
} catch (_) {
54+
return null;
55+
}
56+
}
57+
58+
/** Get the version of a lark-cli binary, or null. */
59+
function getLarkCliVersion(binPath) {
60+
try {
61+
const out = runSilent(binPath, ["--version"], { timeout: 10000 });
62+
const match = out.toString().match(/(\d+\.\d+\.\d+)/);
63+
return match ? match[1] : null;
64+
} catch (_) {
65+
return null;
66+
}
67+
}
68+
69+
/** Check whether lark-cli config already exists. Returns app ID or null. */
70+
function getExistingAppId(binPath) {
71+
try {
72+
const out = runSilent(binPath, ["config", "show"], { timeout: 10000 });
73+
const json = JSON.parse(out.toString());
74+
return json.appId || null;
75+
} catch (_) {
76+
return null;
77+
}
78+
}
79+
80+
// ---------------------------------------------------------------------------
81+
// Steps
82+
// ---------------------------------------------------------------------------
83+
84+
async function stepInstallGlobally() {
85+
console.log("\n[1/4] Installing %s globally...", PKG);
86+
87+
const existing = whichLarkCli();
88+
if (existing) {
89+
const ver = getLarkCliVersion(existing);
90+
console.log(" Already installed (v%s). Skipped.", ver || "unknown");
91+
return;
92+
}
93+
94+
try {
95+
run("npm", ["install", "-g", PKG]);
96+
console.log(" Done.");
97+
} catch (_) {
98+
console.error(
99+
"\n Failed to install globally.\n" +
100+
" Try running manually: npm install -g " + PKG + "\n"
101+
);
102+
process.exit(1);
103+
}
104+
}
105+
106+
function skillsAlreadyInstalled() {
107+
try {
108+
const out = runSilent("npx", ["-y", "skills", "ls", "-g", "--json"], {
109+
timeout: 30000,
110+
});
111+
const list = JSON.parse(out.toString());
112+
return Array.isArray(list) && list.some((s) => s.name && s.name.startsWith("lark-"));
113+
} catch (_) {
114+
return false;
115+
}
116+
}
117+
118+
async function stepInstallSkills() {
119+
console.log("\n[2/4] Installing AI Skills...");
120+
121+
if (skillsAlreadyInstalled()) {
122+
console.log(" Already installed. Skipped.");
123+
return;
124+
}
125+
126+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
127+
let i = 0;
128+
const spinner = setInterval(() => {
129+
process.stdout.write(`\r ${frames[i++ % frames.length]} Installing skills...`);
130+
}, 80);
131+
132+
try {
133+
runSilent("npx", ["-y", "skills", "add", SKILLS_REPO, "-y", "-g"], {
134+
timeout: 120000,
135+
});
136+
clearInterval(spinner);
137+
process.stdout.write("\r Done. \n");
138+
} catch (_) {
139+
clearInterval(spinner);
140+
process.stdout.write("\r");
141+
console.error(
142+
"\n Skills installation failed.\n" +
143+
" Try running manually: npx skills add " + SKILLS_REPO + " -y -g\n"
144+
);
145+
process.exit(1);
146+
}
147+
}
148+
149+
async function stepConfigInit() {
150+
console.log("\n[3/4] Configuring your app...");
151+
152+
const larkCli = whichLarkCli();
153+
if (!larkCli) {
154+
console.error(" lark-cli not found after installation. Aborting.");
155+
process.exit(1);
156+
}
157+
158+
const appId = getExistingAppId(larkCli);
159+
if (appId) {
160+
console.log(" Found existing app (App ID: %s).", appId);
161+
const reuse = await confirm(" Use this app?");
162+
if (reuse) {
163+
console.log(" Skipped.");
164+
return;
165+
}
166+
}
167+
168+
try {
169+
run(larkCli, ["config", "init", "--new"]);
170+
console.log(" Done.");
171+
} catch (_) {
172+
console.error(
173+
"\n App configuration failed.\n" +
174+
' Try running manually: lark-cli config init --new\n'
175+
);
176+
process.exit(1);
177+
}
178+
}
179+
180+
async function stepAuthLogin() {
181+
console.log("\n[4/4] User authorization...");
182+
183+
const larkCli = whichLarkCli();
184+
if (!larkCli) {
185+
console.warn(" lark-cli not found. Skipping authorization.");
186+
return;
187+
}
188+
189+
const yes = await confirm(
190+
" Allow AI to access your Feishu/Lark data (messages, docs, calendar, etc.)?"
191+
);
192+
if (!yes) {
193+
console.log(' Skipped. You can run "lark-cli auth login" later.');
194+
return;
195+
}
196+
197+
try {
198+
run(larkCli, ["auth", "login"]);
199+
console.log(" Done.");
200+
} catch (_) {
201+
console.warn(
202+
' Warning: Authorization failed. You can retry later:\n' +
203+
" lark-cli auth login"
204+
);
205+
}
206+
}
207+
208+
// ---------------------------------------------------------------------------
209+
// Main
210+
// ---------------------------------------------------------------------------
211+
212+
async function main() {
213+
console.log("Setting up Feishu/Lark CLI...");
214+
215+
await stepInstallGlobally();
216+
await stepInstallSkills();
217+
await stepConfigInit();
218+
await stepAuthLogin();
219+
220+
console.log("\n✅ You're all set!");
221+
console.log(
222+
'Now try asking your AI tool (Claude Code, Trae, etc.):\n' +
223+
' "What can Feishu/Lark CLI help me with, and where should I start?"'
224+
);
225+
}
226+
227+
main().catch((err) => {
228+
console.error("Unexpected error:", err.message || err);
229+
process.exit(1);
230+
});

scripts/install.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const path = require("path");
66
const { execSync } = require("child_process");
77
const os = require("os");
88

9-
const VERSION = require("../package.json").version;
9+
const VERSION = require("../package.json").version.replace(/-.*$/, "");
1010
const REPO = "larksuite/cli";
1111
const NAME = "lark-cli";
1212

@@ -85,6 +85,16 @@ function install() {
8585
}
8686
}
8787

88+
// When triggered as a postinstall hook under npx, skip the binary download.
89+
// The "install" wizard doesn't need it, and run.js calls install.js directly
90+
// (with LARK_CLI_RUN=1) for other commands that do need the binary.
91+
const isNpxPostinstall =
92+
process.env.npm_command === "exec" && !process.env.LARK_CLI_RUN;
93+
94+
if (isNpxPostinstall) {
95+
process.exit(0);
96+
}
97+
8898
try {
8999
install();
90100
} catch (err) {

scripts/run.js

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,32 @@ if (process.platform === "win32" && fs.existsSync(oldBin)) {
4141
}
4242
}
4343

44-
if (!fs.existsSync(bin)) {
45-
console.error(
46-
`Error: lark-cli binary not found at ${bin}\n\n` +
47-
`This usually means the postinstall script was skipped.\n` +
48-
`Common causes:\n` +
49-
` - npm is configured with ignore-scripts=true\n` +
50-
` - The postinstall download failed\n\n` +
51-
`To fix, run the install script manually:\n` +
52-
` node "${path.join(__dirname, "install.js")}"\n`
53-
);
54-
process.exit(1);
55-
}
44+
// Intercept "install" subcommand — run the setup wizard directly,
45+
// bypassing the native binary (which may not exist yet under npx).
46+
const args = process.argv.slice(2);
47+
if (args[0] === "install") {
48+
require("./install-wizard.js");
49+
} else {
50+
// Auto-download binary if missing (e.g. npx skipped postinstall).
51+
if (!fs.existsSync(bin)) {
52+
try {
53+
execFileSync(process.execPath, [path.join(__dirname, "install.js")], {
54+
stdio: "inherit",
55+
env: { ...process.env, LARK_CLI_RUN: "true" },
56+
});
57+
} catch (_) {
58+
console.error(
59+
`\nFailed to auto-install lark-cli binary.\n` +
60+
`To fix, run the install script manually:\n` +
61+
` node "${path.join(__dirname, "install.js")}"\n`
62+
);
63+
process.exit(1);
64+
}
65+
}
5666

57-
try {
58-
execFileSync(bin, process.argv.slice(2), { stdio: "inherit" });
59-
} catch (e) {
60-
process.exit(e.status || 1);
67+
try {
68+
execFileSync(bin, args, { stdio: "inherit" });
69+
} catch (e) {
70+
process.exit(e.status || 1);
71+
}
6172
}

0 commit comments

Comments
 (0)