@yep-ai/cli 1.1.0 → 1.2.2

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 CHANGED
@@ -5,14 +5,14 @@
5
5
  ## 安装与使用
6
6
 
7
7
  ```sh
8
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep --help
8
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep --help
9
9
  ```
10
10
 
11
- 以下远程命令适用于 1.1.0 发布之后;本次实现不代表 npm 或网站已更新。用户可通过 npx 运行设备节点安装包,无需全局安装:
11
+ 用户可通过 npx 运行安装包,无需全局安装:
12
12
 
13
13
  ```sh
14
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep --help
15
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep device start
14
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep --help
15
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep device start
16
16
  ```
17
17
 
18
18
  本包包含完整实现。`yep-cli` 是计划中的同版本入口包,当前尚未发布本项目版本,请使用 `@yep-ai/cli`。
@@ -26,31 +26,31 @@ npm install
26
26
  npm run yep -- --help
27
27
  npm run build:yep-node
28
28
  npm run pack:cli
29
- npm install --prefix ./tmp/yep-install ./dist/cli/scoped/yep-ai-cli-1.1.0.tgz ./dist/cli/unscoped/yep-cli-1.1.0.tgz
29
+ npm install --prefix ./tmp/yep-install ./dist/cli/scoped/yep-ai-cli-1.2.2.tgz ./dist/cli/unscoped/yep-cli-1.2.2.tgz
30
30
  ./tmp/yep-install/node_modules/.bin/yep --version
31
31
  ```
32
32
 
33
- 完整实现包为 `scoped/yep-ai-cli-1.1.0.tgz`,入口包为 `unscoped/yep-cli-1.1.0.tgz`。本地验证时可一起指定两份文件,无需从 npm 下载实现包。输出的 `manifest.json` 记录包名、版本、依赖和 SHA-256。
33
+ 完整实现包为 `scoped/yep-ai-cli-1.2.2.tgz`,入口包为 `unscoped/yep-cli-1.2.2.tgz`。本地验证时可一起指定两份文件,无需从 npm 下载实现包。输出的 `manifest.json` 记录包名、版本、依赖和 SHA-256。
34
34
 
35
35
  ## 市场与 Git 内容版本
36
36
 
37
37
  ```sh
38
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep login --registry https://yep.nihao.cool
39
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep whoami
40
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep search assistant
41
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep info @creator/demo
42
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep client set codex,claude
43
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep install @creator/demo --client codex
44
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep list
45
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep list --remote
46
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep list --mine
47
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep uninstall @creator/demo --client codex
48
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep publish ./my-skill
49
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep history @creator/demo
50
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep diff @creator/demo --base published
51
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep publish ./my-agent --kind agent
52
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep submit @creator/demo --kind agent
53
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep withdraw @creator/demo --kind agent
38
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep login --registry https://yep.nihao.cool
39
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep whoami
40
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep search assistant
41
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep info @creator/demo
42
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep client set codex,claude
43
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep install @creator/demo --client codex
44
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep list
45
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep list --remote
46
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep list --mine
47
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep uninstall @creator/demo --client codex
48
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep publish ./my-skill
49
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep history @creator/demo
50
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep diff @creator/demo --base published
51
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep publish ./my-agent --kind agent
52
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep submit @creator/demo --kind agent
53
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep withdraw @creator/demo --kind agent
54
54
  ```
55
55
 
56
56
  默认资源类型为 `skill`,也支持 `--kind agent|plugin`。Skill 私有保存后从网页申请共享审核;Agent/Plugin 的发布会提交审核。审核通过才进入公开市场。`history` 和 `diff` 访问服务端保留的真实 Git 版本。
@@ -60,10 +60,10 @@ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep withd
60
60
  ## 设备节点
61
61
 
62
62
  ```sh
63
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep device login
64
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep device start "/path/to/documents"
65
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep device status
66
- npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep device logout
63
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep device login
64
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep device start "/path/to/documents"
65
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep device status
66
+ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep device logout
67
67
  ```
68
68
 
69
69
  设备通过 HTTPS 配对和 WSS 出站连接;网页批准设备后,还需在某场咨询中选择目录和权限。目录不会整体上传。最多指定 5 个目录;指定目录时仅提供受限内置命令。不指定目录时仅授权当前目录;必须显式使用 `--privacy system` 才启用完整系统终端,该终端拥有当前系统用户权限,可访问其他文件和网络,输入输出不经过文件脱敏。进程退出后节点离线,不安装后台服务。设备授权最长 24 小时,可以从网页或 CLI 撤销。
@@ -75,7 +75,7 @@ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep devic
75
75
  原生程序随 npm 包一起分发,无需用户安装 Go。当前构建目标为 Windows x64 / ARM64、macOS Intel / Apple Silicon、Linux x64 / ARM64;Windows 需要支持 ConPTY 的 Windows 10 1809 / Server 2019 或更新版本。鸿蒙 / OpenHarmony 原生 ABI 尚未适配,不能将 Linux ARM64 包视为鸿蒙包。缺少原生程序时会明确提示并提供兼容的逐条命令模式;受限目录始终使用内置命令模式,不启动系统 Shell。
76
76
 
77
77
  设备配对和网络通道使用 HTTPS / WSS;服务端保存令牌摘要,客户端每小时轮换设备令牌并保留短暂交接窗口,轮换不会延长原有 24 小时授权。客户端先持久化待交接凭据,断线后继续完成交接。TLS 在站点入口终止,集群内部连接不属于端到端加密。
78
- 指定启动目录时仅支持 `pwd`、`ls` / `dir`、`echo`、`cat` / `type`、`head`、`tail`、`grep` / `findstr`、`wc -l` 与终端内的 `cd`,不能使用管道、重定向或外部程序。已有旧版节点若提示不支持终端,请停止旧进程,将原启动命令中的 `yep` 替换为 `npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep`,保留原目录参数。
78
+ 指定启动目录时仅支持 `pwd`、`ls` / `dir`、`echo`、`cat` / `type`、`head`、`tail`、`grep` / `findstr`、`wc -l` 与终端内的 `cd`,不能使用管道、重定向或外部程序。已有旧版节点若提示不支持终端,请停止旧进程,将原启动命令中的 `yep` 替换为 `npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep`,保留原目录参数。
79
79
 
80
80
  设备默认连接 `https://yep.nihao.cool`,使用本站时无需指定服务器。连接自建站点时可使用 `--server https://your-site.example`;只接受 HTTPS 源站(回环开发地址允许 HTTP),显式指定后记住该源站,令牌按源站隔离。市场登录不等于设备授权。
