BPlatform specification
Login
featureStandard

Package manager panel

VS Code Packages view — local project dependencies and registry search via pckg HTTP and CLI mutations.

Document layout: Feature capability layoutconforms
  • ## Purpose
  • ## Requirements

Package manager panel Specification

Purpose

VS Code Packages view — local project dependencies and registry search via pckg HTTP and CLI mutations.

Requirements

Requirement: pckgClient boundary: Decision [D-TOOL-VSC-0001]

The Beskid standard SHALL enforce the following migrated contract section. Accepted ADR decisions are binding; uppercase requirement keywords retain their BCP-14 meaning.

HTTP via pckgClient.ts only.

Stable ID: BSP-REQ-2FDD261EF9D4
Legacy source: site/spec-content/platform-spec/tooling/vscode-extension/package-manager-panel/adr/0001-single-pckg-client-boundary/content.md
Source SHA-256: 1ac2c6e51a22e15258f76f49814ae689d9580b8d4d0ae9301f191878a129f6fa

Scenario: Conformance exercises Decision

  • GIVEN an implementation claims conformance with this capability
  • WHEN behavior governed by this contract section is exercised
  • THEN every MUST, SHALL, REQUIRED, prohibition, and accepted decision in the section is satisfied

Requirement: CLI for lock: Decision [D-TOOL-VSC-0002]

The Beskid standard SHALL enforce the following migrated contract section. Accepted ADR decisions are binding; uppercase requirement keywords retain their BCP-14 meaning.

Use beskid fetch/lock via CLI.

Stable ID: BSP-REQ-A626F4317E12
Legacy source: site/spec-content/platform-spec/tooling/vscode-extension/package-manager-panel/adr/0002-cli-for-lock-mutations/content.md
Source SHA-256: c8eea5ca7263d7073414caf1faba17a395b08f16485e9b2c2383ac3d59bd0172

Scenario: Conformance exercises Decision

  • GIVEN an implementation claims conformance with this capability
  • WHEN behavior governed by this contract section is exercised
  • THEN every MUST, SHALL, REQUIRED, prohibition, and accepted decision in the section is satisfied

Informative Source Provenance

The records below preserve migration history and are not normative except where text was extracted into a requirement above.

Source Record: Package manager panel

Authority: informative provenance
Legacy path: /platform-spec/tooling/vscode-extension/package-manager-panel/
Source: site/spec-content/platform-spec/tooling/vscode-extension/package-manager-panel/content.md
SHA-256: 6b41bd529b527e1f2ff2a61db879a28d90c5fee8fce527cd8524d7d0fa8678c6

Migrated source text
<SpecSection title="What this feature specifies" id="what-this-feature-specifies">
Packages activity-bar view: local dependency listing and registry browse/search with cached pckg HTTP and CLI fetch/lock.
</SpecSection>

<SpecSection title="Implementation anchors" id="implementation-anchors">
- `beskid_vscode/src/packages/pckgClient.ts`, `PackageManagerProvider.ts`
- `beskid_vscode/src/cli/beskidCliRunner.ts`
- `compiler/crates/beskid_pckg_server/Features/Packages/Contracts.cs`
</SpecSection>

<SpecSection title="Contract statement" id="contract-statement">
The **package manager panel** (`beskidPackagesView`) provides two persistent sections: **This project** (declared + `Project.lock` entries from LSP) and **Registry search** (debounced `pckg` HTTP with TTL cache). Lock/fetch mutations run through the **Beskid CLI** (`beskid fetch`, `beskid lock`) with progress on the shared status bar.
</SpecSection>

<SpecSection title="Inputs and outputs" id="inputs-and-outputs">
| API | Endpoint / command |
| --- | --- |
| Search | `GET /api/search?q=&limit=` |
| Details | `GET /api/packages/{name}` |
| Local deps | `beskid.getProjectDependencies` |
| Fetch / lock | `beskid fetch`, `beskid lock` (CLI, `--project` cwd = focused project root) |

Registry base URL resolution order: workspace `default` registry from `beskid.getWorkspaceSummary` → `beskid.pckg.baseUrl` → `https://pckg.beskid-lang.org`.
</SpecSection>

<SpecSection title="Decisions" id="decisions">
No open decisions. **`D-TOOL-VSC-0001`** (pckgClient boundary), **`0002`** (CLI for fetch/lock)—see **`adr/`** and the **ADRs** tab.
</SpecSection>

