@ziniao-open/cli 0.1.0-beta.100

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,130 @@
1
+ # ziniao-cli
2
+
3
+ 紫鸟开放平台命令行工具,为开发者和 AI Agent 提供统一的接口调用入口。
4
+
5
+ 支持服务端 OpenAPI 和本地浏览器 Bridge(ZClaw)两套接口体系,使用统一的 apiKey 认证。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ npm install -g @ziniao-open/cli
11
+ ```
12
+
13
+ 紫鸟客户端自带的 `ziniao` CLI 需要同步到 AppCache 并加入 PATH。执行 `npm install -g @ziniao-open/cli` 时会自动尝试同步;安装紫鸟客户端后,也可以手动执行以下命令:
14
+
15
+ ```bash
16
+ source "$(npm root -g)/@ziniao-open/cli/scripts/setup-ziniao-cli.sh"
17
+ ```
18
+
19
+ Windows PowerShell 执行:
20
+
21
+ ```powershell
22
+ . "$(npm root -g)\@ziniao-open\cli\scripts\setup-ziniao-cli.ps1"
23
+ ```
24
+
25
+ 也可以在安装后手动修复:
26
+
27
+ ```bash
28
+ ziniao-cli doctor fix-path
29
+ ```
30
+
31
+ 遍历当前 PATH 并清理正式目录之外的 `ziniao.exe` 文件(默认只预览):
32
+
33
+ ```powershell
34
+ & "$(npm root -g)\@ziniao-open\cli\scripts\remove-ziniao-cli-shims.ps1"
35
+ & "$(npm root -g)\@ziniao-open\cli\scripts\remove-ziniao-cli-shims.ps1" -Apply
36
+ ```
37
+
38
+ macOS/Linux:
39
+
40
+ ```bash
41
+ "$(npm root -g)/@ziniao-open/cli/scripts/remove-ziniao-cli-shims.sh"
42
+ "$(npm root -g)/@ziniao-open/cli/scripts/remove-ziniao-cli-shims.sh" --apply
43
+ ```
44
+
45
+ 脚本只探测 `ziniao.app` / `ziniao` 的安装目录;Windows 会优先查询卸载注册表中 `DisplayIcon` 包含 `ziniao.exe` 的条目,再从本体目录的 `resources/app.asar.unpacked/envkit/cli` 查找 CLI。Windows 使用 `%APPDATA%\ziniaobrowser\cli`,macOS 使用 `~/Library/Application Support/ziniaobrowser/cli`,Linux 使用 `${XDG_CONFIG_HOME:-~/.config}/ziniaobrowser/cli`,在目标目录缺少 CLI 或文件内容发生变化时同步最新文件,并注册当前用户 PATH。找不到源文件时只提示并继续,不阻断 PATH 配置。若安装目录无法自动探测,可设置 `ZINIAO_INSTALL_DIR`;若目标位置不同,可设置 `ZINIAO_APP_CACHE_DIR`。
46
+ Windows 同时会把找到的紫鸟本体绝对路径写入 `%APPDATA%\ziniaobrowser\gui-path`,供 CLI 定位桌面客户端。
47
+ macOS/Linux 也会在找到桌面本体时写入对应的 `gui-path`(macOS 为 `~/Library/Application Support/ziniaobrowser/gui-path`,Linux 为 `${XDG_CONFIG_HOME:-~/.config}/ziniaobrowser/gui-path`)。
48
+
49
+ 安装时会自动下载当前平台对应的预编译二进制(支持 macOS / Linux / Windows × amd64 / arm64),并做 SHA-256 校验。
50
+
51
+ ## 快速开始
52
+
53
+ ```bash
54
+ # 1. 初始化配置(首次使用必做,会引导你写入 apiKey)
55
+ ziniao-cli config init
56
+
57
+ # 2. 通用 API 调用(覆盖紫鸟开放平台全部接口)
58
+ ziniao-cli api /superbrowser/rest/v1/erp/department/list
59
+
60
+ # 3. 快捷命令(高频操作的封装)
61
+ ziniao-cli staff list --format table
62
+ ziniao-cli store list
63
+ ziniao-cli department list
64
+
65
+ # 4. 环境自检
66
+ ziniao-cli doctor
67
+
68
+ # 5. 查看所有命令
69
+ ziniao-cli --help
70
+ ```
71
+
72
+ ## 命令概览
73
+
74
+ | 类别 | 命令 | 说明 |
75
+ |------|------|------|
76
+ | 基础 | `config` | apiKey 等配置初始化与查看 |
77
+ | 基础 | `doctor` | 环境自检(认证、网络、ZClaw 桥可用性) |
78
+ | 基础 | `completion` | 生成 shell 自动补全 |
79
+ | 通用 | `api <path>` | 调用任意紫鸟 OpenAPI 路径 |
80
+ | 桥接 | `zclaw` | 本地浏览器 Bridge(页面操作、店铺管理等) |
81
+ | 快捷 | `staff` / `store` / `account` / `department` / `role` / `device` / `page` / `automation` | 高频业务操作的封装 |
82
+
83
+ 完整列表见 `ziniao-cli --help`。
84
+
85
+ ## 本地 dev/prod 打包与切换
86
+
87
+ 在仓库根目录执行以下命令,可分别生成本地测试环境包和本地正式环境包:
88
+
89
+ ```bash
90
+ make package-dev
91
+ make package-prod
92
+ ```
93
+
94
+ 两个命令都会生成本地多平台 snapshot 包。版本基于最近的发布 tag,并使用本机递增计数器:最近 tag 为 `v1.0.7` 时,依次生成 `1.0.7-dev-1`、`1.0.7-dev-2`、`1.0.7-dev-3`。
95
+
96
+ 计数器保存在 `~/.ziniao-cli/dev-build-counter`;当最近 tag 变化时计数从 `1` 重新开始。可通过 `ZINIAO_CLI_DEV_COUNTER_FILE` 指定其他计数器路径,便于 CI 或测试隔离。产物写入 `dist/`,该命令只打包,不发布。
97
+
98
+ `package-dev` 内置测试环境 `https://test-sbappstoreapi.ziniao.com` 与 `https://test-open.ziniao.com`,默认使用独立配置目录 `~/.ziniao-cli-dev/`,本地二进制写入 `~/.ziniao-cli-dev/bin/ziniao-cli`。
99
+
100
+ `package-prod` 内置正式环境 `https://sbappstoreapi.ziniao.com` 与 `https://open.ziniao.com`,默认使用独立配置目录 `~/.ziniao-cli-prod/`,本地二进制写入 `~/.ziniao-cli-prod/bin/ziniao-cli`。
101
+
102
+ 仓库内的 `scripts/ziniao-cli-use` 提供统一切换入口;如果你已将该脚本加入 PATH,可直接执行:
103
+
104
+ ```bash
105
+ ziniao-cli-use dev
106
+ ziniao-cli-use prod
107
+ ziniao-cli-use online
108
+ ```
109
+
110
+ - `ziniao-cli-use dev`:切换到 `~/.ziniao-cli-dev/bin/ziniao-cli`
111
+ - `ziniao-cli-use prod`:切换到 `~/.ziniao-cli-prod/bin/ziniao-cli`
112
+ - `ziniao-cli-use online`:切换回 npm 安装的线上版 `ziniao-cli`,继续使用默认线上配置目录 `~/.ziniao-cli/`
113
+
114
+ `make package-dev` 与 `make package-prod` 只会更新各自隔离的本地输出路径,不会改写仓库根目录的 `./ziniao-cli`。如果你需要仓库根目录的开发测试二进制,继续使用 `make build`。
115
+
116
+ dev、本地 prod、online 三套 CLI 默认分别使用 `~/.ziniao-cli-dev/`、`~/.ziniao-cli-prod/`、`~/.ziniao-cli/`,API Key 和配置互不读取。首次运行某个本地环境前,执行 `ziniao-cli config init` 即可在对应目录完成初始化。
117
+
118
+ ## AI Agent Skills
119
+
120
+ 仓库提供 12 个开箱即用的 AI Agent Skill:访问策略、账号、部门、设备、OpenAPI 探索、页面、角色、共享基础、Skill 创建、员工、店铺和批量账号授权。
121
+
122
+ 安装 Skill:
123
+
124
+ ```bash
125
+ ziniao-cli skills install
126
+ ```
127
+
128
+ 备选:`npx skills add ziniao-open/skills -y -g`
129
+
130
+ 详见 [ziniao-skills](https://github.com/ziniao-open/skills)。
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@ziniao-open/cli",
3
+ "version": "0.1.0-beta.100",
4
+ "description": "紫鸟开放平台命令行工具,为开发者和 AI Agent 提供统一的接口调用入口。",
5
+ "license": "UNLICENSED",
6
+ "bin": {
7
+ "ziniao-cli": "scripts/run.js"
8
+ },
9
+ "scripts": {
10
+ "postinstall": "node scripts/install.js",
11
+ "pack:local": "npm pack"
12
+ },
13
+ "files": [
14
+ "scripts/install.js",
15
+ "scripts/run.js",
16
+ "scripts/setup-ziniao-cli.sh",
17
+ "scripts/setup-ziniao-cli.ps1",
18
+ "scripts/remove-ziniao-cli-shims.ps1",
19
+ "scripts/remove-ziniao-cli-shims.sh"
20
+ ],
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "ziniaoCli": {
25
+ "downloadBaseUrl": "https://ziniao-open-test.s3.cn-northwest-1.amazonaws.com.cn/ziniao-cli/releases",
26
+ "binaryName": "ziniao-cli",
27
+ "archivePrefix": "ziniao-cli"
28
+ }
29
+ }
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Postinstall hook: download the platform-matching ziniao-cli binary from S3.
4
+ *
5
+ * Skip paths (in priority order):
6
+ * 1. SKIP_ZINIAO_CLI_INSTALL=1 -> skip everything (CI / monorepo installs)
7
+ * 2. ZINIAO_CLI_BINARY=/path/to/binary -> copy from local path (dev/release-rehearsal)
8
+ * 3. bin/ziniao-cli(.exe) already present -> reuse it (avoid re-download on rebuild)
9
+ * 4. package.json version === "0.0.0" -> developer install in this repo itself
10
+ *
11
+ * Network path:
12
+ * - Read downloadBaseUrl from package.json.ziniaoCli.downloadBaseUrl
13
+ * (overridable via ZINIAO_CLI_DOWNLOAD_BASE env var).
14
+ * - Download: {base}/v{version}/checksums.txt
15
+ * {base}/v{version}/{archivePrefix}-{version}-{goos}-{goarch}.{ext}
16
+ * - Verify SHA-256 against checksums.txt.
17
+ * - Extract via system `tar` (Windows 10+ ships bsdtar capable of .zip and .tar.gz).
18
+ */
19
+
20
+ const {
21
+ chmodSync,
22
+ copyFileSync,
23
+ createWriteStream,
24
+ existsSync,
25
+ mkdirSync,
26
+ mkdtempSync,
27
+ readFileSync,
28
+ rmSync,
29
+ statSync,
30
+ unlinkSync,
31
+ } = require("node:fs");
32
+ const { basename, dirname, join } = require("node:path");
33
+ const { tmpdir } = require("node:os");
34
+ const { spawnSync } = require("node:child_process");
35
+ const { createHash } = require("node:crypto");
36
+ const https = require("node:https");
37
+ const http = require("node:http");
38
+ const { URL } = require("node:url");
39
+
40
+ const PKG = require("../package.json");
41
+ const REPO_ROOT = join(__dirname, "..");
42
+ const BIN_DIR = join(REPO_ROOT, "bin");
43
+ const IS_WIN = process.platform === "win32";
44
+ const BINARY_NAME = IS_WIN ? "ziniao-cli.exe" : "ziniao-cli";
45
+ const TARGET_PATH = join(BIN_DIR, BINARY_NAME);
46
+
47
+ const cfg = PKG.ziniaoCli || {};
48
+ const DOWNLOAD_BASE = (process.env.ZINIAO_CLI_DOWNLOAD_BASE || cfg.downloadBaseUrl || "").replace(/\/+$/, "");
49
+ const ARCHIVE_PREFIX = cfg.archivePrefix || "ziniao-cli";
50
+
51
+ const PLATFORM_MAP = { darwin: "darwin", linux: "linux", win32: "windows" };
52
+ const ARCH_MAP = { x64: "amd64", arm64: "arm64" };
53
+
54
+ function log(msg) {
55
+ console.log(`[ziniao-cli/install] ${msg}`);
56
+ }
57
+ function warn(msg) {
58
+ console.warn(`[ziniao-cli/install] ${msg}`);
59
+ }
60
+ function fail(msg) {
61
+ console.error(`[ziniao-cli/install] ${msg}`);
62
+ process.exit(1);
63
+ }
64
+
65
+ function ensureBinDir() {
66
+ mkdirSync(BIN_DIR, { recursive: true });
67
+ }
68
+
69
+ function makeExecutable(file) {
70
+ if (!IS_WIN) {
71
+ chmodSync(file, 0o755);
72
+ }
73
+ }
74
+
75
+ function copyFromEnv() {
76
+ const src = process.env.ZINIAO_CLI_BINARY;
77
+ if (!src) return false;
78
+ if (!existsSync(src) || !statSync(src).isFile()) {
79
+ fail(`ZINIAO_CLI_BINARY does not point to a file: ${src}`);
80
+ }
81
+ ensureBinDir();
82
+ copyFileSync(src, TARGET_PATH);
83
+ makeExecutable(TARGET_PATH);
84
+ log(`installed from local binary ${basename(src)}`);
85
+ return true;
86
+ }
87
+
88
+ function detectTarget() {
89
+ const goos = PLATFORM_MAP[process.platform];
90
+ const goarch = ARCH_MAP[process.arch];
91
+ if (!goos || !goarch) {
92
+ fail(`unsupported platform: ${process.platform}-${process.arch}`);
93
+ }
94
+ const ext = goos === "windows" ? "zip" : "tar.gz";
95
+ return { goos, goarch, ext };
96
+ }
97
+
98
+ function getRedirected(url, onResponse, redirects = 0) {
99
+ if (redirects > 5) {
100
+ return onResponse(new Error(`too many redirects: ${url}`));
101
+ }
102
+ const lib = url.startsWith("https:") ? https : http;
103
+ const req = lib.get(url, (res) => {
104
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
105
+ const next = new URL(res.headers.location, url).toString();
106
+ res.resume();
107
+ return getRedirected(next, onResponse, redirects + 1);
108
+ }
109
+ if (res.statusCode !== 200) {
110
+ res.resume();
111
+ return onResponse(new Error(`HTTP ${res.statusCode} for ${url}`));
112
+ }
113
+ onResponse(null, res);
114
+ });
115
+ req.on("error", (err) => onResponse(err));
116
+ req.setTimeout(60_000, () => req.destroy(new Error(`download timed out: ${url}`)));
117
+ }
118
+
119
+ function downloadToString(url) {
120
+ return new Promise((resolve, reject) => {
121
+ getRedirected(url, (err, res) => {
122
+ if (err) return reject(err);
123
+ const chunks = [];
124
+ res.on("data", (c) => chunks.push(c));
125
+ res.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
126
+ res.on("error", reject);
127
+ });
128
+ });
129
+ }
130
+
131
+ function downloadToFile(url, dest) {
132
+ return new Promise((resolve, reject) => {
133
+ getRedirected(url, (err, res) => {
134
+ if (err) return reject(err);
135
+ const hash = createHash("sha256");
136
+ const out = createWriteStream(dest);
137
+ res.on("data", (c) => hash.update(c));
138
+ res.pipe(out);
139
+ out.on("finish", () => out.close(() => resolve(hash.digest("hex"))));
140
+ out.on("error", reject);
141
+ res.on("error", reject);
142
+ });
143
+ });
144
+ }
145
+
146
+ function parseChecksums(text) {
147
+ const map = new Map();
148
+ for (const line of text.split(/\r?\n/)) {
149
+ const m = line.trim().match(/^([0-9a-f]{64})\s+\*?(\S+)$/i);
150
+ if (m) map.set(m[2], m[1].toLowerCase());
151
+ }
152
+ return map;
153
+ }
154
+
155
+ function extract(archivePath, destDir) {
156
+ if (process.platform === "win32" && archivePath.endsWith(".zip")) {
157
+ const ps = spawnSync("powershell.exe", [
158
+ "-NoProfile", "-NonInteractive", "-Command",
159
+ `Expand-Archive -LiteralPath '${archivePath}' -DestinationPath '${destDir}' -Force`
160
+ ], { stdio: "inherit" });
161
+ if (!ps.error && ps.status === 0) return;
162
+ }
163
+ let tarCmd = "tar";
164
+ if (process.platform === "win32") {
165
+ const sysRoot = process.env.SystemRoot || "C:\\Windows";
166
+ const candidates = [
167
+ join(sysRoot, "Sysnative", "tar.exe"),
168
+ join(sysRoot, "System32", "tar.exe"),
169
+ ];
170
+ for (const p of candidates) {
171
+ if (existsSync(p)) {
172
+ tarCmd = p;
173
+ break;
174
+ }
175
+ }
176
+ }
177
+ const res = spawnSync(tarCmd, ["-xf", archivePath, "-C", destDir], { stdio: "inherit" });
178
+ if (res.error) {
179
+ fail(`failed to execute tar: ${res.error.message}. Tar is required (preinstalled on Win10+/macOS/Linux).`);
180
+ }
181
+ if (res.status !== 0) {
182
+ fail(`tar exited with code ${res.status}`);
183
+ }
184
+ }
185
+
186
+ function findBinaryIn(dir, name) {
187
+ const fs = require("node:fs");
188
+ const stack = [dir];
189
+ while (stack.length) {
190
+ const cur = stack.pop();
191
+ for (const entry of fs.readdirSync(cur, { withFileTypes: true })) {
192
+ const p = join(cur, entry.name);
193
+ if (entry.isDirectory()) stack.push(p);
194
+ else if (entry.isFile() && entry.name === name) return p;
195
+ }
196
+ }
197
+ return null;
198
+ }
199
+
200
+ function runDesktopCliSetup() {
201
+ const scriptName = IS_WIN ? "setup-ziniao-cli.ps1" : "setup-ziniao-cli.sh";
202
+ const scriptPath = join(__dirname, scriptName);
203
+ if (!existsSync(scriptPath)) {
204
+ warn(`desktop CLI PATH setup script is missing: ${scriptPath}`);
205
+ return;
206
+ }
207
+
208
+ const result = IS_WIN
209
+ ? spawnSync("powershell.exe", [
210
+ "-NoProfile",
211
+ "-NonInteractive",
212
+ "-ExecutionPolicy",
213
+ "Bypass",
214
+ "-File",
215
+ scriptPath,
216
+ ], { stdio: "inherit" })
217
+ : spawnSync("sh", [scriptPath], { stdio: "inherit" });
218
+ if (result.error) {
219
+ warn(`desktop CLI PATH setup skipped: ${result.error.message}`);
220
+ } else if (result.status !== 0) {
221
+ warn(`desktop CLI PATH setup exited with code ${result.status}`);
222
+ }
223
+ }
224
+
225
+ async function downloadAndInstall() {
226
+ const version = PKG.version;
227
+ if (!DOWNLOAD_BASE) {
228
+ fail(
229
+ "ziniaoCli.downloadBaseUrl is not configured. Set it in package.json or " +
230
+ "export ZINIAO_CLI_DOWNLOAD_BASE before installing."
231
+ );
232
+ }
233
+
234
+ const { goos, goarch, ext } = detectTarget();
235
+ const archiveName = `${ARCHIVE_PREFIX}-${version}-${goos}-${goarch}.${ext}`;
236
+ const baseDir = `${DOWNLOAD_BASE}/v${version}`;
237
+ const archiveUrl = `${baseDir}/${archiveName}`;
238
+ const checksumUrl = `${baseDir}/checksums.txt`;
239
+
240
+ log(`downloading ${archiveUrl}`);
241
+ const work = mkdtempSync(join(tmpdir(), "ziniao-cli-"));
242
+ const archivePath = join(work, archiveName);
243
+
244
+ let checksums;
245
+ try {
246
+ checksums = parseChecksums(await downloadToString(checksumUrl));
247
+ } catch (err) {
248
+ fail(`failed to fetch checksums.txt: ${err.message}`);
249
+ }
250
+ const expected = checksums.get(archiveName);
251
+ if (!expected) {
252
+ fail(`checksums.txt does not contain an entry for ${archiveName}`);
253
+ }
254
+
255
+ let actual;
256
+ try {
257
+ actual = await downloadToFile(archiveUrl, archivePath);
258
+ } catch (err) {
259
+ fail(`failed to download ${archiveUrl}: ${err.message}`);
260
+ }
261
+ if (actual !== expected) {
262
+ fail(`SHA-256 mismatch for ${archiveName} (expected ${expected}, got ${actual})`);
263
+ }
264
+ log("SHA-256 verified");
265
+
266
+ extract(archivePath, work);
267
+ const extracted = findBinaryIn(work, BINARY_NAME);
268
+ if (!extracted) {
269
+ fail(`binary ${BINARY_NAME} not found in archive ${archiveName}`);
270
+ }
271
+
272
+ ensureBinDir();
273
+ copyFileSync(extracted, TARGET_PATH);
274
+ makeExecutable(TARGET_PATH);
275
+
276
+ try {
277
+ rmSync(work, { recursive: true, force: true });
278
+ } catch {
279
+ /* best effort */
280
+ }
281
+
282
+ log(`installed ${BINARY_NAME} -> ${TARGET_PATH}`);
283
+ }
284
+
285
+ async function main() {
286
+ if (process.env.SKIP_ZINIAO_CLI_INSTALL === "1") {
287
+ log("SKIP_ZINIAO_CLI_INSTALL=1 set, skipping binary install");
288
+ return;
289
+ }
290
+
291
+ if (PKG.version === "0.0.0") {
292
+ warn("package.json version is 0.0.0; skipping binary install (developer mode).");
293
+ warn("Set ZINIAO_CLI_BINARY=/path/to/binary or run `make build` to use a local build.");
294
+ } else if (existsSync(TARGET_PATH)) {
295
+ makeExecutable(TARGET_PATH);
296
+ log(`binary already present at ${TARGET_PATH}, skipping download`);
297
+ } else if (copyFromEnv()) {
298
+ // Local binary installed.
299
+ } else {
300
+ await downloadAndInstall();
301
+ }
302
+
303
+ runDesktopCliSetup();
304
+ }
305
+
306
+ main().catch((err) => fail(err.stack || err.message || String(err)));
@@ -0,0 +1,43 @@
1
+ # Find and optionally remove ziniao.exe shims created in PATH directories.
2
+ # Run without -Apply first to preview the files that would be removed.
3
+
4
+ [CmdletBinding(SupportsShouldProcess)]
5
+ param(
6
+ [switch]$Apply
7
+ )
8
+
9
+ $ErrorActionPreference = "Stop"
10
+
11
+ function Write-Log([string]$Message) {
12
+ Write-Output "[ziniao-cli-shims] $Message"
13
+ }
14
+
15
+ $targetDir = Join-Path $env:APPDATA "ziniaobrowser\cli"
16
+ $candidates = @()
17
+ foreach ($pathEntry in @($env:Path -split ";" | Where-Object { $_ })) {
18
+ $directory = $pathEntry.TrimEnd('\')
19
+ if (-not $directory -or $directory -ieq $targetDir.TrimEnd('\')) { continue }
20
+ $shimPath = Join-Path $directory "ziniao.exe"
21
+ if (-not (Test-Path -LiteralPath $shimPath -PathType Leaf)) { continue }
22
+ $candidates += $shimPath
23
+ }
24
+
25
+ if (-not $candidates) {
26
+ Write-Log "No ziniao.exe shims found in PATH directories."
27
+ exit 0
28
+ }
29
+
30
+ foreach ($candidate in ($candidates | Select-Object -Unique)) {
31
+ if (-not $Apply) {
32
+ Write-Log "Would remove: $candidate"
33
+ continue
34
+ }
35
+ if ($PSCmdlet.ShouldProcess($candidate, "Remove CLI shim")) {
36
+ Remove-Item -LiteralPath $candidate -Force
37
+ Write-Log "Removed: $candidate"
38
+ }
39
+ }
40
+
41
+ if (-not $Apply) {
42
+ Write-Log "Preview only. Re-run with -Apply to remove these files."
43
+ }
@@ -0,0 +1,52 @@
1
+ #!/bin/sh
2
+ # Find and optionally remove ziniao shims created in PATH directories.
3
+ # Run without --apply first to preview the files that would be removed.
4
+
5
+ apply=0
6
+ if [ "${1:-}" = "--apply" ]; then
7
+ apply=1
8
+ elif [ -n "${1:-}" ]; then
9
+ printf '%s\n' "[ziniao-cli-shims] ERROR: unsupported option: $1" >&2
10
+ exit 2
11
+ fi
12
+
13
+ log() { printf '%s\n' "[ziniao-cli-shims] $*"; }
14
+
15
+ home_dir="${HOME:?HOME is not set}"
16
+ platform="$(uname -s 2>/dev/null || printf 'unknown')"
17
+ case "$platform" in
18
+ Darwin) default_dir="$home_dir/Library/Application Support/ziniaobrowser/cli" ;;
19
+ Linux) default_dir="${XDG_CONFIG_HOME:-$home_dir/.config}/ziniaobrowser/cli" ;;
20
+ *)
21
+ log "ERROR: unsupported operating system: $platform"
22
+ exit 1
23
+ ;;
24
+ esac
25
+
26
+ target_dir="${ZINIAO_APP_CACHE_DIR:-${default_dir%/cli}}/cli"
27
+ found=0
28
+ old_ifs="$IFS"
29
+ IFS=:
30
+ for path_entry in ${PATH:-}; do
31
+ IFS="$old_ifs"
32
+ directory="${path_entry%/}"
33
+ [ -n "$directory" ] || continue
34
+ [ "$directory" = "${target_dir%/}" ] && continue
35
+ shim_path="$directory/ziniao"
36
+ [ -e "$shim_path" ] || [ -L "$shim_path" ] || continue
37
+ found=1
38
+ if [ "$apply" -eq 1 ]; then
39
+ rm -f -- "$shim_path"
40
+ log "Removed: $shim_path"
41
+ else
42
+ log "Would remove: $shim_path"
43
+ fi
44
+ IFS=:
45
+ done
46
+ IFS="$old_ifs"
47
+
48
+ if [ "$found" -eq 0 ]; then
49
+ log "No ziniao shims found in PATH directories."
50
+ elif [ "$apply" -eq 0 ]; then
51
+ log "Preview only. Re-run with --apply to remove these files."
52
+ fi
package/scripts/run.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { existsSync } = require("node:fs");
4
+ const { join } = require("node:path");
5
+ const { spawnSync } = require("node:child_process");
6
+
7
+ const binaryName = process.platform === "win32" ? "ziniao-cli.exe" : "ziniao-cli";
8
+ const binaryPath = join(__dirname, "..", "bin", binaryName);
9
+
10
+ if (!existsSync(binaryPath)) {
11
+ console.error(
12
+ [
13
+ `ziniao-cli binary not found at ${binaryPath}.`,
14
+ "For local package testing, set ZINIAO_CLI_BINARY to a built binary before npm install.",
15
+ "Example: $env:ZINIAO_CLI_BINARY = 'C:\\\\path\\\\to\\\\ziniao-cli.exe'",
16
+ ].join("\n")
17
+ );
18
+ process.exit(1);
19
+ }
20
+
21
+ const result = spawnSync(binaryPath, process.argv.slice(2), {
22
+ stdio: "inherit",
23
+ windowsHide: false,
24
+ });
25
+
26
+ if (result.error) {
27
+ console.error(result.error.message);
28
+ process.exit(1);
29
+ }
30
+
31
+ process.exit(result.status ?? 0);
@@ -0,0 +1,179 @@
1
+ # Synchronize the Ziniao desktop CLI into the per-user AppCache and register
2
+ # that directory in the current user's PATH.
3
+ #
4
+ # Use `. .\scripts\setup-ziniao-cli.ps1` to refresh the current PowerShell.
5
+ # A normal invocation persists PATH but cannot modify its parent PowerShell.
6
+
7
+ $ErrorActionPreference = "Stop"
8
+ $isDotSourced = $MyInvocation.InvocationName -eq "."
9
+
10
+ function Write-Log([string]$Message) {
11
+ Write-Output "[ziniao-cli-path] $Message"
12
+ }
13
+
14
+ $arch = if ([Environment]::Is64BitOperatingSystem) {
15
+ if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE }
16
+ } else { $env:PROCESSOR_ARCHITECTURE }
17
+ switch ($arch.ToLowerInvariant()) {
18
+ "amd64" { $archDir = "x64" }
19
+ "x86_64" { $archDir = "x64" }
20
+ "arm64" { $archDir = "arm64" }
21
+ default { throw "Unsupported CPU architecture: $arch" }
22
+ }
23
+
24
+ $sourceName = "ziniao.exe"
25
+ $sourceRelative = Join-Path "envkit\cli\win32\$archDir" $sourceName
26
+ $resourceRoot = "resources\app.asar.unpacked"
27
+
28
+ $sourcePath = $null
29
+ $guiExecutablePath = $null
30
+ $checkedSourcePaths = @()
31
+ if ($env:ZINIAO_CLI_SOURCE -and (Test-Path -LiteralPath $env:ZINIAO_CLI_SOURCE -PathType Leaf)) {
32
+ $sourcePath = $env:ZINIAO_CLI_SOURCE
33
+ }
34
+ if (-not $sourcePath -and $env:ZINIAO_INSTALL_DIR) {
35
+ $candidate = Join-Path $env:ZINIAO_INSTALL_DIR "$resourceRoot\$sourceRelative"
36
+ $checkedSourcePaths += $candidate
37
+ if (Test-Path -LiteralPath $candidate -PathType Leaf) {
38
+ $sourcePath = $candidate
39
+ }
40
+ }
41
+ if (-not $sourcePath) {
42
+ $uninstallKeys = @(
43
+ "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*",
44
+ "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*",
45
+ "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
46
+ )
47
+ $uninstallEntries = Get-ItemProperty $uninstallKeys -ErrorAction SilentlyContinue |
48
+ Where-Object { $_.DisplayIcon -and $_.DisplayIcon -match "ziniao\.exe" }
49
+ foreach ($entry in $uninstallEntries) {
50
+ $displayIcon = ([string]$entry.DisplayIcon).Trim()
51
+ if ($displayIcon -match '^\s*"([^"]+\.exe)"') {
52
+ $executablePath = $matches[1]
53
+ } elseif ($displayIcon -match '^\s*(.*?\.exe)(?:,\d+)?(?:\s|$)') {
54
+ $executablePath = $matches[1]
55
+ } else {
56
+ continue
57
+ }
58
+ if (-not (Test-Path -LiteralPath $executablePath -PathType Leaf)) { continue }
59
+ $guiExecutablePath = $executablePath
60
+ $installDir = Split-Path -Parent $executablePath
61
+ $candidate = Join-Path $installDir "$resourceRoot\$sourceRelative"
62
+ $checkedSourcePaths += $candidate
63
+ if (Test-Path -LiteralPath $candidate -PathType Leaf) {
64
+ $sourcePath = $candidate
65
+ Write-Log "Found the Ziniao installation from the uninstall registry: $installDir"
66
+ break
67
+ }
68
+ }
69
+ }
70
+ if ($env:ZINIAO_APP_CACHE_DIR) {
71
+ $appCacheDir = $env:ZINIAO_APP_CACHE_DIR
72
+ } else {
73
+ # The Windows GUI installs its CLI under %APPDATA%\ziniaobrowser\cli.
74
+ $appCacheDir = Join-Path $env:APPDATA "ziniaobrowser"
75
+ }
76
+
77
+ $targetDir = Join-Path $appCacheDir "cli"
78
+ $targetPath = Join-Path $targetDir $sourceName
79
+ New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
80
+
81
+ if ($guiExecutablePath) {
82
+ $guiPathFile = Join-Path $env:APPDATA "ziniaobrowser\gui-path"
83
+ try {
84
+ New-Item -ItemType Directory -Path (Split-Path -Parent $guiPathFile) -Force | Out-Null
85
+ [System.IO.File]::WriteAllText(
86
+ $guiPathFile,
87
+ "$guiExecutablePath$([Environment]::NewLine)",
88
+ [System.Text.Encoding]::ASCII
89
+ )
90
+ Write-Log "Wrote GUI path hint: $guiPathFile"
91
+ } catch {
92
+ Write-Log "WARN: Could not write GUI path hint: $($_.Exception.Message)"
93
+ }
94
+ }
95
+
96
+ function Get-FileSha256([string]$Path) {
97
+ $sha256 = [System.Security.Cryptography.SHA256]::Create()
98
+ $stream = [System.IO.File]::OpenRead($Path)
99
+ try {
100
+ return ([BitConverter]::ToString($sha256.ComputeHash($stream))).Replace("-", "")
101
+ } finally {
102
+ $stream.Dispose()
103
+ $sha256.Dispose()
104
+ }
105
+ }
106
+
107
+ if (-not $sourcePath) {
108
+ foreach ($checkedPath in ($checkedSourcePaths | Select-Object -Unique)) {
109
+ Write-Log "Checked CLI source path: $checkedPath"
110
+ }
111
+ if (Test-Path -LiteralPath $targetPath -PathType Leaf) {
112
+ Write-Log "CLI source was not found in the install directory; keeping the existing AppCache CLI: $targetPath"
113
+ } else {
114
+ Write-Log "WARN: ${sourceName} was not found in the install directory; skipping copy. Set `$env:ZINIAO_INSTALL_DIR or `$env:ZINIAO_CLI_SOURCE to override."
115
+ }
116
+ } else {
117
+ $needsCopy = -not (Test-Path -LiteralPath $targetPath -PathType Leaf)
118
+ if (-not $needsCopy) {
119
+ $needsCopy = (Get-FileSha256 $sourcePath) -ne (Get-FileSha256 $targetPath)
120
+ }
121
+ if ($needsCopy) {
122
+ Copy-Item -LiteralPath $sourcePath -Destination $targetPath -Force
123
+ Write-Log "Synced CLI: $sourcePath -> $targetPath"
124
+ } else {
125
+ Write-Log "AppCache CLI is up to date: $targetPath"
126
+ }
127
+ }
128
+
129
+ function Install-PathShim {
130
+ if (-not (Test-Path -LiteralPath $targetPath -PathType Leaf)) { return }
131
+ $windowsAppsDir = Join-Path $env:LOCALAPPDATA "Microsoft\WindowsApps"
132
+ foreach ($pathEntry in @($env:Path -split ";" | Where-Object { $_ })) {
133
+ $normalizedPath = $pathEntry.TrimEnd('\')
134
+ if ($normalizedPath -ieq $windowsAppsDir.TrimEnd('\') -and (Test-Path -LiteralPath $windowsAppsDir -PathType Container)) {
135
+ $shimPath = Join-Path $windowsAppsDir $sourceName
136
+ if (-not (Test-Path -LiteralPath $shimPath)) {
137
+ try {
138
+ Copy-Item -LiteralPath $targetPath -Destination $shimPath -ErrorAction Stop
139
+ $script:shimInstalled = $true
140
+ Write-Log "Created the CLI shim in WindowsApps: $shimPath"
141
+ } catch {
142
+ Write-Log "WARN: Could not create the CLI shim in WindowsApps: $($_.Exception.Message)"
143
+ }
144
+ } else {
145
+ Write-Log "WindowsApps already contains a file named $sourceName; leaving it unchanged"
146
+ }
147
+ return
148
+ }
149
+ if ($normalizedPath -ieq $targetDir.TrimEnd('\')) {
150
+ $script:shimInstalled = $true
151
+ Write-Log "The current PATH already contains the AppCache CLI directory: $targetDir"
152
+ return
153
+ }
154
+ }
155
+ Write-Log "The AppCache CLI directory is not on the current PATH; no shim was created in another directory."
156
+ }
157
+
158
+ $shimInstalled = $false
159
+ Install-PathShim
160
+
161
+ $userPath = [Environment]::GetEnvironmentVariable("Path", "User")
162
+ $entries = @($userPath -split ";" | Where-Object { $_ })
163
+ if (-not ($entries | Where-Object { $_.TrimEnd('\') -ieq $targetDir.TrimEnd('\') })) {
164
+ [Environment]::SetEnvironmentVariable("Path", (($entries + $targetDir) -join ";"), "User")
165
+ Write-Log "Added to the user PATH: $targetDir"
166
+ }
167
+
168
+ $currentEntries = @($env:Path -split ";" | Where-Object { $_ })
169
+ if (-not ($currentEntries | Where-Object { $_.TrimEnd('\') -ieq $targetDir.TrimEnd('\') })) {
170
+ $env:Path = "$targetDir;$env:Path"
171
+ }
172
+ if ($shimInstalled) {
173
+ Write-Log "The CLI is already available on the current PATH; this PowerShell session needs no PATH refresh"
174
+ } elseif ($isDotSourced) {
175
+ Write-Log "The current PowerShell PATH was refreshed"
176
+ } else {
177
+ Write-Log "The current process PATH was refreshed; dot-source this script in the parent PowerShell or reopen the terminal"
178
+ }
179
+ Write-Log "Verification command: Get-Command ziniao"
@@ -0,0 +1,212 @@
1
+ #!/bin/sh
2
+ # Synchronize the Ziniao desktop CLI into the per-user AppCache and register
3
+ # that directory in the current user's shell PATH.
4
+ #
5
+ # Use `source scripts/setup-ziniao-cli.sh` when the current shell should be
6
+ # refreshed immediately. A normal execution can persist PATH but cannot modify
7
+ # its parent shell.
8
+
9
+ log() { printf '%s\n' "[ziniao-cli-path] $*"; }
10
+ fail() { printf '%s\n' "[ziniao-cli-path] ERROR: $*" >&2; return 1 2>/dev/null || exit 1; }
11
+
12
+ is_sourced=0
13
+ if [ -n "${BASH_VERSION:-}" ]; then
14
+ [ "${BASH_SOURCE[0]:-$0}" != "$0" ] && is_sourced=1
15
+ elif [ -n "${ZSH_EVAL_CONTEXT:-}" ]; then
16
+ case "$ZSH_EVAL_CONTEXT" in
17
+ *:file) is_sourced=1 ;;
18
+ esac
19
+ fi
20
+
21
+ if [ "$is_sourced" -eq 0 ]; then
22
+ set -eu
23
+ fi
24
+
25
+ platform="$(uname -s 2>/dev/null || printf 'unknown')"
26
+ machine="$(uname -m 2>/dev/null || printf 'unknown')"
27
+
28
+ case "$platform" in
29
+ Darwin) platform_dir="darwin"; source_name="ziniao"; ;;
30
+ Linux) platform_dir="linux"; source_name="ziniao"; ;;
31
+ MINGW*|MSYS*|CYGWIN*)
32
+ fail "Windows shell detected; use PowerShell: scripts/setup-ziniao-cli.ps1"
33
+ return 1 2>/dev/null || exit 1
34
+ ;;
35
+ *) fail "Unsupported operating system: $platform"; return 1 2>/dev/null || exit 1 ;;
36
+ esac
37
+
38
+ case "$machine" in
39
+ arm64|aarch64) arch_dir="arm64" ;;
40
+ x86_64|amd64) arch_dir="x64" ;;
41
+ *) fail "Unsupported CPU architecture: $machine"; return 1 2>/dev/null || exit 1 ;;
42
+ esac
43
+
44
+ home_dir="${HOME:?HOME is not set}"
45
+
46
+ find_source() {
47
+ if [ -n "${ZINIAO_CLI_SOURCE:-}" ] && [ -f "$ZINIAO_CLI_SOURCE" ]; then
48
+ printf '%s\n' "$ZINIAO_CLI_SOURCE"
49
+ return 0
50
+ fi
51
+
52
+ for candidate_arch in "$arch_dir" x64 arm64; do
53
+ source_rel="envkit/cli/$platform_dir/$candidate_arch/$source_name"
54
+ if [ -n "${ZINIAO_INSTALL_DIR:-}" ]; then
55
+ candidate="$ZINIAO_INSTALL_DIR/$source_rel"
56
+ if [ -f "$candidate" ]; then
57
+ printf '%s\n' "$candidate"
58
+ return 0
59
+ fi
60
+ fi
61
+
62
+ for install_root in \
63
+ "/Applications/ziniao.app/Contents/Resources/app.asar.unpacked" \
64
+ "$home_dir/Applications/ziniao.app/Contents/Resources/app.asar.unpacked" \
65
+ "$home_dir/Library/Application Support/ziniaobrowser/app.asar.unpacked" \
66
+ "/opt/ziniao/resources/app.asar.unpacked"; do
67
+ candidate="$install_root/$source_rel"
68
+ if [ -f "$candidate" ]; then
69
+ printf '%s\n' "$candidate"
70
+ return 0
71
+ fi
72
+ done
73
+ done
74
+ return 1
75
+ }
76
+
77
+ log_source_candidates() {
78
+ [ -n "${ZINIAO_CLI_SOURCE:-}" ] && log "Checked CLI source path: $ZINIAO_CLI_SOURCE"
79
+ for candidate_arch in "$arch_dir" x64 arm64; do
80
+ source_rel="envkit/cli/$platform_dir/$candidate_arch/$source_name"
81
+ [ -n "${ZINIAO_INSTALL_DIR:-}" ] && log "Checked CLI source path: $ZINIAO_INSTALL_DIR/$source_rel"
82
+ for install_root in \
83
+ "/Applications/ziniao.app/Contents/Resources/app.asar.unpacked" \
84
+ "$home_dir/Applications/ziniao.app/Contents/Resources/app.asar.unpacked" \
85
+ "$home_dir/Library/Application Support/ziniaobrowser/app.asar.unpacked" \
86
+ "/opt/ziniao/resources/app.asar.unpacked"; do
87
+ log "Checked CLI source path: $install_root/$source_rel"
88
+ done
89
+ done
90
+ }
91
+
92
+ find_gui_executable() {
93
+ case "$platform_dir" in
94
+ darwin)
95
+ for candidate in \
96
+ "/Applications/ziniao.app/Contents/MacOS/ziniaobrowser" \
97
+ "/Applications/ziniao.app/Contents/MacOS/ziniao" \
98
+ "$home_dir/Applications/ziniao.app/Contents/MacOS/ziniaobrowser" \
99
+ "$home_dir/Applications/ziniao.app/Contents/MacOS/ziniao"; do
100
+ [ -f "$candidate" ] && { printf '%s\n' "$candidate"; return 0; }
101
+ done
102
+ ;;
103
+ linux)
104
+ for candidate in \
105
+ "/opt/ziniao/ziniaobrowser" \
106
+ "/opt/ziniao/ziniao" \
107
+ "/usr/lib/ziniaobrowser/ziniaobrowser" \
108
+ "/usr/lib/ziniaobrowser/ziniao"; do
109
+ [ -f "$candidate" ] && { printf '%s\n' "$candidate"; return 0; }
110
+ done
111
+ ;;
112
+ esac
113
+ return 1
114
+ }
115
+
116
+ if [ -n "${ZINIAO_APP_CACHE_DIR:-}" ]; then
117
+ target_dir="$ZINIAO_APP_CACHE_DIR/cli"
118
+ else
119
+ case "$platform_dir" in
120
+ darwin) target_dir="$home_dir/Library/Application Support/ziniaobrowser/cli" ;;
121
+ *) target_dir="${XDG_CONFIG_HOME:-$home_dir/.config}/ziniaobrowser/cli" ;;
122
+ esac
123
+ fi
124
+
125
+ source_path="$(find_source || true)"
126
+ gui_executable_path="$(find_gui_executable || true)"
127
+ target_path="$target_dir/$source_name"
128
+ mkdir -p "$target_dir"
129
+
130
+ if [ -n "$source_path" ]; then
131
+ log "CLI source path: $source_path"
132
+ else
133
+ log_source_candidates
134
+ fi
135
+
136
+ if [ -n "$gui_executable_path" ]; then
137
+ case "$platform_dir" in
138
+ darwin) gui_path_file="$home_dir/Library/Application Support/ziniaobrowser/gui-path" ;;
139
+ *)
140
+ xdg_config_home="${XDG_CONFIG_HOME:-$home_dir/.config}"
141
+ gui_path_file="$xdg_config_home/ziniaobrowser/gui-path"
142
+ ;;
143
+ esac
144
+ if mkdir -p "$(dirname "$gui_path_file")" && printf '%s\n' "$gui_executable_path" > "$gui_path_file"; then
145
+ log "Wrote GUI path hint: $gui_path_file"
146
+ else
147
+ log "WARN: Could not write GUI path hint: $gui_path_file"
148
+ fi
149
+ fi
150
+ if [ -z "$source_path" ]; then
151
+ if [ -f "$target_path" ]; then
152
+ log "CLI source was not found in the install directory; keeping the existing AppCache CLI: $target_path"
153
+ else
154
+ log "WARN: ${source_name} was not found in the install directory; skipping copy. Set ZINIAO_INSTALL_DIR or ZINIAO_CLI_SOURCE to override."
155
+ fi
156
+ elif [ ! -f "$target_path" ] || ! cmp -s "$source_path" "$target_path"; then
157
+ cp "$source_path" "$target_path"
158
+ chmod 755 "$target_path"
159
+ log "Synced CLI: $source_path -> $target_path"
160
+ else
161
+ log "AppCache CLI is up to date: $target_path"
162
+ fi
163
+
164
+ shim_installed=0
165
+ install_path_shim() {
166
+ [ -f "$target_path" ] || return 0
167
+ case ":${PATH:-}:" in
168
+ *:"$target_dir":*)
169
+ shim_installed=1
170
+ log "The current PATH already contains the AppCache CLI directory: $target_dir"
171
+ ;;
172
+ *)
173
+ log "The AppCache CLI directory is not on the current PATH; no shim will be created elsewhere: $target_dir"
174
+ ;;
175
+ esac
176
+ }
177
+
178
+ install_path_shim
179
+
180
+ export PATH="$target_dir${PATH:+:$PATH}"
181
+
182
+ profile=""
183
+ case "${SHELL:-}" in
184
+ */zsh) profile="$home_dir/.zshrc" ;;
185
+ */bash) profile="$home_dir/.bashrc" ;;
186
+ esac
187
+ if [ -z "$profile" ]; then
188
+ case "$platform_dir" in
189
+ darwin) profile="$home_dir/.zshrc" ;;
190
+ *) profile="$home_dir/.profile" ;;
191
+ esac
192
+ fi
193
+
194
+ marker="# ziniao-cli: AppCache CLI PATH"
195
+ path_line="export PATH=\"$target_dir:\$PATH\""
196
+ if ! grep -Fqx "$marker" "$profile" 2>/dev/null; then
197
+ mkdir -p "$(dirname "$profile")"
198
+ printf '\n%s\n' "$marker" >> "$profile"
199
+ log "Added to shell profile: $profile"
200
+ fi
201
+ if ! grep -Fqx "$path_line" "$profile" 2>/dev/null; then
202
+ printf '%s\n' "$path_line" >> "$profile"
203
+ fi
204
+
205
+ if [ "$shim_installed" -eq 1 ]; then
206
+ log "The CLI is already on the current PATH; this terminal needs no PATH refresh"
207
+ elif [ "$is_sourced" -eq 1 ]; then
208
+ log "The current shell PATH was refreshed"
209
+ else
210
+ log "The current process PATH was refreshed; source \"$profile\" in the parent terminal"
211
+ fi
212
+ log "Verification command: command -v $source_name"