pi-codex-marketplace 0.7.0 → 1.0.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 +38 -175
- 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
|
@@ -1,189 +1,52 @@
|
|
|
1
1
|
# pi-codex-marketplace
|
|
2
2
|
|
|
3
|
-
Bridge Package for Codex and Claude Marketplace compatibility in Pi (`Pi 0.84.2
|
|
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
|
+
## Quickstart
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
11
|
-
pi install npm:pi-codex-marketplace
|
|
12
|
-
|
|
13
|
-
pi install ./path/to/pi-codex-marketplace -l # Local path, Project package location
|
|
14
|
-
pi -e npm:pi-codex-marketplace # Ephemeral try without installing (temporary)
|
|
15
|
-
pi update npm:pi-codex-marketplace # Update one package
|
|
16
|
-
pi update --all # Update pi + all packages
|
|
17
|
-
pi remove npm:pi-codex-marketplace # Remove package
|
|
18
|
-
pi list # List installed packages
|
|
19
|
-
pi config # Enable/disable resources
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
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.
|
|
10
|
+
# 1. 安裝(擇一表面)
|
|
11
|
+
pi install npm:pi-codex-marketplace # Pi Extension(TUI/互動環境)
|
|
12
|
+
npm install -g pi-codex-marketplace # Headless CLI(Shell/CI;亦可 npx 免安裝)
|
|
23
13
|
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
# 2. 註冊 marketplace
|
|
15
|
+
/codex-marketplace add <本機資料夾|GitHub 網址> # TUI 內
|
|
16
|
+
npx pi-codex-marketplace add <本機資料夾|GitHub 網址> # Shell 下
|
|
26
17
|
|
|
27
|
-
|
|
18
|
+
# 3. 裝到當下最新並自動啟用
|
|
19
|
+
/codex-marketplace install <編號|名稱>
|
|
20
|
+
```
|
|
28
21
|
|
|
29
|
-
|
|
22
|
+
Requirements: **Pi 0.84.2** · **Node >=22.19.0** · **macOS / Linux**(Windows not supported)。完整安裝方式見 [docs/installation.md](./docs/installation.md)。
|
|
30
23
|
|
|
31
|
-
|
|
24
|
+
## 子命令速查
|
|
32
25
|
|
|
33
|
-
|
|
34
|
-
/codex-marketplace
|
|
35
|
-
/codex-marketplace help
|
|
36
|
-
```
|
|
26
|
+
TUI `/codex-marketplace` 與 Headless CLI `pi-codex-marketplace` 共用同一組九個子命令(無參數=總覽):
|
|
37
27
|
|
|
38
28
|
| 子命令 | 行為 |
|
|
39
29
|
|--------|------|
|
|
40
|
-
| `add <路徑\|網址>` | 註冊 marketplace(本機資料夾、GitHub
|
|
41
|
-
| `list [名稱]` | 列出 plugins(編號/所屬 marketplace
|
|
42
|
-
| `install <編號\|名稱>` | 裝到**當下最新**並自動啟用+reload
|
|
43
|
-
| `update` |
|
|
44
|
-
| `disable <名稱>` / `enable <名稱>` | 停用/啟用 plugin
|
|
45
|
-
| `remove <名稱>` | 移除單支 plugin
|
|
46
|
-
| `forget <名稱>` | 移除整個 marketplace
|
|
47
|
-
| `help` |
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
| 子命令 | 候選範圍 | 歧義處理 |
|
|
65
|
-
|--------|----------|----------|
|
|
66
|
-
| `install` | 可安裝/可重裝的 plugin(**不含 Unavailable Entry**) | 名稱在完整 enumeration 唯一=插入名稱;同名(含 unavailable sibling)=插入 enumeration 編號(`#N`),描述顯示 `[marketplace]` 與狀態 |
|
|
67
|
-
| `enable` | 僅**已停用**的 Installation | 名稱無法唯一解析的記錄不給候選 |
|
|
68
|
-
| `disable` | 僅**已啟用**的 Installation | 同上 |
|
|
69
|
-
| `remove` | 全部已安裝 plugin(不分啟用/停用) | 同上 |
|
|
70
|
-
| `list` | Marketplace Registrations | 名稱無法唯一解析=依序改插唯一可解析的 alias、其次 Registration id |
|
|
71
|
-
| `forget` | Marketplace Registrations | 同上 |
|
|
72
|
-
| `add` | **不提供 Bridge 候選**:Tab 委派 Pi 原生路徑 completion,Git locator 維持自由輸入 | — |
|
|
73
|
-
|
|
74
|
-
補完只提議當下可執行的動作,候選反映最新 Bridge State,且**被動讀取**——按 Tab 絕不會重置或重寫損壞的 state 文件。其餘輸入(其他 slash 指令、一般文字、檔案/路徑補完)一律原樣委派 Pi 既有 provider;安裝本套件不影響任何其他指令的 autocomplete。
|
|
75
|
-
|
|
76
|
-
> 沒有 custom TUI、沒有自動第二層 selector:所有操作也都可以照舊以純文字輸入完成,autocomplete 只是 discoverability 與輸入效率層。
|
|
77
|
-
|
|
78
|
-
### 私有 Git repo:Credentialed Acquisition(核准式取得)
|
|
79
|
-
|
|
80
|
-
對私有 HTTPS repo,`add`/`update` **開箱即用**:預設會自動偵測本機已存在的憑證來源並逐次核准(**固定白名單**,不讀本機 gitconfig 的任意 helper;偵測結果只限該次呼叫、永不持久化):
|
|
81
|
-
|
|
82
|
-
| 偵測來源 | 核准的 credential helper |
|
|
83
|
-
|---|---|
|
|
84
|
-
| `gh` CLI 已登入(`gh auth status` 成功) | `!gh auth git-credential` |
|
|
85
|
-
| macOS 原生鑰匙圈 helper 存在 | `osxkeychain` |
|
|
86
|
-
| git `credential-store` 存在 | `store`(明文憑證檔,請知悉風險) |
|
|
87
|
-
|
|
88
|
-
已註冊或欲手動控制時,以逗號分隔設定環境變數——**顯式設定完全覆蓋自動偵測**(逐次生效、永不持久化,`add` 與 `update` 共用同一核准來源):
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
PI_CODEX_MARKETPLACE_CREDENTIAL_HELPERS='store, !f() { echo "username=${GITHUB_USER}"; echo "password=${GITHUB_TOKEN}"; }; f'
|
|
92
|
-
/codex-marketplace add https://github.com/acme/private-mkt
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Credentialed Acquisition 語意(安全線):
|
|
96
|
-
|
|
97
|
-
- 預設(未設 env)=自動偵測固定白名單(gh/osxkeychain/store),僅限該次呼叫:**不寫入** Bridge State、設定檔或任何持久化位置;偵測到的 helper 以命令列 `credential.helper=` 傳入,gitconfig 的其他 helper(含 URL-scoped)仍被排除。
|
|
98
|
-
- 設定 env 後完全覆蓋:值為 git `credential.helper` 字串,逗號分隔、各項 trim、空項目忽略;未設定且偵測無結果=無任何 helper(行為與 credential-free 完全相同,安全線其餘禁制不變)。
|
|
99
|
-
- 憑證與核准清單**永不**進入指令輸出、Bridge State、Canonical Git Locator(定位器)、Validation Snapshot(快照)或 cache identity(快取身份)——取得流程的 identity 判定與憑證完全無關。
|
|
100
|
-
- 自動偵測或已核准的 helper 仍被遠端拒絕(401)時,錯誤訊息提示檢查登入(`gh auth status`/keychain)或設環境變數核准其他 helper;本機偵測不到任何憑證來源時提示設環境變數或改用 SSH。
|
|
101
|
-
- 設定的 helper 名稱無效時(例如直接寫 `gh`——它不是 git 原生的 credential helper 執行檔),錯誤訊息指出正確寫法:原生 helper 名稱(`osxkeychain`/`store`)或 shell form(`!gh auth git-credential`)。
|
|
102
|
-
|
|
103
|
-
#### SSH 定位器:私有 repo 的替代路徑
|
|
104
|
-
|
|
105
|
-
私有 repo 可以完全繞過此環境變數,直接用 SSH 定位器註冊(HTTPS 與 SSH 同屬允許的 credential-free 定位器):
|
|
106
|
-
|
|
107
|
-
```
|
|
108
|
-
/codex-marketplace add git@github.com:acme/private-mkt # scp-like 簡寫(canonical:ssh://git@github.com/acme/private-mkt)
|
|
109
|
-
/codex-marketplace add ssh://git@github.com/acme/private-mkt
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
前提(與既有 Acquisition Trust Base 一致):
|
|
113
|
-
|
|
114
|
-
- host key 必須**預先存在** `~/.ssh/known_hosts`——安全線以 `StrictHostKeyChecking=yes` 只信任既有 host key,遇到未知或變更的主機金鑰直接拒絕,不會提示接受;
|
|
115
|
-
- 憑證由 SSH agent 提供,整個取得過程不互動(`BatchMode=yes`)、無任何提示;
|
|
116
|
-
- SSH 定位器本身仍維持 credential-free:不得內嵌密碼(`user:pass@` 拒絕);憑證只能經由 SSH agent 或 Credentialed Acquisition 到達取得流程。
|
|
117
|
-
|
|
118
|
-
## Bridge State storage
|
|
119
|
-
|
|
120
|
-
Bridge State 是唯一權威,存於**單一 Global Scope 文件** `{getAgentDir()}/codex-marketplace/state.json`(`~/.pi/agent/codex-marketplace/state.json`):
|
|
121
|
-
|
|
122
|
-
```jsonc
|
|
123
|
-
{
|
|
124
|
-
"schemaVersion": 1, // 固定,永不遷移
|
|
125
|
-
"registrations": [], // immutable Registration ID = UUIDv4;sourceKind: "local" | "git";git 帶 snapshot fingerprint(cache 位址)
|
|
126
|
-
"installations": [] // Installed Plugins(enabled/disabled),含 manifestName、skills 與 snapshot(git)
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
- **重建=重置**:壞檔或不認識的格式一律重置為空、重新註冊重裝;沒有 repair、沒有 migration、沒有 State Revision。
|
|
131
|
-
- 寫入防護:`write-to-temp → fsync → rename`(原子)+ 檔案鎖(`.lock` sibling,last-write-wins、無 stale 偵測)+ read-after-verify。
|
|
132
|
-
- Git marketplace 的 snapshot fingerprint 是 **Source Cache 位址鑰匙**(`cache/entries/<fingerprint>`);投影直讀該 cache entry,指紋不可替換成別種身份值。Cache 只對非 pinned entry 做 LRU 驅逐。
|
|
133
|
-
|
|
134
|
-
See `src/bridge/state.ts`(Minimal Bridge State)、`src/bridge-state/atomic.ts`(原子寫入+檔案鎖)、`src/cache/source-cache.ts`。
|
|
135
|
-
|
|
136
|
-
## Support matrix
|
|
137
|
-
|
|
138
|
-
| Dimension | Supported | Notes |
|
|
139
|
-
|-----------|-----------|-------|
|
|
140
|
-
| OS | **macOS**, **Linux** | Windows not supported (path containment, symlink, `flock` semantics are POSIX-only) |
|
|
141
|
-
| Node | **>=22.19.0** | `engines.node` enforced |
|
|
142
|
-
| 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 |
|
|
144
|
-
|
|
145
|
-
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
|
-
|
|
147
|
-
## Versioning & release flow
|
|
148
|
-
|
|
149
|
-
- **Package**: `pi-codex-marketplace` published to **npm** as primary, **Git tag** `v*` as mirror.
|
|
150
|
-
- **SemVer**: starts at `0.1.0`; `0.y` maintenance window until `1.0.0` signals a stable Bridge State contract.
|
|
151
|
-
- **Publishing**: `v*` tag → CI **full matrix green** (below) is a **release gate** → `npm publish --provenance` (OIDC). `latest` tracks stable tags (`v0.*` stable line and later `v1.*`); `next` tracks pre-release tags. Provenance is required (`--provenance`) and verified post-publish by the publish workflow. See `.github/workflows/ci.yml` and `.github/workflows/publish.yml`.
|
|
152
|
-
|
|
153
|
-
## Verification matrix (發版阻擋 gate)
|
|
154
|
-
|
|
155
|
-
Every row is a **release blocker**: `v*` may not publish unless the full matrix is green.
|
|
156
|
-
|
|
157
|
-
| Layer | What is covered |
|
|
158
|
-
|-------|-----------------|
|
|
159
|
-
| unit — 縫層 | `runCommand` 指令分派(add/list/install/update/disable/enable/remove/forget/help、重複註冊拒絕、重裝覆寫、衝突未投影清單、corrupt→重置、unavailable 顯示、git 重抓)、Minimal Bridge State 原子持久化 |
|
|
160
|
-
| 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
|
-
| integration | 真 Pi 縫:extension 註冊 `resources_discover` → 投影 skillPaths(startup/reload 一致、trust flag 無關、被動不變異) |
|
|
162
|
-
| E2E | `/codex-marketplace` 薄 Pi adapter:overview/help 輸出路由、corrupt 重置通知、reload 門控 |
|
|
163
|
-
|
|
164
|
-
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
|
-
|
|
166
|
-
Run locally:
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
npm run typecheck
|
|
170
|
-
npm test # full matrix (unit + integration + E2E)
|
|
171
|
-
npm run test:acceptance # acceptance matrix only
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
## Development
|
|
175
|
-
|
|
176
|
-
```bash
|
|
177
|
-
npm install
|
|
178
|
-
npm run typecheck
|
|
179
|
-
npm test
|
|
180
|
-
npm run test:acceptance
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
## Domain vocabulary
|
|
184
|
-
|
|
185
|
-
Canonical terms are defined in [`CONTEXT.md`](./CONTEXT.md) — use them verbatim (Bridge Package vs Bridge Extension, Bridge State vs Effective State, Marketplace Source, Source Key, Validation Snapshot, Projected Skill, etc.).
|
|
186
|
-
|
|
187
|
-
## Changelog & Releases
|
|
188
|
-
|
|
189
|
-
See [`CHANGELOG.md`](./CHANGELOG.md) and [GitHub Releases](../../releases). Version `0.1.0` is the first SemVer release; Git tags mirror npm versions (`v0.1.0` → `0.1.0`).
|
|
30
|
+
| `add <路徑\|網址>` | 註冊 marketplace(本機資料夾、GitHub 網址、`owner/repo` 簡寫、SSH 定位器) |
|
|
31
|
+
| `list [名稱]` | 列出 plugins(編號/所屬 marketplace/狀態,可帶名稱過濾) |
|
|
32
|
+
| `install <編號\|名稱>` | 裝到**當下最新**並自動啟用+reload |
|
|
33
|
+
| `update` | 全部已註冊 marketplace 重抓最新 |
|
|
34
|
+
| `disable <名稱>` / `enable <名稱>` | 停用/啟用 plugin |
|
|
35
|
+
| `remove <名稱>` | 移除單支 plugin |
|
|
36
|
+
| `forget <名稱>` | 移除整個 marketplace(含其全部安裝) |
|
|
37
|
+
| `help` | 子命令清單 |
|
|
38
|
+
|
|
39
|
+
## Documentation
|
|
40
|
+
|
|
41
|
+
完整文件結構化於 `docs/` 下(GitHub 檢視;npm 上以本 README 為入口):
|
|
42
|
+
|
|
43
|
+
| 文件 | 內容 |
|
|
44
|
+
|------|------|
|
|
45
|
+
| [docs/installation.md](./docs/installation.md) | 安裝/更新/移除:Pi Extension 各模式、Headless CLI、`-l` 語意、requirements |
|
|
46
|
+
| [docs/usage.md](./docs/usage.md) | `/codex-marketplace` 使用手冊:九子命令細節、語意鐵則、Pi 原生 autocomplete |
|
|
47
|
+
| [docs/cli.md](./docs/cli.md) | Headless Bridge CLI:執行方式、輸出與退出代碼契約、狀態生效時機 |
|
|
48
|
+
| [docs/private-repos.md](./docs/private-repos.md) | 私有 Git repo:Credentialed Acquisition(核准式取得)、SSH 定位器 |
|
|
49
|
+
| [docs/architecture.md](./docs/architecture.md) | 架構:Bridge State storage、支援矩陣、領域詞彙、ADR |
|
|
50
|
+
| [docs/development.md](./docs/development.md) | 開發:驗證矩陣(發版 gate)、版本化與發布流程 |
|
|
51
|
+
|
|
52
|
+
領域術語(正典詞彙)見 [`CONTEXT.md`](./CONTEXT.md);發布紀錄見 [`CHANGELOG.md`](./CHANGELOG.md) 與 [GitHub Releases](https://github.com/SamWang32191/pi-codex-marketplace/releases)。
|
|
@@ -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": "1.0.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
|
+
}
|