pi-codex-marketplace 0.3.0 → 0.5.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.
Files changed (62) hide show
  1. package/README.md +67 -50
  2. package/extensions/pi/index.ts +17 -265
  3. package/package.json +2 -2
  4. package/src/bridge/command.ts +1415 -0
  5. package/src/bridge/state.ts +217 -0
  6. package/src/bridge-state/atomic.ts +3 -4
  7. package/src/bridge-state/paths.ts +1 -17
  8. package/src/cache/paths.ts +0 -6
  9. package/src/cache/source-cache.ts +16 -75
  10. package/src/projection/exposure.ts +71 -72
  11. package/src/registration/budget.ts +3 -21
  12. package/src/registration/catalog.ts +30 -33
  13. package/src/registration/claude-catalog.ts +20 -127
  14. package/src/registration/credential-helpers.ts +25 -0
  15. package/src/registration/findings.ts +11 -107
  16. package/src/registration/format.ts +3 -3
  17. package/src/registration/git-acquisition.ts +120 -210
  18. package/src/registration/snapshot.ts +15 -42
  19. package/src/registration/source-key.ts +4 -11
  20. package/extensions/pi/bridge-ledger.ts +0 -973
  21. package/extensions/pi/effective-state-view.ts +0 -70
  22. package/extensions/pi/git-registration.ts +0 -203
  23. package/extensions/pi/installation.ts +0 -495
  24. package/extensions/pi/journal.ts +0 -588
  25. package/extensions/pi/lifecycle.ts +0 -686
  26. package/extensions/pi/registration.ts +0 -222
  27. package/extensions/pi/terminal-presentation.ts +0 -137
  28. package/extensions/pi/transaction-sheet.ts +0 -317
  29. package/extensions/pi/ui-strings.ts +0 -612
  30. package/src/bridge-state/index.ts +0 -5
  31. package/src/bridge-state/migrate.ts +0 -309
  32. package/src/bridge-state/repair.ts +0 -377
  33. package/src/bridge-state/schema.ts +0 -69
  34. package/src/bridge-state/store.ts +0 -545
  35. package/src/bridge-state/types.ts +0 -179
  36. package/src/compatibility/index.ts +0 -1
  37. package/src/compatibility/profile.ts +0 -821
  38. package/src/installation/flow.ts +0 -924
  39. package/src/installation/index.ts +0 -1
  40. package/src/installation/inspection.ts +0 -255
  41. package/src/journal/active-chains.ts +0 -98
  42. package/src/journal/index.ts +0 -3
  43. package/src/journal/journal.ts +0 -386
  44. package/src/journal/types.ts +0 -97
  45. package/src/lifecycle/index.ts +0 -5
  46. package/src/lifecycle/rebind.ts +0 -291
  47. package/src/lifecycle/refresh.ts +0 -558
  48. package/src/lifecycle/removal.ts +0 -370
  49. package/src/lifecycle/update-plan.ts +0 -214
  50. package/src/lifecycle/update.ts +0 -295
  51. package/src/projection/effective-state.ts +0 -28
  52. package/src/projection/index.ts +0 -4
  53. package/src/projection/runtime.ts +0 -329
  54. package/src/reconciliation/startup.ts +0 -64
  55. package/src/registration/entry-acquisition.ts +0 -854
  56. package/src/registration/fence.ts +0 -66
  57. package/src/registration/flow.ts +0 -647
  58. package/src/registration/git-flow.ts +0 -666
  59. package/src/registration/git-selector.ts +0 -275
  60. package/src/registration/index.ts +0 -18
  61. package/src/registration/receipt.ts +0 -442
  62. package/src/registration/registration.ts +0 -97
package/README.md CHANGED
@@ -2,7 +2,7 @@
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` opens a responsive Bridge Ledger with Global authority, guarded lifecycle transactions, and three-orthogonal Receipt reporting.
5
+ > **One-line:** `pi install npm:pi-codex-marketplace` → `/codex-marketplace add <本機資料夾|GitHub 網址>` 註冊 marketplace `install <編號>` 裝到當下最新並立刻在 Pi 可用。
6
6
 
7
7
  ## Install
8
8
 
@@ -26,64 +26,88 @@ Source types follow `docs/packages.md`: `npm:` for registry, `git:`/`https://` f
26
26
 
27
27
  Requirements: **Pi 0.84.2**, **Node >=22.19.0**, **macOS / Linux** (Windows not supported).
28
28
 
29
- ## Usage — `/codex-marketplace` (聚合指令)
29
+ ## Usage — `/codex-marketplace`(純文字,無 TUI)
30
30
 
31
- Single aggregated command in Pi TUI, faithful to `prototype/tui-management-flow@c9107d2`:
31
+ Nine subcommands, no arguments = 總覽:
32
32
 
33
33
  ```
34
34
  /codex-marketplace
35
+ /codex-marketplace help
35
36
  ```
36
37
 