## Decisions
<!-- spec:generate:adr-index -->
No open decisions. Closed choices are normative ADRs under **`adr/`** (`D-TOOL-VSC-0001` … `D-TOOL-VSC-0002`); use the reader **ADRs** tab for expandable detail.
<!-- /spec:generate:adr-index -->
## Articles
<!-- spec:generate:article-index -->
- [Contracts and edge cases](./articles/contracts-and-edge-cases/)
- [Decisions record (legacy index)](./articles/decisions-record/)
- [Design model](./articles/design-model/)
- [Examples](./articles/examples/)
- [Flow and algorithm](./articles/flow-and-algorithm/)
<!-- /spec:generate:article-index -->

Source Record: pckgClient boundary

Authority: informative provenance
Legacy path: /platform-spec/tooling/vscode-extension/package-manager-panel/adr/0001-single-pckg-client-boundary/
Source: site/spec-content/platform-spec/tooling/vscode-extension/package-manager-panel/adr/0001-single-pckg-client-boundary/content.md
SHA-256: 1ac2c6e51a22e15258f76f49814ae689d9580b8d4d0ae9301f191878a129f6fa

Migrated source text
## Context

HTTP scatter.

## Decision

HTTP via pckgClient.ts only.

## Consequences

Shared auth.

## Verification anchors

Grep fetch in packages/.

Source Record: CLI for lock

Authority: informative provenance
Legacy path: /platform-spec/tooling/vscode-extension/package-manager-panel/adr/0002-cli-for-lock-mutations/
Source: site/spec-content/platform-spec/tooling/vscode-extension/package-manager-panel/adr/0002-cli-for-lock-mutations/content.md
SHA-256: c8eea5ca7263d7073414caf1faba17a395b08f16485e9b2c2383ac3d59bd0172

Migrated source text
## Context

Lock integrity.

## Decision

Use beskid fetch/lock via CLI.

## Consequences

Shared resolver.

## Verification anchors

Panel CLI invocations.

Source Record: Contracts and edge cases

Authority: informative provenance
Legacy path: /platform-spec/tooling/vscode-extension/package-manager-panel/articles/contracts-and-edge-cases/
Source: site/spec-content/platform-spec/tooling/vscode-extension/package-manager-panel/articles/contracts-and-edge-cases/content.md
SHA-256: 2bae5c3fe1fa6844c506f9ccb55e2b533eabe54287c82b8ced36aaaa07acdd34

Migrated source text
## Purpose and scope

Testable rules for **`pckgClient`**, registry auth, CLI mutations, and Packages view commands.

## Settings

| Setting | Type | Default | Rule |
| --- | --- | --- | --- |
| `beskid.pckg.baseUrl` | string | `https://pckg.beskid-lang.org` | **Must** be absolute HTTP(S) URL without trailing slash normalization issues |
| `beskid.pckg.apiKey` | string (secret) | empty | Stored via `SecretStorage`; **must not** sync to global settings |

## Authentication

| ID | Rule |
| --- | --- |
| A-01 | When `beskid.pckg.apiKey` is set, `createPckgFetch` **must** send `Authorization: Bearer <token>` on every registry request. |
| A-02 | Auth scheme **must** match pckg `ApiKeyAuthentication` (Bearer). |
| A-03 | On HTTP 401/403 **for a request that requires auth** (private package or publisher action), UI **must** show actionable error (“Configure API key…”). Public search/details **must not** prompt for a key when anonymous access succeeds. |
| A-04 | Missing API key **must not** block public catalog browse; connection label **should** read “Public catalog · {url}” when anonymous probe succeeds. |
| A-05 | Command `beskid.packages.configureApiKey` **must** prompt for a key and persist it via `SecretStorage` (`beskid.pckg.apiKey` secret id). |
| A-06 | Registry connection state **must** be read from LSP `beskid.pckg.getConnectionStatus` first; the client passes `authConfigured` (boolean only). API keys **must not** be stored or logged by the language server. |
| A-07 | After storing an API key, `beskid.packages.configureApiKey` **must** call LSP `beskid.pckg.validateConnection` with the key in command args (one-shot); secrets remain in VS Code `SecretStorage`. |

## LSP registry commands

| Command | Arguments (JSON object) | Response |
| --- | --- | --- |
| `beskid.pckg.getConnectionStatus` | `{ workspaceUri?: string, authConfigured: boolean }` | `{ baseUrl, registryName?, workspaceDefaultRegistryUrl?, workspaceDefaultRegistryName?, authConfigured, validation: { status, message? }, connected }` |
| `beskid.pckg.setRegistry` | `{ baseUrl: string, registryName?: string }` | none |
| `beskid.pckg.validateConnection` | `{ baseUrl?: string, workspaceUri?: string, apiKey?: string }` | `{ ok, error?, validation: { status, message? } }` — `apiKey` is never echoed or logged |

