pi-codex-marketplace 0.1.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/LICENSE +21 -0
- package/README.md +134 -0
- package/extensions/pi/git-registration.ts +138 -0
- package/extensions/pi/index.ts +293 -0
- package/extensions/pi/installation.ts +90 -0
- package/extensions/pi/journal.ts +80 -0
- package/extensions/pi/lifecycle.ts +285 -0
- package/extensions/pi/registration.ts +143 -0
- package/extensions/pi/scope-overrides.ts +170 -0
- package/package.json +60 -0
- package/src/barrier/global-barrier.ts +105 -0
- package/src/bridge-state/atomic.ts +237 -0
- package/src/bridge-state/index.ts +5 -0
- package/src/bridge-state/migrate.ts +261 -0
- package/src/bridge-state/paths.ts +75 -0
- package/src/bridge-state/repair.ts +185 -0
- package/src/bridge-state/schema.ts +70 -0
- package/src/bridge-state/store.ts +489 -0
- package/src/bridge-state/types.ts +170 -0
- package/src/cache/index.ts +2 -0
- package/src/cache/paths.ts +42 -0
- package/src/cache/source-cache.ts +365 -0
- package/src/compatibility/index.ts +1 -0
- package/src/compatibility/profile.ts +328 -0
- package/src/installation/flow.ts +443 -0
- package/src/installation/index.ts +1 -0
- package/src/installation/inspection.ts +129 -0
- package/src/journal/active-chains.ts +99 -0
- package/src/journal/index.ts +3 -0
- package/src/journal/journal.ts +215 -0
- package/src/journal/types.ts +49 -0
- package/src/lifecycle/index.ts +5 -0
- package/src/lifecycle/rebind.ts +290 -0
- package/src/lifecycle/refresh.ts +407 -0
- package/src/lifecycle/removal.ts +457 -0
- package/src/lifecycle/update-plan.ts +222 -0
- package/src/lifecycle/update.ts +303 -0
- package/src/projection/collision.ts +120 -0
- package/src/projection/effective-state.ts +182 -0
- package/src/projection/index.ts +4 -0
- package/src/projection/overrides.ts +230 -0
- package/src/projection/project.ts +359 -0
- package/src/reconciliation/startup.ts +144 -0
- package/src/registration/budget.ts +28 -0
- package/src/registration/catalog.ts +224 -0
- package/src/registration/contained.ts +140 -0
- package/src/registration/fence.ts +86 -0
- package/src/registration/findings.ts +188 -0
- package/src/registration/flow.ts +619 -0
- package/src/registration/git-acquisition.ts +481 -0
- package/src/registration/git-flow.ts +654 -0
- package/src/registration/git-locator.ts +380 -0
- package/src/registration/git-selector.ts +279 -0
- package/src/registration/index.ts +16 -0
- package/src/registration/receipt.ts +305 -0
- package/src/registration/registration.ts +102 -0
- package/src/registration/snapshot.ts +382 -0
- package/src/registration/source-key.ts +111 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sam Wang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# pi-codex-marketplace
|
|
2
|
+
|
|
3
|
+
Bridge Package for Codex Marketplace compatibility in Pi (`Pi 0.84.2`).
|
|
4
|
+
|
|
5
|
+
> **One-line:** `pi install pi-codex-marketplace` → `/codex-marketplace` shows Global / Project partitioned Bridge State and lifecycle controls with Validation Disclosure, dual Confirmation, and three-orthogonal Receipt reporting.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pi install pi-codex-marketplace # Global Scope (writes to ~/.pi/agent/settings.json)
|
|
11
|
+
pi install -l pi-codex-marketplace # Project Scope (.pi/settings.json)
|
|
12
|
+
pi install -e ./path/to/pi-codex-marketplace # External link (try without publishing)
|
|
13
|
+
pi update pi-codex-marketplace # Update to latest compatible Bridge Package
|
|
14
|
+
pi remove pi-codex-marketplace # Remove Registration + Installations atomically
|
|
15
|
+
pi -e ./path/to/repo # Ephemeral run without installing
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
All `pi install / -e / -l / update / remove` semantics are handled by the Pi host; this package declares a single `pi` extension entry (`extensions/pi/index.ts`) loaded via `jiti` and requires no build step.
|
|
19
|
+
|
|
20
|
+
Requirements: **Pi 0.84.2**, **Node >=22.19.0**, **macOS / Linux** (Windows not supported).
|
|
21
|
+
|
|
22
|
+
## Usage — `/codex-marketplace` (聚合指令)
|
|
23
|
+
|
|
24
|
+
Single aggregated command in Pi TUI, faithful to `prototype/tui-management-flow@c9107d2`:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
/codex-marketplace
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
混合發現/導向 (hybrid discovery/guided) flow — every mutation forces **explicit scope choice → Validation Disclosure → State Revision + Validation Snapshot bound confirmation (Default No, never remembered/batched) → scope-atomic commit → immediate reload → three-orthogonal Receipt report**:
|
|
31
|
+
|
|
32
|
+
- **檢視 Global / Project 分區** — partitioned list of `Global Scope` (`{getAgentDir()}/codex-marketplace/state.json`, baseline) and `Project Scope` (`{cwd}/.pi/codex-marketplace/state.json`, overlay + sparse overrides). Shows `schemaVersion`, `stateRevision`, partitioned registrations/installations/overrides, empty-state guidance, and provenance notes.
|
|
33
|
+
- **註冊本地 / Git Marketplace…** — local or Git Marketplace Source Registration. 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, scope, Marketplace name, 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 same-scope attempt is blocked by Attempt Fence (`FENCE-01`); changed State Revision or snapshot yields `Rejected as Stale` (`STALE-01/02`).
|
|
34
|
+
- **安裝 Compatible Plugin…** — browse registered Marketplace Entries by **Marketplace Entry ID** (`/plugins/<序號>`) with explicit Unavailable reason (unsupported source kind / parse failure / Invalid / Incompatible / Plugin ID collision). Compatibility Profile v1 classifies every Plugin atomically 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 → Project → 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.
|
|
35
|
+
- **管理已安裝 Plugin(Enable / Disable)…** — toggle Installation State; disabling preserves provenance, enabling re-validates under current profile/ruleset/budget.
|
|
36
|
+
- **建立 / 移除 Scope Override…** — fine-grained Project Scope suppression of inherited Global records (Registration override suppresses marketplace subtree, Installation override suppresses single Plugin). Removing an override reveals the inherited record immediately without mutating the global document. Effective State view marks every record's participation and suppression reason.
|
|
37
|
+
- **檢視 Effective State 與 Projected Skills…** — read-time derived Effective State (`global-baseline + project-additions – overrides`, only `enabled` installations, `project-over-global` Plugin ID precedence) and Projected Skills with Runtime Skill Collision resolution (`Pi → Project Scope → Global Scope` exact name layering, same-scope Bridge colliders all unavailable, only surviving higher-layer skill reserves the name, lower layer survives when no higher contender). Whole-Plugin Blocking Findings block the Plugin; collision affects only that skill; `Available` is established only by independent host evidence (`AVAIL-01`).
|
|
38
|
+
- **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 scope-atomic Lifecycle Operation replacing the Registration's Validation Snapshot and applying every disclosed same-scope consequence without mixing revisions.
|
|
39
|
+
- **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.
|
|
40
|
+
- **移除 Registration / Installation…** — Registration Removal discloses that all same-scope Installations will be atomically removed; Installation Removal discloses which inherited Installation will become effective afterward.
|
|
41
|
+
- **檢視 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.
|
|
42
|
+
- **執行 State Repair…** — explicit, fence-guarded verification and recovery of a degraded Bridge State; never auto-retried.
|
|
43
|
+
|
|
44
|
+
Diagnostics throughout are shown with **synchronized ordering** `class → phase → target → pointer → rule` and **closed rule codes** (`CONT-01`, `COMP-02`, `BARRIER-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. A held Global Scope Attempt Fence, `Persistence Indeterminate`, or Receipt Journal degradation triggers the **Global Pending Barrier** that blocks every Project Scope mutation/application (but leaves inspection/Refresh available) until global recovery (`global-first`). Pending state is reconciled at startup by producing a new reconciling receipt without implicit retry or rollback.
|
|
45
|
+
|
|
46
|
+
## Bridge State storage & migration
|
|
47
|
+
|
|
48
|
+
Bridge State is the sole authority, stored as **two scope-local documents**:
|
|
49
|
+
|
|
50
|
+
```jsonc
|
|
51
|
+
{
|
|
52
|
+
"schemaVersion": 1,
|
|
53
|
+
"stateRevision": "1", // opaque monotonic per scope (string "0" -> "1" -> "2" ...)
|
|
54
|
+
"registrations": [], // immutable Registration ID = UUIDv4 allocated before preflight
|
|
55
|
+
"installations": [], // Installed Plugins (enabled/disabled), each bound to a Validation Snapshot
|
|
56
|
+
"scopeOverrides": [] // Project-only: sparse suppression of Global records
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Only authoritative fields are persisted. `Effective State`, catalogs, compatibility results, diagnostics are **derived at read time**.
|
|
61
|
+
|
|
62
|
+
- `State Revision` increments monotonically under file lock; commit is CAS-guarded by `expectedStateRevision` when supplied.
|
|
63
|
+
- Writes are **atomic**: `write-to-temp → fsync → rename` + `fsync` parent dir + **file lock** (`.lock` sibling) + **WAL** (`state.json.wal`) + **read-after-verify**.
|
|
64
|
+
- 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.
|
|
65
|
+
- **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. 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.
|
|
66
|
+
- 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`.
|
|
67
|
+
|
|
68
|
+
See `src/bridge-state/` for `store.ts`, `atomic.ts`, `schema.ts`, `migrate.ts`, `paths.ts`.
|
|
69
|
+
|
|
70
|
+
## Project Trust
|
|
71
|
+
|
|
72
|
+
Project Scope mutations and Effective-State participation require Pi's `Project Trust` (host-owned, never granted/persisted by this package). Without trust, project records remain stored but excluded from Effective State and no Project Scope Lifecycle Operation may mutate them.
|
|
73
|
+
|
|
74
|
+
## Support matrix
|
|
75
|
+
|
|
76
|
+
| Dimension | Supported | Notes |
|
|
77
|
+
|-----------|-----------|-------|
|
|
78
|
+
| OS | **macOS**, **Linux** | Windows not supported (path containment, symlink, `flock` semantics are POSIX-only) |
|
|
79
|
+
| Node | **>=22.19.0** | `engines.node` enforced; `npm-shrinkwrap.json` pins Pi 0.84.2 host |
|
|
80
|
+
| 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. |
|
|
81
|
+
| Semantics | `pi install` / `pi install -e` / `pi install -l` / `pi update` / `pi remove` | Single `pi` extension package; `files` ships `extensions/`, `src/`, `README.md`, `LICENSE` only |
|
|
82
|
+
|
|
83
|
+
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.
|
|
84
|
+
|
|
85
|
+
## Versioning & release flow
|
|
86
|
+
|
|
87
|
+
- **Package**: `pi-codex-marketplace` published to **npm** as primary, **Git tag** `v*` as mirror.
|
|
88
|
+
- **SemVer**: starts at `0.1.0`; `0.y` maintenance window until `1.0.0` signals a stable Bridge State contract.
|
|
89
|
+
- **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.
|
|
90
|
+
- **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`.
|
|
91
|
+
- **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.
|
|
92
|
+
|
|
93
|
+
## Verification matrix (發版阻擋 gate)
|
|
94
|
+
|
|
95
|
+
Every row is a **release blocker**: `v*` may not publish unless the full matrix is green.
|
|
96
|
+
|
|
97
|
+
| Layer | Fixture | OS | Node | Pi host | What is covered |
|
|
98
|
+
|-------|---------|----|------|---------|-----------------|
|
|
99
|
+
| unit | **synthetic** | macOS + Linux | 22.19.0 | — | selector/locator normalization, Contained Path/Symlink, budget, compatibility atomic classification, precedence, collision, fence/sync ordering |
|
|
100
|
+
| unit | **pinned** `SamWang32191/codex-plugins@98e78ca` | macOS + Linux | 22.19.0 | — | catalog parsing + validation against a real pinned marketplace snapshot (fingerprint-stable) |
|
|
101
|
+
| unit | **adversarial** | macOS + Linux | 22.19.0 | — | malformed manifests, path-escapes, symlink loops, budget overflows, ID collisions, parser depth |
|
|
102
|
+
| 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), Global Barrier |
|
|
103
|
+
| 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 |
|
|
104
|
+
| integration | adversarial | macOS + Linux | 22.19.0 | 0.84.2 | Source Drift (Blocking), Stale Snapshot rejection, Persistence Indeterminate fail-closed, Fence/Barrier admission, Cache stale-snapshot never promotes |
|
|
105
|
+
| E2E (highest seam — **TUI**) | synthetic | macOS + Linux | 22.19.0 | **0.84.2** | `/codex-marketplace` → scope choice → disclosure → confirmation → commit → reload → receipt (three-orthogonal) → partitioned list → skill-granular `Available` |
|
|
106
|
+
| 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 → Override → Barrier) with fence/cache/external observability |
|
|
107
|
+
| E2E (highest seam — **TUI**) | adversarial | macOS + Linux | 22.19.0 | **0.84.2** | collision (`Pi → Project → Global`), barrier (`Global Pending` blocks project, Refresh/inspect still allowed), cache (`offline exact fingerprint hit` vs `stale never success`) |
|
|
108
|
+
|
|
109
|
+
Fixtures: `tests/fixtures/synthetic/`, `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).
|
|
110
|
+
|
|
111
|
+
Run locally:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm run typecheck
|
|
115
|
+
npm test # full matrix (unit + integration + E2E)
|
|
116
|
+
npm run test:acceptance # acceptance matrix only (three-tier × three-fixture)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm install
|
|
123
|
+
npm run typecheck
|
|
124
|
+
npm test
|
|
125
|
+
npm run test:acceptance
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Domain vocabulary
|
|
129
|
+
|
|
130
|
+
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.).
|
|
131
|
+
|
|
132
|
+
## Changelog & Releases
|
|
133
|
+
|
|
134
|
+
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`).
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Marketplace Registration — interactive TUI flow (Issue #18).
|
|
3
|
+
* Mirrors local registration contract: explicit scope → Git locator + Git Selector
|
|
4
|
+
* → normalization (Canonical Locator + selector canonical) + Acquisition Trust Base + snapshot
|
|
5
|
+
* → Validation Disclosure → Registration Confirmation (Snapshot+Revision bound, Default No) → atomic commit → Attempt Receipt.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Theme } from '@earendil-works/pi-coding-agent';
|
|
9
|
+
import type { ExtensionCommandContext, ExtensionUIContext } from '@earendil-works/pi-coding-agent';
|
|
10
|
+
import { truncateToWidth } from '@earendil-works/pi-tui';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
preflightGitRegistration,
|
|
14
|
+
confirmGitRegistration,
|
|
15
|
+
disclosureSummaryGit,
|
|
16
|
+
} from '../../src/registration/git-flow.js';
|
|
17
|
+
import type { GitSelectorInput } from '../../src/registration/git-selector.js';
|
|
18
|
+
import type { GitRegistrationOutcome as RegistrationOutcome } from '../../src/registration/git-flow.js';
|
|
19
|
+
import { formatFindings, reportOutcome } from './registration.js';
|
|
20
|
+
|
|
21
|
+
class DisclosureComponent {
|
|
22
|
+
private lines: string[];
|
|
23
|
+
private theme: Theme;
|
|
24
|
+
private onClose: () => void;
|
|
25
|
+
|
|
26
|
+
constructor(lines: string[], theme: Theme, onClose: () => void) {
|
|
27
|
+
this.lines = lines;
|
|
28
|
+
this.theme = theme;
|
|
29
|
+
this.onClose = onClose;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
handleInput(data: string): void {
|
|
33
|
+
if (data) this.onClose();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
render(width: number): string[] {
|
|
37
|
+
const th = this.theme;
|
|
38
|
+
const out: string[] = [];
|
|
39
|
+
out.push('');
|
|
40
|
+
out.push(truncateToWidth(th.fg('accent', th.bold(' Validation Disclosure (Git) ')) + th.fg('borderMuted', '─'.repeat(Math.max(0, width - 26))), width));
|
|
41
|
+
for (const ln of this.lines) {
|
|
42
|
+
out.push(truncateToWidth(` ${ln}`, width));
|
|
43
|
+
}
|
|
44
|
+
out.push('');
|
|
45
|
+
out.push(truncateToWidth(` ${th.fg('dim', 'Any key: continue to Registration Confirmation (Default No) · Confirm is snapshot + State Revision bound')}`, width));
|
|
46
|
+
out.push(truncateToWidth(` ${th.fg('dim', 'Canonical Locator 與 Git Selector 正規化結果已於上方披露;Acquisition 採 clone --no-checkout 且未執行 hooks/filters/submodules')}`, width));
|
|
47
|
+
out.push('');
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
invalidate(): void {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function runGitRegistrationFlow(ctx: ExtensionCommandContext): Promise<void> {
|
|
55
|
+
const ui: ExtensionUIContext = ctx.ui;
|
|
56
|
+
const scopeChoice = await ui.select('Marketplace Registration (Git) — 選擇 Scope', [
|
|
57
|
+
'Global Scope',
|
|
58
|
+
'Project Scope',
|
|
59
|
+
]);
|
|
60
|
+
if (!scopeChoice) {
|
|
61
|
+
ui.notify('已取消 Git Registration', 'info');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const scope: 'global' | 'project' = scopeChoice.startsWith('Global') ? 'global' : 'project';
|
|
65
|
+
|
|
66
|
+
const locator = await ui.input('Git Marketplace Locator(https:// 或 ssh:// 或 scp-like user@host:path,無憑證、無 query/fragment)', 'https://github.com/owner/repo.git');
|
|
67
|
+
if (!locator) {
|
|
68
|
+
ui.notify('已取消 Git Registration', 'info');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const selectorKindChoice = await ui.select('Git Selector — 選擇型別', [
|
|
73
|
+
'default (跟隨遠端預設分支 HEAD)',
|
|
74
|
+
'branch (→ refs/heads/*)',
|
|
75
|
+
'tag (→ refs/tags/*)',
|
|
76
|
+
'commit (小寫完整 40/64 hex)',
|
|
77
|
+
]);
|
|
78
|
+
if (!selectorKindChoice) {
|
|
79
|
+
ui.notify('已取消 Git Registration', 'info');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
let selectorInput: GitSelectorInput;
|
|
83
|
+
if (selectorKindChoice.startsWith('default')) {
|
|
84
|
+
selectorInput = { kind: 'default' };
|
|
85
|
+
} else if (selectorKindChoice.startsWith('branch')) {
|
|
86
|
+
const branch = await ui.input('Branch 名稱(例:main / feature/foo,將正規化為 refs/heads/<name>)', 'main');
|
|
87
|
+
if (!branch) {
|
|
88
|
+
ui.notify('已取消 Git Registration', 'info');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
selectorInput = { kind: 'branch', value: branch };
|
|
92
|
+
} else if (selectorKindChoice.startsWith('tag')) {
|
|
93
|
+
const tag = await ui.input('Tag 名稱(例:v1.2.3,將正規化為 refs/tags/<name>)', 'v1.0.0');
|
|
94
|
+
if (!tag) {
|
|
95
|
+
ui.notify('已取消 Git Registration', 'info');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
selectorInput = { kind: 'tag', value: tag };
|
|
99
|
+
} else {
|
|
100
|
+
const commit = await ui.input('Commit(完整 40 或 64 hex,將轉為小寫)', 'abc123def456abc123def456abc123def456abcd12');
|
|
101
|
+
if (!commit) {
|
|
102
|
+
ui.notify('已取消 Git Registration', 'info');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
selectorInput = { kind: 'commit', value: commit };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const opts = { cwd: ctx.cwd, projectTrusted: ctx.isProjectTrusted() };
|
|
109
|
+
const res = await preflightGitRegistration(scope, locator, selectorInput, opts);
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
reportOutcome(ctx, res.outcome);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const pf = res.preflight;
|
|
116
|
+
const lines = [
|
|
117
|
+
...disclosureSummaryGit(pf).split('\n'),
|
|
118
|
+
'',
|
|
119
|
+
...formatFindings(pf.findings),
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
if (ctx.mode !== 'tui') {
|
|
123
|
+
ui.notify('Git Registration 需要 TUI 模式; disclosure:\n' + lines.join('\n'), 'info');
|
|
124
|
+
} else {
|
|
125
|
+
await ui.custom<void>(
|
|
126
|
+
(_tui, theme, _kb, done) =>
|
|
127
|
+
new DisclosureComponent(lines, theme, () => done(undefined)),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const yes = await ui.confirm(
|
|
132
|
+
'Registration Confirmation — 預設 No(綁定 State Revision + Validation Snapshot,不可記憶、不可批次)',
|
|
133
|
+
`確認註冊 ${pf.locator.canonicalUrl}#${pf.selector.canonical} (${pf.resolvedRevision.slice(0, 8)}…) 至 ${scope}?\n${lines.slice(0, 10).join('\n')}`,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const outcome = await confirmGitRegistration(pf, yes, opts);
|
|
137
|
+
reportOutcome(ctx, outcome);
|
|
138
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge Extension — Pi runtime entry for pi-codex-marketplace
|
|
3
|
+
* Single extension "pi" package, Pi 0.84.2 compatible.
|
|
4
|
+
*
|
|
5
|
+
* Provides:
|
|
6
|
+
* - /codex-marketplace command: partitioned Global Scope / Project Scope empty state
|
|
7
|
+
* - Bridge State reading via dual-document store (global + project)
|
|
8
|
+
* - Startup Reconciliation on session_start
|
|
9
|
+
* - Receipt Journal inspection & State Repair flows
|
|
10
|
+
*
|
|
11
|
+
* Domain vocabulary follows CONTEXT.md (Bridge Package, Bridge Extension, Bridge State, State Revision, etc.)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ExtensionAPI, Theme } from '@earendil-works/pi-coding-agent';
|
|
15
|
+
import { truncateToWidth } from '@earendil-works/pi-tui';
|
|
16
|
+
|
|
17
|
+
import { readBridgeStateSync } from '../../src/bridge-state/store.js';
|
|
18
|
+
import type { BridgeState, ReadResult } from '../../src/bridge-state/types.js';
|
|
19
|
+
import { runStartupReconciliation } from '../../src/reconciliation/startup.js';
|
|
20
|
+
import { formatThreeOrthogonalReport } from '../../src/registration/receipt.js';
|
|
21
|
+
import { checkGlobalPendingBarrier } from '../../src/barrier/global-barrier.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 {
|
|
27
|
+
runEffectiveStateView,
|
|
28
|
+
runRemoveScopeOverrideFlow,
|
|
29
|
+
runScopeOverrideFlow,
|
|
30
|
+
} from './scope-overrides.js';
|
|
31
|
+
import { runReceiptJournalView, runRepairStateFlow } from './journal.js';
|
|
32
|
+
|
|
33
|
+
// Closed helper to format state summary for disclosure
|
|
34
|
+
function formatStateSummary(result: ReadResult, scopeLabel: string): string {
|
|
35
|
+
if (result.status === 'missing') {
|
|
36
|
+
const s = result.state!;
|
|
37
|
+
return `${scopeLabel}: empty · schema v${s.schemaVersion} · revision ${s.stateRevision} · 0 registrations · 0 installations`;
|
|
38
|
+
}
|
|
39
|
+
if (result.status === 'ok') {
|
|
40
|
+
const s = result.state!;
|
|
41
|
+
const regCount = s.registrations.length;
|
|
42
|
+
const instEnabled = s.installations.filter((i) => i.installationState === 'enabled').length;
|
|
43
|
+
const instDisabled = s.installations.filter((i) => i.installationState === 'disabled').length;
|
|
44
|
+
const ov = s.scopeOverrides.length;
|
|
45
|
+
const ovPart = scopeLabel === 'Project Scope' ? ` · ${ov} overrides` : '';
|
|
46
|
+
return `${scopeLabel}: revision ${s.stateRevision} · ${regCount} registrations · ${instEnabled} enabled / ${instDisabled} disabled${ovPart}`;
|
|
47
|
+
}
|
|
48
|
+
if (result.status === 'incompatible') {
|
|
49
|
+
return `${scopeLabel}: incompatible — ${result.error} (requires newer Bridge Package)`;
|
|
50
|
+
}
|
|
51
|
+
return `${scopeLabel}: corrupted — ${result.error} (Persistence Indeterminate, no auto-rollback)`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class MarketplaceComponent {
|
|
55
|
+
private theme: Theme;
|
|
56
|
+
private onClose: () => void;
|
|
57
|
+
private global: ReadResult;
|
|
58
|
+
private project: ReadResult;
|
|
59
|
+
private cwd: string;
|
|
60
|
+
private width?: number;
|
|
61
|
+
private cached?: string[];
|
|
62
|
+
|
|
63
|
+
constructor(
|
|
64
|
+
global: ReadResult,
|
|
65
|
+
project: ReadResult,
|
|
66
|
+
cwd: string,
|
|
67
|
+
theme: Theme,
|
|
68
|
+
onClose: () => void,
|
|
69
|
+
) {
|
|
70
|
+
this.global = global;
|
|
71
|
+
this.project = project;
|
|
72
|
+
this.cwd = cwd;
|
|
73
|
+
this.theme = theme;
|
|
74
|
+
this.onClose = onClose;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
handleInput(data: string): void {
|
|
78
|
+
if (data === '\x1b' || data === 'q' || data === '\x03') {
|
|
79
|
+
this.onClose();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
render(width: number): string[] {
|
|
84
|
+
if (this.cached && this.width === width) return this.cached;
|
|
85
|
+
const th = this.theme;
|
|
86
|
+
const lines: string[] = [];
|
|
87
|
+
const hr = th.fg('borderMuted', '─'.repeat(Math.max(0, width - 2)));
|
|
88
|
+
|
|
89
|
+
lines.push('');
|
|
90
|
+
lines.push(truncateToWidth(th.fg('accent', th.bold(' Codex Marketplace ')) + th.fg('borderMuted', '─'.repeat(Math.max(0, width - 22))), width));
|
|
91
|
+
lines.push(truncateToWidth(` ${th.fg('dim', 'Bridge State · partitioned by Global Scope / Project Scope · State Revision per scope · Effective State derived at read time')}`, width));
|
|
92
|
+
lines.push('');
|
|
93
|
+
|
|
94
|
+
// Global Section
|
|
95
|
+
lines.push(truncateToWidth(` ${th.fg('accent', '▸ Global Scope')} ${th.fg('dim', formatStateSummary(this.global, 'Global Scope'))}`, width));
|
|
96
|
+
lines.push(truncateToWidth(` ${th.fg('dim', 'Global document: {getAgentDir()}/codex-marketplace/state.json — authoritative fields only: schemaVersion / stateRevision / registrations / installations / scopeOverrides')}`, width));
|
|
97
|
+
if (this.global.status === 'ok' || this.global.status === 'missing') {
|
|
98
|
+
const s: BridgeState = this.global.state!;
|
|
99
|
+
if (s.registrations.length === 0 && s.installations.length === 0) {
|
|
100
|
+
lines.push(truncateToWidth(` ${th.fg('muted', '— No marketplace registrations —')}`, width));
|
|
101
|
+
lines.push(truncateToWidth(` ${th.fg('dim', 'Empty registration list — use the Registration flow (「註冊本地 Marketplace…」menu) to add a local Marketplace Source. Each Registration gets an immutable Registration ID (UUIDv4) before preflight.')}`, width));
|
|
102
|
+
lines.push(truncateToWidth(` ${th.fg('dim', 'Projected Plugins will appear here once installations are created. Collision is per-skill; whole-Plugin classification is atomic.')}`, width));
|
|
103
|
+
} else {
|
|
104
|
+
for (const r of s.registrations) {
|
|
105
|
+
lines.push(truncateToWidth(` ${th.fg('text', `• ${r.alias ?? r.marketplaceName ?? r.id.slice(0, 8)}`)} ${th.fg('dim', `(${r.sourceKind ?? 'unknown'} · ${r.id.slice(0, 8)}…)`)}`, width));
|
|
106
|
+
}
|
|
107
|
+
for (const inst of s.installations) {
|
|
108
|
+
const badge = inst.installationState === 'enabled' ? th.fg('success', 'enabled') : th.fg('dim', 'disabled');
|
|
109
|
+
lines.push(truncateToWidth(` ${th.fg('muted', inst.pluginId)} — ${badge}`, width));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
lines.push(truncateToWidth(` ${th.fg('error', this.global.status === 'incompatible' ? 'Incompatible schema — update Bridge Package' : 'Persistence Indeterminate — file corrupted, no auto-rollback')}`, width));
|
|
114
|
+
if (this.global.error) lines.push(truncateToWidth(` ${th.fg('dim', this.global.error)}`, width));
|
|
115
|
+
}
|
|
116
|
+
lines.push('');
|
|
117
|
+
|
|
118
|
+
// Project Section
|
|
119
|
+
lines.push(truncateToWidth(` ${th.fg('accent', '▸ Project Scope')} ${th.fg('dim', formatStateSummary(this.project, 'Project Scope'))}`, width));
|
|
120
|
+
lines.push(truncateToWidth(` ${th.fg('dim', `Project document: ${this.cwd}/.pi/codex-marketplace/state.json — Project Trust gates mutation/effective participation; overrides suppress Global without mutating it`)}`, width));
|
|
121
|
+
if (this.project.status === 'ok' || this.project.status === 'missing') {
|
|
122
|
+
const s: BridgeState = this.project.state!;
|
|
123
|
+
if (s.registrations.length === 0 && s.installations.length === 0 && s.scopeOverrides.length === 0) {
|
|
124
|
+
lines.push(truncateToWidth(` ${th.fg('muted', '— No project registrations —')}`, width));
|
|
125
|
+
lines.push(truncateToWidth(` ${th.fg('dim', 'Project Scope inherits Global registrations via Effective State; add project-local registrations or Scope Overrides to diverge.')}`, width));
|
|
126
|
+
lines.push(truncateToWidth(` ${th.fg('dim', 'Overrides are sparse, keyed by Registration ID / Installation ID; removing an override reveals the inherited Global record.')}`, width));
|
|
127
|
+
} else {
|
|
128
|
+
for (const r of s.registrations) {
|
|
129
|
+
lines.push(truncateToWidth(` ${th.fg('text', `• ${r.alias ?? r.marketplaceName ?? r.id.slice(0, 8)}`)} ${th.fg('dim', `(${r.sourceKind ?? 'unknown'} · ${r.id.slice(0, 8)}…)`)}`, width));
|
|
130
|
+
}
|
|
131
|
+
for (const inst of s.installations) {
|
|
132
|
+
const badge = inst.installationState === 'enabled' ? th.fg('success', 'enabled') : th.fg('dim', 'disabled');
|
|
133
|
+
lines.push(truncateToWidth(` ${th.fg('muted', inst.pluginId)} — ${badge}`, width));
|
|
134
|
+
}
|
|
135
|
+
for (const ov of s.scopeOverrides) {
|
|
136
|
+
lines.push(truncateToWidth(` ${th.fg('warning', `⊘ override ${ov.kind} ${ov.targetId.slice(0, 8)}…`)}`, width));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
lines.push(truncateToWidth(` ${th.fg('error', this.project.status === 'incompatible' ? 'Incompatible schema — update Bridge Package' : 'Persistence Indeterminate — file corrupted, no auto-rollback')}`, width));
|
|
141
|
+
if (this.project.error) lines.push(truncateToWidth(` ${th.fg('dim', this.project.error)}`, width));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
lines.push('');
|
|
145
|
+
lines.push(truncateToWidth(hr, width));
|
|
146
|
+
lines.push(truncateToWidth(` ${th.fg('dim', 'Bridge State holds only registrations / installations (with Installation State) / scopeOverrides / schemaVersion / stateRevision. Effective State, catalogs, compatibility, diagnostics are recomputed.')}`, width));
|
|
147
|
+
lines.push(truncateToWidth(` ${th.fg('dim', 'State Revision is opaque monotonic per scope; writes are atomic (temp→fsync→rename) under file lock with read-after-verify. Corrupted/unknown schema ⇒ Indeterminate/incompatible, never auto-rollback.')}`, width));
|
|
148
|
+
lines.push(truncateToWidth(` ${th.fg('dim', 'Scope Override / Effective State / Runtime Skill Collision flows are available: 建立或移除 Override、檢視投影與碰撞診斷。Available 僅由宿主獨立證據確立。')}`, width));
|
|
149
|
+
lines.push('');
|
|
150
|
+
lines.push(truncateToWidth(` ${th.fg('dim', 'Press Esc / q to close · 選擇相應選單以執行完整驗證、Attempt Summary 與 Recovery Action 的操作流程。')}`, width));
|
|
151
|
+
lines.push('');
|
|
152
|
+
|
|
153
|
+
this.width = width;
|
|
154
|
+
this.cached = lines;
|
|
155
|
+
return lines;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
invalidate(): void {
|
|
159
|
+
this.cached = undefined;
|
|
160
|
+
this.width = undefined;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default function (pi: ExtensionAPI) {
|
|
165
|
+
pi.on('session_start', async (_event, ctx) => {
|
|
166
|
+
// Startup reconciliation: Global-first pass
|
|
167
|
+
try {
|
|
168
|
+
const recon = await runStartupReconciliation({
|
|
169
|
+
cwd: ctx.cwd,
|
|
170
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
171
|
+
});
|
|
172
|
+
if (recon.globalReconciled && recon.globalReceipt) {
|
|
173
|
+
ctx.ui.notify(formatThreeOrthogonalReport(recon.globalReceipt), recon.globalReceipt.summary === 'Completed' ? 'info' : 'warning');
|
|
174
|
+
}
|
|
175
|
+
if (recon.projectReconciled && recon.projectReceipt) {
|
|
176
|
+
ctx.ui.notify(formatThreeOrthogonalReport(recon.projectReceipt), recon.projectReceipt.summary === 'Completed' ? 'info' : 'warning');
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
// Non-blocking in extension bootstrap
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
pi.registerCommand('codex-marketplace', {
|
|
184
|
+
description: 'Manage Codex Marketplaces — partitioned Global / Project Bridge State + lifecycle controls (prototype tui-management-flow@c9107d2)',
|
|
185
|
+
handler: async (args, ctx) => {
|
|
186
|
+
const cwd = ctx.cwd;
|
|
187
|
+
|
|
188
|
+
// Hybrid discovery/guided: support /codex-marketplace list|inspect <args> for non-TUI quick paths
|
|
189
|
+
const rawArgs = (args ?? '').trim();
|
|
190
|
+
if (rawArgs.length > 0 && (rawArgs.startsWith('list') || rawArgs.startsWith('inspect') || rawArgs === '--help' || rawArgs === '-h')) {
|
|
191
|
+
const global = readBridgeStateSync('global', { cwd });
|
|
192
|
+
const project = readBridgeStateSync('project', { cwd });
|
|
193
|
+
const g = formatStateSummary(global, 'Global Scope');
|
|
194
|
+
const p = formatStateSummary(project, 'Project Scope');
|
|
195
|
+
const barrier = await checkGlobalPendingBarrier({ cwd });
|
|
196
|
+
const banner = barrier.active ? `\n⚠ Global Pending Barrier 活躍:${barrier.reason}(專案變異已阻擋,僅檢查/Refresh 可用)` : '';
|
|
197
|
+
ctx.ui.notify(`${g}\n${p}${banner}\n(完整導向流請於 TUI 內執行 /codex-marketplace)` , barrier.active ? 'warning' : 'info');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Non-TUI fallback: notify with summary + barrier hint
|
|
202
|
+
if (ctx.mode !== 'tui' || !ctx.hasUI) {
|
|
203
|
+
const global = readBridgeStateSync('global', { cwd });
|
|
204
|
+
const project = readBridgeStateSync('project', { cwd });
|
|
205
|
+
const g = formatStateSummary(global, 'Global Scope');
|
|
206
|
+
const p = formatStateSummary(project, 'Project Scope');
|
|
207
|
+
const barrier = await checkGlobalPendingBarrier({ cwd });
|
|
208
|
+
const banner = barrier.active ? `\n⚠ Global Pending Barrier:${barrier.reason}` : '';
|
|
209
|
+
ctx.ui.notify(`${g}\n${p}${banner}\n互動流程需 TUI 模式(/codex-marketplace 於 TUI 內)`, barrier.active ? 'warning' : 'info');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// TUI mode: surface Pending/Global Barrier hint before menu (closed, per prototype Variant C banner)
|
|
214
|
+
const barrier = await checkGlobalPendingBarrier({ cwd });
|
|
215
|
+
if (barrier.active) {
|
|
216
|
+
ctx.ui.notify(`⚠ Global Pending Barrier 已阻擋所有 Project Scope 變異/套用(Reserve Global-first 復原):${barrier.reason}。仍可執行 檢查 / Refresh。`, 'warning');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const choice = await ctx.ui.select('Codex Marketplace — Bridge State', [
|
|
220
|
+
'檢視 Global / Project 分區',
|
|
221
|
+
'註冊本地 Marketplace…',
|
|
222
|
+
'註冊 Git Marketplace…',
|
|
223
|
+
'安裝 Compatible Plugin…',
|
|
224
|
+
'管理已安裝 Plugin(Enable / Disable)…',
|
|
225
|
+
'建立 Scope Override(抑制繼承全域紀錄)…',
|
|
226
|
+
'移除 Scope Override(還原繼承)…',
|
|
227
|
+
'檢視 Effective State 與 Projected Skills…',
|
|
228
|
+
'Refresh / 更新 Registration…',
|
|
229
|
+
'Rebind Registration(更換來源)…',
|
|
230
|
+
'移除 Registration / Installation…',
|
|
231
|
+
'檢視 Receipt Journal(Active Chains 與歷史)…',
|
|
232
|
+
'執行 State Repair(修復與驗證 Bridge State)…',
|
|
233
|
+
]);
|
|
234
|
+
if (!choice) return;
|
|
235
|
+
|
|
236
|
+
if (choice === '註冊本地 Marketplace…') {
|
|
237
|
+
await runLocalRegistrationFlow(ctx);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (choice === '註冊 Git Marketplace…') {
|
|
241
|
+
await runGitRegistrationFlow(ctx);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (choice === '安裝 Compatible Plugin…') {
|
|
245
|
+
await runPluginInstallationFlow(ctx);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (choice === '管理已安裝 Plugin(Enable / Disable)…') {
|
|
249
|
+
await runPluginStateFlow(ctx);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (choice === '建立 Scope Override(抑制繼承全域紀錄)…') {
|
|
253
|
+
await runScopeOverrideFlow(ctx);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (choice === '移除 Scope Override(還原繼承)…') {
|
|
257
|
+
await runRemoveScopeOverrideFlow(ctx);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (choice === '檢視 Effective State 與 Projected Skills…') {
|
|
261
|
+
await runEffectiveStateView(ctx);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (choice === 'Refresh / 更新 Registration…') {
|
|
265
|
+
await runRefreshFlow(ctx);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (choice === 'Rebind Registration(更換來源)…') {
|
|
269
|
+
await runRebindFlow(ctx);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (choice === '移除 Registration / Installation…') {
|
|
273
|
+
await runRemovalFlow(ctx);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (choice === '檢視 Receipt Journal(Active Chains 與歷史)…') {
|
|
277
|
+
await runReceiptJournalView(ctx);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (choice === '執行 State Repair(修復與驗證 Bridge State)…') {
|
|
281
|
+
await runRepairStateFlow(ctx);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const global = readBridgeStateSync('global', { cwd });
|
|
286
|
+
const project = readBridgeStateSync('project', { cwd });
|
|
287
|
+
|
|
288
|
+
await ctx.ui.custom<void>((_tui, theme, _kb, done) => {
|
|
289
|
+
return new MarketplaceComponent(global, project, cwd, theme, () => done());
|
|
290
|
+
});
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
}
|