37
- TUI mode opens a persistent **Bridge Ledger** instead of a flat action list. The workspace presents the Global authority with its State Revision, counts, and health. Navigation is grouped into **Observe**, **Sources**, **Plugins**, and **Recovery & receipts**; every action is attached to a canonical object identity rather than dispatched from its translated label.
38
+ | 子命令 | 行為 |
39
+ |--------|------|
40
+ | `add <路徑\|網址>` | 註冊 marketplace(本機資料夾、GitHub 完整網址、`owner/repo` 簡寫皆收),自動偵測 codex/claude 格式並告知 `偵測:<format> marketplace · N plugins`。重複註冊同來源被拒絕並提示下一步。Git 來源以安全線取得(`core.hooksPath=/dev/null`、`GIT_LFS_SKIP_SMUDGE=1`、`GIT_TERMINAL_PROMPT=0`),catalog 解析失敗明示錯誤、不註冊。私有 HTTPS repo 預設 credential-free(有權限也可能 401)——先以 `PI_CODEX_MARKETPLACE_CREDENTIAL_HELPERS` 核准 credential helper 再 add(逐次生效、永不持久化),或改用 SSH 定位器;用法與範例見下方〈私有 Git repo:Credentialed Acquisition〉。 |
41
+ | `list [名稱]` | 列出 plugins(編號/所屬 marketplace/狀態:可安裝・已裝啟用・已裝停用・unavailable+原因),可帶 marketplace 名稱過濾。 |
42
+ | `install <編號\|名稱>` | 裝到**當下最新**並自動啟用+reload。成功話術如 `安裝 "name"(N skills:a, b, c)· 已重新載入生效`;同名衝突列出 `⚠ skill "b" 與既有同名,未投影(名稱衝突)`。**重複安裝=重抓最新覆寫**(重裝=更新,不報錯)。 |
43
+ | `update` | 對全部已註冊 marketplace 重抓最新:有變化的 plugin 升到最新、無變化各自顯示「無變化」;整體以「已重新載入生效」收尾(有變時)。 |
44
+ | `disable <名稱>` / `enable <名稱>` | 停用/啟用 plugin(enable 重新投影+reload)。 |
45
+ | `remove <名稱>` | 移除單支 plugin(不動 marketplace、不動來源資料)。 |
46
+ | `forget <名稱>` | 移除整個 marketplace(含其全部安裝)。 |
47
+ | `help` | 子命令清單。 |
38
48
 
39
- At 120/80 columns the Ledger uses navigation and detail panes; below 64 columns it switches to a single-column drill-down. Use arrows or `j/k` to move, `Enter` to open/activate, `Esc` to go back or cancel, `q`/Ctrl-C to exit, and `?` for help. On Validation sheets, `d` expands or collapses the full ordered disclosure after the initial verdict and finding counts.
49
+ 語意鐵則:
40
50
 
41
- Every mutation uses the shared transaction sequence **`Intent → Validation → Consent → Plan → Commit → Receipt`**. The target authority, State Revision, Validation Snapshot, separate Default-No confirmations, atomic commit boundary, and final Durable / Findings / Runtime outcomes remain visible across the flow. After every completed, declined, blocked, stale, or cancelled action, the workspace re-reads the authoritative state document and receipts before rendering again. Existing `list` / `inspect` and non-TUI summary paths remain non-interactive.
51
+ - 安裝語意不分「安裝/啟用」兩步;輸出不得宣稱 reload skill 已在 host 內可見(host 無內省 API),只說「已重新載入生效」。
52
+ - catalog 內 git 型或不支援來源的 entry 一律 `unavailable` 並顯示原因;解析失敗顯示明確錯誤、不給裝。
53
+ - 安裝成功後由指令層主動要求 reload;reload 失敗不影響已記錄狀態,下次 session start 或 `/reload` 仍生效。
54
+ - `--no-skills` 啟動 Pi 不影響 Bridge 投影。
42
55
 
43
- The Ledger exposes these existing capabilities without changing their lifecycle semantics:
56
+ ### 私有 Git repo:Credentialed Acquisition(核准式取得)
44
57
 
