@zan_team/cli 0.0.2 → 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 +21 -5
- package/dist/cli.js +199 -54
- package/package.json +2 -2
- package/dist/cli.js.map +0 -1
package/README.md
CHANGED
|
@@ -360,15 +360,31 @@ zan project rpc-key pull
|
|
|
360
360
|
```
|
|
361
361
|
|
|
362
362
|
命令写入当前工作目录中固定的 `ZAN_API_KEY`:已存在时替换,缺少时追加,文件
|
|
363
|
-
|
|
364
|
-
|
|
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、变量名
|
|
365
375
|
或输出路径,也不会在人工输出、JSON 输出、错误或日志中包含 Key。新文件及更新
|
|
366
376
|
后的文件在 POSIX 系统上使用 `0600` 权限,并通过文件锁和原子替换写入;符号链接
|
|
367
377
|
或非普通 `.env` 会被拒绝。
|
|
368
378
|
|
|
369
|
-
`pull`
|
|
370
|
-
|
|
371
|
-
|
|
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 都可能读取这个明文
|
|
372
388
|
文件;“不泄露”保证只覆盖该命令自身的参数、输出、错误和日志边界。
|
|
373
389
|
|
|
374
390
|
管理默认 Project 和 Network:
|
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
|
],
|