81
81
 
@@ -95,9 +95,9 @@ npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep devic
95
95
 
96
96
  市场凭据保存为 `~/.config/yep/config.json`;设备身份和各源站的授权保存在 `~/.config/yep/devices/`。目录权限 `0700`、凭据文件 `0600`;Windows 使用当前用户专属 ACL。市场和设备令牌分别管理。域名改变后需要在新源站重新登录、配对,不会把旧源站令牌发送到新地址。
97
97
 
98
- 执行 `npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep --help` 或 `npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.1.0.tgz yep device --help` 查看全部选项。
98
+ 执行 `npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep --help` 或 `npx --yes --package=https://yep.nihao.cool/downloads/yep-cli-1.2.2.tgz yep device --help` 查看全部选项。
99
99
 
100
- ## 本地脱敏(1.1.0)
100
+ ## 本地脱敏(1.2.2)
101
101
 
102
102
  默认 `device start` 为严格模式,无目录参数时仅授权当前目录;完整系统终端需显式 `--privacy system`,其输入输出不经过文件脱敏。读取、搜索、下载及发布前使用本机 Go 核心,规则修改不需要用户编译。
103
103
 
@@ -113,4 +113,28 @@ yep privacy export ./report.txt --out ./report.safe.txt --offline
113
113
 
114
114
  支持 10 MiB 以内的 UTF-8 文本、源码、JSON/YAML/CSV;PDF、Office、图片、压缩包与其他无法解析为文本的文件默认允许原样传输。可在「设备节点 → 脱敏规则」选择阻断,或设置本地 `unsupportedFiles: block`;任一适用策略要求阻断时以阻断为准。原件不覆盖。任意 JS 脚本不在默认引擎中执行。检测不保证穷尽自然语言秘密,发送前仍需本机复核。详见仓库 `docs/privacy.md`。
115
115
 
116
- 本仓库已准备 1.1.0;npm 发布与网站部署完成后,对应远程版本命令才可使用。
116
+ 本仓库已准备 1.2.2;npm 发布与网站部署完成后,对应远程版本命令才可使用。
117
+
118
+ ## 飞书智能体节点
119
+
120
+ 飞书节点由独立的 Go 进程负责,与 `yep device start` 的设备、目录和权限分开管理。
121
+
122
+ ```sh
123
+ npx --yes @yep-ai/cli@1.2.2 feishu setup --install-service
124
+ npx --yes @yep-ai/cli@1.2.2 feishu status
125
+ npx --yes @yep-ai/cli@1.2.2 feishu doctor --fix
126
+ npx --yes @yep-ai/cli@1.2.2 feishu restart
127
+ npx --yes @yep-ai/cli@1.2.2 feishu logs --follow
128
+ ```
129
+
130
+ `setup` 自动打开平台授权页、飞书官方创建/授权二维码;完成扫码后保存节点专用凭据,启动长连接并检查权限。飞书官方扫码和企业管理员要求的审批需要本人完成。网页「飞书节点」也支持直接托管,关闭浏览器后继续运行。
131
+
132
+ 在网页选择允许的私聊用户、群和回复规则。机器人消息需显式允许发送方机器人,而且必须 @ 本机器人,防止循环回复。群消息只处理白名单群;默认只回复 @ 消息,可选择关键词或群内全部消息(后者需额外权限)。当前支持文本收发、会话上下文、自动回复和消息处理记录,用户画像与知识库暂未接入回复。
133
+
134
+ `doctor --fix` 打开权限修复页并重新检查,不会代替飞书的授权审批。`feishu stop` 停止本机服务,`feishu remove` 撤销节点。系统服务使用当前用户的 systemd / LaunchAgent / Windows 任务计划程序;运行文件、凭据和日志独立保存。完整说明见仓库 `packages/yep-feishu-node/README.md`。
135
+
136
+ ### Windows 配置权限修复(1.2.2)
137
+
138
+ 已有飞书配置目录或文件继承了 Windows 用户目录权限时,CLI 会自动限制本人拥有的配置路径,只允许本人、SYSTEM 和 Administrators 访问。目录权限传递给子目录与文件;配置内容保持。使用独立脚本和 `-NoProfile` 执行,路径包含空格、中文、方括号或单引号均按字面值处理。无法修复时显示路径及可操作说明。
139
+
140
+ 从旧版升级后执行 `npx --yes @yep-ai/cli@1.2.2 feishu setup`;不需要删除已有凭据或切换终端。PowerShell 启动配置中的 Docker 报错与飞书节点无关。
package/bin/market.js CHANGED
@@ -592,6 +592,8 @@ Usage:
592
592
  ${cliName} login Sign in and save a registry token
593
593
  ${cliName} logout Revoke the current saved login
594
594
  ${cliName} whoami Show the signed-in account
595
+ ${cliName} feishu setup Connect an independent Feishu bot node
596
+ ${cliName} feishu --help Feishu status, diagnostics and service control
595
597
  ${cliName} search <query> Search marketplace skills
596
598
  ${cliName} info <skill-name> Print skill metadata
597
599
  ${cliName} client [list|set <clients>|reset] Show, pin or clear default clients