45
- - **檢視 Global 分區** single `Global Scope` (`{getAgentDir()}/codex-marketplace/state.json`) workspace. Shows `schemaVersion` (v3), `stateRevision`, registrations/installations, empty-state guidance, and provenance notes.
46
- - **註冊本地 / Git Marketplace…** — local or Git Marketplace Source Registration supporting dual Marketplace Formats (`codex` via `.agents/plugins/marketplace.json` and `claude` via `.claude-plugin/marketplace.json`, with codex precedence when both exist; missing both fails closed as `CATALOG_MISSING`). The derived `format` is fixed to the Registration record. Local uses canonical real path; Git uses Canonical Git Locator (credential-free) + Normalized Git Selector (`default` / `branch refs/heads/*` / `tag refs/tags/*` / `commit` lower 40/64 hex) → Resolved Revision. Validation Disclosure shows source, Marketplace name, Marketplace Format, State Revision, Validation Snapshot fingerprint, entry outcomes, findings summary. **Registration Confirmation** is snapshot+revision bound, Default No. Blocking Findings (Contained Path / Contained Symlink / Budget / duplicate Source Key / locator/selector trust) block the attempt; concurrent attempt is blocked by Attempt Fence (`FENCE-01`); changed State Revision or snapshot yields `Rejected as Stale` (`STALE-01/02`).
47
- - **安裝 Compatible Plugin…** — the Plugins ledger is keyed directly by stable **Marketplace Entry ID** (`/plugins/<序號>`), classification, skills, Invocation Policies, and resources; each compatible row exposes `Install Disabled` and `Install and Enable` without reopening a display-label Entry selector. Unavailable rows remain visible with their exact reason (unsupported source kind / entry-defined strict:false / parse failure / Invalid / Incompatible / Plugin ID collision). Compatibility Profile v2 classifies every Plugin atomically across Codex and Claude manifests as Compatible / Incompatible / Invalid; collision never changes classification. `Install Disabled` persists provenance without Activation Confirmation; `Install and Enable` discloses exact Plugin, skill list, Skill Resources, Invocation Policies, `Pi → Global` precedence and findings, then requires a separate **Default No** Activation Confirmation (bound to same snapshot+revision). Re-enabling a disabled Installation repeats validation and confirmation; disabling preserves its Installation ID.
48
- - **管理已安裝 Plugin(Enable / Disable)…** — toggle Installation State; disabling preserves provenance, enabling re-validates under current profile/ruleset/budget.
49
- - **檢視 Effective State 與 Projected Skills…** — read-time derived Effective State (Global Scope enabled installations) and Projected Skills with Runtime Skill Collision resolution (`Pi → Global Scope` exact name layering: pre-existing Pi skills reserve their names; non-colliding Bridge skills are projected; colliding Bridge skills within the same layer are unavailable). Whole-Plugin Blocking Findings block the Plugin; collision affects only that skill; `Available` is established only by independent host evidence (`AVAIL-01`).
50
- - **Refresh / 更新 Registration… (Marketplace Refresh → Update Candidate → Update Plan Checklist → Apply Update)** — Refresh is non-mutating and produces an Update Candidate when the validated source state differs (Plugin version alone does not; full-commit Git selector ref movement alone does not). Update Plan Checklist requires fresh **Registration Confirmation** + one explicit outcome per Installation (`update` / `disable` / `remove`, with `update` only when a Compatible candidate exists) + **Activation Confirmation** per enabled installation that remains enabled. Commit is a single atomic Lifecycle Operation replacing the Registration's Validation Snapshot and applying every disclosed consequence without mixing revisions.
51
- - **Rebind Registration…** — explicitly replace a Registration's locator/selector with fresh validation, Registration Confirmation, and a complete Update Plan for every existing Installation; prior activation consent never carries over.
52
- - **移除 Registration / Installation…** — Registration Removal discloses that all associated Installations will be atomically removed; Installation Removal removes the selected Installation.
53
- - **檢視 Receipt Journal…** — bounded immutable Receipt Journal (redacted, non-authoritative, with active recovery chain preservation across restarts); degraded journal surfaces `JOURNAL-01/02` without changing Persistence Failed outcome, and Repair State can atomically reconstruct validated lines while preserving parsed active chains. An exact active Pending Application chain exposes an Attempt Fence-held Retry Application transaction bound to State Revision and Validation Snapshot; missing snapshots remain visible but Retry is disabled in favor of a fresh validated intent, interaction-time drift fails closed without reload, reload failure remains Pending Application, and the chain resolves only after exact post-reload verification.
54
- - **執行 State Repair…** — explicit, fence-guarded verification for an unreadable state or an exact Persistence Indeterminate recovery chain; it stays visibly disabled for Pending Application, Persistence Failed, and healthy states because those require a different Recovery Action. It is never auto-retried.
58
+ 預設 `add`/`update` Git 來源完全 credential-free:不執行任何 credential helper,因此「有權限卻失敗」時,私有 HTTPS repo 會以 401 失敗(並提示核准或改用 SSH)。若要核准 credential helper,以逗號分隔設定環境變數(**逐次生效、永不持久化**,`add` `update` 共用同一核准來源):
55
59
 
56
- Diagnostics throughout are shown with **synchronized ordering** `class → phase → target → pointer → rule` and **closed rule codes** (`CONT-01`, `COMP-02`, `FENCE-01`, `MIGRATE-01`, etc.), grouped by severity (`Blocking` / `Validation Warning` / `Operational Notice`). Closed **Recovery Actions** (`Retry` / `Revalidate` / `Refresh` / `Rebind` / `Retry Application` / `Disable` / `Remove` / `Repair State` / `Inspect`) list only the currently safe next step under the exact current State Revision. Every committed operation reports a **three-orthogonal Attempt Summary** (`Completed` / `Completed with diagnostics` / `Declined` / `Blocked` / `Rejected as Stale` / `Persistence Failed` / `Persistence Indeterminate` / `Pending Application`) with separate persistence, findings, and runtime (`Applied` / `Pending Application` / `none`) diagnostics. A post-commit `reload` is attempted immediately; if not host-verifiable at the expected revision it is reported as `Pending Application` and no inspection or Refresh supersedes it. Pending state is carried by active recovery chains (Retry Application and other Recovery Actions) and is reconciled at startup by producing a new reconciling receipt without implicit retry or rollback.
60
+ ```
61
+ PI_CODEX_MARKETPLACE_CREDENTIAL_HELPERS='store, !f() { echo "username=${GITHUB_USER}"; echo "password=${GITHUB_TOKEN}"; }; f'
62
+ /codex-marketplace add https://github.com/acme/private-mkt
63
+ ```
64
+
65
+ Credentialed Acquisition 語意(安全線):
66
+
67
+ - 核准僅限該次呼叫(per-invocation):環境變數逐指令讀取,**不寫入** Bridge State、設定檔或任何持久化位置;重開 session 後需重新設定。
68
+ - 值為 git `credential.helper` 字串,逗號分隔、各項 trim、空項目忽略;未設定或空白=未核准(行為與 credential-free 完全相同,安全線其餘禁制不變)。
69
+ - 憑證與核准清單**永不**進入指令輸出、Bridge State、Canonical Git Locator(定位器)、Validation Snapshot(快照)或 cache identity(快取身份)——取得流程的 identity 判定與憑證完全無關。
70
+ - 已核准 helper 仍被遠端拒絕(401)時,錯誤訊息提示檢查憑證;未核准時提示核准或改用 SSH。
71
+
72
+ #### SSH 定位器:私有 repo 的替代路徑
73
+
74
+ 私有 repo 可以完全繞過此環境變數,直接用 SSH 定位器註冊(HTTPS 與 SSH 同屬允許的 credential-free 定位器):
57
75
 
