@keruyun/agent-cli 1.4.4-beta.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 ADDED
@@ -0,0 +1,29 @@
1
+ # @keruyun/agent-cli
2
+
3
+ 客如云 CLI 工具
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm i @keruyun/agent-cli@latest -g --registry=https://registry.npmmirror.com
9
+ ```
10
+
11
+ 本包只含壳与 SDK。平台二进制在 CDN 上(每平台一个 brotli 压缩件,URL 与 sha256 见包内 `binaries.json`),
12
+ `postinstall`(`install.cjs`)会按当前平台下载、校验 sha256、解压后放到 `bin/kry-cli.exe`。
13
+
14
+ **不要带 `--ignore-scripts`**,否则会装出一个没有二进制的壳。
15
+
16
+ ## 支持平台
17
+
18
+ | 平台 | 解压后体积 |
19
+ | --- | --- |
20
+ | darwin-arm64 | 65.73 MB |
21
+ | darwin-x64(baseline 变体,兼容无 AVX2 的老 CPU) | 71.16 MB |
22
+ | linux-x64 | 95.39 MB |
23
+ | linux-arm64 | 94.54 MB |
24
+ | linux-x64-musl | 92.13 MB |
25
+ | win32-x64 | 99.1 MB |
26
+
27
+ ## 校验
28
+
29
+ 各平台二进制的 CDN URL 与 sha256(解压后)在 `binaries.json` 里,`install.cjs` 放置前会校验。
@@ -0,0 +1,17 @@
1
+ # 占位文件模板:真正的平台二进制由 postinstall(install.cjs)替换进来。
2
+ #
3
+ # **按壳包渲染**,由 scripts/pack-release.mjs 替换 @keruyun/agent-cli / https://registry.npmmirror.com。
4
+ # 原先这里硬编码 `npm i -g @keruyun/agent-cli`,而同一个文件被原样拷进两个壳包 ——
5
+ # 内网用户(@alife/krycli)照着这条提示做,会去装外网包,而内网根本装不上。
6
+ # 修复指引指错方向比不给指引更糟,所以两个壳包各自渲染自己的包名与 registry。
7
+ #
8
+ # 故意不写 shebang。npm 的 cmd-shim 在 Windows 上是**在 postinstall 之前**按 bin 目标
9
+ # 文件的内容生成的:这里一旦出现 `#!/bin/sh`,生成的 shim 会永久变成 `sh kry-cli.exe`,
10
+ # 连 postinstall 成功之后的正常路径也一起带坏。
11
+ # 代价是 Unix 下被直接 execve(如 Node 的 child_process)时会 ENOEXEC 且没有任何输出,
12
+ # 经 shell 调用则回落 /bin/sh,提示照样打得出来。这个取舍在 pack-release.mjs 的
13
+ # 端到端断言处有记录。
14
+ echo "[krycli] 平台二进制尚未安装:postinstall 没有执行,或安装时跳过了 optional 依赖。" >&2
15
+ echo "[krycli] 修复:npm i -g @keruyun/agent-cli --registry=https://registry.npmmirror.com" >&2
16
+ echo "[krycli] 不要带 --omit=optional / --ignore-scripts" >&2
17
+ exit 1
package/bin/log.py ADDED
@@ -0,0 +1,301 @@
1
+ """
2
+ 轻量日志模块,通过 HTTP POST 上报到阿里云 SLS。
3
+ 仅依赖 Python 标准库(3.8+)。
4
+
5
+ 用法:
6
+ from log import logger, set_log_info
7
+
8
+ set_log_info(brand_id="12345", version="1.0.0")
9
+
10
+ log = logger.child(tag="my_module")
11
+ log.info("操作成功", extra={"count": 42})
12
+ log.error("操作失败", extra={"reason": "网络超时"})
13
+ """
14
+
15
+ import json
16
+ import os
17
+ import platform
18
+ import socket
19
+ import sys
20
+ import threading
21
+ import traceback
22
+ import uuid
23
+ from typing import Any, Callable, Dict, Optional, Union
24
+ from urllib.request import Request, urlopen
25
+ from urllib.error import URLError
26
+
27
+ LOG_URL = "https://tnnm-alsc-saas-merchant-spider-fy21.cn-wulanchabu.log.aliyuncs.com/logstores/kry-cli/track?APIVersion=0.6.0"
28
+
29
+ _ENV_MAP = {"daily": "gld", "pre": "pre", "prod": "prod"}
30
+ _SESSION_ID = os.environ.get("REWIND_SESSION_ID", "")
31
+ _ROBOT_ID = os.environ.get("ROBOT_UID", "")
32
+ _TRACE_ID = uuid.uuid4().hex
33
+
34
+ # 全局上下文(set_log_info 写入)
35
+ _ctx = {
36
+ "brand_id": None,
37
+ "brand_name": None,
38
+ "brand_count": 0,
39
+ "shop_count": 0,
40
+ "version": None,
41
+ "user_id": None,
42
+ "source": None,
43
+ "mobile": None,
44
+ "pkg": None,
45
+ "call_path": None,
46
+ "call_skill": None,
47
+ }
48
+
49
+
50
+ # ──────────────────────────────────────────────
51
+ # 环境
52
+ # ──────────────────────────────────────────────
53
+
54
+ def get_env() -> str:
55
+ """获取当前环境标识,与 JS 版 getEnv() 逻辑一致。"""
56
+ claw_env = os.environ.get("CLIENT_CLAW_ENV", "")
57
+ if claw_env and claw_env in _ENV_MAP:
58
+ return _ENV_MAP[claw_env]
59
+ return "pre"
60
+
61
+
62
+ def is_test_runner() -> bool:
63
+ """检测是否运行在 eval 测试环境中。"""
64
+ session_id = os.environ.get("REWIND_SESSION_ID", "")
65
+ return session_id.startswith("eval-sess-eval")
66
+
67
+
68
+ # ──────────────────────────────────────────────
69
+ # 全局上下文设置
70
+ # ──────────────────────────────────────────────
71
+
72
+ def set_log_info(
73
+ brand_id: Optional[str] = None,
74
+ brand_name: Optional[str] = None,
75
+ brand_count: Optional[int] = None,
76
+ shop_count: Optional[int] = None,
77
+ version: Optional[str] = None,
78
+ user_id: Optional[str] = None,
79
+ source: Optional[str] = None,
80
+ mobile: Optional[str] = None,
81
+ pkg: Optional[str] = None,
82
+ ) -> None:
83
+ """设置日志全局上下文,与 JS 版 setLogInfo() 对应。"""
84
+ if brand_id is not None:
85
+ _ctx["brand_id"] = brand_id
86
+ if brand_name is not None:
87
+ _ctx["brand_name"] = brand_name
88
+ if brand_count is not None:
89
+ _ctx["brand_count"] = brand_count
90
+ if shop_count is not None:
91
+ _ctx["shop_count"] = shop_count
92
+ if version is not None:
93
+ _ctx["version"] = version
94
+ if user_id is not None:
95
+ _ctx["user_id"] = user_id
96
+ if source is not None:
97
+ _ctx["source"] = source
98
+ if mobile is not None:
99
+ _ctx["mobile"] = mobile
100
+ if pkg is not None:
101
+ _ctx["pkg"] = pkg
102
+
103
+
104
+ def get_caller(argv: list) -> Dict[str, str]:
105
+ """从命令行参数中提取 kry-* skill 调用路径,与 JS 版 getCaller() 对应。"""
106
+ result = {"path": "", "skill": ""}
107
+ file_path = ""
108
+ for arg in argv or []:
109
+ if "kry-" in arg:
110
+ file_path = arg
111
+ break
112
+ if not file_path:
113
+ return result
114
+
115
+ segments = file_path.replace("\\", "/").split("/")
116
+ skill_index = -1
117
+ for i, seg in enumerate(segments):
118
+ if "kry-" in seg and seg != "kry-claw-skills":
119
+ skill_index = i
120
+ break
121
+ if skill_index == -1:
122
+ return result
123
+
124
+ result["skill"] = segments[skill_index]
125
+ result["path"] = "/".join(segments[skill_index + 1:])
126
+ return result
127
+
128
+
129
+ def set_caller(argv: Optional[list] = None) -> Optional[Dict[str, str]]:
130
+ """从 sys.argv 或指定参数中提取调用者信息并写入全局上下文。"""
131
+ try:
132
+ call = get_caller(argv if argv is not None else sys.argv)
133
+ if call["skill"]:
134
+ _ctx["call_skill"] = call["skill"]
135
+ if call["path"]:
136
+ _ctx["call_path"] = call["path"]
137
+ return call
138
+ except Exception:
139
+ return None
140
+
141
+
142
+ # ──────────────────────────────────────────────
143
+ # 序列化辅助
144
+ # ──────────────────────────────────────────────
145
+
146
+ def _safe_value(v: Any) -> Any:
147
+ """将值转为可 JSON 序列化的形式。"""
148
+ if isinstance(v, str):
149
+ return v
150
+ if isinstance(v, BaseException):
151
+ err: Dict[str, Any] = {
152
+ "type": type(v).__name__,
153
+ "message": str(v),
154
+ "traceback": "".join(traceback.format_exception(type(v), v, v.__traceback__)),
155
+ }
156
+ # 附加自定义属性
157
+ for k, val in vars(v).items():
158
+ if k not in err:
159
+ err[k] = val
160
+ return err
161
+ try:
162
+ json.dumps(v)
163
+ return v
164
+ except (TypeError, ValueError):
165
+ return str(v)
166
+
167
+
168
+ def _stringify(data: Dict[str, Any]) -> Dict[str, Any]:
169
+ """将 dict 中的非字符串值序列化为 JSON 字符串,与 JS 版 stringify() 对应。"""
170
+ result = {}
171
+ for k, v in data.items():
172
+ result[k] = _safe_value(v) if isinstance(v, str) else json.dumps(_safe_value(v), ensure_ascii=False)
173
+ return result
174
+
175
+
176
+ # ──────────────────────────────────────────────
177
+ # 核心上报
178
+ # ──────────────────────────────────────────────
179
+
180
+ def _send_log(level: str, message: str, extra: Optional[Dict[str, Any]] = None) -> None:
181
+ """异步上报日志到阿里云 SLS。"""
182
+ env = get_env()
183
+ if env == "prod" and level == "debug":
184
+ return
185
+
186
+ common = {"traceId": _TRACE_ID, "level": level, "message": message}
187
+
188
+ tags = {
189
+ "env": env,
190
+ "brandId": _ctx.get("brand_id"),
191
+ "brandName": _ctx.get("brand_name"),
192
+ "brandCount": str(_ctx.get("brand_count", 0)),
193
+ "shopCount": str(_ctx.get("shop_count", 0)),
194
+ "version": _ctx.get("version"),
195
+ "sessionId": _SESSION_ID,
196
+ "userId": _ctx.get("user_id"),
197
+ "hostname": socket.gethostname(),
198
+ "platform": sys.platform,
199
+ "arch": platform.machine(),
200
+ "source": _ctx.get("source"),
201
+ "mobile": _ctx.get("mobile"),
202
+ "pkg": _ctx.get("pkg"),
203
+ "callPath": _ctx.get("call_path"),
204
+ "callSkill": _ctx.get("call_skill"),
205
+ }
206
+
207
+ if is_test_runner():
208
+ tags["test"] = "TestRunner"
209
+
210
+ try:
211
+ extra = extra or {}
212
+ tag = extra.pop("tag", None)
213
+ logs = {**common, "tag": tag}
214
+ if extra:
215
+ logs["data"] = extra
216
+ body = json.dumps({"__logs__": [_stringify(logs)], "__tags__": tags}, ensure_ascii=False)
217
+ except Exception:
218
+ body = json.dumps(
219
+ {
220
+ "__logs__": [json.dumps({**common, "level": "error", "message": message, "data": "[Unserializable]"})],
221
+ "__tags__": tags,
222
+ }
223
+ )
224
+
225
+ def _post() -> None:
226
+ try:
227
+ req = Request(LOG_URL, data=body.encode("utf-8"), method="POST")
228
+ req.add_header("Content-Type", "application/json")
229
+ with urlopen(req, timeout=5) as _:
230
+ pass
231
+ except (URLError, OSError, Exception):
232
+ pass
233
+
234
+ # 异步上报,不阻塞主线程
235
+ threading.Thread(target=_post, daemon=True).start()
236
+
237
+
238
+ # ──────────────────────────────────────────────
239
+ # Logger
240
+ # ──────────────────────────────────────────────
241
+
242
+ class Logger:
243
+ """
244
+ 轻量日志记录器,支持 child() 创建带固定 tag 的子 logger。
245
+ API 与 JS 版 logger 保持一致。
246
+ """
247
+
248
+ def __init__(self, default_extra: Optional[Dict[str, Any]] = None):
249
+ self._default_extra: Dict[str, Any] = default_extra or {}
250
+
251
+ def info(self, message: str, extra: Optional[Dict[str, Any]] = None) -> None:
252
+ merged = {**self._default_extra, **(extra or {})}
253
+ _send_log("info", message, merged)
254
+
255
+ def warn(self, message: str, extra: Optional[Dict[str, Any]] = None) -> None:
256
+ merged = {**self._default_extra, **(extra or {})}
257
+ _send_log("warn", message, merged)
258
+
259
+ def error(self, message: str, extra: Optional[Union[Dict[str, Any], BaseException]] = None) -> None:
260
+ if isinstance(extra, BaseException):
261
+ err_dict = _safe_value(extra)
262
+ merged = {**self._default_extra, **err_dict}
263
+ else:
264
+ merged = {**self._default_extra, **(extra or {})}
265
+ _send_log("error", message, merged)
266
+
267
+ def debug(self, message: str, extra: Optional[Dict[str, Any]] = None) -> None:
268
+ merged = {**self._default_extra, **(extra or {})}
269
+ _send_log("debug", message, merged)
270
+
271
+ def child(self, extra: Optional[Dict[str, Any]] = None) -> "Logger":
272
+ """创建子 logger,合并固定的 extra 字段(通常传 tag)。"""
273
+ return Logger({**self._default_extra, **(extra or {})})
274
+
275
+
276
+ # 全局 logger 实例
277
+ logger = Logger()
278
+
279
+
280
+ # ──────────────────────────────────────────────
281
+ # CLI 入口(用于测试)
282
+ # ──────────────────────────────────────────────
283
+
284
+ if __name__ == "__main__":
285
+ import time
286
+
287
+ set_log_info(version="1.0.0", source="test")
288
+ set_caller()
289
+
290
+ log = logger.child({"tag": "test_module"})
291
+ log.info("Python 日志模块测试", {"env": get_env(), "trace_id": _TRACE_ID})
292
+ log.warn("这是一条警告日志")
293
+
294
+ try:
295
+ raise ValueError("模拟错误")
296
+ except Exception as e:
297
+ log.error("捕获到异常", e)
298
+
299
+ # 等待异步上报完成
300
+ time.sleep(2)
301
+ print("日志测试完成")
package/bin/sdk.mjs ADDED
@@ -0,0 +1 @@
1
+ var Y=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(n){throw t=0,n}};var G=Y((Pe,ue)=>{ue.exports={name:"@keruyun/agent-cli",version:"1.4.3",description:"\u5BA2\u5982\u4E91 CLI \u5DE5\u5177",keywords:[],license:"MIT",author:"\u7384\u5BAB",repository:{type:"git",url:"http://gitlab.alibaba-inc.com/alsc-kry-open/krycli.git"},bin:{krycli:"./bin/index.js"},files:["bin"],exports:{"/sdk":{types:"./bin/sdk.mjs",import:"./bin/sdk.mjs"}},scripts:{prepare:"husky install",postinstall:"node bin/where_is_kry_cli.js",prepublishOnly:"tnpm run build",dev:"node scripts/dev.mjs",check:"tsc --noEmit && vitest run",ci:"tnpm run lint && tnpm run verify",verify:"node scripts/verify-refactor.mjs","verify:baseline":"node scripts/verify-refactor.mjs --baseline",test:"vitest run",lint:"eslint . --ext js,jsx,ts,tsx","lint:fix":"eslint --fix . --ext js,jsx,ts,tsx",build:"tsc --noEmit && tnpm run prod && tnpm run sdk && cp -f ./py/log.py ./bin/log.py",prod:"esbuild src/index.ts --bundle --minify --format=cjs --outfile=./bin/index.js --platform=node --legal-comments=none $ESBUILD_DEFINE",sdk:"esbuild src/sdk.ts --bundle --minify --format=esm --outfile=./bin/sdk.mjs --platform=node --legal-comments=none $ESBUILD_DEFINE","build:bin":"node scripts/build-binaries.mjs","release:pack":"node scripts/pack-release.mjs --tarball","release:verify":"node scripts/pack-release.mjs --verify","release:outer:beta":"node scripts/publish-binary.mjs --target=outer --tag=beta --prerelease","release:outer":"node scripts/publish-binary.mjs --target=outer --tag=latest","release:inner:beta":"node scripts/publish-binary.mjs --target=inner --tag=beta --prerelease","release:inner":"node scripts/publish-binary.mjs --target=inner --tag=latest","regr:base":'node regression/snapshot.mjs --target "node bin/index.js" --label node --layer L0,L1,L2',"regr:bun":'node regression/snapshot.mjs --target "./dist-bun/kry-cli" --label bun --layer L0,L1,L2',"regr:diff":"node regression/compare.mjs --base node --target bun",doc:"node dist/schema/gen.js",pack:"node dist/pack/index.js",wasm:"node scripts/gen-sql-wasm.mjs","legacy:release:outer":"node scripts/publish-release.mjs","legacy:release:inner":"node scripts/publish-beta.mjs --pre"},dependencies:{xlsx:"^0.18.5"},devDependencies:{"@ali/eslint-config-att":"^1.0.6","@ali/stylelint-config-xy":"^1.0.14","@commitlint/cli":"^17.7.1","@commitlint/config-conventional":"^17.7.0","@commitlint/cz-commitlint":"^17.7.1","@commitlint/format":"^17.4.4","@fast-csv/format":"^5.0.7","@puppeteer/browsers":"^3.0.3","@types/fs-extra":"^11.0.4","@types/node":"^25.6.0","@types/sql.js":"^1.4.11","@typescript-eslint/eslint-plugin":"^6.4.0","@typescript-eslint/parser":"^6.4.0",commander:"^15.0.0","cz-conventional-changelog":"^3.3.0",dayjs:"^1.11.20",esbuild:"^0.28.0",eslint:"^8.47.0","eslint-plugin-react":"^7.33.2","eslint-plugin-react-hooks":"^4.6.0",exceljs:"^4.4.0","fs-extra":"^11.3.5",husky:"^8.0.3",jsonrepair:"^3.14.0",jszip:"^3.10.1","lint-staged":"^14.0.0","puppeteer-core":"^25.0.4","sql.js":"^1.14.1",stylelint:"^15.10.3","ts-node":"^10.9.2",tslib:"^2.4.0",typescript:"^5.0.0",vitest:"^4.1.6",zod:"^4.4.3"},"lint-staged":{"**/*.{ts,tsx,js,jsx}":"eslint","**/*.less":"stylelint"},config:{commitizen:{path:"@commitlint/cz-commitlint"}}}});import X from"path";import q from"os";var Z={prod:{api:"https://openapi.keruyun.com",auth:"https://open.keruyun.com",clientId:"kry-cli"},pre:{api:"https://preopenapi.tongyun0815.com",auth:"https://preopen.tongyun0815.com",clientId:"kry-cli"},gld:{api:"https://gldopenapi.keruyun.com",auth:"https://gldopen.keruyun.com",clientId:"kry-cli"},safety:{api:"https://openapi.keruyun.com",auth:"https://open.keruyun.com",clientId:"kry-cli",extraHeaders:{"kry-api-brand-id":"32296"}}},v={daily:"gld",pre:"pre",prod:"prod"};var Q="prod";function k(){if(process.env?.CLIENT_CLAW_ENV&&v[process.env.CLIENT_CLAW_ENV])return v[process.env.CLIENT_CLAW_ENV];let e=process.env.KRY_FORCE_ENV;return e&&Z[e]?e:Q}function S(...e){return X.join(process.env.AGENT_WORK_ROOT||q.homedir(),...e)}var w=(...e)=>{let t=k();return t==="prod"?S(".kry-cli",...e):S(`.kry-cli-${t}`,...e)},ee="-o /temp_path/file.json",m="-o /path/file.xlsx",te="-o /path/file.csv",ne={viewBrand:"krycli brand",viewShop:"krycli shop",viewShopByKeyword:"krycli shop -k '\u5173\u952E\u8BCD1|\u5173\u952E\u8BCD2'",viewShopParams:e=>`krycli shop -b ${e?.brandId||"<brandId>"}`,callShop:"krycli call /open/xxx -s <shopId> -d '<JSON>'",callShopParams:e=>`krycli call ${e?.url||"/open/xxx"} -s ${e?.shopId||"<shopId>"} -d '<JSON>'`,callBrand:"krycli call /open/xxx -b <brandId> -d '<JSON>'",callBrandOutput:`krycli call /open/xxx -b <brandId> ${ee} -d '<JSON>'`,callBrandParams:e=>`krycli call ${e?.url||"/open/xxx"} -b ${e?.brandId||"<brandId>"} -d '<JSON>'`,callOutput:"\u5982\u679C\u6570\u636E\u91CF\u8FC7\u5927\u6216\u8005\u95E8\u5E97\u8D85\u8FC7 10 \u5BB6\uFF0C\u4F7F\u7528\u53C2\u6570 `-o <tempFilePath>` \u5C06\u6570\u636E\u4FDD\u5B58\u5230\u4E34\u65F6\u6587\u4EF6",login:"krycli login",logout:"krycli logout",viewAllApi:"krycli view",viewSpcApi:"krycli view <\u63A5\u53E3\u8DEF\u5F84>",searchApi:"krycli view '\u5173\u952E\u8BCD1|\u5173\u952E\u8BCD2'",comment:{kry:"krycli comment keruyun -b 123 --shopIds 456,789 --start 2026-04-01 --end 2026-04-30",kryAll:"krycli comment keruyun -b 123 --start 2026-04-01 --end 2026-04-30",kryOutput:`krycli comment keruyun -b 123 ${te}`,meituan:`krycli comment meituan ${m} --start 2026-04-01 --end 2026-04-30`,brandKey:"krycli comment brandKey -b 123",weibo:{brand:"krycli comment weibo -b 123",key:"krycli comment weibo -k \u54C1\u724C\u5173\u952E\u8BCD"},xiaohongshu:{brand:"krycli comment xiaohongshu -b 123",key:"krycli comment xiaohongshu -k \u54C1\u724C\u5173\u952E\u8BCD"}},export:{star:`krycli export ecom star ${m}`,star30:`krycli export ecom star ${m} --start 2026-04-01 --end 2026-04-30`},viewDate:"krycli date",upgrade:"krycli upgrade",upgradeCheck:"krycli upgrade --check",upgradeForce:"krycli upgrade --force"},re=process.env.AGENT_WORK_ROOT||"";function oe(e){return e?.match(/(?:^|[/\\])(user-[^/\\]+)[/\\]?$/)?.[1]||""}var b=oe(re),C=!!b,T=(process.env.REWIND_SESSION_ID||"").match(/^eval-sess-eval/);function O(){return process.env.CLIENT_CLAW_TYPE==="KRY_CLAW"}import*as s from"fs";import*as l from"path";import x from"os";var ie="https://tnnm-alsc-saas-merchant-spider-fy21.cn-wulanchabu.log.aliyuncs.com/logstores/kry-cli/track?APIVersion=0.6.0",se=process.env.REWIND_SESSION_ID||"",be=process.env.ROBOT_UID||"",ce=crypto.randomUUID().replaceAll("-",""),I,E,N=0,A=0,j,D,L,R,P,$,z;function F(e){e?.brandId&&(I=e.brandId),e?.brandName&&(E=e.brandName),e?.brandCount!==void 0&&(N=e.brandCount),e?.shopCount!==void 0&&(A=e.shopCount),e?.version&&(j=e.version),e?.userId&&(D=e.userId),e?.source&&(L=e.source),e?.mobile&&(R=e.mobile),e?.pkg&&(P=e.pkg)}function ae(e){let t={path:"",skill:""},o=((e||[]).find(i=>i.includes("kry-"))||"").split(/[/\\]+/),r=o.findIndex(i=>i.includes("kry-")&&i!=="kry-claw-skills");return r===-1||(t.skill=o[r],t.path=o.slice(r+1).join("/")),t}function J(){try{let e=ae(process.argv);return e.skill&&(z=e.skill),e.path&&($=e.path),e}catch{}return null}function U(e){return e instanceof Error}function B(e){let t={};return Object.keys(e).forEach(n=>{t[n]=e[n]}),t.stack=e.stack,t}function le(e){return Object.fromEntries(Object.entries(e).map(([t,n])=>[t,typeof n=="string"?n:JSON.stringify(n,(o,r)=>U(r)?B(r):r)]))}function h(e,t,n){let o,r=k();if(r==="prod"&&e==="debug")return;let i={traceId:ce,level:e,message:t},c={env:r,brandId:I,brandName:E,brandCount:N.toString(),shopCount:A.toString(),version:j,sessionId:se,userId:D,hostname:x.hostname(),platform:x.platform(),arch:x.arch(),source:L,mobile:R,pkg:P,callPath:$,callSkill:z};T&&(c.test="TestRunner");try{let{tag:d,...f}=n||{},a={...i,tag:d,data:f};Object.keys(a.data||{}).length===0&&delete a.data,o=JSON.stringify({__logs__:[le(a)],__tags__:c})}catch{o=JSON.stringify({__logs__:[JSON.stringify({...i,level:"error",message:t,data:"[Unserializable]"})],__tags__:c})}return fetch(ie,{method:"POST",headers:new Headers,body:o}).then(async d=>{}).catch(async d=>{})}function H(e){return{info:(t,n)=>h("info",t,{...e,...n}),warn:(t,n)=>h("warn",t,{...e,...n}),error:(t,n)=>h("error",t,{...e,...U(n)?B(n):n}),debug:(t,n)=>h("debug",t,{...e,...n}),child:t=>H({...e,...t})}}var u=H();var g=w(),Ie=l.join(g,"credentials.json"),Ee=l.join(g,"token-cache.json"),M=l.join(g,"authorized-orgs.json"),Ne=3e3*1e3;var K=l.join(g,"token.json"),Ae=l.join(g,"discovery.json"),je=l.join(g,"login.pid");function de(){let e=u.child({tag:"loadOAuth2Token"});if(!s.existsSync(K))return null;try{let t=s.readFileSync(K,"utf-8");return JSON.parse(t)}catch(t){return e.error("\u8BFB\u53D6 OAuth2 Token \u5931\u8D25",t),null}}var Le=1440*60*1e3;function pe(){let e=u.child({tag:"loadAuthorizedOrgs"});if(!s.existsSync(M))return null;try{let t=s.readFileSync(M,"utf-8");return JSON.parse(t)}catch(t){return e.error("\u672C\u5730\u8BFB\u53D6\u5DF2\u6388\u6743\u54C1\u724C/\u95E8\u5E97\u5217\u8868\u5931\u8D25",t),null}}function V(){let e={success:!1,brandId:"",brandName:"",brandCount:0,shopCount:0,brandMap:{},shopMap:{},brands:[],shops:[]},t=pe();if(t){let n=t.brandList||[],o=n.length,r={},i={},c=n[0]||{},d=String(c.id||"")||"",f=c.name||"",a=0;for(let p of n){r[String(p.id)]=p,e.brands.push(p);let y=p.shopList||[];y.length>a&&(a=y.length,d=String(p.id),f=p.name);for(let _ of y)i[String(_.id)]=_}e.success=!0,e.brandMap=r,e.shopMap=i,e.brandCount=o,e.shopCount=a,e.brandId=d,e.brandName=f}return e}function W(){try{let o=(de()?.access_token||"").split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),r=o.padEnd(o.length+(4-o.length%4)%4,"="),i=Buffer.from(r,"base64").toString("utf8");return JSON.parse(i)}catch{return{}}}try{let e=u.child({tag:"sdk:init"}),t=V(),n=W(),o=C?"wukong":"other";O()&&(o="kryclaw");let r=G();F({version:r.version,brandId:t.brandId,brandName:t.brandName,brandCount:t.brandCount,shopCount:t.shopCount,userId:b,source:o,mobile:n?.sub,pkg:r.name});let i=J();e.info("SDK \u542F\u52A8\uFF0C\u8BBE\u7F6E\u65E5\u5FD7\u53C2\u6570",{args:process.argv.join(" "),call:i})}catch{}export{u as logger};
package/binaries.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "version": "1.4.4-beta.0",
3
+ "targets": {
4
+ "darwin-arm64": {
5
+ "url": "https://s.koubei.com/DBrPWcSRcmrk83QaMdk6EKMcZp2KXKQW.br",
6
+ "sha256": "04b3605331ba7ddb95f04c8b71bd4422c59edada2bed8c68b7e8f6d1d0564d44",
7
+ "brSha256": "12a8224259fa3f52516a6ba0660bf2c8b8b3b9ac86edb155b836eda6e0d9a892",
8
+ "brBytes": 19898901,
9
+ "bytes": 68928098
10
+ },
11
+ "darwin-x64": {
12
+ "url": "https://s.koubei.com/hSeeFj6zDzP5SXyY5K4J76GsSRZzydJ8.br",
13
+ "sha256": "a8c157a8136f4b0c5090e723e7895492d79e13453c7174c50f596d00d4e117a8",
14
+ "brSha256": "9fcf82624aba92676404e59717a660cc1a755535a0938695b41889885827be20",
15
+ "brBytes": 22330335,
16
+ "bytes": 74612816
17
+ },
18
+ "linux-x64": {
19
+ "url": "https://s.koubei.com/GkkPAcxJawak3AwPWH4MBBBBEi2N8s5G.br",
20
+ "sha256": "16d46a232be180f0f1976a65c1bcb4d2685ef7b6987f459383e7a5a0387932e9",
21
+ "brSha256": "b3a9b1f12f2ac7724884eb1e4bfe512cd3aeebde5e3dd2125a7346cc65309bb5",
22
+ "brBytes": 30756723,
23
+ "bytes": 100026496
24
+ },
25
+ "linux-arm64": {
26
+ "url": "https://s.koubei.com/ktPDcHwKFWcMyd2D6yemcTKN6MG4G8Ff.br",
27
+ "sha256": "2b80097bce9d1959a5315f7a42b7393176130138312048d2881efe37da53a11c",
28
+ "brSha256": "668df1940914a488cc65b5789931cda20afe2e7abf6e89668516d853d5ebd05a",
29
+ "brBytes": 30150107,
30
+ "bytes": 99133584
31
+ },
32
+ "linux-x64-musl": {
33
+ "url": "https://s.koubei.com/WZpmZmaQedS6nKT83ffSQ2DAnAK4wJkN.br",
34
+ "sha256": "c651fb6745e0439c16284eb37faa80ad98b8f8881e9fe46092d76a46fd158a7e",
35
+ "brSha256": "22c4cd545671f56f356acb71e748136cd1fb95c2b7f744fbabf0293fa4dca851",
36
+ "brBytes": 29948350,
37
+ "bytes": 96606496
38
+ },
39
+ "win32-x64": {
40
+ "url": "https://s.koubei.com/rzjp6Hzxtm2SD4siY5SbmBT5mepSndMX.br",
41
+ "sha256": "c2818d773cb7b9de94aa0cdc32ebf2993c029db48e70066929870e6c35d54fdb",
42
+ "brSha256": "b994c2a1f52bdea9addba2300544d340d1b04250e7d993199cab2ddb03a2330a",
43
+ "brBytes": 32619725,
44
+ "bytes": 103917056
45
+ }
46
+ }
47
+ }
package/install.cjs ADDED
@@ -0,0 +1,214 @@
1
+ /**
2
+ * 壳包 postinstall:从 CDN 下载当前平台的二进制,放到 bin/kry-cli.exe。
3
+ *
4
+ * 分发结构(2026-08 起,二进制不再走 npm 平台包):
5
+ *
6
+ * <壳包> 几十 KB。外网 @keruyun/agent-cli,内网 @alife/krycli
7
+ * ├── bin/kry-cli.exe 占位文件,postinstall 后被替换成真二进制
8
+ * ├── bin/sdk.mjs SDK 仍是 ESM,二进制替代不了
9
+ * ├── install.cjs ← 本文件
10
+ * └── binaries.json 每平台 { url, sha256(解压后), brSha256, brBytes, bytes }
11
+ *
12
+ * 二进制以 brotli 压缩件(.br)存放在客如云 CDN(s.koubei.com),URL 由发布时上传后写死进
13
+ * binaries.json —— fileId 随机颁发、URL 不可变,同一版本的链接永远指向同一份字节。
14
+ * 本脚本按平台挑 URL,下载 → sha256 校验 → 解压 → 放置。
15
+ *
16
+ * 之前的做法是 6 个平台包挂在 optionalDependencies 上由 npm 分发。放弃它是因为发布要
17
+ * 串行 publish 12 个 25~39 MB 的包,慢到不可接受;CDN 方案每通道只发 1 个壳包。
18
+ *
19
+ * 三个容易踩的细节(选型文档 §6):
20
+ * 1. musl 检测读 process.report.getReport().header.glibcVersionRuntime,
21
+ * 不要 spawn `ldd` —— ldd 不在 PATH 时会误判。
22
+ * 2. Rosetta 纠正:x64 Node 跑在 Apple Silicon 上时 os.arch() 返回 x64,
23
+ * 但 x64 构建需要 AVX 而 Rosetta 不模拟,装上去直接 SIGILL。查 sysctl.proc_translated。
24
+ * 3. 目标文件名固定 kry-cli.exe:.exe 后缀 + 无 shebang,让 npm 的 cmd-shim 在
25
+ * Windows 上直接 exec;Unix 忽略后缀。
26
+ */
27
+
28
+ 'use strict';
29
+
30
+ const fs = require('fs');
31
+ const path = require('path');
32
+ const crypto = require('crypto');
33
+ const zlib = require('zlib');
34
+ const { execFileSync } = require('child_process');
35
+
36
+ const BIN_DIR = path.join(__dirname, 'bin');
37
+ const TARGET = path.join(BIN_DIR, 'kry-cli.exe');
38
+ const LOG_PREFIX = '[krycli]';
39
+ const DOWNLOAD_TIMEOUT_MS = 300000;
40
+
41
+ function log(msg) {
42
+ process.stdout.write(`${LOG_PREFIX} ${msg}\n`);
43
+ }
44
+ function warn(msg) {
45
+ process.stderr.write(`${LOG_PREFIX} ${msg}\n`);
46
+ }
47
+
48
+ function readJson(file) {
49
+ try {
50
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /** x64 Node 跑在 Apple Silicon 上(Rosetta 翻译)。x64 构建需要 AVX,Rosetta 不模拟 → 必须换 arm64 包 */
57
+ function isRosetta() {
58
+ if (process.platform !== 'darwin') return false;
59
+ try {
60
+ const out = execFileSync('sysctl', ['-n', 'sysctl.proc_translated'], {
61
+ encoding: 'utf8',
62
+ stdio: ['ignore', 'pipe', 'ignore'],
63
+ });
64
+ return out.trim() === '1';
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ /** glibcVersionRuntime 缺失即 musl(Alpine)。不 spawn ldd —— 它不在 PATH 时会误判 */
71
+ function isMusl() {
72
+ if (process.platform !== 'linux') return false;
73
+ try {
74
+ const header = process.report.getReport().header;
75
+ return !header || !header.glibcVersionRuntime;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+
81
+ function detectTarget() {
82
+ const platform = process.platform;
83
+ let arch = process.arch;
84
+ let note = '';
85
+ if (platform === 'darwin' && arch === 'x64' && isRosetta()) {
86
+ arch = 'arm64';
87
+ note = '(检测到 Rosetta,改用 arm64 构建)';
88
+ }
89
+ let name = `${platform}-${arch}`;
90
+ if (platform === 'linux' && isMusl()) {
91
+ name += '-musl';
92
+ note = '(检测到 musl libc)';
93
+ }
94
+ return { name, note };
95
+ }
96
+
97
+ function downloadFailedHelp(entry, target) {
98
+ return [
99
+ `下载平台二进制失败(${target})`,
100
+ '',
101
+ '可能原因:',
102
+ ' 1. 当前网络无法访问 CDN(s.koubei.com),如公司代理/防火墙拦截',
103
+ ' 2. CDN 暂时不可用',
104
+ '',
105
+ '修复:确认网络可达后重装,或手动下载后解压(brotli)放到 bin/kry-cli.exe:',
106
+ ` ${entry.url}`,
107
+ ` (解压后 sha256 应为 ${entry.sha256})`,
108
+ '',
109
+ '若确认要跳过这一步(会得到一个不可用的 krycli):设置 KRY_CLI_SKIP_POSTINSTALL=1',
110
+ ].join('\n');
111
+ }
112
+
113
+ /** 下载 .br 到内存,失败重试一次。engines 声明 node>=18,全局 fetch 可用 */
114
+ async function download(url) {
115
+ const once = async () => {
116
+ const r = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
117
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
118
+ return Buffer.from(await r.arrayBuffer());
119
+ };
120
+ try {
121
+ return await once();
122
+ } catch (e) {
123
+ warn(`下载失败(${e && e.message ? e.message : e}),重试一次…`);
124
+ return once();
125
+ }
126
+ }
127
+
128
+ async function main() {
129
+ if (process.env.KRY_CLI_SKIP_POSTINSTALL === '1' || process.env.KRY_CLI_SKIP_POSTINSTALL === 'true') {
130
+ warn('KRY_CLI_SKIP_POSTINSTALL=1,跳过平台二进制安装(krycli 将不可用)');
131
+ return 0;
132
+ }
133
+
134
+ const { name: target, note } = detectTarget();
135
+ const binaries = readJson(path.join(__dirname, 'binaries.json'));
136
+ const entry = binaries && binaries.targets && binaries.targets[target];
137
+ if (!entry || !entry.url || !entry.sha256) {
138
+ warn(`binaries.json 里没有当前平台(${target})的二进制,暂未提供该平台的预编译产物`);
139
+ return 1;
140
+ }
141
+
142
+ let brBuf;
143
+ const started = Date.now();
144
+ try {
145
+ brBuf = await download(entry.url);
146
+ } catch (error) {
147
+ warn(downloadFailedHelp(entry, target));
148
+ warn(` 错误:${error && error.message ? error.message : String(error)}`);
149
+ return 1;
150
+ }
151
+
152
+ // 先校验压缩件本身,再解压 —— 传输层坏了在这一步就能说清楚
153
+ if (entry.brSha256) {
154
+ const brActual = crypto.createHash('sha256').update(brBuf).digest('hex');
155
+ if (brActual !== entry.brSha256) {
156
+ warn(`下载内容校验失败(${target})`);
157
+ warn(` 期望 sha256: ${entry.brSha256}`);
158
+ warn(` 实际 sha256: ${brActual}`);
159
+ warn(' 下载可能已损坏或被篡改,请重装重试');
160
+ return 1;
161
+ }
162
+ }
163
+
164
+ let out;
165
+ try {
166
+ out = zlib.brotliDecompressSync(brBuf);
167
+ } catch (error) {
168
+ warn(`解压平台二进制失败:${error && error.message ? error.message : String(error)}`);
169
+ warn(' 下载可能已损坏,请重装重试');
170
+ return 1;
171
+ }
172
+
173
+ // 解压后 sha256 是硬校验:binaries.json 里没有它上面已经拒绝,这里不一致就拒绝落盘
174
+ const actual = crypto.createHash('sha256').update(out).digest('hex');
175
+ if (actual !== entry.sha256) {
176
+ warn(`平台二进制校验失败(${target})`);
177
+ warn(` 期望 sha256: ${entry.sha256}`);
178
+ warn(` 实际 sha256: ${actual}`);
179
+ warn(' 下载可能已损坏或被篡改,请重装重试');
180
+ return 1;
181
+ }
182
+
183
+ fs.mkdirSync(BIN_DIR, { recursive: true });
184
+ try {
185
+ fs.unlinkSync(TARGET);
186
+ } catch {
187
+ // 不存在就算了
188
+ }
189
+ try {
190
+ // mode 直接给可执行位,省一次 chmod;Windows 上 mode 被忽略
191
+ fs.writeFileSync(TARGET, out, { mode: 0o755 });
192
+ } catch (error) {
193
+ warn(`放置二进制失败:${error && error.message ? error.message : String(error)}`);
194
+ return 1;
195
+ }
196
+ try {
197
+ fs.chmodSync(TARGET, 0o755);
198
+ } catch {
199
+ // Windows 上无意义
200
+ }
201
+
202
+ const sizeMB = (out.length / 1048576).toFixed(1);
203
+ const packedMB = (brBuf.length / 1048576).toFixed(1);
204
+ log(`已安装 ${target}${note} ${sizeMB} MB(CDN 下载 ${packedMB} MB,${((Date.now() - started) / 1000).toFixed(1)}s)`);
205
+ log(`installed at: ${TARGET}`);
206
+ return 0;
207
+ }
208
+
209
+ main()
210
+ .catch((error) => {
211
+ warn(`安装失败:${error && error.stack ? error.stack : String(error)}`);
212
+ return 1;
213
+ })
214
+ .then((code) => process.exit(code));
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@keruyun/agent-cli",
3
+ "version": "1.4.4-beta.0",
4
+ "description": "客如云 CLI 工具",
5
+ "author": "玄宫",
6
+ "license": "MIT",
7
+ "keywords": [],
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "http://gitlab.alibaba-inc.com/alsc-kry-open/krycli.git"
11
+ },
12
+ "bin": {
13
+ "krycli": "bin/kry-cli.exe"
14
+ },
15
+ "exports": {
16
+ "/sdk": {
17
+ "types": "./bin/sdk.mjs",
18
+ "import": "./bin/sdk.mjs"
19
+ }
20
+ },
21
+ "files": [
22
+ "bin",
23
+ "install.cjs",
24
+ "binaries.json"
25
+ ],
26
+ "scripts": {
27
+ "postinstall": "node install.cjs"
28
+ },
29
+ "engines": {
30
+ "node": ">=18"
31
+ }
32
+ }