pi-codex-marketplace 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -3
- package/bin/pi-codex-marketplace.js +12 -0
- package/bin/ts-resolver.mjs +15 -0
- package/package.json +6 -2
- package/src/bridge/command.ts +45 -12
- package/src/cli/index.ts +85 -0
package/README.md
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Bridge Package for Codex and Claude Marketplace compatibility in Pi (`Pi 0.84.2`).
|
|
4
4
|
|
|
5
|
-
> **One-line:** `pi install npm:pi-codex-marketplace` → `/codex-marketplace add <本機資料夾|GitHub
|
|
5
|
+
> **One-line:** `pi install npm:pi-codex-marketplace` → `/codex-marketplace add <本機資料夾|GitHub 網址>`(或 Shell 下 `npx pi-codex-marketplace add ...`)註冊 marketplace → `install <編號>` 裝到當下最新並在 Pi 可用。
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
+
### Pi Extension(TUI / 互動環境)
|
|
10
|
+
|
|
9
11
|
```bash
|
|
10
12
|
pi install npm:pi-codex-marketplace # Global package (writes to ~/.pi/agent/settings.json)
|
|
11
13
|
pi install npm:pi-codex-marketplace -l # Project package location (.pi/settings.json; Pi host package setting only)
|
|
@@ -19,6 +21,17 @@ pi list # List installed packages
|
|
|
19
21
|
pi config # Enable/disable resources
|
|
20
22
|
```
|
|
21
23
|
|
|
24
|
+
### Headless CLI(Shell / CI/CD 自動化)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 免安裝直接以 npx 執行
|
|
28
|
+
npx pi-codex-marketplace [subcommand]
|
|
29
|
+
|
|
30
|
+
# 或全域安裝使用
|
|
31
|
+
npm install -g pi-codex-marketplace
|
|
32
|
+
pi-codex-marketplace [subcommand]
|
|
33
|
+
```
|
|
34
|
+
|
|
22
35
|
Source types follow `docs/packages.md`: `npm:` for registry, `git:`/`https://` for git, and absolute/relative paths for local. Ephemeral runs use `pi -e <source>` (not `pi install -e`). This package declares a single `pi` extension entry (`extensions/pi/index.ts`) loaded via `jiti` and requires no build step.
|
|
23
36
|
|
|
24
37
|
> [!NOTE]
|
|
@@ -115,6 +128,61 @@ Credentialed Acquisition 語意(安全線):
|
|
|
115
128
|
- 憑證由 SSH agent 提供,整個取得過程不互動(`BatchMode=yes`)、無任何提示;
|
|
116
129
|
- SSH 定位器本身仍維持 credential-free:不得內嵌密碼(`user:pass@` 拒絕);憑證只能經由 SSH agent 或 Credentialed Acquisition 到達取得流程。
|
|
117
130
|
|
|
131
|
+
## Headless Bridge CLI — `pi-codex-marketplace`
|
|
132
|
+
|
|
133
|
+
Bridge Package 除了提供 Pi Extension TUI 指令,亦內建純 Node 輕量 CLI bin shim(`pi-codex-marketplace` / `npx pi-codex-marketplace`),專為 CI/CD 流程、自動化腳本與純 Shell 環境設計。無須啟動 Pi TUI 或 Pi runtime,即可直接管理 Marketplace 註冊與外掛安裝。
|
|
134
|
+
|
|
135
|
+
### 執行方式
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# 1. 無參數:總覽(列出已註冊 Marketplace、已安裝外掛與指令用法)
|
|
139
|
+
npx pi-codex-marketplace
|
|
140
|
+
|
|
141
|
+
# 2. 版本與說明
|
|
142
|
+
npx pi-codex-marketplace --version # 或 -v
|
|
143
|
+
npx pi-codex-marketplace help
|
|
144
|
+
|
|
145
|
+
# 3. 執行子命令(與 TUI 指令完全一致)
|
|
146
|
+
npx pi-codex-marketplace add <路徑|網址>
|
|
147
|
+
npx pi-codex-marketplace list [名稱]
|
|
148
|
+
npx pi-codex-marketplace install <編號|名稱>
|
|
149
|
+
npx pi-codex-marketplace update
|
|
150
|
+
npx pi-codex-marketplace disable <名稱>
|
|
151
|
+
npx pi-codex-marketplace enable <名稱>
|
|
152
|
+
npx pi-codex-marketplace remove <名稱>
|
|
153
|
+
npx pi-codex-marketplace forget <名稱>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 九個子命令一覽
|
|
157
|
+
|
|
158
|
+
| 子命令 | 行為 |
|
|
159
|
+
|--------|------|
|
|
160
|
+
| `add <路徑\|網址>` | 註冊 marketplace(本機資料夾、GitHub 完整網址、`owner/repo` 簡寫或 SSH 定位器),自動偵測並回報格式。重複來源拒絕。私有 HTTPS repo 支援固定白名單自動偵測與 `PI_CODEX_MARKETPLACE_CREDENTIAL_HELPERS` 環境變數。 |
|
|
161
|
+
| `list [名稱]` | 列出 plugins(編號/所屬 marketplace/狀態:可安裝・已裝啟用・已裝停用・unavailable+原因),支援 marketplace 名稱過濾。 |
|
|
162
|
+
| `install <編號\|名稱>` | 裝到當下最新並自動啟用。重複安裝=重抓最新覆寫(重裝=更新)。衝突時提示同名未投影警告。 |
|
|
163
|
+
| `update` | 對全部已註冊 marketplace 重抓最新並更新有變化的外掛。 |
|
|
164
|
+
| `disable <名稱>` / `enable <名稱>` | 停用/啟用 plugin。 |
|
|
165
|
+
| `remove <名稱>` | 移除單支 plugin(不動 marketplace 與來源資料)。 |
|
|
166
|
+
| `forget <名稱>` | 移除整個 marketplace 及其全部安裝。 |
|
|
167
|
+
| `help` | 輸出子命令清單與用法。 |
|
|
168
|
+
|
|
169
|
+
### 輸出與退出代碼契約(Output & Exit Contract)
|
|
170
|
+
|
|
171
|
+
CLI 遵循嚴格的標準命令列契約,確保 CI/CD 與腳本整合的確定性:
|
|
172
|
+
|
|
173
|
+
- **標準輸出(stdout)**:所有正常操作輸出(總覽、說明、版本、查詢清單、成功訊息)寫入 `stdout`,退出代碼為 `0`。
|
|
174
|
+
- **標準錯誤(stderr)**:所有錯誤情況(未知子命令、缺少或非法參數、重複註冊、目標不存在或歧義、Git 取得失敗等)寫入 `stderr`,退出代碼為非零(`1`)。
|
|
175
|
+
- **絕對免提示(Never prompt)**:全流程不發起任何終端互動提示(no interactive prompts),遇到歧義或錯誤立即明確報錯並退出,背景執行安全無虞。
|
|
176
|
+
|
|
177
|
+
### 狀態生效時機(Same-State Caveat)
|
|
178
|
+
|
|
179
|
+
- **單一 Global Scope 一致性**:CLI 與 Extension 共用相同的 `getAgentDir()` 與 Bridge State 儲存位址(`~/.pi/agent/codex-marketplace/state.json`),完全支援 `PI_CODING_AGENT_DIR` / `PI_AGENT_DIR` 環境變數覆寫。
|
|
180
|
+
- **無 In-process Reload 提示語轉換**:CLI 執行於 Pi 外部獨立 Node 行程,無 Pi runtime 內部的即時 reload 機制(`ctx.reload`)。狀態變更指令(`install`、`enable`、`update`)輸出將 TUI 的「已重新載入生效」替換為:
|
|
181
|
+
```
|
|
182
|
+
已寫入 Bridge State · 下次 pi session//reload 生效
|
|
183
|
+
```
|
|
184
|
+
- **投影時機**:CLI 寫入的變更已持久化至 Bridge State,將在下次啟動 Pi session 或於 Pi TUI 內執行 `/reload` 時,經由 Pi host 資源發現接縫(`resources_discover`)自動完成技能投影。
|
|
185
|
+
|
|
118
186
|
## Bridge State storage
|
|
119
187
|
|
|
120
188
|
Bridge State 是唯一權威,存於**單一 Global Scope 文件** `{getAgentDir()}/codex-marketplace/state.json`(`~/.pi/agent/codex-marketplace/state.json`):
|
|
@@ -140,7 +208,7 @@ See `src/bridge/state.ts`(Minimal Bridge State)、`src/bridge-state/atomic.t
|
|
|
140
208
|
| OS | **macOS**, **Linux** | Windows not supported (path containment, symlink, `flock` semantics are POSIX-only) |
|
|
141
209
|
| Node | **>=22.19.0** | `engines.node` enforced |
|
|
142
210
|
| Pi host | **0.84.2** | `peerDependencies` exact `0.84.2`; expected compatible range `^0.84.2` (devDeps). `pi-ai`/`pi-tui` peers `*` per Pi extension docs. |
|
|
143
|
-
| Semantics | `pi install` / `pi -e` / `pi install -l` / `pi update` / `pi remove` / `pi list` / `pi config` | Single `pi` extension package; `files` ships `extensions/`, `src/`, `README.md`, `LICENSE` only |
|
|
211
|
+
| Semantics | `pi install` / `pi -e` / `pi install -l` / `pi update` / `pi remove` / `pi list` / `pi config` + `npx pi-codex-marketplace` / `bin/pi-codex-marketplace.js` CLI | Single `pi` extension package + headless CLI bin; `files` ships `bin/`, `extensions/`, `src/`, `README.md`, `LICENSE` only |
|
|
144
212
|
|
|
145
213
|
Peer declaration (dual): **精確 `0.84.2`** in `peerDependencies` (exact host that this version was validated against) + **預期 `^0.84.2`** in `devDependencies` (range expected to remain compatible). `pi-ai` and `pi-tui` remain `*` because they are bundled by Pi.
|
|
146
214
|
|
|
@@ -159,7 +227,7 @@ Every row is a **release blocker**: `v*` may not publish unless the full matrix
|
|
|
159
227
|
| unit — 縫層 | `runCommand` 指令分派(add/list/install/update/disable/enable/remove/forget/help、重複註冊拒絕、重裝覆寫、衝突未投影清單、corrupt→重置、unavailable 顯示、git 重抓)、Minimal Bridge State 原子持久化 |
|
|
160
228
|
| unit — 低層 | 雙格式 catalog 解析(codex+claude、open 政策、unavailable entry)、git locator/source-key(fixed `default` selector)、contained path/symlink、collision、投影(exposure)、source-cache(store/hit/LRU/pin/flock)、git acquisition(mock executor) |
|
|
161
229
|
| integration | 真 Pi 縫:extension 註冊 `resources_discover` → 投影 skillPaths(startup/reload 一致、trust flag 無關、被動不變異) |
|
|
162
|
-
| E2E | `/codex-marketplace` 薄 Pi adapter
|
|
230
|
+
| E2E | `/codex-marketplace` 薄 Pi adapter 與 `pi-codex-marketplace` CLI 適配層(overview/help、--version、雙格式 add/list、install/update、lifecycle 停用啟用移除、狀態變更 reload 提示轉換、確定性退出碼與環境隔離) |
|
|
163
231
|
|
|
164
232
|
Fixtures: `tests/fixtures/synthetic/`, `tests/fixtures/pinned/`(captured `SamWang32191/codex-plugins@98e78ca` snapshot)、`tests/fixtures/adversarial/`。See `tests/acceptance/` for the matrix runner that enforces per-row gating (any row failure blocks publish).
|
|
165
233
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { register } from "node:module";
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
register("./ts-resolver.mjs", import.meta.url);
|
|
6
|
+
} catch {
|
|
7
|
+
// Module registration unsupported or already active
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { runCli } = await import("../src/cli/index.js");
|
|
11
|
+
const code = await runCli(process.argv.slice(2), process);
|
|
12
|
+
process.exitCode = code;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
2
|
+
try {
|
|
3
|
+
return await nextResolve(specifier, context);
|
|
4
|
+
} catch (err) {
|
|
5
|
+
if (specifier.endsWith(".js")) {
|
|
6
|
+
const tsSpecifier = specifier.slice(0, -3) + ".ts";
|
|
7
|
+
try {
|
|
8
|
+
return await nextResolve(tsSpecifier, context);
|
|
9
|
+
} catch {
|
|
10
|
+
// Fall back to original error
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
throw err;
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-codex-marketplace",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Bridge Package for Codex and Claude Marketplace compatibility in Pi — 極簡 /codex-marketplace
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Bridge Package for Codex and Claude Marketplace compatibility in Pi — 極簡 /codex-marketplace 純文字指令與 Headless CLI(add/list/install/update/disable/enable/remove/forget)、單一 Global Bridge State、當下最新安裝與即時投影",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=22.19.0"
|
|
22
22
|
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"pi-codex-marketplace": "./bin/pi-codex-marketplace.js"
|
|
25
|
+
},
|
|
23
26
|
"pi": {
|
|
24
27
|
"extensions": [
|
|
25
28
|
"./extensions/pi/index.ts"
|
|
@@ -56,6 +59,7 @@
|
|
|
56
59
|
"prepublishOnly": "npm run typecheck && npm test"
|
|
57
60
|
},
|
|
58
61
|
"files": [
|
|
62
|
+
"bin",
|
|
59
63
|
"extensions",
|
|
60
64
|
"src",
|
|
61
65
|
"README.md",
|
package/src/bridge/command.ts
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
|
-
import { basename, isAbsolute, join, resolve } from 'node:path';
|
|
9
|
+
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
10
10
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
11
12
|
|
|
12
13
|
import { parseFrontmatter } from '@earendil-works/pi-coding-agent';
|
|
13
14
|
|
|
@@ -59,6 +60,17 @@ export interface CommandResult {
|
|
|
59
60
|
output: string;
|
|
60
61
|
reload: boolean;
|
|
61
62
|
stateReset?: boolean;
|
|
63
|
+
ok: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getPackageVersion(): string {
|
|
67
|
+
try {
|
|
68
|
+
const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../package.json');
|
|
69
|
+
const pkgJson = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
70
|
+
return pkgJson.version ?? '0.0.0';
|
|
71
|
+
} catch {
|
|
72
|
+
return '0.0.0';
|
|
73
|
+
}
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
const USAGE_LINE = '用法:/codex-marketplace <add|list|install|update|disable|enable|remove|forget|help>';
|
|
@@ -495,7 +507,7 @@ export async function runCommand(
|
|
|
495
507
|
} catch (e) {
|
|
496
508
|
const msg = e instanceof Error ? e.message : String(e);
|
|
497
509
|
const messages = [`錯誤:讀取 Bridge State 失敗:${msg}`];
|
|
498
|
-
return { messages, lines: messages, output: messages.join('\n\n'), reload: false, stateReset: false };
|
|
510
|
+
return { messages, lines: messages, output: messages.join('\n\n'), reload: false, stateReset: false, ok: false };
|
|
499
511
|
}
|
|
500
512
|
|
|
501
513
|
const messages: string[] = [];
|
|
@@ -513,6 +525,12 @@ export async function runCommand(
|
|
|
513
525
|
const subargs = rawArgs.slice(1);
|
|
514
526
|
|
|
515
527
|
switch (subcmd) {
|
|
528
|
+
case '--version':
|
|
529
|
+
case '-v':
|
|
530
|
+
case 'version': {
|
|
531
|
+
messages.push(getPackageVersion());
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
516
534
|
case 'help':
|
|
517
535
|
case '-h':
|
|
518
536
|
case '--help': {
|
|
@@ -732,13 +750,17 @@ export async function runCommand(
|
|
|
732
750
|
const filteredRegs = state.registrations.filter(
|
|
733
751
|
(r) => r.marketplaceName === filter || r.alias === filter || r.id === filter,
|
|
734
752
|
);
|
|
735
|
-
if (filteredRegs.length === 0
|
|
753
|
+
if (filteredRegs.length === 0) {
|
|
736
754
|
messages.push(`找不到 marketplace "${filter}"`);
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
755
|
+
if (state.registrations.length > 0) {
|
|
756
|
+
// Still show all for discoverability
|
|
757
|
+
messages.push(...formatOverview(state));
|
|
758
|
+
const pluginLines = formatPluginListLines(state, undefined, opts);
|
|
759
|
+
if (pluginLines.length > 0) messages.push(pluginLines.join('\n'));
|
|
760
|
+
} else {
|
|
761
|
+
messages.push(USAGE_LINE);
|
|
762
|
+
}
|
|
763
|
+
} else {
|
|
742
764
|
// Show filtered overview (reuse format but only filtered regs)
|
|
743
765
|
const filteredState: MinimalBridgeState = {
|
|
744
766
|
...state,
|
|
@@ -748,10 +770,6 @@ export async function runCommand(
|
|
|
748
770
|
messages.push(...formatOverview(filteredState));
|
|
749
771
|
const pluginLines = formatPluginListLines(state, filter, opts);
|
|
750
772
|
if (pluginLines.length > 0) messages.push(pluginLines.join('\n'));
|
|
751
|
-
} else {
|
|
752
|
-
// No registrations at all
|
|
753
|
-
messages.push('尚無可列出的 plugin 或 marketplace。');
|
|
754
|
-
messages.push(USAGE_LINE);
|
|
755
773
|
}
|
|
756
774
|
} else {
|
|
757
775
|
if (state.registrations.length === 0 && state.installations.length === 0) {
|
|
@@ -1274,11 +1292,26 @@ export async function runCommand(
|
|
|
1274
1292
|
}
|
|
1275
1293
|
}
|
|
1276
1294
|
|
|
1295
|
+
const ok = !messages.some(
|
|
1296
|
+
(m) =>
|
|
1297
|
+
m.startsWith('錯誤:') ||
|
|
1298
|
+
m.startsWith('未知子命令') ||
|
|
1299
|
+
m.startsWith('用法:/codex-marketplace add') ||
|
|
1300
|
+
m.startsWith('用法:/codex-marketplace install') ||
|
|
1301
|
+
m.startsWith('用法:/codex-marketplace disable') ||
|
|
1302
|
+
m.startsWith('用法:/codex-marketplace enable') ||
|
|
1303
|
+
m.startsWith('用法:/codex-marketplace remove') ||
|
|
1304
|
+
m.startsWith('用法:/codex-marketplace forget') ||
|
|
1305
|
+
m.startsWith('已註冊過相同來源') ||
|
|
1306
|
+
m.startsWith('找不到 marketplace'),
|
|
1307
|
+
);
|
|
1308
|
+
|
|
1277
1309
|
return {
|
|
1278
1310
|
messages,
|
|
1279
1311
|
lines: messages,
|
|
1280
1312
|
output: messages.join('\n\n'),
|
|
1281
1313
|
reload,
|
|
1282
1314
|
stateReset: wasReset,
|
|
1315
|
+
ok,
|
|
1283
1316
|
};
|
|
1284
1317
|
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Adapter — Pure headless command-line entry seam for pi-codex-marketplace (#132, #133)
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - runCli(argv, io, opts): Pure adapter driving runCommand with stdout/stderr/exit code contract
|
|
6
|
+
* - formatCliOutput(result): Formats CommandResult for CLI output, replacing in-process reload notices
|
|
7
|
+
* - getPackageVersion(): Retrieves package version from package.json
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { runCommand, getPackageVersion, type CommandOptions, type CommandResult } from "../bridge/command.js";
|
|
11
|
+
|
|
12
|
+
export { getPackageVersion };
|
|
13
|
+
|
|
14
|
+
export interface CliIO {
|
|
15
|
+
stdout?: { write: (chunk: string) => unknown } | ((chunk: string) => unknown);
|
|
16
|
+
stderr?: { write: (chunk: string) => unknown } | ((chunk: string) => unknown);
|
|
17
|
+
exit?: (code: number) => unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const RELOAD_NOTICE = "已寫入 Bridge State · 下次 pi session//reload 生效";
|
|
21
|
+
export const RELOAD_PLACEHOLDER = "已重新載入生效";
|
|
22
|
+
|
|
23
|
+
export function formatCliOutput(result: CommandResult): string {
|
|
24
|
+
let text = result.output;
|
|
25
|
+
if (result.reload) {
|
|
26
|
+
if (text.includes(RELOAD_PLACEHOLDER)) {
|
|
27
|
+
text = text.replaceAll(RELOAD_PLACEHOLDER, RELOAD_NOTICE);
|
|
28
|
+
} else {
|
|
29
|
+
text = text.length > 0 ? `${text}\n\n${RELOAD_NOTICE}` : RELOAD_NOTICE;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return text;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeStream(
|
|
36
|
+
target: { write: (chunk: string) => unknown } | ((chunk: string) => unknown) | undefined,
|
|
37
|
+
message: string,
|
|
38
|
+
): void {
|
|
39
|
+
if (!target) return;
|
|
40
|
+
const chunk = message.endsWith("\n") ? message : `${message}\n`;
|
|
41
|
+
if (typeof target === "function") {
|
|
42
|
+
target(chunk);
|
|
43
|
+
} else if (typeof target.write === "function") {
|
|
44
|
+
target.write(chunk);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function runCli(
|
|
49
|
+
argv: string[],
|
|
50
|
+
io: CliIO = process,
|
|
51
|
+
opts: CommandOptions = {},
|
|
52
|
+
): Promise<number> {
|
|
53
|
+
const exitWith = (code: number): number => {
|
|
54
|
+
if (typeof io.exit === "function") {
|
|
55
|
+
io.exit(code);
|
|
56
|
+
}
|
|
57
|
+
return code;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v" || argv[0] === "version")) {
|
|
62
|
+
writeStream(io.stdout, getPackageVersion());
|
|
63
|
+
return exitWith(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const result = await runCommand(argv, opts);
|
|
67
|
+
const output = formatCliOutput(result);
|
|
68
|
+
|
|
69
|
+
if (result.ok) {
|
|
70
|
+
if (output) {
|
|
71
|
+
writeStream(io.stdout, output);
|
|
72
|
+
}
|
|
73
|
+
return exitWith(0);
|
|
74
|
+
} else {
|
|
75
|
+
if (output) {
|
|
76
|
+
writeStream(io.stderr, output);
|
|
77
|
+
}
|
|
78
|
+
return exitWith(1);
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
82
|
+
writeStream(io.stderr, `錯誤:${msg}`);
|
|
83
|
+
return exitWith(1);
|
|
84
|
+
}
|
|
85
|
+
}
|