@@ -1318,7 +1320,7 @@ async function privatePackageFiles(packageDir, registry, token) {
1318
1320
  for (const [name, content] of Object.entries(files)) {
1319
1321
  await manager.checkPath(name); await manager.metadata(name);
1320
1322
  const result = await manager.scan(Buffer.from(content), name, "marketplace-publication");
1321
- sanitized[name] = result.data; changed ||= !Buffer.from(content).equals(result.data);
1323
+ sanitized[name] = result.data; changed ||= result.action === "redact";
1322
1324
  }
1323
1325
  if (changed) {
1324
1326
  const review = join(manager.directory, "review", String(Date.now()));
package/bin/yep.js CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { runMarketplace } from "./market.js";
3
+ import { runFeishu } from "../lib/feishu/command.js";
3
4
  import { runDevice } from "../lib/device/command.js";
4
5
  import { runPrivacy } from "../lib/privacy/command.js";
5
6
  const argv = process.argv.slice(2);
6
7
  try {
7
8
  if (argv[0] === "device") await runDevice(argv.slice(1));
9
+ else if (argv[0] === "feishu") await runFeishu(argv.slice(1));
8
10
  else if (argv[0] === "privacy") await runPrivacy(argv.slice(1));
9
11
  else await runMarketplace(argv);
10
12
  } catch (error) {
@@ -0,0 +1,534 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { spawn, execFile } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+ import { fileURLToPath } from "node:url";
8
+ import { setTimeout as delay } from "node:timers/promises";
9
+ import { service } from "./service.js";
10
+
11
+ const exec = promisify(execFile);
12
+ const windowsACLScript = fileURLToPath(new URL("./private-path.ps1", import.meta.url));
13
+ const help = `Yep 飞书智能体(独立 Go 服务)
14
+ yep feishu setup [--server https://yep.nihao.cool] [--app-id cli_…] [--all-group-messages] [--no-open] [--install-service]
15
+ yep feishu start [--foreground] [--install-service]
16
+ yep feishu status [--json]
17
+ yep feishu doctor [--fix] [--no-open]
18
+ yep feishu logs [--follow]
19
+ yep feishu stop | restart | remove
20
+ yep feishu service install | uninstall
21
+
22
+ 可选:--node <节点 ID>、--server <平台地址>。
23
+ setup 自动打开账号确认页与飞书授权页,确认后启动服务。
24
+ remove 撤销平台节点令牌并删除本机凭据;飞书应用保留。
25
+ 平台管理:<server>/feishu。升级:npm install -g @yep-ai/cli@latest,然后 yep feishu restart。`;
26
+ export function origin(value) {
27
+ const u = new URL(value);
28
+ if (
29
+ u.username ||
30
+ u.password ||
31
+ u.search ||
32
+ u.hash ||
33
+ u.pathname !== "/" ||
34
+ (u.protocol !== "https:" &&
35
+ !(
36
+ u.protocol === "http:" &&
37
+ ["localhost", "127.0.0.1", "[::1]"].includes(u.hostname)
38
+ ))
39
+ )
40
+ throw new Error("平台地址须为 HTTPS origin;本机开发支持 HTTP。");
41
+ return u.origin;
42
+ }
43
+ export async function privatePath(location, directory = false) {
44
+ const st = await fs.lstat(location);
45
+ if (st.isSymbolicLink() || (directory ? !st.isDirectory() : !st.isFile()))
46
+ throw new Error("飞书配置路径不安全。");
47
+ if (
48
+ process.platform !== "win32" &&
49
+ (st.mode & 0o077 || st.uid !== process.getuid())
50
+ )
51
+ throw new Error("飞书配置权限不安全,请设为目录 0700、文件 0600。");
52
+ if (process.platform === "win32") {
53
+ try {
54
+ await exec("powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", windowsACLScript], {
55
+ env: { ...process.env, YEP_FEISHU_ACL_PATH: location, YEP_FEISHU_ACL_DIRECTORY: directory ? "1" : "0" },
56
+ windowsHide: true,
57
+ timeout: 15000,
58
+ });
59
+ } catch (cause) {
60
+ throw new Error(`无法保护飞书配置路径:${location}。请使用创建该目录的 Windows 账号运行,并确认目录可写。`, { cause });
61
+ }
62
+ }
63
+ return st;
64
+ }
65
+ async function protect(location, directory = false) {
66
+ if (process.platform !== "win32")
67
+ return fs.chmod(location, directory ? 0o700 : 0o600);
68
+ await privatePath(location, directory);
69
+ }
70
+ async function privateWrite(location, value) {
71
+ const temp = location + "." + randomUUID();
72
+ try {
73
+ const f = await fs.open(temp, "wx", 0o600);
74
+ try {
75
+ await f.writeFile(JSON.stringify(value, null, 2));
76
+ await protect(temp);
77
+ await f.sync();
78
+ } finally {
79
+ await f.close();
80
+ }
81
+ await fs.rename(temp, location);
82
+ } finally {
83
+ await fs.rm(temp, { force: true });
84
+ }
85
+ }
86
+ export async function request(
87
+ server,
88
+ route,
89
+ { token, method = "GET", body, signal } = {},
90
+ ) {
91
+ const res = await fetch(server + route, {
92
+ method,
93
+ headers: {
94
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
95
+ ...(body ? { "Content-Type": "application/json" } : {}),
96
+ },
97
+ ...(body ? { body: JSON.stringify(body) } : {}),
98
+ signal: signal || AbortSignal.timeout(30000),
99
+ redirect: "error",
100
+ });
101
+ const value = await res.json();
102
+ if (!res.ok)
103
+ throw new Error(value.error || `飞书服务返回 HTTP ${res.status}`);
104
+ return value;
105
+ }
106
+ async function openURL(value, noOpen) {
107
+ console.log(`打开:${value}`);
108
+ if (noOpen) return;
109
+ const [bin, args] =
110
+ process.platform === "darwin"
111
+ ? ["open", [value]]
112
+ : process.platform === "win32"
113
+ ? ["rundll32.exe", ["url.dll,FileProtocolHandler", value]]
114
+ : ["xdg-open", [value]];
115
+ try {
116
+ await exec(bin, args, { timeout: 5000, windowsHide: true });
117
+ } catch {
118
+ console.log("浏览器未自动打开,请使用上方地址。");
119
+ }
120
+ }
121
+ export async function nativeBinary() {
122
+ const target = `${process.platform}-${process.arch}`,
123
+ file =
124
+ process.platform === "win32" ? "yep-feishu-node.exe" : "yep-feishu-node",
125
+ root = fileURLToPath(new URL("../../", import.meta.url)),
126
+ manifest = JSON.parse(
127
+ await fs.readFile(
128
+ path.join(root, "native", "feishu-manifest.json"),
129
+ "utf8",
130
+ ),
131
+ ),
132
+ relative = `${target}/${file}`,
133
+ binary = path.join(root, "native", relative);
134
+ const expected = manifest.files[relative]?.sha256;
135
+ if (!expected)
136
+ throw new Error(`当前系统 ${target} 尚无飞书节点,请使用平台托管。`);
137
+ const hash = createHash("sha256")
138
+ .update(await fs.readFile(binary))
139
+ .digest("hex");
140
+ if (hash !== expected)
141
+ throw new Error("飞书节点文件校验失败,请重新安装 Yep CLI。");
142
+ if (process.platform !== "win32") await fs.chmod(binary, 0o755);
143
+ return binary;
144
+ }
145
+ async function control(dir, action = "status") {
146
+ await privatePath(path.join(dir, "control.json"));
147
+ const c = JSON.parse(
148
+ await fs.readFile(path.join(dir, "control.json"), "utf8"),
149
+ );
150
+ const u = new URL(c.url);
151
+ if (
152
+ u.protocol !== "http:" ||
153
+ u.hostname !== "127.0.0.1" ||
154
+ !u.port ||
155
+ u.pathname !== "/" ||
156
+ !c.token
157
+ )
158
+ throw new Error("本机飞书控制地址不安全。");
159
+ return request(u.origin, `/${action}`, {
160
+ token: c.token,
161
+ method: action === "status" ? "GET" : "POST",
162
+ body: action === "status" ? undefined : {},
163
+ signal: AbortSignal.timeout(5000),
164
+ });
165
+ }
166
+ async function running(dir) {
167
+ try {
168
+ return await control(dir);
169
+ } catch (e) {
170
+ if (
171
+ e.code === "ENOENT" ||
172
+ e.cause?.code === "ECONNREFUSED" ||
173
+ e.name === "TimeoutError" ||
174
+ e.message === "fetch failed"
175
+ )
176
+ return null;
177
+ throw e;
178
+ }
179
+ }
180
+ async function stop(dir) {
181
+ const current = await running(dir);
182
+ if (!current) return;
183
+ await control(dir, "stop");
184
+ for (let i = 0; i < 80; i++) {
185
+ if (!(await running(dir))) return;
186
+ await delay(100);
187
+ }
188
+ throw new Error("节点仍在退出,请稍后查看状态。");
189
+ }
190
+ async function start(dir, foreground = false) {
191
+ if (await running(dir)) {
192
+ console.log("飞书节点已在运行。");
193
+ return;
194
+ }
195
+ const binary = await nativeBinary(),
196
+ config = path.join(dir, "credential.json");
197
+ await privatePath(config);
198
+ if (foreground) {
199
+ const child = spawn(binary, ["--config", config], {
200
+ stdio: "inherit",
201
+ windowsHide: true,
202
+ });
203
+ const close = () => child.kill("SIGTERM");
204
+ process.once("SIGINT", close);
205
+ process.once("SIGTERM", close);
206
+ try {
207
+ await new Promise((resolve, reject) => {
208
+ child.once("error", reject);
209
+ child.once("exit", (code) =>
210
+ code === 0 ? resolve() : reject(new Error(`飞书节点退出(${code})`)),
211
+ );
212
+ });
213
+ } finally {
214
+ process.removeListener("SIGINT", close);
215
+ process.removeListener("SIGTERM", close);
216
+ }
217
+ return;
218
+ }
219
+ const log = path.join(dir, "service.log");
220
+ try {
221
+ const st = await fs.lstat(log);
222
+ if (st.isSymbolicLink()) throw new Error("日志路径不安全");
223
+ if (st.size > 8 * 1024 * 1024) await fs.truncate(log, 0);
224
+ } catch (e) {
225
+ if (e.code !== "ENOENT") throw e;
226
+ }
227
+ const out = await fs.open(log, "a", 0o600);
228
+ await protect(log);
229
+ let child;
230
+ try {
231
+ child = spawn(binary, ["--config", config], {
232
+ stdio: ["ignore", out.fd, out.fd],
233
+ detached: true,
234
+ windowsHide: true,
235
+ });
236
+ await new Promise((resolve, reject) => {
237
+ child.once("spawn", resolve);
238
+ child.once("error", reject);
239
+ });
240
+ child.unref();
241
+ } finally {
242
+ await out.close();
243
+ }
244
+ for (let i = 0; i < 80; i++) {
245
+ if (await running(dir)) {
246
+ console.log("飞书节点已启动;权限检测正在后台进行。");
247
+ return;
248
+ }
249
+ await delay(100);
250
+ }
251
+ throw new Error("节点尚未就绪,请运行 yep feishu logs 查看原因。");
252
+ }
253
+ export async function runFeishu(argv) {
254
+ const command = argv[0] || "help";
255
+ if (["help", "--help", "-h"].includes(command)) {
256
+ console.log(help);
257
+ return;
258
+ }
259
+ const allowed = new Set([
260
+ "--server",
261
+ "--node",
262
+ "--app-id",
263
+ "--name",
264
+ "--no-open",
265
+ "--all-group-messages",
266
+ "--install-service",
267
+ "--foreground",
268
+ "--fix",
269
+ "--follow",
270
+ "--json",
271
+ ]),
272
+ opts = {};
273
+ let serviceAction;
274
+ for (let i = 1; i < argv.length; i++) {
275
+ const arg = argv[i];
276
+ if (command === "service" && i === 1 && !arg.startsWith("--")) {
277
+ serviceAction = arg;
278
+ continue;
279
+ }
280
+ if (!allowed.has(arg)) throw new Error(`未知参数:${arg}`);
281
+ if (["--server", "--node", "--app-id", "--name"].includes(arg)) {
282
+ if (!argv[i + 1] || argv[i + 1].startsWith("--"))
283
+ throw new Error(`${arg} 缺少值`);
284
+ opts[arg] = argv[++i];
285
+ } else opts[arg] = true;
286
+ }
287
+ if (
288
+ ![
289
+ "setup",
290
+ "start",
291
+ "status",
292
+ "doctor",
293
+ "logs",
294
+ "stop",
295
+ "restart",
296
+ "remove",
297
+ "service",
298
+ ].includes(command)
299
+ )
300
+ throw new Error("未知命令;运行 yep feishu --help。");
301
+ const base =
302
+ process.env.YEP_FEISHU_CONFIG_DIR ||
303
+ path.join(
304
+ process.env.YEP_CONFIG_DIR || path.join(os.homedir(), ".config", "yep"),
305
+ "feishu",
306
+ );
307
+ const made = await fs.mkdir(base, { recursive: true, mode: 0o700 });
308
+ if (made) await protect(base, true);
309
+ await privatePath(base, true);
310
+ let server = opts["--server"];
311
+ if (!server) {
312
+ try {
313
+ await privatePath(path.join(base, "default.json"));
314
+ server = JSON.parse(
315
+ await fs.readFile(path.join(base, "default.json"), "utf8"),
316
+ ).server;
317
+ } catch (e) {
318
+ if (e.code !== "ENOENT") throw e;
319
+ }
320
+ }
321
+ server = origin(server || "https://yep.nihao.cool");
322
+ const platform = path.join(
323
+ base,
324
+ createHash("sha256").update(server).digest("hex"),
325
+ );
326
+ if (await fs.mkdir(platform, { recursive: true, mode: 0o700 }))
327
+ await protect(platform, true);
328
+ await privatePath(platform, true);
329
+ let node = opts["--node"];
330
+ if (!node && command !== "setup") {
331
+ try {
332
+ await privatePath(path.join(platform, "default.json"));
333
+ node = JSON.parse(
334
+ await fs.readFile(path.join(platform, "default.json"), "utf8"),
335
+ ).node;
336
+ } catch (e) {
337
+ if (e.code !== "ENOENT") throw e;
338
+ }
339
+ }
340
+ if (node && !/^[A-Za-z0-9_-]{20,100}$/.test(node))
341
+ throw new Error("节点 ID 不合法。");
342
+ if (command === "setup") {
343
+ if (!node) {
344
+ const v = await request(server, "/api/feishu/setups", {
345
+ method: "POST",
346
+ body: {
347
+ name: opts["--name"] || "我的飞书智能体",
348
+ mode: "local",
349
+ appId: opts["--app-id"] || "",
350
+ fullGroups: Boolean(opts["--all-group-messages"]),
351
+ },
352
+ });
353
+ if (new URL(v.approvalUrl).origin !== server)
354
+ throw new Error("账号确认地址不属于当前平台。");
355
+ await openURL(v.approvalUrl, opts["--no-open"]);
356
+ let seen = "",
357
+ credential;
358
+ while (Date.now() < Date.parse(v.setup.expiresAt)) {
359
+ const s = await request(server, `/api/feishu/setups/${v.setup.id}`, {
360
+ token: v.pollToken,
361
+ });
362
+ if (s.status !== seen) {
363
+ console.log(
364
+ {
365
+ approval: "1/4 等待浏览器确认 Yep 账号",
366
+ registering: "2/4 准备飞书官方授权页",
367
+ authorizing: "2/4 等待飞书授权",
368
+ ready: "3/4 应用授权完成",
369
+ }[s.status] || s.status,
370
+ );
371
+ seen = s.status;
372
+ }
373
+ if (s.url) {
374
+ const url = new URL(s.url);
375
+ if (
376
+ url.protocol !== "https:" ||
377
+ ![
378
+ "accounts.feishu.cn",
379
+ "accounts.larksuite.com",
380
+ "open.feishu.cn",
381
+ "open.larksuite.com",
382
+ ].includes(url.hostname)
383
+ )
384
+ throw new Error("飞书授权地址不可信。");
385
+ if (opts.openedURL !== s.url) {
386
+ await openURL(s.url, opts["--no-open"]);
387
+ opts.openedURL = s.url;
388
+ }
389
+ }
390
+ if (["failed", "expired"].includes(s.status))
391
+ throw new Error(s.error || "飞书授权已过期,请重新 setup。");
392
+ if (s.status === "ready") {
393
+ credential = await request(
394
+ server,
395
+ `/api/feishu/setups/${v.setup.id}/claim`,
396
+ { method: "POST", token: v.pollToken, body: {} },
397
+ );
398
+ break;
399
+ }
400
+ await delay(2500);
401
+ }
402
+ if (!credential) throw new Error("飞书授权已超时,请重新 setup。");
403
+ node = credential.nodeId;
404
+ if (!/^[A-Za-z0-9_-]{20,100}$/.test(node) || credential.server !== server)
405
+ throw new Error("节点凭据格式不正确。");
406
+ const dir = path.join(platform, node);
407
+ await fs.mkdir(dir, { mode: 0o700 });
408
+ await protect(dir, true);
409
+ await privateWrite(path.join(dir, "credential.json"), credential);
410
+ await request(server, `/api/feishu/setups/${v.setup.id}/ack`, {
411
+ method: "POST",
412
+ token: v.pollToken,
413
+ body: {},
414
+ }).catch(() => {});
415
+ await privateWrite(path.join(platform, "default.json"), { node });
416
+ await privateWrite(path.join(base, "default.json"), { server });
417
+ console.log("凭据已保存到独立飞书配置目录。");
418
+ }
419
+ }
420
+ if (!node) throw new Error("尚未连接飞书;先运行 yep feishu setup。");
421
+ const dir = path.join(platform, node);
422
+ await privatePath(dir, true);
423
+ const credentialFile = path.join(dir, "credential.json");
424
+ await privatePath(credentialFile);
425
+ const credential = JSON.parse(await fs.readFile(credentialFile, "utf8"));
426
+ if (credential.server !== server || credential.nodeId !== node)
427
+ throw new Error("节点凭据与平台不一致。");
428
+ const managedStart=async(install=false,foreground=false,refresh=false)=>{
429
+ if(foreground){await start(dir,true);return}
430
+ let installed=false;try{await fs.access(path.join(dir,"service.json"));installed=true}catch(e){if(e.code!=="ENOENT")throw e}
431
+ if(install||installed&&refresh){try{await service("install",dir,await nativeBinary());installed=true}catch(e){console.error("开机服务未安装成功,将启动普通后台进程:"+e.message);installed=false}}
432
+ if(installed){await service("start",dir);for(let i=0;i<80;i++){if(await running(dir)){console.log("飞书开机服务已启动。");return};await delay(100)};throw new Error("开机服务尚未就绪,请运行 yep feishu logs 检查。")}
433
+ await start(dir);
434
+ };
435
+ if(command==="setup"||command==="start"){await managedStart(Boolean(opts["--install-service"]),Boolean(opts["--foreground"]));console.log(`管理页面:${server}/feishu`);return}
436
+ if(command==="restart"){await service("stop",dir);await stop(dir);await managedStart(false,false,true);return}
437
+ if (command === "stop") {
438
+ await service("stop", dir);
439
+ await stop(dir);
440
+ console.log("本机飞书节点已停止。");
441
+ return;
442
+ }
443
+ if (command === "service") {
444
+ if (!["install", "uninstall"].includes(serviceAction))
445
+ throw new Error("使用 yep feishu service install | uninstall");
446
+ if(serviceAction==="install"){await stop(dir);await managedStart(true)}else{await service("uninstall",dir);await stop(dir)}
447
+ console.log("飞书开机服务设置已更新。");
448
+ return;
449
+ }
450
+ if (command === "remove") {
451
+ await request(server, "/api/feishu/runtime/self", {
452
+ method: "DELETE",
453
+ token: credential.token,
454
+ });
455
+ await service("uninstall", dir);
456
+ await stop(dir);
457
+ await fs.rm(dir, { recursive: true });
458
+ try {
459
+ const selected = JSON.parse(
460
+ await fs.readFile(path.join(platform, "default.json"), "utf8"),
461
+ );
462
+ if (selected.node === node)
463
+ await fs.rm(path.join(platform, "default.json"), { force: true });
464
+ } catch (e) {
465
+ if (e.code !== "ENOENT") throw e;
466
+ }
467
+ console.log("飞书节点已解绑,本机凭据已删除。");
468
+ return;
469
+ }
470
+ if (command === "logs") {
471
+ const log = path.join(dir, "service.log");
472
+ await privatePath(log);
473
+ let offset = 0;
474
+ do {
475
+ const st = await fs.stat(log);
476
+ if (st.size < offset) offset = 0;
477
+ const f = await fs.open(log, "r");
478
+ try {
479
+ const size = Math.min(st.size - offset, 65536);
480
+ if (size > 0) {
481
+ const buffer = Buffer.alloc(size);
482
+ const { bytesRead } = await f.read(
483
+ buffer,
484
+ 0,
485
+ size,
486
+ Math.max(offset, st.size - 65536),
487
+ );
488
+ process.stdout.write(buffer.subarray(0, bytesRead));
489
+ offset = st.size;
490
+ }
491
+ } finally {
492
+ await f.close();
493
+ }
494
+ if (opts["--follow"]) await delay(1000);
495
+ } while (opts["--follow"]);
496
+ return;
497
+ }
498
+ if (command === "doctor" && opts["--fix"]) {
499
+ if (await running(dir)) await control(dir, "doctor");
500
+ else await start(dir);
501
+ console.log("已重新连接并触发权限检测;约 15 秒后可查看更新。");
502
+ await delay(1500);
503
+ }
504
+ const remote = await request(server, "/api/feishu/runtime/self", {
505
+ token: credential.token,
506
+ }),
507
+ local = await running(dir);
508
+ if (opts["--json"]) {
509
+ console.log(
510
+ JSON.stringify(
511
+ { local: local || { running: false }, node: remote },
512
+ null,
513
+ 2,
514
+ ),
515
+ );
516
+ return;
517
+ }
518
+ console.log(
519
+ `节点:${remote.name}\n本机进程:${local ? "运行中" : "已停止"}\n飞书连接:${remote.connection}\n版本:${remote.workerVersion || "待连接"}\n配置:${remote.appliedRevision}/${remote.revision}\n最近接收:${remote.lastReceived}\n最近回复:${remote.lastReplied}`,
520
+ );
521
+ if (command === "doctor") {
522
+ for (const check of remote.checks || [])
523
+ console.log(`${check.status.padEnd(8)} ${check.id}:${check.evidence}`);
524
+ console.log("--fix 自动修复本机启动和连接;权限授权需在飞书官方页面确认。");
525
+ const missing = (remote.checks || []).some((c) =>
526
+ ["missing", "pending", "failed"].includes(c.status),
527
+ );
528
+ if (missing && opts["--fix"])
529
+ await openURL(
530
+ `https://open.feishu.cn/app/${encodeURIComponent(remote.appId)}/auth`,
531
+ opts["--no-open"],
532
+ );
533
+ }
534
+ }
@@ -0,0 +1,50 @@
1
+ $ErrorActionPreference = 'Stop'
2
+ $path = $env:YEP_FEISHU_ACL_PATH
3
+ $directory = $env:YEP_FEISHU_ACL_DIRECTORY -eq '1'
4
+ $item = Get-Item -LiteralPath $path -Force
5
+ if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 -or $item.PSIsContainer -ne $directory) {
6
+ throw 'Unsafe Feishu configuration path'
7
+ }
8
+ $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
9
+ $sid = $identity.User
10
+ $allowed = @($sid.Value, 'S-1-5-18', 'S-1-5-32-544')
11
+ $acl = Get-Acl -LiteralPath $path
12
+ $rules = $acl.GetAccessRules($true, $true, [Security.Principal.SecurityIdentifier])
13
+ $safe = $acl.AreAccessRulesProtected
14
+ $ownerAccess = $false
15
+ foreach ($rule in $rules) {
16
+ if ($rule.AccessControlType -eq 'Allow') {
17
+ if ($rule.IdentityReference.Value -notin $allowed) { $safe = $false }
18
+ if ($rule.IdentityReference.Value -eq $sid.Value -and
19
+ ($rule.FileSystemRights -band [Security.AccessControl.FileSystemRights]::FullControl) -eq [Security.AccessControl.FileSystemRights]::FullControl -and
20
+ (!$directory -or ($rule.InheritanceFlags -band 3) -eq 3)) { $ownerAccess = $true }
21
+ }
22
+ }
23
+ if ($safe -and $ownerAccess) { exit 0 }
24
+
25
+ # Repair only a path owned by this account (or its elevated Administrators token).
26
+ $owner = $acl.GetOwner([Security.Principal.SecurityIdentifier]).Value
27
+ $principal = [Security.Principal.WindowsPrincipal]::new($identity)
28
+ if ($owner -ne $sid.Value -and !($owner -eq 'S-1-5-32-544' -and $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))) {
29
+ throw 'Feishu configuration belongs to another account'
30
+ }
31
+ $acl.SetAccessRuleProtection($true, $false)
32
+ foreach ($rule in $acl.GetAccessRules($true, $false, [Security.Principal.SecurityIdentifier])) {
33
+ [void]$acl.RemoveAccessRuleSpecific($rule)
34
+ }
35
+ $inheritance = [Security.AccessControl.InheritanceFlags]::None
36
+ if ($directory) { $inheritance = [Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit' }
37
+ foreach ($value in @($allowed | Select-Object -Unique)) {
38
+ $account = [Security.Principal.SecurityIdentifier]::new($value)
39
+ $rule = [Security.AccessControl.FileSystemAccessRule]::new($account, [Security.AccessControl.FileSystemRights]::FullControl, $inheritance, [Security.AccessControl.PropagationFlags]::None, [Security.AccessControl.AccessControlType]::Allow)
40
+ $acl.AddAccessRule($rule)
41
+ }
42
+ Set-Acl -LiteralPath $path -AclObject $acl
43
+ $actual = Get-Acl -LiteralPath $path
44
+ if (!$actual.AreAccessRulesProtected) { throw 'Feishu configuration ACL remains inherited' }
45
+ foreach ($rule in $actual.GetAccessRules($true, $true, [Security.Principal.SecurityIdentifier])) {
46
+ if ($rule.AccessControlType -eq 'Allow' -and $rule.IdentityReference.Value -notin $allowed) {
47
+ throw 'Feishu configuration ACL remains shared'
48
+ }
49
+ }
50
+ exit 0
@@ -0,0 +1,53 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { createHash } from "node:crypto";
5
+ import { execFile } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+ const exec = promisify(execFile);
8
+ const xml = value => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
9
+ const quote = value => '"' + value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%").replaceAll("$", "$$") + '"';
10
+ export async function service(action, dir, sourceBinary) {
11
+ const id = createHash("sha256").update(dir).digest("hex").slice(0, 20), name = `yep-feishu-${id}`, config = path.join(dir, "credential.json"), marker = path.join(dir, "service.json");
12
+ let installed = false, metadata;
13
+ try { const st = await fs.lstat(marker); if (!st.isFile() || st.isSymbolicLink()) throw new Error("开机服务配置路径不安全"); metadata = JSON.parse(await fs.readFile(marker, "utf8")); installed = true; } catch (e) { if (e.code !== "ENOENT") throw e; }
14
+ if (action !== "install" && !installed) return false;
15
+ let binary = sourceBinary;
16
+ if (action === "install") {
17
+ if (/[\r\n\0]/.test(dir + binary)) throw new Error("开机服务路径包含不支持的字符");
18
+ // npx cache paths may disappear. Keep an independently verified executable
19
+ // beside the node configuration, with no credentials in service arguments.
20
+ const data = await fs.readFile(binary), digest = createHash("sha256").update(data).digest("hex");
21
+ binary = path.join(dir, `runtime-${digest.slice(0, 16)}${process.platform === "win32" ? ".exe" : ""}`);
22
+ try { await fs.writeFile(binary, data, { mode: 0o700, flag: "wx" }); } catch (e) { if (e.code !== "EEXIST") throw e; const st = await fs.lstat(binary); if (!st.isFile() || st.isSymbolicLink() || createHash("sha256").update(await fs.readFile(binary)).digest("hex") !== digest) throw new Error("开机服务节点文件校验失败"); }
23
+ }
24
+ if (process.platform === "linux") {
25
+ const folder = path.join(os.homedir(), ".config", "systemd", "user"), file = path.join(folder, `${name}.service`);
26
+ await fs.mkdir(folder, { recursive: true, mode: 0o700 });
27
+ if (action === "install") {
28
+ await fs.writeFile(file, `[Unit]\nDescription=Yep Feishu node\nAfter=network-online.target\n[Service]\nExecStart=${quote(binary)} --config ${quote(config)} --log-file ${quote(path.join(dir,"service.log"))}\nRestart=on-failure\nRestartSec=10\nUMask=0077\n[Install]\nWantedBy=default.target\n`, { mode: 0o600 });
29
+ await exec("systemctl", ["--user", "daemon-reload"]); await exec("systemctl", ["--user", "enable", `${name}.service`]);
30
+ } else if (action === "start") await exec("systemctl", ["--user", "start", `${name}.service`]);
31
+ else { await exec("systemctl", ["--user", "stop", `${name}.service`]); if (action === "uninstall") { await exec("systemctl", ["--user", "disable", `${name}.service`]); await fs.rm(file, { force: true }); await exec("systemctl", ["--user", "daemon-reload"]); } }
32
+ } else if (process.platform === "darwin") {
33
+ const label = `cool.nihao.${name}`, folder = path.join(os.homedir(), "Library", "LaunchAgents"), file = path.join(folder, `${label}.plist`), target = `gui/${process.getuid()}/${label}`;
34
+ await fs.mkdir(folder, { recursive: true, mode: 0o700 });
35
+ if (action === "install") {
36
+ if (installed) await exec("launchctl", ["bootout", target]);
37
+ await fs.writeFile(file, `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>${label}</string><key>ProgramArguments</key><array><string>${xml(binary)}</string><string>--config</string><string>${xml(config)}</string><string>--log-file</string><string>${xml(path.join(dir,"service.log"))}</string></array><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict><key>StandardOutPath</key><string>${xml(path.join(dir, "service.log"))}</string><key>StandardErrorPath</key><string>${xml(path.join(dir, "service.log"))}</string></dict></plist>`, { mode: 0o600 });
38
+ await exec("launchctl", ["bootstrap", `gui/${process.getuid()}`, file]);
39
+ } else if (action === "start") await exec("launchctl", ["kickstart", target]);
40
+ else if (action === "uninstall") { await exec("launchctl", ["bootout", target]); await fs.rm(file, { force: true }); }
41
+ else await exec("launchctl", ["kill", "SIGTERM", target]).catch(() => {});
42
+ } else if (process.platform === "win32") {
43
+ if (action === "install") {
44
+ const task = path.join(dir, "service-task.xml"), identity = await exec("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", "[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value"], { windowsHide: true }), user = identity.stdout.trim();
45
+ await fs.writeFile(task, `\ufeff<?xml version="1.0" encoding="UTF-16"?><Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"><Triggers><LogonTrigger><Enabled>true</Enabled></LogonTrigger></Triggers><Principals><Principal id="Author"><UserId>${xml(user)}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals><Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><RestartOnFailure><Interval>PT1M</Interval><Count>3</Count></RestartOnFailure></Settings><Actions Context="Author"><Exec><Command>${xml(binary)}</Command><Arguments>--config &quot;${xml(config)}&quot; --log-file &quot;${xml(path.join(dir,"service.log"))}&quot;</Arguments></Exec></Actions></Task>`, { encoding: "utf16le", mode: 0o600 });
46
+ await exec("schtasks.exe", ["/Create", "/TN", name, "/XML", task, "/F"], { windowsHide: true }); await fs.rm(task, { force: true });
47
+ } else if (action === "start") await exec("schtasks.exe", ["/Run", "/TN", name], { windowsHide: true });
48
+ else { await exec("schtasks.exe", ["/End", "/TN", name], { windowsHide: true }).catch(() => {}); if (action === "uninstall") await exec("schtasks.exe", ["/Delete", "/TN", name, "/F"], { windowsHide: true }); }
49
+ } else throw new Error("此系统暂不支持安装开机服务,可使用 yep feishu start。");
50
+ if (action === "install") await fs.writeFile(marker, JSON.stringify({ name, binary, platform: process.platform }), { mode: 0o600 });
51
+ if (action === "uninstall") await fs.rm(marker, { force: true });
52
+ return true;
53
+ }
Binary file
Binary file
@@ -0,0 +1,37 @@
1
+ {
2
+ "schema": 1,
3
+ "version": "1.2.0",
4
+ "sourceSHA256": "219834c05c18424d9caf09b3871c59b69ce556dcf9e8963120493ec7447c9534",
5
+ "files": {
6
+ "win32-x64/yep-feishu-node.exe": {
7
+ "sha256": "4b3e1c702d983aebd2fddb9686da9b944dfb4a4efc214180c81a19c6d7cd8d5c",
8
+ "goos": "windows",
9
+ "goarch": "amd64"
10
+ },
11
+ "win32-arm64/yep-feishu-node.exe": {
12
+ "sha256": "402f753d18fb4e09dfd3f440553d2164e3cae85e0eef2aa658872e2732413ec9",
13
+ "goos": "windows",
14
+ "goarch": "arm64"
15
+ },
16
+ "darwin-x64/yep-feishu-node": {
17
+ "sha256": "a5f2c64081d8cde42ca5d65a2b783d404c88e02b96e05f59768e3a232a7b71fb",
18
+ "goos": "darwin",
19
+ "goarch": "amd64"
20
+ },
21
+ "darwin-arm64/yep-feishu-node": {
22
+ "sha256": "994611d04d5e18a8b906b9b4a75d02d8e8381d459c703eb6e98b75514f84ac42",
23
+ "goos": "darwin",
24
+ "goarch": "arm64"
25
+ },
26
+ "linux-x64/yep-feishu-node": {
27
+ "sha256": "86ddf8a4981410cc363d45ab1c9e304b6f1d95ea67903f8c18184cab1d5b8ebe",
28
+ "goos": "linux",
29
+ "goarch": "amd64"
30
+ },
31
+ "linux-arm64/yep-feishu-node": {
32
+ "sha256": "85cc788a0d07f0bfb1e403a7cdbf5fd7944304469c117b5cbf0c822586b392b9",
33
+ "goos": "linux",
34
+ "goarch": "arm64"
35
+ }
36
+ }
37
+ }
Binary file
Binary file
Binary file
@@ -3,32 +3,32 @@
3
3
  "sourceSHA256": "f50333c164d2c15cc8b72308e0d7660bb87298f086019cd7d596e878f1f1daca",
4
4
  "files": {
5
5
  "win32-x64/yep-node.exe": {
6
- "sha256": "82ea2f49afca3d46defb4cbfe09158635e00c5f4fe8980633ba9909ddef16843",
6
+ "sha256": "591c825af2f9055d43518feea2fc71f9c4006e76ef3d53a5fde218db7d775029",
7
7
  "goos": "windows",
8
8
  "goarch": "amd64"
9
9
  },
10
10
  "win32-arm64/yep-node.exe": {
11
- "sha256": "3d974c4b22c97ea5ef2389d55d6daf3dffe27ed50bd54dd6cbcbf309001f2196",
11
+ "sha256": "ba90902f88f9c2f96c011c46bfcfc174b8fca051d71dc8bd20e1dd22e84691d6",
12
12
  "goos": "windows",
13
13
  "goarch": "arm64"
14
14
  },
15
15
  "darwin-x64/yep-node": {
16
- "sha256": "d3379f423d57598382ca56018b206b4f6eddafd3f1e733cf22070fd9d27a5eda",
16
+ "sha256": "e355792f5da8b267ed610732b977390b00cb0387e17ef4f9b97805f4f3909a5b",
17
17
  "goos": "darwin",
18
18
  "goarch": "amd64"
19
19
  },
20
20
  "darwin-arm64/yep-node": {
21
- "sha256": "ec47bef2b4f5f221dc0363710e95bd7df412e509e411b93ec9b786c1473fea41",
21
+ "sha256": "c35be324d425adf394e5bb951d92303f10b806b0a8707337cde3272725030e85",
22
22
  "goos": "darwin",
23
23
  "goarch": "arm64"
24
24
  },
25
25
  "linux-x64/yep-node": {
26
- "sha256": "5bc06697598bb6bb0d36b08bf0eabe877204c3f73f48cbfac675fdf1590d0499",
26
+ "sha256": "fb0f1b62f54e6f069ac83e8334f317793bdf8e5981a3e1eb0766260778e6880d",
27
27
  "goos": "linux",
28
28
  "goarch": "amd64"
29
29
  },
30
30
  "linux-arm64/yep-node": {
31
- "sha256": "8eee868c965b7388b23935c9da6124cb2b59f9d03ff6d2ba36981e8d04fb2136",
31
+ "sha256": "76176e184e872e4a6a9601d7e86b2a33f1cdf2e3f1a86f5429717ba883be0dde",
32
32
  "goos": "linux",
33
33
  "goarch": "arm64"
34
34
  }
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yep-ai/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.2",
4
4
  "description": "Yep Marketplace unified CLI: skills, agents, plugins and authorized device nodes.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,9 +26,9 @@
26
26
  "registry": "https://registry.npmjs.org"
27
27
  },
28
28
  "scripts": {
29
- "prepack": "node ../../scripts/verify-yep-node.mjs",
29
+ "prepack": "node ../../scripts/verify-yep-node.mjs && node ../../scripts/verify-feishu-node.mjs",
30
30
  "build": "node bin/yep.js --version",
31
- "check": "node --check bin/yep.js && node --check bin/market.js && node --check lib/device/command.js && node --check lib/device/client.js && node --check lib/device/tools.js",
31
+ "check": "node --check bin/yep.js && node --check bin/market.js && node --check lib/device/command.js && node --check lib/device/client.js && node --check lib/device/tools.js && node --check lib/feishu/command.js && node --check lib/feishu/service.js",
32
32
  "test": "node --test"
33
33
  }
34
34
  }