@zan_team/cli 0.0.1 → 0.0.3
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 +34 -107
- package/dist/cli.js +199 -54
- package/package.json +2 -2
- package/dist/cli.js.map +0 -1
package/README.md
CHANGED
|
@@ -28,8 +28,7 @@ pnpm build
|
|
|
28
28
|
node dist/cli.js --help
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
下文统一使用 `zan
|
|
32
|
-
本地源码联调推荐使用下方的 `cli:local` 入口,无需反复构建。
|
|
31
|
+
下文统一使用 `zan`。运行源码构建产物时,将 `zan` 替换为 `node dist/cli.js`。
|
|
33
32
|
|
|
34
33
|
### 配置服务地址
|
|
35
34
|
|
|
@@ -37,74 +36,12 @@ CLI 未显式配置服务地址时默认连接生产环境 `https://zan.top`。
|
|
|
37
36
|
环境变量覆盖 Auth/API Origin:
|
|
38
37
|
|
|
39
38
|
```sh
|
|
40
|
-
export ZAN_ENV=dev
|
|
41
39
|
export ZAN_AUTH_BASE_URL=https://auth.example.com
|
|
42
40
|
export ZAN_API_BASE_URL=https://api.example.com
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
| 环境 | 配置文件 | Origin |
|
|
48
|
-
| ---- | ----------- | -------------------------------------- |
|
|
49
|
-
| DEV | `.env.dev` | `http://127.0.0.1:8088` |
|
|
50
|
-
| SIT | `.env.sit` | `https://zan.unchartedw3s.com` |
|
|
51
|
-
| PRE | `.env.pre` | `https://pre.zan.top` |
|
|
52
|
-
| PROD | `.env.prod` | `https://zan.top` |
|
|
53
|
-
|
|
54
|
-
### 本地源码联调
|
|
55
|
-
|
|
56
|
-
开发者可在 CLI 仓库中通过 `pnpm cli:local` 选择一次环境,后续命令会自动沿用;
|
|
57
|
-
源码修改后下一次调用立即生效:
|
|
58
|
-
|
|
59
|
-
```sh
|
|
60
|
-
pnpm cli:local -- env use dev
|
|
61
|
-
pnpm cli:local -- env status
|
|
62
|
-
pnpm cli:local -- --json --no-interactive usage
|
|
63
|
-
|
|
64
|
-
# 切换到 SIT
|
|
65
|
-
pnpm cli:local -- env use sit
|
|
66
|
-
|
|
67
|
-
# 仅本次使用 DEV,不改变保存的选择;--env 必须放在最前面
|
|
68
|
-
pnpm cli:local -- --env dev --json --no-interactive usage
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
选择保存在当前工作目录的 `.zan-dev.json`,只包含 `environment`,支持
|
|
72
|
-
`dev`、`sit`、`pre`。仅读取当前目录,不向上查找;未选择环境时会报错,
|
|
73
|
-
也可以直接用 `--env sit` 单次指定。`env status` 始终以 JSON 返回环境、
|
|
74
|
-
配置路径、工作目录、源码仓库和 Auth/API 地址,不包含凭据。
|
|
75
|
-
|
|
76
|
-
测试 Agent 应直接通过 Node 调用源码入口,避免 pnpm 的脚本横幅影响 JSON 解析。
|
|
77
|
-
在其他业务项目中,保留该项目的工作目录,使用源码入口的绝对路径:
|
|
78
|
-
|
|
79
|
-
```sh
|
|
80
|
-
node /absolute/path/to/zan-cli/scripts/zan-dev.mjs env use sit
|
|
81
|
-
node /absolute/path/to/zan-cli/scripts/zan-dev.mjs --json --no-interactive usage
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
该入口从 CLI 仓库读取依赖、源码和环境文件,保留调用方工作目录。
|
|
85
|
-
每个测试项目分别保存环境选择;登录、Project/Network 等仍是用户级、按环境共享的状态。
|
|
86
|
-
同目录并发测试不同环境时,各命令显式使用 `--env`。
|
|
87
|
-
|
|
88
|
-
此功能仅用于源码联调:正式 `zan` 和发布的 CLI 不读取 `.zan-dev.json`,
|
|
89
|
-
开发脚本不随 npm 包发布。给最终用户的 Skill 只保留正式 CLI 用法。
|
|
90
|
-
Agent 入口约定、写文件命令和状态隔离说明见[本地开发指南](docs/local-development.md)。
|
|
91
|
-
|
|
92
|
-
也可以通过 DEV、SIT、PRE 和 PROD 脚本直接指定环境,`--` 后的参数会完整传给 CLI:
|
|
93
|
-
|
|
94
|
-
```sh
|
|
95
|
-
pnpm cli:dev -- auth login
|
|
96
|
-
pnpm cli:sit -- auth status --verify
|
|
97
|
-
pnpm cli:pre -- --json project list
|
|
98
|
-
pnpm cli:prod -- auth login
|
|
99
|
-
pnpm cli:prod -- --json project list
|
|
100
|
-
pnpm cli:prod -- usage
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
环境脚本会覆盖当前终端残留的 `ZAN_ENV`、`ZAN_AUTH_BASE_URL` 和
|
|
104
|
-
`ZAN_API_BASE_URL`。DEV、SIT、PRE 分别使用独立的系统凭据 Profile、Session、
|
|
105
|
-
Project/Network 设置和 Endpoint 缓存;PROD 继续使用兼容既有安装的
|
|
106
|
-
`profile-default`、`session.json` 和 `settings.json`。非生产设置分别保存在
|
|
107
|
-
`settings.dev.json`、`settings.sit.json` 和 `settings.pre.json`。
|
|
43
|
+
上述 `example.com` 地址仅用于展示配置方式,不能直接使用。日常使用无需覆盖
|
|
44
|
+
服务地址;内部环境地址和配置请通过授权渠道获取,不要写入公开文档或示例。
|
|
108
45
|
|
|
109
46
|
### 登录
|
|
110
47
|
|
|
@@ -400,27 +337,17 @@ zan project cache clear
|
|
|
400
337
|
Project 使用不同缓存文件与密钥。普通设置文件仍只保存 Project ID 和 Network
|
|
401
338
|
ID,不保存 Endpoint 或 API Key。
|
|
402
339
|
|
|
403
|
-
####
|
|
404
|
-
|
|
405
|
-
源码调试时可以直接使用环境脚本:
|
|
406
|
-
|
|
407
|
-
```sh
|
|
408
|
-
pnpm cli:sit -- project use <project-id>
|
|
409
|
-
pnpm cli:sit -- evm networks
|
|
410
|
-
pnpm cli:sit -- evm networks --refresh
|
|
411
|
-
pnpm cli:sit -- project cache status
|
|
412
|
-
pnpm cli:sit -- project cache clear
|
|
413
|
-
```
|
|
340
|
+
#### RPC API Key 覆盖
|
|
414
341
|
|
|
415
342
|
需要临时替换当前 Project 的 RPC API Key 时,通过隐藏输入或 stdin 写入系统
|
|
416
343
|
凭据库:
|
|
417
344
|
|
|
418
345
|
```sh
|
|
419
|
-
|
|
346
|
+
zan project rpc-key set
|
|
420
347
|
printf '%s\n' "$TEST_RPC_API_KEY" \
|
|
421
|
-
|
|
|
422
|
-
|
|
423
|
-
|
|
348
|
+
| zan project rpc-key set --stdin
|
|
349
|
+
zan project rpc-key status
|
|
350
|
+
zan project rpc-key clear
|
|
424
351
|
```
|
|
425
352
|
|
|
426
353
|
覆盖值不会出现在命令参数、普通配置、Endpoint 缓存或命令输出中。启用覆盖后,
|
|
@@ -430,20 +357,34 @@ RPC 401/403 不会自动换回服务端下发的 Key,便于稳定测试指定
|
|
|
430
357
|
|
|
431
358
|
```sh
|
|
432
359
|
zan project rpc-key pull
|
|
433
|
-
# 源码 SIT 调试:
|
|
434
|
-
pnpm cli:sit -- project rpc-key pull
|
|
435
360
|
```
|
|
436
361
|
|
|
437
362
|
命令写入当前工作目录中固定的 `ZAN_API_KEY`:已存在时替换,缺少时追加,文件
|
|
438
|
-
|
|
439
|
-
|
|
363
|
+
不存在时创建。API Key 按环境和 Project ID 独立保存在系统凭据库(macOS Keychain
|
|
364
|
+
等),与 Endpoint 缓存的加密密钥、手动覆盖值分别存储。`pull` 优先使用手动覆盖值,
|
|
365
|
+
否则直接读取所选 Project 已保存的 API Key,不需要读取网络缓存或请求后端。
|
|
366
|
+
|
|
367
|
+
首次缺失时,CLI 请求该 Project 的网络列表,从首个可识别的 RPC 地址提取 API Key
|
|
368
|
+
并保存到系统凭据库;现有后端接口通过地址携带 Key。刷新网络列表时同步更新保存的
|
|
369
|
+
API Key,服务端不再返回可识别 Key 时移除旧记录。`zan project cache clear` 会同时
|
|
370
|
+
清理当前项目保存的 API Key;退出登录会清理当前环境各项目的 Key 和覆盖值。
|
|
371
|
+
|
|
372
|
+
API Key 属于所选 Project,不要求选择 Network。未选择 Project 时,交互终端会
|
|
373
|
+
列出项目供选择,非交互模式需使用 `--project <id>` 或先执行 `zan project use <id>`。
|
|
374
|
+
命令不接受 Key、stdin、变量名
|
|
440
375
|
或输出路径,也不会在人工输出、JSON 输出、错误或日志中包含 Key。新文件及更新
|
|
441
376
|
后的文件在 POSIX 系统上使用 `0600` 权限,并通过文件锁和原子替换写入;符号链接
|
|
442
377
|
或非普通 `.env` 会被拒绝。
|
|
443
378
|
|
|
444
|
-
`pull`
|
|
445
|
-
`.
|
|
446
|
-
|
|
379
|
+
`pull` 会确保 `.env` 被 Git 忽略:已有生效规则(包括父目录、通配符和全局规则)时
|
|
380
|
+
不修改 `.gitignore`,否则在当前目录的 `.gitignore` 末尾追加 `/.env`。尚未初始化 Git
|
|
381
|
+
仓库的目录也支持此行为,不会自动创建项目仓库。此检查使用本机 Git;Git 不可用时会提示风险。
|
|
382
|
+
|
|
383
|
+
若 `.env` 已被 Git 跟踪,或无法检查、更新忽略规则,命令仍写入 Key,并在终端提示
|
|
384
|
+
误提交 Key 的风险;JSON 结果通过 `warnings` 返回同样的提示。已跟踪时可执行
|
|
385
|
+
`git rm --cached -- .env` 取消跟踪,并在提交前检查暂存内容;CLI 不会自动修改 Git
|
|
386
|
+
索引,忽略规则也不能清除历史提交中的内容。其他环境配置文件不会被修改。
|
|
387
|
+
写入后,任何拥有当前目录读取权限的进程或 Agent 都可能读取这个明文
|
|
447
388
|
文件;“不泄露”保证只覆盖该命令自身的参数、输出、错误和日志边界。
|
|
448
389
|
|
|
449
390
|
管理默认 Project 和 Network:
|
|
@@ -518,7 +459,9 @@ printf '["0x0000000000000000000000000000000000000000","latest"]' \
|
|
|
518
459
|
~/.zan-cli/config.yaml
|
|
519
460
|
```
|
|
520
461
|
|
|
521
|
-
`~` 由操作系统用户目录 API 解析。该文件是个人 CLI
|
|
462
|
+
`~` 由操作系统用户目录 API 解析。该文件是个人 CLI 策略,与环境配置文件、
|
|
463
|
+
`ZAN_ENV` 及所选 ZAN 后端环境无关;`ZAN_HOME` 不会改变它的位置,所有环境
|
|
464
|
+
读取同一个文件。
|
|
522
465
|
|
|
523
466
|
YAML schema 支持两个顶层属性,分别控制 Raw JSON-RPC 和 Hyperliquid Info 方法:
|
|
524
467
|
|
|
@@ -683,17 +626,6 @@ Agent 可用 `inspect` 检查本地状态;`ready: false` 需结合实际命令
|
|
|
683
626
|
Project/Network 选择、套餐与 Key 用量、EVM/Hyperliquid 查询、RPC Key 拉取及钱包签名指引。
|
|
684
627
|
`skills/` 随 npm 包发布;安装 CLI 后仍需将技能目录放入 Agent 的发现路径。
|
|
685
628
|
|
|
686
|
-
本仓库已通过 `.agents/skills/zan-cli` 软链接指向 `skills/zan-cli`。在 Codex
|
|
687
|
-
中打开本仓库即可发现技能,修改源文件无需复制。若未显示,重启 Codex 后尝试:
|
|
688
|
-
|
|
689
|
-
```text
|
|
690
|
-
使用 $zan-cli 列出当前支持的命令。本任务把技能中的 zan 统一替换为
|
|
691
|
-
node /absolute/path/to/zan-cli/scripts/zan-dev.mjs --env sit。
|
|
692
|
-
```
|
|
693
|
-
|
|
694
|
-
将示例路径替换为实际 CLI 仓库路径。该入口约定仅用于本地测试;
|
|
695
|
-
环境选择和状态隔离说明见[本地开发指南](docs/local-development.md)。
|
|
696
|
-
|
|
697
629
|
npm 包发布后,安装 CLI 并选择 Agent 安装技能:
|
|
698
630
|
|
|
699
631
|
```sh
|
|
@@ -796,13 +728,6 @@ zan completions fish
|
|
|
796
728
|
名称,或通过 `zan project list` 查找项目后执行 `zan project use <id>` 使用已有
|
|
797
729
|
项目;此错误不可重试,仍使用 `ZAN_API_ERROR` 和退出码 `8`。
|
|
798
730
|
|
|
799
|
-
本地联调遇到 “No free Node Service plan is configured for this environment” 时,
|
|
800
|
-
需在 `webtservice` 的本地数据库中配置 `/default/AVAILABLE_FREE_SPEC`,其中
|
|
801
|
-
`AVAILABLE_FREE_SPEC.NODE_SERVICE.specId` 必须指向有效的 `drw3_service_spec`
|
|
802
|
-
商品规格。只启动 MySQL、Redis 或重新登录不会生成这些商品数据;应导入环境
|
|
803
|
-
对应的测试商品配置,并确认该规格所需的套餐开通及回调链路可用。后端代码
|
|
804
|
-
修改后,需要重新编译并重启实际监听本地 API 端口的进程。
|
|
805
|
-
|
|
806
731
|
已认证的 Project 与 Network 请求如果通过 HTTP 401 或已发布的 WebResult
|
|
807
732
|
语义表明 Access Token 无效,会刷新一次 Access Token 并重放一次请求。第二次
|
|
808
733
|
仍失败时返回 `AUTH_EXPIRED`,不会继续刷新。普通 HTTP 403 和 Scope 不足不会
|
|
@@ -810,6 +735,8 @@ zan completions fish
|
|
|
810
735
|
|
|
811
736
|
### 安全注意事项
|
|
812
737
|
|
|
738
|
+
- 对外文档、截图和问题反馈中不要包含内部开发、测试或预发域名、IP、端口及环境配置;
|
|
739
|
+
示例统一使用 `example.com` 等占位地址,分享命令输出前先检查并脱敏。
|
|
813
740
|
- 不要把 Access Token、Refresh Token、Project Token 或完整 Endpoint 写入脚本、日志和普通配置。
|
|
814
741
|
- 不要使用 `--verbose` 输出处理结果中的敏感字段。
|
|
815
742
|
- 完整 RPC Endpoint 只在当前进程内解密使用;本地仅保存 AES-256-GCM 密文,
|
package/dist/cli.js
CHANGED
|
@@ -1809,6 +1809,10 @@ function registerProject(program, outputWriter, getRuntimeContext, input, rpcKey
|
|
|
1809
1809
|
rpcKey.command("pull").description("Write the active Project RPC API Key to .env").action(async () => {
|
|
1810
1810
|
const { rpcKeyPullService } = await getRuntimeContext();
|
|
1811
1811
|
const result = await rpcKeyPullService.pull();
|
|
1812
|
+
for (const warning of result.warnings ?? []) {
|
|
1813
|
+
outputWriter.writeStderr(`Warning: ${warning}
|
|
1814
|
+
`);
|
|
1815
|
+
}
|
|
1812
1816
|
outputWriter.success(
|
|
1813
1817
|
result,
|
|
1814
1818
|
"ZAN_API_KEY was written to .env"
|
|
@@ -1891,7 +1895,7 @@ function registerSign(program, outputWriter, getRuntimeContext) {
|
|
|
1891
1895
|
}
|
|
1892
1896
|
|
|
1893
1897
|
// src/version.ts
|
|
1894
|
-
var CLI_VERSION = true ? "0.0.
|
|
1898
|
+
var CLI_VERSION = true ? "0.0.3" : pkg.version;
|
|
1895
1899
|
|
|
1896
1900
|
// src/commands/version.ts
|
|
1897
1901
|
function registerVersion(program, outputWriter) {
|
|
@@ -2166,7 +2170,7 @@ var ReadlineProjectPrompt = class {
|
|
|
2166
2170
|
|
|
2167
2171
|
// src/runtime/context.ts
|
|
2168
2172
|
import { homedir as readHomedir2 } from "os";
|
|
2169
|
-
import { join as
|
|
2173
|
+
import { join as join7 } from "path";
|
|
2170
2174
|
|
|
2171
2175
|
// src/auth/session-metadata-store.ts
|
|
2172
2176
|
import { z as z8 } from "zod";
|
|
@@ -2630,9 +2634,9 @@ var BrowserFlow = class {
|
|
|
2630
2634
|
// src/auth/browser-opener.ts
|
|
2631
2635
|
import { execFile as nodeExecFile } from "child_process";
|
|
2632
2636
|
var BrowserOpener = class {
|
|
2633
|
-
constructor(platform = process.platform,
|
|
2637
|
+
constructor(platform = process.platform, execFile2 = nodeExecFile) {
|
|
2634
2638
|
this.platform = platform;
|
|
2635
|
-
this.execFile =
|
|
2639
|
+
this.execFile = execFile2;
|
|
2636
2640
|
}
|
|
2637
2641
|
platform;
|
|
2638
2642
|
execFile;
|
|
@@ -5133,7 +5137,7 @@ var NetworkSelector = class {
|
|
|
5133
5137
|
};
|
|
5134
5138
|
|
|
5135
5139
|
// src/projects/dotenv-api-key-writer.ts
|
|
5136
|
-
import { randomUUID as
|
|
5140
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
5137
5141
|
import {
|
|
5138
5142
|
chmod as nodeChmod2,
|
|
5139
5143
|
lstat as nodeLstat2,
|
|
@@ -5142,7 +5146,111 @@ import {
|
|
|
5142
5146
|
rename as nodeRename,
|
|
5143
5147
|
unlink as nodeUnlink
|
|
5144
5148
|
} from "fs/promises";
|
|
5145
|
-
import { isAbsolute as isAbsolute3, join as
|
|
5149
|
+
import { isAbsolute as isAbsolute3, join as join5 } from "path";
|
|
5150
|
+
|
|
5151
|
+
// src/projects/dotenv-gitignore.ts
|
|
5152
|
+
import { execFile } from "child_process";
|
|
5153
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
5154
|
+
import { lstat as lstat4, mkdtemp as mkdtemp2, open as open2, readFile as readFile3, rename as rename2, rm as rm2 } from "fs/promises";
|
|
5155
|
+
import { tmpdir } from "os";
|
|
5156
|
+
import { join as join4 } from "path";
|
|
5157
|
+
import { promisify } from "util";
|
|
5158
|
+
var execute = promisify(execFile);
|
|
5159
|
+
var UNCONFIRMED_WARNING = "Unable to confirm .env is ignored by Git; there is a risk of committing the API Key. Check Git and .gitignore before committing.";
|
|
5160
|
+
function protectionFailure() {
|
|
5161
|
+
return new Error("Unable to verify Git ignore rules.");
|
|
5162
|
+
}
|
|
5163
|
+
async function appendRule(directory) {
|
|
5164
|
+
const filename = join4(directory, ".gitignore");
|
|
5165
|
+
let current = "";
|
|
5166
|
+
let mode = 420;
|
|
5167
|
+
try {
|
|
5168
|
+
const info = await lstat4(filename);
|
|
5169
|
+
if (!info.isFile() || info.isSymbolicLink()) throw protectionFailure();
|
|
5170
|
+
mode = info.mode & 511;
|
|
5171
|
+
current = await readFile3(filename, "utf8");
|
|
5172
|
+
} catch (error) {
|
|
5173
|
+
if (error.code !== "ENOENT") throw error;
|
|
5174
|
+
}
|
|
5175
|
+
const newline = current.includes("\r\n") ? "\r\n" : "\n";
|
|
5176
|
+
const separator = current.length === 0 || current.endsWith("\n") ? "" : newline;
|
|
5177
|
+
const temporary = join4(directory, `.gitignore.${randomUUID3()}.tmp`);
|
|
5178
|
+
try {
|
|
5179
|
+
const handle = await open2(temporary, "wx", mode);
|
|
5180
|
+
try {
|
|
5181
|
+
await handle.writeFile(`${current}${separator}/.env${newline}`, "utf8");
|
|
5182
|
+
await handle.sync();
|
|
5183
|
+
} finally {
|
|
5184
|
+
await handle.close();
|
|
5185
|
+
}
|
|
5186
|
+
await rename2(temporary, filename);
|
|
5187
|
+
} finally {
|
|
5188
|
+
await rm2(temporary, { force: true });
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
async function ensureDotenvIgnored(directory) {
|
|
5192
|
+
const warnings = [];
|
|
5193
|
+
const env = { ...process.env, LC_ALL: "C" };
|
|
5194
|
+
for (const name of ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR", "GIT_PREFIX"]) {
|
|
5195
|
+
delete env[name];
|
|
5196
|
+
}
|
|
5197
|
+
const git = async (args) => {
|
|
5198
|
+
try {
|
|
5199
|
+
return { code: 0, ...await execute("git", args, {
|
|
5200
|
+
cwd: directory,
|
|
5201
|
+
env,
|
|
5202
|
+
encoding: "utf8",
|
|
5203
|
+
timeout: 1e4,
|
|
5204
|
+
maxBuffer: 64 * 1024
|
|
5205
|
+
}) };
|
|
5206
|
+
} catch (error) {
|
|
5207
|
+
const failure = error;
|
|
5208
|
+
if (typeof failure.code !== "number") throw protectionFailure();
|
|
5209
|
+
return { code: failure.code, stdout: failure.stdout ?? "", stderr: failure.stderr ?? "" };
|
|
5210
|
+
}
|
|
5211
|
+
};
|
|
5212
|
+
let temporaryRepository;
|
|
5213
|
+
try {
|
|
5214
|
+
const repository = await git(["rev-parse", "--is-inside-work-tree"]);
|
|
5215
|
+
let prefix = [];
|
|
5216
|
+
if (repository.code === 0 && repository.stdout.trim() === "true") {
|
|
5217
|
+
const tracked = await git(["ls-files", "--error-unmatch", "--", ".env"]);
|
|
5218
|
+
if (tracked.code === 0) {
|
|
5219
|
+
warnings.push(
|
|
5220
|
+
".env is already tracked by Git; there is a risk of committing the API Key even with .gitignore. Run: git rm --cached -- .env, and review staged changes before committing."
|
|
5221
|
+
);
|
|
5222
|
+
} else if (tracked.code !== 1) {
|
|
5223
|
+
throw protectionFailure();
|
|
5224
|
+
}
|
|
5225
|
+
} else if (repository.code === 128 && repository.stderr.includes("not a git repository")) {
|
|
5226
|
+
temporaryRepository = await mkdtemp2(join4(tmpdir(), "zan-ignore-"));
|
|
5227
|
+
const initialized = await git(["init", "--bare", "--quiet", "--template=", temporaryRepository]);
|
|
5228
|
+
if (initialized.code !== 0) throw protectionFailure();
|
|
5229
|
+
prefix = [`--git-dir=${temporaryRepository}`, `--work-tree=${directory}`, "-c", "core.bare=false"];
|
|
5230
|
+
} else {
|
|
5231
|
+
throw protectionFailure();
|
|
5232
|
+
}
|
|
5233
|
+
const ignored = await git([...prefix, "check-ignore", "--quiet", "--no-index", "--", ".env"]);
|
|
5234
|
+
if (ignored.code === 0) return warnings;
|
|
5235
|
+
if (ignored.code !== 1) throw protectionFailure();
|
|
5236
|
+
await appendRule(directory);
|
|
5237
|
+
const verified = await git([...prefix, "check-ignore", "--quiet", "--no-index", "--", ".env"]);
|
|
5238
|
+
if (verified.code !== 0) throw protectionFailure();
|
|
5239
|
+
} catch {
|
|
5240
|
+
warnings.push(UNCONFIRMED_WARNING);
|
|
5241
|
+
} finally {
|
|
5242
|
+
if (temporaryRepository !== void 0) {
|
|
5243
|
+
try {
|
|
5244
|
+
await rm2(temporaryRepository, { recursive: true, force: true });
|
|
5245
|
+
} catch {
|
|
5246
|
+
warnings.push("Temporary Git metadata could not be removed; review the system temporary directory.");
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
return warnings;
|
|
5251
|
+
}
|
|
5252
|
+
|
|
5253
|
+
// src/projects/dotenv-api-key-writer.ts
|
|
5146
5254
|
var API_KEY_ASSIGNMENT = /^[ \t]*(?:export[ \t]+)?ZAN_API_KEY[ \t]*=/u;
|
|
5147
5255
|
function unsafeKeyFailure() {
|
|
5148
5256
|
return new CliFailure(
|
|
@@ -5220,7 +5328,7 @@ var DotenvApiKeyWriter = class {
|
|
|
5220
5328
|
this.options = options;
|
|
5221
5329
|
this.platform = options.platform ?? process.platform;
|
|
5222
5330
|
this.fileSystem = options.fileSystem ?? nodeDotenvApiKeyFileSystem;
|
|
5223
|
-
this.createTemporaryId = options.createTemporaryId ??
|
|
5331
|
+
this.createTemporaryId = options.createTemporaryId ?? randomUUID4;
|
|
5224
5332
|
}
|
|
5225
5333
|
options;
|
|
5226
5334
|
platform;
|
|
@@ -5234,8 +5342,9 @@ var DotenvApiKeyWriter = class {
|
|
|
5234
5342
|
if (!isAbsolute3(this.options.directory)) {
|
|
5235
5343
|
throw unsafeDotenvPathFailure();
|
|
5236
5344
|
}
|
|
5237
|
-
const lockPath =
|
|
5345
|
+
const lockPath = join5(this.options.directory, ".env.zan.lock");
|
|
5238
5346
|
let renamed = false;
|
|
5347
|
+
let warnings = [];
|
|
5239
5348
|
try {
|
|
5240
5349
|
await this.assertSafeOptionalPath(lockPath);
|
|
5241
5350
|
const lock = new FileLock(lockPath, {
|
|
@@ -5244,11 +5353,15 @@ var DotenvApiKeyWriter = class {
|
|
|
5244
5353
|
await lock.withLock(async () => {
|
|
5245
5354
|
const current = await this.readExistingFile();
|
|
5246
5355
|
const next = updateApiKeyAssignment(current, parsed.data);
|
|
5356
|
+
warnings = await ensureDotenvIgnored(this.options.directory);
|
|
5247
5357
|
await this.replaceAtomically(next, () => {
|
|
5248
5358
|
renamed = true;
|
|
5249
5359
|
});
|
|
5250
5360
|
});
|
|
5251
5361
|
} catch (error) {
|
|
5362
|
+
if (error instanceof CliFailure) {
|
|
5363
|
+
throw error;
|
|
5364
|
+
}
|
|
5252
5365
|
if (error instanceof UnsafeDotenvPathError) {
|
|
5253
5366
|
throw unsafeDotenvPathFailure();
|
|
5254
5367
|
}
|
|
@@ -5257,6 +5370,9 @@ var DotenvApiKeyWriter = class {
|
|
|
5257
5370
|
}
|
|
5258
5371
|
throw dotenvWriteFailure();
|
|
5259
5372
|
}
|
|
5373
|
+
if (warnings.length > 0) {
|
|
5374
|
+
return { warnings };
|
|
5375
|
+
}
|
|
5260
5376
|
}
|
|
5261
5377
|
async assertSafeOptionalPath(path) {
|
|
5262
5378
|
try {
|
|
@@ -5272,7 +5388,7 @@ var DotenvApiKeyWriter = class {
|
|
|
5272
5388
|
}
|
|
5273
5389
|
}
|
|
5274
5390
|
async readExistingFile() {
|
|
5275
|
-
const filename =
|
|
5391
|
+
const filename = join5(this.options.directory, ".env");
|
|
5276
5392
|
await this.assertSafeOptionalPath(filename);
|
|
5277
5393
|
try {
|
|
5278
5394
|
return await this.fileSystem.readFile(filename, "utf8");
|
|
@@ -5284,8 +5400,8 @@ var DotenvApiKeyWriter = class {
|
|
|
5284
5400
|
}
|
|
5285
5401
|
}
|
|
5286
5402
|
async replaceAtomically(content, onRenamed) {
|
|
5287
|
-
const filename =
|
|
5288
|
-
const temporaryFile =
|
|
5403
|
+
const filename = join5(this.options.directory, ".env");
|
|
5404
|
+
const temporaryFile = join5(
|
|
5289
5405
|
this.options.directory,
|
|
5290
5406
|
`.env.${process.pid}.${this.createTemporaryId()}.tmp`
|
|
5291
5407
|
);
|
|
@@ -5479,22 +5595,22 @@ import { posix, win32 } from "path";
|
|
|
5479
5595
|
// src/storage/atomic-json.ts
|
|
5480
5596
|
import {
|
|
5481
5597
|
chmod as chmod3,
|
|
5482
|
-
lstat as
|
|
5598
|
+
lstat as lstat5,
|
|
5483
5599
|
mkdir as mkdir3,
|
|
5484
|
-
open as
|
|
5485
|
-
readFile as
|
|
5486
|
-
rename as
|
|
5600
|
+
open as open3,
|
|
5601
|
+
readFile as readFile4,
|
|
5602
|
+
rename as rename3,
|
|
5487
5603
|
unlink as unlink2
|
|
5488
5604
|
} from "fs/promises";
|
|
5489
5605
|
import { dirname as dirname2 } from "path";
|
|
5490
|
-
import { randomUUID as
|
|
5606
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
5491
5607
|
var nodeAtomicJsonFileSystem = Object.freeze({
|
|
5492
5608
|
chmod: chmod3,
|
|
5493
|
-
lstat:
|
|
5609
|
+
lstat: lstat5,
|
|
5494
5610
|
mkdir: mkdir3,
|
|
5495
|
-
open:
|
|
5496
|
-
readFile:
|
|
5497
|
-
rename:
|
|
5611
|
+
open: open3,
|
|
5612
|
+
readFile: readFile4,
|
|
5613
|
+
rename: rename3,
|
|
5498
5614
|
unlink: unlink2
|
|
5499
5615
|
});
|
|
5500
5616
|
function hasErrorCode5(error, code) {
|
|
@@ -5538,7 +5654,7 @@ var AtomicJsonStore = class {
|
|
|
5538
5654
|
this.filename = filename;
|
|
5539
5655
|
this.fileSystem = options.fileSystem ?? nodeAtomicJsonFileSystem;
|
|
5540
5656
|
this.platform = options.platform ?? process.platform;
|
|
5541
|
-
this.createTemporaryId = options.createTemporaryId ??
|
|
5657
|
+
this.createTemporaryId = options.createTemporaryId ?? randomUUID5;
|
|
5542
5658
|
}
|
|
5543
5659
|
schema;
|
|
5544
5660
|
filename;
|
|
@@ -5840,6 +5956,13 @@ var ProjectEndpointCache = class {
|
|
|
5840
5956
|
).toString("base64url");
|
|
5841
5957
|
return `endpoint-cache-key-${scope}`;
|
|
5842
5958
|
}
|
|
5959
|
+
apiKeyAccount(projectId) {
|
|
5960
|
+
const scope = Buffer6.from(
|
|
5961
|
+
`${this.options.environment}:${projectId}`,
|
|
5962
|
+
"utf8"
|
|
5963
|
+
).toString("base64url");
|
|
5964
|
+
return `project-rpc-key-${scope}`;
|
|
5965
|
+
}
|
|
5843
5966
|
overrideAccount(projectId) {
|
|
5844
5967
|
const scope = Buffer6.from(
|
|
5845
5968
|
`${this.options.environment}:${projectId}`,
|
|
@@ -5895,6 +6018,18 @@ var ProjectEndpointCache = class {
|
|
|
5895
6018
|
}
|
|
5896
6019
|
throw error;
|
|
5897
6020
|
}
|
|
6021
|
+
let apiKey;
|
|
6022
|
+
for (const network of networks) {
|
|
6023
|
+
apiKey = this.extractCredential(network.endpoints.https);
|
|
6024
|
+
if (apiKey !== void 0) {
|
|
6025
|
+
break;
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
if (apiKey === void 0) {
|
|
6029
|
+
await this.options.secretStore.delete(this.apiKeyAccount(projectId));
|
|
6030
|
+
} else {
|
|
6031
|
+
await this.options.secretStore.write(this.apiKeyAccount(projectId), apiKey);
|
|
6032
|
+
}
|
|
5898
6033
|
return networks;
|
|
5899
6034
|
}
|
|
5900
6035
|
status(projectId) {
|
|
@@ -5903,7 +6038,8 @@ var ProjectEndpointCache = class {
|
|
|
5903
6038
|
async clear(projectId) {
|
|
5904
6039
|
const results = await Promise.allSettled([
|
|
5905
6040
|
this.options.store.delete(projectId),
|
|
5906
|
-
this.options.secretStore.delete(this.cacheKeyAccount(projectId))
|
|
6041
|
+
this.options.secretStore.delete(this.cacheKeyAccount(projectId)),
|
|
6042
|
+
this.options.secretStore.delete(this.apiKeyAccount(projectId))
|
|
5907
6043
|
]);
|
|
5908
6044
|
const failure = results.find(
|
|
5909
6045
|
(result) => result.status === "rejected"
|
|
@@ -5962,20 +6098,23 @@ var ProjectEndpointCache = class {
|
|
|
5962
6098
|
this.overrideAccount(projectId)
|
|
5963
6099
|
);
|
|
5964
6100
|
if (override !== null) {
|
|
5965
|
-
const
|
|
5966
|
-
if (!
|
|
6101
|
+
const parsed2 = rpcApiKeySchema.safeParse(override);
|
|
6102
|
+
if (!parsed2.success) {
|
|
5967
6103
|
throw unavailableApiKey();
|
|
5968
6104
|
}
|
|
5969
|
-
return
|
|
6105
|
+
return parsed2.data;
|
|
5970
6106
|
}
|
|
5971
|
-
const
|
|
5972
|
-
|
|
5973
|
-
)
|
|
5974
|
-
|
|
5975
|
-
|
|
6107
|
+
const account = this.apiKeyAccount(projectId);
|
|
6108
|
+
let key = await this.options.secretStore.read(account);
|
|
6109
|
+
if (key === null) {
|
|
6110
|
+
await this.refresh(projectId);
|
|
6111
|
+
key = await this.options.secretStore.read(account);
|
|
6112
|
+
}
|
|
6113
|
+
const parsed = rpcApiKeySchema.safeParse(key);
|
|
6114
|
+
if (!parsed.success) {
|
|
5976
6115
|
throw unavailableApiKey();
|
|
5977
6116
|
}
|
|
5978
|
-
return
|
|
6117
|
+
return parsed.data;
|
|
5979
6118
|
}
|
|
5980
6119
|
async applyOverride(projectId, network) {
|
|
5981
6120
|
const key = await this.options.secretStore.read(
|
|
@@ -6032,33 +6171,39 @@ var ProjectEndpointCache = class {
|
|
|
6032
6171
|
);
|
|
6033
6172
|
}
|
|
6034
6173
|
}
|
|
6035
|
-
extractCredential(
|
|
6174
|
+
extractCredential(endpoint) {
|
|
6036
6175
|
try {
|
|
6037
|
-
const url = new URL(
|
|
6176
|
+
const url = new URL(endpoint);
|
|
6038
6177
|
const trustedHost = url.hostname === "zan.top" || url.hostname.endsWith(".zan.top");
|
|
6039
6178
|
const segments = url.pathname.slice(1).split("/");
|
|
6040
6179
|
if (url.protocol !== "https:" || !trustedHost || url.port.length > 0 || url.username.length > 0 || url.password.length > 0 || url.search.length > 0 || url.hash.length > 0 || !url.pathname.startsWith("/") || segments.some((segment) => segment.length === 0) || url.pathname !== `/${segments.join("/")}`) {
|
|
6041
6180
|
throw new Error("unsafe");
|
|
6042
6181
|
}
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6182
|
+
if (segments[0] !== "node" || segments[1] !== "v1" || !networkIdSchema.safeParse(segments[2]).success || !networkIdSchema.safeParse(segments[3]).success) {
|
|
6183
|
+
return void 0;
|
|
6184
|
+
}
|
|
6185
|
+
let suffix = "";
|
|
6186
|
+
switch (segments[2]) {
|
|
6187
|
+
case "hyperliquid":
|
|
6188
|
+
suffix = "evm";
|
|
6189
|
+
break;
|
|
6190
|
+
case "avax":
|
|
6191
|
+
suffix = "ext/bc/C/rpc";
|
|
6192
|
+
break;
|
|
6193
|
+
case "tron":
|
|
6194
|
+
suffix = "jsonrpc";
|
|
6195
|
+
break;
|
|
6054
6196
|
}
|
|
6055
|
-
|
|
6197
|
+
if (segments.slice(5).join("/") !== suffix) {
|
|
6198
|
+
return void 0;
|
|
6199
|
+
}
|
|
6200
|
+
const parsed = rpcApiKeySchema.safeParse(segments[4]);
|
|
6056
6201
|
if (!parsed.success) {
|
|
6057
6202
|
throw new Error("unsafe");
|
|
6058
6203
|
}
|
|
6059
6204
|
return parsed.data;
|
|
6060
6205
|
} catch {
|
|
6061
|
-
|
|
6206
|
+
return void 0;
|
|
6062
6207
|
}
|
|
6063
6208
|
}
|
|
6064
6209
|
};
|
|
@@ -6072,21 +6217,22 @@ var RpcKeyPullService = class {
|
|
|
6072
6217
|
async pull() {
|
|
6073
6218
|
const projectId = await this.dependencies.projects.activeId();
|
|
6074
6219
|
const key = await this.dependencies.keys.effectiveApiKey(projectId);
|
|
6075
|
-
await this.dependencies.writer.write(key);
|
|
6220
|
+
const result = await this.dependencies.writer.write(key);
|
|
6076
6221
|
return {
|
|
6077
6222
|
file: ".env",
|
|
6078
6223
|
variable: "ZAN_API_KEY",
|
|
6079
|
-
updated: true
|
|
6224
|
+
updated: true,
|
|
6225
|
+
...result?.warnings.length ? { warnings: result.warnings } : {}
|
|
6080
6226
|
};
|
|
6081
6227
|
}
|
|
6082
6228
|
};
|
|
6083
6229
|
|
|
6084
6230
|
// src/projects/project-service.ts
|
|
6085
|
-
import { randomUUID as
|
|
6231
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
6086
6232
|
var ProjectService = class {
|
|
6087
6233
|
constructor(options) {
|
|
6088
6234
|
this.options = options;
|
|
6089
|
-
this.createIdempotencyKey = options.createIdempotencyKey ??
|
|
6235
|
+
this.createIdempotencyKey = options.createIdempotencyKey ?? randomUUID6;
|
|
6090
6236
|
}
|
|
6091
6237
|
options;
|
|
6092
6238
|
createIdempotencyKey;
|
|
@@ -6480,7 +6626,7 @@ var SettingsStore = class {
|
|
|
6480
6626
|
};
|
|
6481
6627
|
|
|
6482
6628
|
// src/settings/paths.ts
|
|
6483
|
-
import { join as
|
|
6629
|
+
import { join as join6, posix as posix2, win32 as win322 } from "path";
|
|
6484
6630
|
function nonEmpty(value) {
|
|
6485
6631
|
return value === void 0 || value.length === 0 ? void 0 : value;
|
|
6486
6632
|
}
|
|
@@ -6513,7 +6659,7 @@ function resolveZanHome(env, platform, homedir2) {
|
|
|
6513
6659
|
}
|
|
6514
6660
|
if (platform === "darwin") {
|
|
6515
6661
|
return validateHomePath(
|
|
6516
|
-
|
|
6662
|
+
join6(readHomedir(homedir2), "Library", "Application Support", "zan"),
|
|
6517
6663
|
platform
|
|
6518
6664
|
);
|
|
6519
6665
|
}
|
|
@@ -7336,7 +7482,7 @@ function createRuntimeContext(input) {
|
|
|
7336
7482
|
walletService ??= new WalletService({
|
|
7337
7483
|
index: new AtomicJsonStore(
|
|
7338
7484
|
walletIndexSchema,
|
|
7339
|
-
|
|
7485
|
+
join7(home, "wallets.json"),
|
|
7340
7486
|
{ platform }
|
|
7341
7487
|
),
|
|
7342
7488
|
secrets: secretStore,
|
|
@@ -7652,4 +7798,3 @@ export {
|
|
|
7652
7798
|
isMainModule,
|
|
7653
7799
|
runCli
|
|
7654
7800
|
};
|
|
7655
|
-
//# sourceMappingURL=cli.js.map
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zan_team/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Unified ZAN command-line interface for developers, automation, and AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"zan": "./dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist",
|
|
10
|
+
"dist/cli.js",
|
|
11
11
|
"skills",
|
|
12
12
|
"README.md"
|
|
13
13
|
],
|