`status` is `unknown`, `ok`, or `error`. `connected` is `true` when validation is `ok` and auth requirements are satisfied (`authConfigured` when the registry returns 401/403 without a key).

## searchPackages

| ID | Rule |
| --- | --- |
| S-01 | **Must** call `GET /api/search` with `limit` (default 20, max 50); `q` optional (empty loads browse catalog). |
| S-02 | **Must** return typed `PackageSearchResponse[]` (array of hits). |
| S-03 | **Must** cache responses ~30 seconds per `(registryBaseUrl, q, limit)`. |
| S-04 | **Must** debounce user input ≥300ms before network. |

## listPackages

| ID | Rule |
| --- | --- |
| B-01 | **Must** call `GET /api/packages` (or search with empty `q`) for initial document panel load. |
| B-02 | **Must** work without API key for public packages. |
| B-03 | **Must** cache ~30 seconds per `(registryBaseUrl, limit)`. |

## getPackageDetails

| ID | Rule |
| --- | --- |
| D-01 | **Must** call `GET /api/packages/{name}` where `{name}` is URL-encoded package id. |
| D-02 | **Must** map `versions[]`, `dependencies[]`, `readme`, `health`, `latestVersion` from response. |
| D-03 | Yanked versions (`isYanked: true`) **must** render with warning in tree. |

## Local project section

| ID | Rule |
| --- | --- |
| L-01 | **ThisProject** children **must** come only from `beskid.getProjectDependencies` for `focusedProjectUri`. |
| L-02 | Each row **must** show `name`, effective version (`locked.resolvedVersion` or declared descriptor), `source`, `registry`. |
| L-03 | Tooltip **should** include `materializedRoot` when present in locked entries. |
| L-04 | Context menu: Open manifest, Open materialized folder, Copy `name@version`. |

## Mutation commands

| Command | Rule |
| --- | --- |
| `beskid.packages.fetch` | **Must** run `beskid fetch` with `cwd` = focused project directory via shared CLI runner |
| `beskid.packages.lock` | **Must** run `beskid lock` likewise |
| `beskid.packages.addDependency` | **Must** prompt for name and version; apply via CLI subcommand when available, else structured `Project.proj` dependency entry + `refreshWorkspace` |
| `beskid.packages.refresh` | **Must** clear `pckgClient` caches and refresh tree |
| `beskid.packages.configureApiKey` | **Must** prompt, store registry API key in `SecretStorage`, validate via `beskid.pckg.validateConnection`, then refresh; subsequent search/details **must** use Bearer auth |
| `beskid.packages.open` | **Must** open/focus registry document WebviewPanel |
| `beskid.packages.openRegistryUri` | **Must** open browser URL from `buildRegistryPackageUrl` |
| `beskid.packages.openManifest` | **Must** open focused `Project.proj` for a local dependency row |
| `beskid.packages.openMaterializedFolder` | **Must** reveal materialized package path when `materializedPath` is present |
| `beskid.packages.copyDependencyLabel` | **Must** copy `name@version` label for local dependency rows |

## Edge cases

| Scenario | Expected behavior |
| --- | --- |
| Offline / DNS failure | Registry section error node; retry action |
| Empty search query | Document panel shows browse catalog from `listPackages` |
| No `Project.lock` | **ThisProject** shows declared only; badge “not locked” on rows |
| Focus cleared | **ThisProject** shows select-project message |
| Concurrent fetch + search | Status bar shows dominant phase; both complete independently |

## Related topics

