@ybbapp/push-cli 0.0.1-alpha.0 → 0.0.2-alpha.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.
- package/README.md +11 -50
- package/package.json +2 -2
- package/scripts/install.js +32 -26
package/README.md
CHANGED
|
@@ -1,77 +1,38 @@
|
|
|
1
1
|
# push-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
向 Lark 群聊机器人单向发送 Markdown 消息的 CLI。只使用自定义机器人 webhook,不读取消息或提供其他 Lark API。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 安装
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
需要 Node.js 18 或更新版本。
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
也可以把 `skills/lark-push/` 复制到 Agent 支持的 skills 目录。Skill 提供操作说明和示例;`push-cli` 可执行文件及本地 webhook 配置需要另行准备。构建命令:
|
|
14
|
-
|
|
15
|
-
```sh
|
|
16
|
-
go build -o push-cli ./cmd
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## 分发和安装
|
|
20
|
-
|
|
21
|
-
发布 tag 会构建并分发 npm 包、GitHub Release 二进制和 GHCR Docker 镜像。npm 包是推荐入口;它第一次运行时会按当前 OS/CPU 下载对应 Release 二进制并校验 SHA-256。
|
|
22
|
-
|
|
23
|
-
alpha 版本:
|
|
24
|
-
|
|
25
|
-
```sh
|
|
26
|
-
npx --yes @ybbapp/push-cli@alpha --version
|
|
10
|
+
npm install --global @ybbapp/push-cli@alpha
|
|
27
11
|
```
|
|
28
12
|
|
|
29
|
-
|
|
13
|
+
也可以一次性运行:
|
|
30
14
|
|
|
31
15
|
```sh
|
|
32
|
-
|
|
33
|
-
--repo ybbapp/push-cli \
|
|
34
|
-
--pattern 'push-cli-alpha-0.0.1-*-*.tar.gz'
|
|
16
|
+
npx --yes @ybbapp/push-cli@alpha lark send --title '每日市场报告' --markdown '报告正文'
|
|
35
17
|
```
|
|
36
18
|
|
|
37
|
-
Docker 镜像支持 Linux 多架构:
|
|
38
|
-
|
|
39
|
-
```sh
|
|
40
|
-
docker run --rm --env LARK_WEBHOOK_URL \
|
|
41
|
-
ghcr.io/ybbapp/push-cli:alpha \
|
|
42
|
-
lark send --title '每日市场报告' --markdown '报告正文'
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
首次引导:npm 要求包先存在,才能配置 Trusted Publisher。仓库公开后,先推送 `alpha-0.0.1` 生成公开 Release,再从该版本源码目录手动运行一次 `npm publish --access public --tag alpha`(使用 npm 交互式登录;该版本是 `0.0.1-alpha.0`)。包存在后,在 npm 包的 **Settings → Trusted publishing** 绑定 GitHub Actions:owner `ybbapp`、repository `push-cli`、workflow filename `release.yml`,并允许 `npm publish`。之后每次推送 `alpha-*` 或 `v*` tag,workflow 使用 OIDC 发布,不需要保存 npm token。包尚未创建时,workflow 会跳过 npm job,但仍会发布 GitHub Release 和 GHCR 镜像。
|
|
46
|
-
|
|
47
|
-
首次向 GHCR 发布后,需要在 GitHub Packages 的 `push-cli` 镜像设置中将可见性改为 Public,公开后 Docker 用户才能匿名拉取。
|
|
48
|
-
|
|
49
19
|
## 配置和使用
|
|
50
20
|
|
|
51
|
-
|
|
21
|
+
设置群聊机器人的 webhook:
|
|
52
22
|
|
|
53
23
|
```sh
|
|
54
|
-
export LARK_WEBHOOK_URL='https://open.larksuite.com/open-apis/bot/v2/hook
|
|
24
|
+
export LARK_WEBHOOK_URL='https://open.larksuite.com/open-apis/bot/v2/hook/你的机器人密钥'
|
|
55
25
|
```
|
|
56
26
|
|
|
57
27
|
发送 Markdown:
|
|
58
28
|
|
|
59
29
|
```sh
|
|
30
|
+
push-cli lark send --title '每日市场报告' --markdown '报告正文'
|
|
60
31
|
push-cli lark send --title '每日市场报告' --file report.md
|
|
61
|
-
cat report.md | push-cli lark send --title '每日市场报告'
|
|
62
32
|
```
|
|
63
33
|
|
|
64
|
-
|
|
34
|
+
也可以通过 `--chart-file` 附加原生图表;仓库内的 [Lark skill](skills/lark-push/SKILL.md) 提供 Agent 调用说明和示例。安装 skill:
|
|
65
35
|
|
|
66
36
|
```sh
|
|
67
|
-
push-cli lark
|
|
68
|
-
--title 'NVDA 行情' \
|
|
69
|
-
--markdown 'NVDA 收盘价走势' \
|
|
70
|
-
--chart-file skills/lark-push/examples/nvda-line-chart.json
|
|
37
|
+
gh skill install ybbapp/push-cli lark-push
|
|
71
38
|
```
|
|
72
|
-
|
|
73
|
-
## 版本和平台
|
|
74
|
-
|
|
75
|
-
首个 Git tag `alpha-0.0.1` 对应 npm SemVer `0.0.1-alpha.0`,发布到 npm 的 `alpha` dist-tag;GitHub Release 与 Docker tag 保留 `alpha-0.0.1`,并更新 Docker `alpha` tag。
|
|
76
|
-
|
|
77
|
-
原生二进制支持 macOS amd64/arm64、Linux amd64/arm64/riscv64、Windows amd64/arm64。Docker 镜像支持 Linux amd64/arm64/riscv64。
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ybbapp/push-cli",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.2-alpha.0",
|
|
4
|
+
"description": "One-way messages to Lark group bots.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
package/scripts/install.js
CHANGED
|
@@ -37,20 +37,28 @@ function assetName(tag, platform, arch) {
|
|
|
37
37
|
return `push-cli-${tag}-${platform}-${arch}.${extension}`;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
if (!REDIRECT_HOSTS.has(
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
function download(url, destination) {
|
|
41
|
+
const initialHost = new URL(url).hostname;
|
|
42
|
+
if (!REDIRECT_HOSTS.has(initialHost)) throw new Error(`unexpected download host: ${initialHost}`);
|
|
43
|
+
const result = spawnSync("curl", [
|
|
44
|
+
"--fail", "--location", "--silent", "--show-error",
|
|
45
|
+
"--connect-timeout", "10", "--max-time", "120", "--max-redirs", "3",
|
|
46
|
+
"--proto", "=https", "--proto-redir", "=https",
|
|
47
|
+
"--output", destination, "--write-out", "%{url_effective}", url,
|
|
48
|
+
], { encoding: "utf8", maxBuffer: 1024 * 1024 });
|
|
49
|
+
if (result.error) {
|
|
50
|
+
if (process.platform !== "win32") throw new Error("curl is required to download the push-cli binary");
|
|
51
|
+
const command = "$ErrorActionPreference='Stop'; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -UseBasicParsing -Uri $env:PUSH_CLI_URL -OutFile $env:PUSH_CLI_DEST";
|
|
52
|
+
const fallback = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command], {
|
|
53
|
+
stdio: "ignore",
|
|
54
|
+
env: { ...process.env, PUSH_CLI_URL: url, PUSH_CLI_DEST: destination },
|
|
55
|
+
});
|
|
56
|
+
if (fallback.error || fallback.status !== 0) throw new Error("could not download the push-cli release asset");
|
|
57
|
+
return;
|
|
49
58
|
}
|
|
50
|
-
if (
|
|
51
|
-
const finalHost = new URL(
|
|
59
|
+
if (result.status !== 0) throw new Error(`release download failed (curl exit ${result.status})`);
|
|
60
|
+
const finalHost = new URL(result.stdout.trim()).hostname;
|
|
52
61
|
if (!REDIRECT_HOSTS.has(finalHost)) throw new Error(`unexpected download host: ${finalHost}`);
|
|
53
|
-
return Buffer.from(await response.arrayBuffer());
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
function extract(archive, directory, platform) {
|
|
@@ -69,29 +77,27 @@ function extract(archive, directory, platform) {
|
|
|
69
77
|
if (result.status !== 0) throw new Error("could not extract the release archive; install tar and retry");
|
|
70
78
|
}
|
|
71
79
|
|
|
72
|
-
|
|
80
|
+
function ensureBinary() {
|
|
73
81
|
if (fs.existsSync(BINARY_PATH)) return BINARY_PATH;
|
|
74
82
|
|
|
75
83
|
const { platform, arch } = target();
|
|
76
84
|
const tag = releaseTag();
|
|
77
85
|
const asset = assetName(tag, platform, arch);
|
|
78
86
|
const base = `https://github.com/${OWNER}/${REPOSITORY}/releases/download/${encodeURIComponent(tag)}`;
|
|
79
|
-
const [archiveData, checksums] = await Promise.all([
|
|
80
|
-
get(`${base}/${asset}`),
|
|
81
|
-
get(`${base}/checksums.txt`),
|
|
82
|
-
]);
|
|
83
|
-
const checksumLine = checksums.toString("utf8").split(/\r?\n/).find((line) => line.trim().endsWith(` ${asset}`));
|
|
84
|
-
if (!checksumLine) throw new Error(`release checksums do not include ${asset}`);
|
|
85
|
-
const expected = checksumLine.trim().split(/\s+/)[0].toLowerCase();
|
|
86
|
-
const actual = crypto.createHash("sha256").update(archiveData).digest("hex");
|
|
87
|
-
if (!/^[a-f0-9]{64}$/.test(expected) || actual !== expected) {
|
|
88
|
-
throw new Error(`SHA-256 verification failed for ${asset}`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
87
|
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "push-cli-"));
|
|
92
88
|
try {
|
|
93
89
|
const archivePath = path.join(temporary, asset);
|
|
94
|
-
|
|
90
|
+
const checksumsPath = path.join(temporary, "checksums.txt");
|
|
91
|
+
download(`${base}/${asset}`, archivePath);
|
|
92
|
+
download(`${base}/checksums.txt`, checksumsPath);
|
|
93
|
+
const checksums = fs.readFileSync(checksumsPath, "utf8");
|
|
94
|
+
const checksumLine = checksums.split(/\r?\n/).find((line) => line.trim().endsWith(` ${asset}`));
|
|
95
|
+
if (!checksumLine) throw new Error(`release checksums do not include ${asset}`);
|
|
96
|
+
const expected = checksumLine.trim().split(/\s+/)[0].toLowerCase();
|
|
97
|
+
const actual = crypto.createHash("sha256").update(fs.readFileSync(archivePath)).digest("hex");
|
|
98
|
+
if (!/^[a-f0-9]{64}$/.test(expected) || actual !== expected) {
|
|
99
|
+
throw new Error(`SHA-256 verification failed for ${asset}`);
|
|
100
|
+
}
|
|
95
101
|
extract(archivePath, temporary, platform);
|
|
96
102
|
const extracted = path.join(temporary, BINARY_NAME);
|
|
97
103
|
if (!fs.existsSync(extracted)) throw new Error(`release archive does not contain ${BINARY_NAME}`);
|