lark-agent-bot 0.3.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 (4) hide show
  1. package/README.md +25 -0
  2. package/install.js +216 -0
  3. package/package.json +35 -0
  4. package/run.js +77 -0
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # lark-agent-bot
2
+
3
+ Bridge local AI coding agents (Claude Code, Codex, Cursor, Gemini CLI, and more) to Feishu / Lark.
4
+ Chat with your coding agent from the Feishu / Lark app — no public IP required.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npm install -g lark-agent-bot
10
+ ```
11
+
12
+ The postinstall step downloads the prebuilt binary for your platform from
13
+ [GitHub Releases](https://github.com/ClaymanTwinkle/lark-agent-bot/releases).
14
+
15
+ ## Usage
16
+
17
+ ```bash
18
+ lark-agent-bot feishu setup # scan a QR code to create / bind a Feishu bot
19
+ lark-agent-bot # start (reads ./config.toml or ~/.lark-agent-bot/config.toml)
20
+ lark-agent-bot --config /path/to/config.toml
21
+ ```
22
+
23
+ ## Documentation
24
+
25
+ https://github.com/ClaymanTwinkle/lark-agent-bot
package/install.js ADDED
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const { execSync } = require("child_process");
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const https = require("https");
9
+ const http = require("http");
10
+ const zlib = require("zlib");
11
+
12
+ const PACKAGE = require("./package.json");
13
+ const VERSION = `v${PACKAGE.version}`;
14
+ const NAME = "lark-agent-bot";
15
+
16
+ const GITHUB_REPO = "ClaymanTwinkle/lark-agent-bot";
17
+
18
+ const PLATFORM_MAP = {
19
+ darwin: "darwin",
20
+ linux: "linux",
21
+ win32: "windows",
22
+ };
23
+
24
+ const ARCH_MAP = {
25
+ x64: "amd64",
26
+ arm64: "arm64",
27
+ };
28
+
29
+ function getPlatformInfo() {
30
+ const platform = PLATFORM_MAP[process.platform];
31
+ const arch = ARCH_MAP[process.arch];
32
+ if (!platform || !arch) {
33
+ throw new Error(
34
+ `Unsupported platform: ${process.platform}/${process.arch}. ` +
35
+ `Supported: linux/darwin/windows x64/arm64`
36
+ );
37
+ }
38
+ const ext = platform === "windows" ? ".zip" : ".tar.gz";
39
+ const filename = `${NAME}-${VERSION}-${platform}-${arch}${ext}`;
40
+ return { platform, arch, ext, filename };
41
+ }
42
+
43
+ function getDownloadURLs(filename) {
44
+ return [
45
+ `https://github.com/${GITHUB_REPO}/releases/download/${VERSION}/${filename}`,
46
+ ];
47
+ }
48
+
49
+ function fetch(url, redirects = 5) {
50
+ return new Promise((resolve, reject) => {
51
+ if (redirects <= 0) return reject(new Error("Too many redirects"));
52
+ const mod = url.startsWith("https") ? https : http;
53
+ mod
54
+ .get(url, { headers: { "User-Agent": "lark-agent-bot-npm" } }, (res) => {
55
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
56
+ return resolve(fetch(res.headers.location, redirects - 1));
57
+ }
58
+ if (res.statusCode !== 200) {
59
+ res.resume();
60
+ return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
61
+ }
62
+ const chunks = [];
63
+ res.on("data", (c) => chunks.push(c));
64
+ res.on("end", () => resolve(Buffer.concat(chunks)));
65
+ res.on("error", reject);
66
+ })
67
+ .on("error", reject);
68
+ });
69
+ }
70
+
71
+ async function download(urls) {
72
+ for (const url of urls) {
73
+ try {
74
+ console.log(`[lark-agent-bot] Downloading from ${url}`);
75
+ const data = await fetch(url);
76
+ console.log(`[lark-agent-bot] Downloaded ${(data.length / 1024 / 1024).toFixed(1)} MB`);
77
+ return data;
78
+ } catch (err) {
79
+ console.warn(`[lark-agent-bot] Failed: ${err.message}, trying next source...`);
80
+ }
81
+ }
82
+ throw new Error(
83
+ `[lark-agent-bot] Could not download binary from any source.\n` +
84
+ ` Tried: ${urls.join(", ")}\n` +
85
+ ` You can download manually from https://github.com/${GITHUB_REPO}/releases`
86
+ );
87
+ }
88
+
89
+ function extractTarGz(buffer, destDir, binaryName) {
90
+ const tmpFile = path.join(destDir, "_tmp.tar.gz");
91
+ fs.writeFileSync(tmpFile, buffer);
92
+ try {
93
+ execSync(`tar xzf "${tmpFile}" -C "${destDir}"`, { stdio: "pipe" });
94
+ } finally {
95
+ fs.unlinkSync(tmpFile);
96
+ }
97
+ const extracted = fs.readdirSync(destDir).find((f) => f.startsWith(NAME) && !f.endsWith(".tar.gz"));
98
+ if (extracted && extracted !== binaryName) {
99
+ fs.renameSync(path.join(destDir, extracted), path.join(destDir, binaryName));
100
+ }
101
+ }
102
+
103
+ function extractZip(buffer, destDir, binaryName) {
104
+ const tmpFile = path.join(destDir, "_tmp.zip");
105
+ fs.writeFileSync(tmpFile, buffer);
106
+ try {
107
+ try {
108
+ execSync(`unzip -o "${tmpFile}" -d "${destDir}"`, { stdio: "pipe" });
109
+ } catch {
110
+ execSync(`powershell -Command "Expand-Archive -Force '${tmpFile}' '${destDir}'"`, {
111
+ stdio: "pipe",
112
+ });
113
+ }
114
+ } finally {
115
+ try { fs.unlinkSync(tmpFile); } catch {}
116
+ }
117
+ const extracted = fs.readdirSync(destDir).find((f) => f.startsWith(NAME) && f.endsWith(".exe"));
118
+ if (extracted && extracted !== binaryName) {
119
+ fs.renameSync(path.join(destDir, extracted), path.join(destDir, binaryName));
120
+ }
121
+ }
122
+
123
+ // parseVersion splits "1.2.3-beta.1" into { nums: [1,2,3], preTag: "beta", preNum: 1 }
124
+ function parseVersion(v) {
125
+ v = v.replace(/^v/, "").trim();
126
+ const [base, ...rest] = v.split("-");
127
+ const nums = base.split(".").map(Number);
128
+ const pre = rest.join("-");
129
+ const m = pre.match(/^([a-zA-Z]+)\.?(\d+)?$/);
130
+ return { nums, preTag: m ? m[1] : pre, preNum: m && m[2] ? parseInt(m[2], 10) : 0, hasPre: pre !== "" };
131
+ }
132
+
133
+ // isNewerOrEqual returns true if installed >= expected
134
+ function isNewerOrEqual(installed, expected) {
135
+ const a = parseVersion(installed);
136
+ const b = parseVersion(expected);
137
+ const len = Math.max(a.nums.length, b.nums.length);
138
+ for (let i = 0; i < len; i++) {
139
+ const av = a.nums[i] || 0;
140
+ const bv = b.nums[i] || 0;
141
+ if (av > bv) return true;
142
+ if (av < bv) return false;
143
+ }
144
+ if (!a.hasPre && b.hasPre) return true;
145
+ if (a.hasPre && !b.hasPre) return false;
146
+ if (!a.hasPre && !b.hasPre) return true;
147
+ // Both pre-release: compare tag then number (rc > beta, beta.10 > beta.9)
148
+ if (a.preTag !== b.preTag) return a.preTag > b.preTag;
149
+ return a.preNum >= b.preNum;
150
+ }
151
+
152
+ async function main() {
153
+ const { platform, arch, ext, filename } = getPlatformInfo();
154
+ console.log(`[lark-agent-bot] Platform: ${platform}/${arch}`);
155
+
156
+ const binDir = path.join(__dirname, "bin");
157
+ fs.mkdirSync(binDir, { recursive: true });
158
+
159
+ const binaryName = platform === "windows" ? `${NAME}.exe` : NAME;
160
+ const binaryPath = path.join(binDir, binaryName);
161
+
162
+ if (fs.existsSync(binaryPath)) {
163
+ try {
164
+ const out = execSync(`"${binaryPath}" --version`, { encoding: "utf8", timeout: 5000 });
165
+ const expectedVer = VERSION.slice(1); // remove leading "v"
166
+ if (out.includes(expectedVer)) {
167
+ console.log(`[lark-agent-bot] Binary ${VERSION} already installed, skipping.`);
168
+ return;
169
+ }
170
+ // Don't downgrade: if existing binary is newer, keep it
171
+ const match = out.match(/(\d+\.\d+\.\d+[^\s]*)/);
172
+ if (match && isNewerOrEqual(match[1], expectedVer)) {
173
+ console.log(`[lark-agent-bot] Binary ${match[1]} is newer than ${VERSION}, skipping.`);
174
+ return;
175
+ }
176
+ console.log(`[lark-agent-bot] Existing binary is outdated, upgrading to ${VERSION}...`);
177
+ fs.unlinkSync(binaryPath);
178
+ } catch {
179
+ console.log(`[lark-agent-bot] Replacing existing binary with ${VERSION}...`);
180
+ fs.unlinkSync(binaryPath);
181
+ }
182
+ }
183
+
184
+ const urls = getDownloadURLs(filename);
185
+ const data = await download(urls);
186
+
187
+ if (ext === ".tar.gz") {
188
+ extractTarGz(data, binDir, binaryName);
189
+ } else {
190
+ extractZip(data, binDir, binaryName);
191
+ }
192
+
193
+ if (platform !== "windows") {
194
+ fs.chmodSync(binaryPath, 0o755);
195
+ }
196
+
197
+ if (platform === "darwin") {
198
+ try {
199
+ execSync(`xattr -d com.apple.quarantine "${binaryPath}"`, { stdio: "pipe" });
200
+ console.log(`[lark-agent-bot] Removed macOS quarantine attribute`);
201
+ } catch {
202
+ // xattr fails if the attribute doesn't exist, which is fine
203
+ }
204
+ }
205
+
206
+ console.log(`[lark-agent-bot] Installed to ${binaryPath}`);
207
+ }
208
+
209
+ main().catch((err) => {
210
+ console.error(err.message);
211
+ console.error(
212
+ "[lark-agent-bot] Installation failed. You can install manually:\n" +
213
+ ` https://github.com/${GITHUB_REPO}/releases/tag/${VERSION}`
214
+ );
215
+ process.exit(1);
216
+ });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "lark-agent-bot",
3
+ "version": "0.3.0",
4
+ "description": "Bridge local AI coding agents (Claude Code, Codex, Cursor, Gemini CLI, ...) to Feishu / Lark",
5
+ "keywords": [
6
+ "feishu",
7
+ "lark",
8
+ "claude-code",
9
+ "codex",
10
+ "ai-coding",
11
+ "chatbot",
12
+ "bridge"
13
+ ],
14
+ "homepage": "https://github.com/ClaymanTwinkle/lark-agent-bot",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/ClaymanTwinkle/lark-agent-bot.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/ClaymanTwinkle/lark-agent-bot/issues"
21
+ },
22
+ "license": "MIT",
23
+ "author": "ClaymanTwinkle",
24
+ "bin": {
25
+ "lark-agent-bot": "run.js"
26
+ },
27
+ "scripts": {
28
+ "postinstall": "node install.js"
29
+ },
30
+ "files": [
31
+ "install.js",
32
+ "run.js",
33
+ "README.md"
34
+ ]
35
+ }
package/run.js ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const { execFileSync, execSync } = require("child_process");
6
+ const path = require("path");
7
+ const fs = require("fs");
8
+
9
+ const PACKAGE = require("./package.json");
10
+ const EXPECTED_VER = PACKAGE.version; // e.g. "1.1.0-beta.4"
11
+ const NAME = "lark-agent-bot";
12
+ const binDir = path.join(__dirname, "bin");
13
+ const ext = process.platform === "win32" ? ".exe" : "";
14
+ const binaryPath = path.join(binDir, NAME + ext);
15
+
16
+ // parseVersion splits "1.2.3-beta.1" into { nums: [1,2,3], preTag: "beta", preNum: 1 }
17
+ function parseVersion(v) {
18
+ v = v.replace(/^v/, "").trim();
19
+ const [base, ...rest] = v.split("-");
20
+ const nums = base.split(".").map(Number);
21
+ const pre = rest.join("-");
22
+ const m = pre.match(/^([a-zA-Z]+)\.?(\d+)?$/);
23
+ return { nums, preTag: m ? m[1] : pre, preNum: m && m[2] ? parseInt(m[2], 10) : 0, hasPre: pre !== "" };
24
+ }
25
+
26
+ // isNewerOrEqual returns true if installed >= expected
27
+ function isNewerOrEqual(installed, expected) {
28
+ const a = parseVersion(installed);
29
+ const b = parseVersion(expected);
30
+ const len = Math.max(a.nums.length, b.nums.length);
31
+ for (let i = 0; i < len; i++) {
32
+ const av = a.nums[i] || 0;
33
+ const bv = b.nums[i] || 0;
34
+ if (av > bv) return true;
35
+ if (av < bv) return false;
36
+ }
37
+ // Same base: no pre-release >= any pre-release (1.2.3 >= 1.2.3-beta.1)
38
+ if (!a.hasPre && b.hasPre) return true;
39
+ if (a.hasPre && !b.hasPre) return false;
40
+ if (!a.hasPre && !b.hasPre) return true;
41
+ // Both pre-release: compare tag then number (rc > beta, beta.10 > beta.9)
42
+ if (a.preTag !== b.preTag) return a.preTag > b.preTag;
43
+ return a.preNum >= b.preNum;
44
+ }
45
+
46
+ function needsReinstall() {
47
+ if (!fs.existsSync(binaryPath)) return true;
48
+ try {
49
+ const out = execFileSync(binaryPath, ["--version"], { encoding: "utf8", timeout: 5000 });
50
+ if (out.includes(EXPECTED_VER)) return false;
51
+ // Extract version from output (e.g. "lark-agent-bot 1.2.2-beta.1" or "1.2.2-beta.1")
52
+ const match = out.match(/(\d+\.\d+\.\d+[^\s]*)/);
53
+ if (match && isNewerOrEqual(match[1], EXPECTED_VER)) return false;
54
+ return true;
55
+ } catch {
56
+ return true;
57
+ }
58
+ }
59
+
60
+ if (needsReinstall()) {
61
+ console.log(`[lark-agent-bot] Binary missing or outdated, installing v${EXPECTED_VER}...`);
62
+ try {
63
+ execSync("node " + JSON.stringify(path.join(__dirname, "install.js")), {
64
+ stdio: "inherit",
65
+ cwd: __dirname,
66
+ });
67
+ } catch {
68
+ console.error("[lark-agent-bot] Auto-install failed. Run manually: npm uninstall -g lark-agent-bot && npm install -g lark-agent-bot");
69
+ process.exit(1);
70
+ }
71
+ }
72
+
73
+ try {
74
+ execFileSync(binaryPath, process.argv.slice(2), { stdio: "inherit" });
75
+ } catch (err) {
76
+ process.exit(err.status || 1);
77
+ }