@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 +130 -0
- package/package.json +29 -0
- package/scripts/install.js +306 -0
- package/scripts/remove-ziniao-cli-shims.ps1 +43 -0
- package/scripts/remove-ziniao-cli-shims.sh +52 -0
- package/scripts/run.js +31 -0
- package/scripts/setup-ziniao-cli.ps1 +179 -0
- package/scripts/setup-ziniao-cli.sh +212 -0
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"
|