58
- ## Bridge State storage & migration
76
+ ```
77
+ /codex-marketplace add git@github.com:acme/private-mkt # scp-like 簡寫(canonical:ssh://git@github.com/acme/private-mkt)
78
+ /codex-marketplace add ssh://git@github.com/acme/private-mkt
79
+ ```
80
+
81
+ 前提(與既有 Acquisition Trust Base 一致):
59
82
 
60
- Bridge State is the sole authority, stored in a **single Global Scope document** at `{getAgentDir()}/codex-marketplace/state.json` (`~/.pi/agent/codex-marketplace/state.json`):
83
+ - host key 必須**預先存在** `~/.ssh/known_hosts`——安全線以 `StrictHostKeyChecking=yes` 只信任既有 host key,遇到未知或變更的主機金鑰直接拒絕,不會提示接受;
84
+ - 憑證由 SSH agent 提供,整個取得過程不互動(`BatchMode=yes`)、無任何提示;
85
+ - SSH 定位器本身仍維持 credential-free:不得內嵌密碼(`user:pass@` 拒絕);憑證只能經由 SSH agent 或 Credentialed Acquisition 到達取得流程。
86
+
87
+ ## Bridge State storage
88
+
89
+ Bridge State 是唯一權威,存於**單一 Global Scope 文件** `{getAgentDir()}/codex-marketplace/state.json`(`~/.pi/agent/codex-marketplace/state.json`):
61
90
 
62
91
  ```jsonc
63
92
  {
64
- "schemaVersion": 3,
65
- "stateRevision": "1", // opaque monotonic (string "0" -> "1" -> "2" ...)
66
- "registrations": [], // immutable Registration ID = UUIDv4, with format: "codex" | "claude"
67
- "installations": [] // Installed Plugins (enabled/disabled), each bound to a Validation Snapshot
93
+ "schemaVersion": 1, // 固定,永不遷移
94
+ "registrations": [], // immutable Registration ID = UUIDv4;sourceKind: "local" | "git";git 帶 snapshot fingerprint(cache 位址)
95
+ "installations": [] // Installed Plugins(enabled/disabled),含 manifestName、skills snapshot(git)
68
96
  }
69
97
  ```
70
98
 
71
- Only authoritative fields are persisted. `Effective State`, catalogs, compatibility results, diagnostics are **derived at read time**.
72
-
73
- - `State Revision` increments monotonically under file lock; commit is CAS-guarded by `expectedStateRevision` when supplied.
74
- - Writes are **atomic**: `write-to-temp → fsync → rename` + `fsync` parent dir + **file lock** (`.lock` sibling) + **WAL** (`state.json.wal`) + **read-after-verify**.
75
- - Cross-process concurrency is safe: lock serializes RMW; rename prevents torn reads; per-fingerprint `flock` guards Source Cache fetches with `p50 <200ms` on cache hit.
76
- - **WAL migration** (`src/bridge-state/migrate.ts`): supported forward migrations are applied atomically via WAL (`state.json.wal` fsynced before commit, replayed on the next read after a crash, cleaned after commit success). Migrations are **non-waivable, opt-in per version**, require no implicit activation, and preserve the active recovery chain. Schema v1 → v2 migration strips legacy `scopeOverrides` (non-empty overrides generate a non-blocking `MIGRATE-01` diagnostic finding without fail-closed) and normalizes Installation IDs (stripping legacy `global/` prefix). Schema v2 → v3 migration populates `format: "codex"` on existing registrations without data loss while newly written registrations store `format: "codex" | "claude"`. Unknown/older-without-path and newer (`schemaVersion > CURRENT_SCHEMA_VERSION`) versions are treated as **incompatible** — fail-closed, no auto-migration, no rollback. **Downgrade never writes back** (`isDowngradeAttempt` guard): a newer durable file is never overwritten by an older Bridge Package; the operator must update the package first.
77
- - Corrupted / unknown `schemaVersion` → classified as **corrupted / incompatible (Persistence Indeterminate)**, **never auto-rollback** — fail-closed. `validateSchema` and `migrateForward` enforce the closed set `CORRUPTED_JSON` / `INVALID_SCHEMA` / `INCOMPATIBLE_SCHEMA_VERSION` / `UNKNOWN_OLD_VERSION` / `MIGRATION_FAILED`.
99
+ - **重建=重置**:壞檔或不認識的格式一律重置為空、重新註冊重裝;沒有 repair、沒有 migration、沒有 State Revision。
100
+ - 寫入防護:`write-to-temp → fsync → rename`(原子)+ 檔案鎖(`.lock` sibling,last-write-wins、無 stale 偵測)+ read-after-verify。
101
+ - Git marketplace snapshot fingerprint **Source Cache 位址鑰匙**(`cache/entries/<fingerprint>`);投影直讀該 cache entry,指紋不可替換成別種身份值。Cache 只對非 pinned entry 做 LRU 驅逐。
78
102
 