- [Workspace explorer contracts](../workspace-project-explorer/contracts-and-edge-cases/#beskidgetprojectdependencies)
- Server contracts: `compiler/crates/beskid_pckg_server/Features/Packages/Contracts.cs`

Source Record: Decisions record (legacy index)

Authority: informative provenance
Legacy path: /platform-spec/tooling/vscode-extension/package-manager-panel/articles/decisions-record/
Source: site/spec-content/platform-spec/tooling/vscode-extension/package-manager-panel/articles/decisions-record/content.md
SHA-256: bcd215a130320172d9328dbd3257cfeaa4a5d2503df7de676619b53457039477

Migrated source text
## Purpose

Legacy URL. See **`adr/`** and the hub **ADRs** tab for normative text.

| adrId | Title |
| --- | --- |
| D-TOOL-VSC-0001 | [pckgClient boundary](./adr/0001-single-pckg-client-boundary/) |
| D-TOOL-VSC-0002 | [CLI for fetch and lock](./adr/0002-cli-for-lock-mutations/) |

Source Record: Design model

Authority: informative provenance
Legacy path: /platform-spec/tooling/vscode-extension/package-manager-panel/articles/design-model/
Source: site/spec-content/platform-spec/tooling/vscode-extension/package-manager-panel/articles/design-model/content.md
SHA-256: ded5850b63dec9404a5ec0d3c646983a8be5f7962885a5da2651f45cbfc8ad61

Migrated source text
## Purpose and scope

**`pckgClient`** TypeScript contract, sidebar **This project** deps, and **registry document panel** (WebviewPanel) for browse/search/details. Status-bar activity phases shared with LSP and CLI.

## View layout

Sidebar **`beskidPackagesView`** shows **This project** only (declared + locked deps from LSP).

Registry browse uses **`PackageRegistryPanel`** (editor-area WebviewPanel, command `beskid.packages.open`):

```arch
flowchart LR
  subgraph sidebar [beskidPackagesView]
    TP[ThisProject]
    TP --> D1[Declared/Locked rows]
  end
  subgraph docPanel [PackageRegistryPanel]
    LIST[Package list]
    DET[Detail pane]
    LIST --> DET
  end
  docPanel --> BROWSER[Open in browser]
```

On open, the document panel **must** load the public catalog via `GET /api/packages` or `GET /api/search?limit=…` without requiring an API key.

## packageKind UI routing

Branch detail actions on `package.packageKind` (see [package kinds](/platform-spec/tooling/registry-client/package-kinds/)):

| Kind | Show | Primary actions |
| --- | --- | --- |
| `library` | readme, versions, deps | Add dependency, fetch, lock |
| `template` | shortName, tags.type, readme | `beskid new install`, `beskid new <shortName>` |
| `tool` | install card, readme | download snippet |

## PackageTreeItem kinds (sidebar)

| `section` | `kind` | Children |
| --- | --- | --- |
| `ThisProject` | `section` | Dependency rows from LSP |
| `ThisProject` | `dependency` | Optional context actions only |

Icons: dependency **`package`**; unresolved **`warning`**.

## pckgClient functions (normative)

| Function | HTTP | Cache key |
| --- | --- | --- |
| `listPackages({ limit, registryBaseUrl })` | `GET {base}/api/packages` or search with empty `q` | `list:{base}:{limit}` ~30s TTL |
| `searchPackages({ q, limit, registryBaseUrl })` | `GET {base}/api/search?q=…&limit=…` | `search:{base}:{q}:{limit}` ~30s TTL |
| `getPackageDetails({ name, registryBaseUrl })` | `GET {base}/api/packages/{name}` | `details:{base}:{name}` |
| `buildRegistryPackageUrl({ name, registryBaseUrl })` | — | Opens `{base}/packages/{name}` in browser |
| `createPckgFetch(context)` | — | Optional Bearer from settings or SecretStorage |

All functions **must** use `createPckgFetch` for transport. Public catalog endpoints **must** work with an empty API key.

## Response typing (camelCase)

Align TypeScript types with pckg records:

- **`PackageSummary`**: includes `packageKind` (`library` | `template` | `tool`), optional template fields (`shortName`, `tags`)
- **`PackageDetailsResponse`**: `{ package, versions, dependencies, dependentsCount, readme, health, … }`
- **`PackageDependencyResponse`**: `{ name, version, source, registry }`

## Registry base URL resolution

1. If focused project belongs to a workspace, use `registries` entry with `alias: "default"` from `beskid.getWorkspaceSummary`.
2. Else `vscode.workspace.getConfiguration('beskid.pckg').get('baseUrl')`.
3. Else `https://pckg.beskid-lang.org`.

## Status-bar phases

Extend shared `PckgActivityPhase` (and CLI phases) with:

| Phase | When |
| --- | --- |
| `search` | Registry search in flight |
| `details` | Package details fetch |
| `fetch` | `beskid fetch` running |
| `lock` | `beskid lock` running |

LSP scan phases remain separate on the same status controller.

## View title and welcome actions

| Action | Command |
| --- | --- |
| Open registry | `beskid.packages.open` |
| Search | `beskid.packages.search` |
| Refresh | `beskid.packages.refresh` |
| Fetch | `beskid.packages.fetch` |
| Lock | `beskid.packages.lock` |

Sidebar welcome **should** offer “Browse registry…” linking to `beskid.packages.open`.

## Related topics

- [Contracts](./contracts-and-edge-cases/) — auth and endpoint tables
- [pckg client contract](/platform-spec/tooling/registry-client/pckg-client-contract/)

Source Record: Examples

Authority: informative provenance
Legacy path: /platform-spec/tooling/vscode-extension/package-manager-panel/articles/examples/
Source: site/spec-content/platform-spec/tooling/vscode-extension/package-manager-panel/articles/examples/content.md
SHA-256: 1f3938567c3a5184a404e8cb04afda5a470a76c4b430395c831b67fb0ad0ac2a

Migrated source text
## Purpose and scope

Worked examples for local and registry sections of the package panel.

## Corelib — This project

**Precondition:** `compiler/corelib` open; member `collections` focused; `Project.lock` present.

**Steps:**

1. Open **Packages** view.
2. Expand **This project**.

**Expected rows:** Dependencies such as `corelib` with `resolvedVersion` from lock, `registry: default`, tooltip showing materialized path under `.beskid/packages/`.

**Fetch/Lock:** View title **Lock** runs `beskid lock` at `packages/collections/`; status bar shows `lock` phase; on success, locked versions refresh without restarting LSP.

## Registry search — `corelib`

**Precondition:** Registry reachable at resolved base URL (workspace default or localhost).

**Steps:**

1. View title **Search** → query `corelib`.
2. Expand hit `corelib`.

**Expected:** Versions list from `PackageDetailsResponse.versions`; latest non-yanked version highlighted; dependencies under expanded version match `PackageDependencyResponse` shapes.

**Actions:**

- **Open in browser** uses `buildRegistryPackageUrl`.
- **Add dependency** prompts version; after apply, user runs **Fetch** from view title.

## Private registry with API key

**Precondition:** Registry requires authentication; package `my.internal.lib` not visible anonymously.

**Steps:**

1. Command palette → configure **Beskid: Set Package Registry API Key** (stores secret).
2. Search `my.internal.lib`.

**Expected:** Authenticated search returns hits; without key, 401 error node with configuration hint.

## Related topics

- [Workspace explorer examples](../workspace-project-explorer/examples/)
- [FAQ](../extension-surface/faq-and-troubleshooting/)

Source Record: Flow and algorithm

Authority: informative provenance
Legacy path: /platform-spec/tooling/vscode-extension/package-manager-panel/articles/flow-and-algorithm/
Source: site/spec-content/platform-spec/tooling/vscode-extension/package-manager-panel/articles/flow-and-algorithm/content.md
SHA-256: e69db618b996b79242ed61df33601afe75d43ce1b95f6f66bf6806c731629bbc

Migrated source text
## Purpose and scope

Operational sequences for registry browse and project mutation from the Packages view.

## Panel refresh on focus change

1. Read `focusedProjectUri` from workspace state.
2. If set, `executeCommand('beskid.getProjectDependencies', { projectUri })`.
3. Replace **ThisProject** children; preserve **RegistrySearch** expansion state.
4. Re-resolve registry base URL from workspace summary when workspace membership changes.

## Registry search flow

1. User invokes `beskid.packages.search` or view-title Search.
2. Show `InputBox` for query `q`.
3. Debounce 300ms; set status phase `search`.
4. `searchPackages({ q, registryBaseUrl })` via cache.
5. Populate **RegistrySearch** with `searchResult` nodes (package name + health summary).
6. Clear status phase on completion or error.

## Details expansion

1. User expands a `searchResult` node.
2. If details not cached, set phase `details`, call `getPackageDetails({ name })`.
3. Attach `version` child nodes sorted by semver descending.
4. Expanding a version loads `registryDependency` leaves from `dependencies[]` in details response (no extra HTTP unless stale).

## Fetch and lock

1. User chooses Fetch or Lock from view title (requires focus).
2. CLI runner spawns `beskid` with `cwd` = directory containing focused `Project.proj`.
3. Stream output to **Beskid LSP** output channel; status phase `fetch` or `lock`.
4. On exit code 0: `beskid.packages.refresh` + `beskid.refreshWorkspace` + tree providers update.
5. On non-zero: error notification with last stderr lines.

## Add dependency

1. Prompt package name and version (quick pick optional for known search result).
2. If CLI exposes `beskid add` (or equivalent), invoke via runner.
3. Else insert dependency stanza into `Project.proj` at canonical location; save; trigger LSP refresh.
4. User **should** run Lock after add when lockfile policy requires it.

## Related topics

- [Extension surface flow](../extension-surface/flow-and-algorithm/)
- [Examples](./examples/)