79
- See `src/bridge-state/` for `store.ts`, `atomic.ts`, `schema.ts`, `migrate.ts`, `paths.ts`.
103
+ See `src/bridge/state.ts`(Minimal Bridge State)、`src/bridge-state/atomic.ts`(原子寫入+檔案鎖)、`src/cache/source-cache.ts`。
80
104
 
81
105
  ## Support matrix
82
106
 
83
107
  | Dimension | Supported | Notes |
84
108
  |-----------|-----------|-------|
85
109
  | OS | **macOS**, **Linux** | Windows not supported (path containment, symlink, `flock` semantics are POSIX-only) |
86
- | Node | **>=22.19.0** | `engines.node` enforced; `npm-shrinkwrap.json` pins Pi 0.84.2 host |
110
+ | Node | **>=22.19.0** | `engines.node` enforced |
87
111
  | 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. |
88
112
  | 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 |
89
113
 
@@ -93,34 +117,27 @@ Peer declaration (dual): **精確 `0.84.2`** in `peerDependencies` (exact host t
93
117
 
94
118
  - **Package**: `pi-codex-marketplace` published to **npm** as primary, **Git tag** `v*` as mirror.
95
119
  - **SemVer**: starts at `0.1.0`; `0.y` maintenance window until `1.0.0` signals a stable Bridge State contract.
96
- - **schemaVersion is bound to the package version**: bumping `schemaVersion` requires a package version bump and a WAL migration entry in `src/bridge-state/migrate.ts`; unknown `schemaVersion` is incompatible and never silently accepted.
97
120
  - **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`.
98
- - **Maintenance windows**: `0.y` (current) may include additive schema migrations with WAL forward paths; `1.0` will freeze the `schemaVersion` contract and only accept forward-compatible additive changes via new `schemaVersion`s.
99
121
 
100
122
  ## Verification matrix (發版阻擋 gate)
101
123
 
102
124
  Every row is a **release blocker**: `v*` may not publish unless the full matrix is green.
103
125
 
104
- | Layer | Fixture | OS | Node | Pi host | What is covered |
105
- |-------|---------|----|------|---------|-----------------|
106
- | unit | **synthetic** | macOS + Linux | 22.19.0 | — | selector/locator normalization, Contained Path/Symlink, budget, compatibility atomic classification, precedence, collision, fence/sync ordering |
107
- | unit | **pinned** `SamWang32191/codex-plugins@98e78ca` | macOS + Linux | 22.19.0 | — | catalog parsing + validation against a real pinned marketplace snapshot (fingerprint-stable) |
108
- | unit | **adversarial** | macOS + Linux | 22.19.0 | | malformed manifests, path-escapes, symlink loops, budget overflows, ID collisions, parser depth |
109
- | integration | synthetic | macOS + Linux | 22.19.0 | 0.84.2 | Bridge State atomic WAL + file lock + read-after-verify, Cache pinning/LRU/flock, Receipt Journal rebuild & prune (active chain) |
110
- | integration | pinned | macOS + Linux | 22.19.0 | 0.84.2 | Git acquisition (non-executing), Snapshot fingerprinting, Installation dual-path, Effective State, projection/collision, Refresh/Rebind/Removal WAL commit |
111
- | integration | adversarial | macOS + Linux | 22.19.0 | 0.84.2 | Source Drift (Blocking), Stale Snapshot rejection, Persistence Indeterminate fail-closed, Fence admission, Cache stale-snapshot never promotes |
112
- | E2E (highest seam — **TUI**) | synthetic | macOS + Linux | 22.19.0 | **0.84.2** | `/codex-marketplace` → Bridge Ledger custom component → structured object intent → transaction sheet → commit → authoritative reload → three-orthogonal Receipt |
113
- | E2E (highest seam — **TUI**) | pinned | macOS + Linux | 22.19.0 | **0.84.2** | full lifecycle (Register → Install Disabled / Install and Enable → Disable/Enable → Refresh → Update Plan Checklist → Apply Update / Rebind → Removal) with fence/cache/external observability |
114
- | E2E (highest seam — **TUI**) | adversarial | macOS + Linux | 22.19.0 | **0.84.2** | collision (`Pi → Global`), cache (`offline exact fingerprint hit` vs `stale never success`) |
126
+ | Layer | What is covered |
127
+ |-------|-----------------|
128
+ | unit 縫層 | `runCommand` 指令分派(add/list/install/update/disable/enable/remove/forget/help、重複註冊拒絕、重裝覆寫、衝突未投影清單、corrupt→重置、unavailable 顯示、git 重抓)、Minimal Bridge State 原子持久化 |
129
+ | 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) |
130
+ | integration | Pi 縫:extension 註冊 `resources_discover` 投影 skillPaths(startup/reload 一致、trust flag 無關、被動不變異) |
131
+ | E2E | `/codex-marketplace` Pi adapter:overview/help 輸出路由、corrupt 重置通知、reload 門控 |
115
132
 
116
- Fixtures: `tests/fixtures/synthetic/` (including synthetic Claude and mattpocock-shaped marketplace fixtures), `tests/fixtures/pinned/` (captured `SamWang32191/codex-plugins@98e78ca` snapshot + fingerprint manifest), `tests/fixtures/adversarial/` (path-escape / symlink-loop / budget-overflow / malformed frontmatter corpora). See `tests/acceptance/` for the matrix runner that enforces per-row gating (any row failure blocks publish).
133
+ 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).
117
134
 
118
135
  Run locally:
119
136
 
120
137
  ```bash
121
138
  npm run typecheck
122
139
  npm test # full matrix (unit + integration + E2E)
123
- npm run test:acceptance # acceptance matrix only (three-tier × three-fixture)
140
+ npm run test:acceptance # acceptance matrix only
124
141
  ```
125
142
 
126
143
  ## Development
@@ -134,8 +151,8 @@ npm run test:acceptance
134
151
 
135
152
  ## Domain vocabulary
136
153
 
137
- Canonical terms are defined in [`CONTEXT.md`](./CONTEXT.md) — use them verbatim (Bridge Package vs Bridge Extension, Bridge State vs Effective State, State Revision, Registration ID, Source Key, etc.).
154
+ 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.).
138
155
 
139
156
  ## Changelog & Releases
140
157
 
141
- 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`).
158
+ 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`).
@@ -3,253 +3,24 @@
3
3
  * Single extension "pi" package, Pi 0.84.2 compatible.
4
4
  *
5
5
  * Provides:
6
- * - /codex-marketplace command: persistent Bridge Ledger workspace
7
- * - Bridge State reading via the single-document store (Global)
8
- * - Startup Reconciliation on session_start
9
- * - Receipt Journal inspection & State Repair flows
6
+ * - /codex-marketplace command: Thin Pi adapter delegating to pure runCommand
7
+ * - resources_discover: Runtime Skill Exposure contributing Projected Skill paths
10
8
  *
11
- * Domain vocabulary follows CONTEXT.md (Bridge Package, Bridge Extension, Bridge State, State Revision, etc.)
9
+ * 極簡表面(#87):無 TUI、無 ledger/journal/生命周期機械。指令輸出與 reload 旗標
10
+ * 完全由 runCommand 決定;reload 是唯一生效動作,失敗不影響已記錄狀態。
12
11
  */
13
12
 
14
13
  import type { ExtensionAPI, ExtensionCommandContext } from '@earendil-works/pi-coding-agent';
15
- import { stripTerminalSequences } from '@earendil-works/pi-tui';
16
14
 
17
- import { readBridgeStateSync } from '../../src/bridge-state/store.js';
18
- import type { ReadResult } from '../../src/bridge-state/types.js';
15
+ import { runCommand } from '../../src/bridge/command.js';
19
16
  import { discoverProjectedSkillPaths } from '../../src/projection/exposure.js';
20
- import { runStartupReconciliation } from '../../src/reconciliation/startup.js';
21
- import type { AttemptReceipt } from '../../src/registration/receipt.js';
22
- import { runLocalRegistrationFlow } from './registration.js';
23
- import { runGitRegistrationFlow } from './git-registration.js';
24
- import { runPluginInstallationFlow, runPluginStateFlow } from './installation.js';
25
- import { runRefreshFlow, runRebindFlow, runRemovalFlow } from './lifecycle.js';
26
- import { runEffectiveStateView } from './effective-state-view.js';
27
- import {
28
- runReceiptJournalView,
29
- runRepairStateFlow,
30
- runRetryApplicationFlow,
31
- } from './journal.js';
32
- import {
33
- BridgeLedgerComponent,
34
- buildBridgeLedgerModel,
35
- loadBridgeLedgerSnapshot,
36
- type LedgerActionIntent,
37
- } from './bridge-ledger.js';
38
- import { quoteTerminalText } from './terminal-presentation.js';
39
- import { renderTransactionSheet } from './transaction-sheet.js';
40
- import { uiText } from './ui-strings.js';
41
-
42
- const STARTUP_RECEIPT_THEME = {
43
- fg: (_color: string, text: string) => text,
44
- bg: (_token: string, text: string) => text,
45
- bold: (text: string) => text,
46
- };
47
-
48
- export function formatStartupReceipt(receipt: AttemptReceipt): string {
49
- return renderTransactionSheet({
50
- step: 'Receipt',
51
- actionLabel: receipt.operation,
52
- authority: 'global',
53
- target: receipt.trigger,
54
- stateRevision: receipt.observedStateRevision ?? receipt.expectedStateRevision,
55
- validationSnapshot: receipt.validationSnapshot,
56
- receipt,
57
- }, STARTUP_RECEIPT_THEME, 80).map(stripTerminalSequences).join('\n');
58
- }
59
-
60
- function requiredTarget(intent: LedgerActionIntent): string {
61
- if (intent.targetId) return intent.targetId;
62
- throw new Error(`Ledger action ${intent.actionId} requires a stable target identity`);
63
- }
64
-
65
- function requiredStateRevision(intent: LedgerActionIntent): string {
66
- if (intent.stateRevision) return intent.stateRevision;
67
- throw new Error(`Ledger action ${intent.actionId} requires a selected State Revision`);
68
- }
69
-
70
- function requiredMarketplaceEntryTarget(intent: LedgerActionIntent): {
71
- registrationId: string;
72
- entryPointer: string;
73
- marketplaceEntryId: string;
74
- validationSnapshot: string;
75
- } {
76
- if (
77
- intent.targetKind === 'marketplace-entry'
78
- && intent.registrationId
79
- && intent.entryPointer
80
- && intent.targetId
81
- ) {
82
- if (!intent.validationSnapshot) {
83
- throw new Error(`Ledger action ${intent.actionId} requires a bound Validation Snapshot`);
84
- }
85
- return {
86
- registrationId: intent.registrationId,
87
- entryPointer: intent.entryPointer,
88
- marketplaceEntryId: intent.targetId,
89
- validationSnapshot: intent.validationSnapshot,
90
- };
91
- }
92
- throw new Error(`Ledger action ${intent.actionId} requires a stable Marketplace Entry identity`);
93
- }
94
-
95
- /** Localized state summary for TUI surfaces (the non-TUI list/inspect output stays canonical English). */
96
- function formatLocalizedStateSummary(result: ReadResult): string {
97
- const scopeLabel = uiText('common.scope.global');
98
- if (result.status === 'missing') {
99
- const s = result.state!;
100
- return uiText('cmd.state.empty', {
101
- scope: scopeLabel,
102
- version: s.schemaVersion,
103
- revision: s.stateRevision,
104
- });
105
- }
106
- if (result.status === 'ok') {
107
- const s = result.state!;
108
- const regCount = s.registrations.length;
109
- const instEnabled = s.installations.filter((i) => i.installationState === 'enabled').length;
110
- const instDisabled = s.installations.filter((i) => i.installationState === 'disabled').length;
111
- const base = uiText('cmd.state.ok', {
112
- scope: scopeLabel,
113
- revision: s.stateRevision,
114
- registrations: regCount,
115
- enabled: instEnabled,
116
- disabled: instDisabled,
117
- });
118
- return base;
119
- }
120
- if (result.status === 'incompatible') {
121
- return uiText('cmd.state.incompatible', {
122
- scope: scopeLabel,
123
- error: quoteTerminalText(result.error ?? uiText('common.unknown')),
124
- });
125
- }
126
- return uiText('cmd.state.corrupted', {
127
- scope: scopeLabel,
128
- error: quoteTerminalText(result.error ?? uiText('common.unknown')),
129
- });
130
- }
131
-
132
- /** Dispatches only by stable semantic identity; display labels never select behavior. */
133
- export async function dispatchLedgerAction(
134
- ctx: ExtensionCommandContext,
135
- intent: LedgerActionIntent,
136
- ): Promise<void> {
137
- switch (intent.actionId) {
138
- case 'observe-authority': {
139
- const global = readBridgeStateSync();
140
- ctx.ui.notify(formatLocalizedStateSummary(global), 'info');
141
- return;
142
- }
143
- case 'observe-effective-state':
144
- await runEffectiveStateView(ctx);
145
- return;
146
- case 'register-local':
147
- await runLocalRegistrationFlow(ctx);
148
- return;
149
- case 'register-git':
150
- await runGitRegistrationFlow(ctx);
151
- return;
152
- case 'refresh-registration':
153
- await runRefreshFlow(ctx, {
154
- registrationId: requiredTarget(intent),
155
- });
156
- return;
157
- case 'rebind-registration':
158
- await runRebindFlow(ctx, {
159
- registrationId: requiredTarget(intent),
160
- });
161
- return;
162
- case 'remove-registration':
163
- await runRemovalFlow(ctx, {
164
- targetKind: 'registration',
165
- targetId: requiredTarget(intent),
166
- });
167
- return;
168
- case 'install-disabled':
169
- case 'install-and-enable': {
170
- const entry = requiredMarketplaceEntryTarget(intent);
171
- await runPluginInstallationFlow(ctx, {
172
- registrationId: entry.registrationId,
173
- entryPointer: entry.entryPointer,
174
- marketplaceEntryId: entry.marketplaceEntryId,
175
- targetState: intent.actionId === 'install-and-enable' ? 'enabled' : 'disabled',
176
- expectedStateRevision: requiredStateRevision(intent),
177
- expectedValidationSnapshot: entry.validationSnapshot,
178
- });
179
- return;
180
- }
181
- case 'enable-installation':
182
- case 'disable-installation':
183
- await runPluginStateFlow(ctx, {
184
- installationId: requiredTarget(intent),
185
- desiredState: intent.actionId === 'enable-installation' ? 'enabled' : 'disabled',
186
- expectedStateRevision: requiredStateRevision(intent),
187
- });
188
- return;
189
- case 'remove-installation':
190
- await runRemovalFlow(ctx, {
191
- targetKind: 'installation',
192
- targetId: requiredTarget(intent),
193
- });
194
- return;
195
- case 'view-receipt-journal':
196
- await runReceiptJournalView(ctx);
197
- return;
198
- case 'inspect-receipt':
199
- await runReceiptJournalView(ctx, {
200
- receiptId: requiredTarget(intent),
201
- });
202
- return;
203
- case 'repair-state':
204
- await runRepairStateFlow(ctx, {
205
- expectedStateRevision: intent.stateRevision,
206
- });
207
- return;
208
- case 'retry-application':
209
- await runRetryApplicationFlow(ctx, {
210
- receiptId: requiredTarget(intent),
211
- });
212
- return;
213
- }
214
- }
215
-
216
- // Closed helper to format state summary for disclosure
217
- function formatStateSummary(result: ReadResult, scopeLabel: string): string {
218
- if (result.status === 'missing') {
219
- const s = result.state!;
220
- return `${scopeLabel}: empty · schema v${s.schemaVersion} · revision ${s.stateRevision} · 0 registrations · 0 installations`;
221
- }
222
- if (result.status === 'ok') {
223
- const s = result.state!;
224
- const regCount = s.registrations.length;
225
- const instEnabled = s.installations.filter((i) => i.installationState === 'enabled').length;
226
- const instDisabled = s.installations.filter((i) => i.installationState === 'disabled').length;
227
- return `${scopeLabel}: revision ${s.stateRevision} · ${regCount} registrations · ${instEnabled} enabled / ${instDisabled} disabled`;
228
- }
229
- if (result.status === 'incompatible') {
230
- return `${scopeLabel}: incompatible — ${quoteTerminalText(result.error ?? 'unknown schema')} (requires newer Bridge Package)`;
231
- }
232
- return `${scopeLabel}: corrupted — ${quoteTerminalText(result.error ?? 'unreadable Bridge State')} (Persistence Indeterminate, no auto-rollback)`;
233
- }
234
17
 
235
18
  export default function (pi: ExtensionAPI) {
236
- pi.on('session_start', async (_event, ctx) => {
237
- // Startup reconciliation: Global pass
238
- try {
239
- const recon = await runStartupReconciliation({});
240
- if (recon.reconciled && recon.receipt) {
241
- ctx.ui.notify(formatStartupReceipt(recon.receipt), recon.receipt.summary === 'Completed' ? 'info' : 'warning');
242
- }
243
- } catch {
244
- // Non-blocking in extension bootstrap
245
- }
246
- });
247
-
248
19
  // Runtime Skill Exposure (ADR 0001): contribute Projected Skills through Pi's
249
20
  // resource-discovery seam at every startup and reload. Passive existence inspection over the
250
- // current Effective State only — no fingerprint validation, no Bridge State mutation, and no
251
- // Attempt Receipt. Missing snapshot material is skipped individually; discovery never fails
252
- // the host's resource pass.
21
+ // current Effective State only — no fingerprint validation and no Bridge State mutation.
22
+ // Missing snapshot material is skipped individually; discovery never fails the host's
23
+ // resource pass.
253
24
  pi.on('resources_discover', async (_event, _ctx) => {
254
25
  try {
255
26
  return {
@@ -261,37 +32,18 @@ export default function (pi: ExtensionAPI) {
261
32
  });
262
33
 
263
34
  pi.registerCommand('codex-marketplace', {
264
- description: uiText('cmd.description'),
265
- handler: async (args, ctx) => {
266
- // Hybrid discovery/guided: support /codex-marketplace list|inspect <args> for non-TUI quick paths
35
+ description: 'codex / claude marketplace 管理(add/list/install/update/disable/enable/remove/forget/help)',
36
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
267
37
  const rawArgs = (args ?? '').trim();
268
- if (rawArgs.length > 0 && (rawArgs.startsWith('list') || rawArgs.startsWith('inspect') || rawArgs === '--help' || rawArgs === '-h')) {
269
- const global = readBridgeStateSync();
270
- const g = formatStateSummary(global, 'Global Scope');
271
- ctx.ui.notify(`${g}\n(完整導向流請於 TUI 內執行 /codex-marketplace)`, 'info');
272
- return;
273
- }
38
+ const argv = rawArgs.length > 0 ? rawArgs.split(/\s+/) : [];
39
+ const result = await runCommand(argv);
274
40
 
275
- // Non-TUI fallback: notify with summary
276
- if (ctx.mode !== 'tui' || !ctx.hasUI) {
277
- const global = readBridgeStateSync();
278
- const g = formatStateSummary(global, 'Global Scope');
279
- ctx.ui.notify(`${g}\n互動流程需 TUI 模式(/codex-marketplace 於 TUI 內)`, 'info');
280
- return;
41
+ if (result.output) {
42
+ ctx.ui.notify(result.output, 'info');
281
43
  }
282
-
283
- // The workspace is deliberately reopened from a fresh snapshot after every action.
284
- // Neither cached revisions nor presentation-derived eligibility become authority.
285
- while (true) {
286
- const snapshot = await loadBridgeLedgerSnapshot({});
287
- const model = buildBridgeLedgerModel(snapshot);
288
- const intent = await ctx.ui.custom<LedgerActionIntent | undefined>(
289
- (tui, theme, _keybindings, done) =>
290
- new BridgeLedgerComponent(model, theme, tui, done),
291
- );
292
- if (!intent) return;
293
- await dispatchLedgerAction(ctx, intent);
44
+ if (result.reload) {
45
+ await ctx.reload();
294
46
  }
295
47
  },
296
48
  });
297
- }
49
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-codex-marketplace",
3
- "version": "0.3.0",
4
- "description": "Bridge Package for Codex and Claude Marketplace compatibility in Pi — Global Bridge State, atomic persistence, and /codex-marketplace TUI",
3
+ "version": "0.5.0",
4
+ "description": "Bridge Package for Codex and Claude Marketplace compatibility in Pi — 極簡 /codex-marketplace 純文字指令(add/list/install/update/disable/enable/remove/forget)、單一 Global Bridge State、當下最新安裝與即時投影",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "pi-package",