Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions docs/prd/PRD-004-cli-onboarding-and-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
offline vs daemon-required command categorization)
- Revised: 2026-05-24 (bootstrap-only `init`, domain-oriented `config`,
init-owned identity re-entry, explicit reset flow)
- Revised: 2026-09-15 (separate plugin catalogs from package lifecycle)
- Depends on: `PRD-001`, `PRD-002`

## Goal
Expand Down Expand Up @@ -108,9 +109,10 @@ Top-level domains:
The daemon restores its system skills from the installed binary. The `Skill Sources`
domain configures local folders and private server sources. It does not configure system skill updates.
Operators can use `netclaw skill sync` to run the configured external source
sync pass. The command does not add sources or write configuration.
sync pass. This pass includes private skill feeds, installed Git plugins,
and registered plugin catalogs. The command does not add sources or write configuration.

The top-level `netclaw plugin` family manages package sources from public GitHub repositories.
The top-level `netclaw plugin` family manages packages and catalogs from public GitHub repositories.
Agent Plugins 1.0.0 is the primary package contract.
Host-specific package formats are explicit compatibility contracts.
The daemon can publish supported plugin skills into the existing skill inventory.
Expand Down Expand Up @@ -203,9 +205,13 @@ Command ownership stays explicit:

- `netclaw plugin install <source>` — configure, acquire, validate, and publish a managed plugin
- `netclaw plugin list [--json]` — show configured sources and installed package state
- `netclaw plugin update <source-id>|--all` — run the shared sync pass and report plugin results
- `netclaw plugin update <source-id>|--all` — run a plugin-only pass and report plugin results
- `netclaw plugin enable|disable <source-id>` — change source availability
- `netclaw plugin remove <source-id>` — remove a source and its durable sync state
- `netclaw plugin marketplace add|list|update|remove` — manage public GitHub catalogs
- `netclaw plugin search <query>` — find catalog entries
- `netclaw plugin show <name>@<marketplace>` — inspect one catalog entry
- `netclaw plugin install <name>@<marketplace>` — install one selected catalog entry

## Requirements

Expand Down Expand Up @@ -378,6 +384,30 @@ and Codex compatibility manifests. It excludes marketplaces, private Git
credentials, plugin subagents, MCP activation, hooks, LSP configuration, and
host execution.

### CLI-015 Plugin Catalog Lifecycle

The next capability SHALL let an operator register a public GitHub marketplace.
Registration and catalog refresh SHALL save metadata only.
Neither action SHALL install a package or grant runtime authority.

The operator SHALL install a named catalog entry with an explicit command.
The daemon SHALL apply the same acquisition and security checks as direct Git installation.
The daemon SHALL retain the catalog origin in the installed source record.
A later catalog change SHALL not silently replace that installed source location.

`netclaw skill sync` SHALL refresh private feeds, installed plugins, and registered catalogs.
Each source type SHALL have a separate result row.
A failed catalog refresh SHALL retain the last valid snapshot and report the failure.
Feed and plugin work SHALL continue after a catalog failure.

A named plugin lifecycle command SHALL start work only for its named package.
`netclaw plugin update --all` SHALL start plugin work without feed or catalog work.
Marketplace update SHALL refresh catalog metadata without package work.
Only `netclaw skill sync` SHALL start the complete external-source pass.

The first catalog release SHALL support public GitHub sources only.
Private Git credentials, automatic catalog-based installation, and executable plugin components remain out of scope.

## UX Requirements

- human-readable output by default, machine-friendly JSON opt-in (`--json`)
Expand Down
7 changes: 7 additions & 0 deletions docs/spec/GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@ A package rename does not silently change the source ID.

**Code anchors:** `ManagedPluginSource`, `ManagedPluginStateStore`

### Plugin marketplace catalog

A plugin marketplace catalog lists package names, descriptions, and source locations.
It is metadata, not an installed package or a grant of runtime authority.
A catalog snapshot is the last validated revision that the daemon stores.
An installed plugin keeps its own source ID and package receipt.

### Workspace tool

A workspace tool reads, lists, writes, edits, attaches, or selects files and
Expand Down
1 change: 1 addition & 0 deletions docs/spec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ narrow a glossary term only when it states the exception.
- `SPEC-010-testing-and-smoke-strategy.md` (PRD-001, PRD-005)
- `SPEC-011-daemon-architecture.md` (PRD-001, PRD-002, PRD-004)
- `SPEC-016-tool-liveness-and-stall-detection.md` (PRD-001, PRD-006)
- `SPEC-017-plugin-marketplace-catalogs.md` (PRD-004, PRD-002)

OpenSpec equivalents live in `openspec/specs/` and should remain aligned.
38 changes: 29 additions & 9 deletions docs/spec/SPEC-004-cli-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,29 +96,33 @@ Behavior:

### 7) External Skill Sync

`netclaw skill sync` runs the daemon's configured external source job immediately.
`netclaw skill sync` runs the daemon's complete external source job immediately.
It does not save configuration or add sources. System skills come from the installed binary.
See the [engineering glossary](GLOSSARY.md) for shared terms.

The command sends an authenticated `POST /api/skills/sync` request.
The endpoint uses the existing daemon authorization policy.
An unauthenticated request receives HTTP 401 and cannot start a pass.
An authenticated request can join a pass that the startup path, timer, or another operator started.
An authenticated complete request can join a compatible pass that the startup path, timer, or another operator started.

`ServerFeedSkillSyncActor` owns the timer, active pass state, waiters, and lifetime token.
This state is actor-local. The CLI owns only its call-local request wait.
The external sync coordinator runs one pass through server-feed and managed plugin participants.
The external sync coordinator runs one pass through server-feed, managed plugin, and catalog participants.
The feed helpers retain the existing durable files and sync receipts.

```text
CLI -> daemon authorization -> ServerFeedSkillSyncActor
actor stops during a pass: return HTTP 503
add the caller to the actor-local waiter set
active pass exists: wait for that pass
compatible active pass exists: join its actor-local waiter set
incompatible active pass exists: queue this request
otherwise: call ServerFeedSkillSyncService with the actor lifetime token
for each enabled feed:
run the existing RFC skill and native sub-agent sync
collect its result; continue after a source failure
for each enabled installed plugin:
run the managed plugin participant; continue after a source failure
for each registered catalog:
refresh validated metadata; retain its prior snapshot after failure
refresh the complete inventory through SkillInventoryRefresher
send the result to the actor
actor -> send the same result to all waiters
Expand Down Expand Up @@ -146,7 +150,9 @@ The response includes one pass ID, per-source counts, source type, sidecar statu
The source type distinguishes a server feed from a Git plugin when both sources use the same name.
The service assigns the pass ID before source work and includes it in its start and completion logs.
The response contains no derived overall success field. The CLI computes its exit code from the source and inventory results.
Overlapping callers receive the same pass ID. Source errors in this response do not include credentials or remote response bodies.
Compatible overlapping callers receive the same pass ID.
Incompatible callers receive a later pass ID.
Source errors in this response do not include credentials or remote response bodies.
Inventory rejection counts can include pre-existing source conflicts. They do not make a completed inventory refresh fail.
This command does not add a transaction across feed files, the registry, and the prompt index.
It preserves the existing per-skill replacement and prune rules.
Expand All @@ -168,8 +174,8 @@ The CLI never writes plugin configuration on the client host.
|---|---|
| `netclaw plugin install <owner/repository> [options]` | Configure a source, restart the daemon, run a sync, and verify installation |
| `netclaw plugin list [--json]` | List configured sources and installed package state |
| `netclaw plugin update <source-id> [--retry-rejected]` | Run the shared sync pass and report one source result |
| `netclaw plugin update --all [--retry-rejected]` | Run the shared sync pass and report all plugin results |
| `netclaw plugin update <source-id> [--retry-rejected]` | Run a source-scoped plugin pass and report its result |
| `netclaw plugin update --all [--retry-rejected]` | Run a plugin-only pass and report all plugin results |
| `netclaw plugin enable <source-id>` | Enable a source and verify its installation |
| `netclaw plugin disable <source-id>` | Disable a source and remove it from the live inventory |
| `netclaw plugin remove <source-id>` | Remove a source and its durable sync state |
Expand All @@ -194,7 +200,7 @@ CLI -> authenticated daemon route
write the canonical source to SkillFeeds.Plugins
return the current restart generation
CLI -> wait for a later healthy daemon generation
CLI -> request one immediate skill sync
CLI -> request one source-scoped plugin pass
daemon -> download, inspect, scan, and publish the candidate
CLI -> read the plugin state and report success or failure
```
Expand Down Expand Up @@ -261,6 +267,20 @@ Each route uses the existing authenticated daemon policy.
The daemon returns safe RFC 9457 problem details for expected failures.
The CLI shows a valid safe detail and otherwise shows a bounded status message.

### 9) Plugin Marketplace Catalogs

`netclaw plugin marketplace add|list|update|remove` manages public GitHub catalog sources.
`netclaw plugin search` reads validated catalog snapshots.
`netclaw plugin show <name>@<marketplace>` shows one catalog entry.
`netclaw plugin install <name>@<marketplace>` installs only the selected package.
Catalog add and update never install a package.

The complete `netclaw skill sync` pass refreshes feeds, installed plugins, and catalogs.
Named plugin lifecycle commands start only source-scoped plugin work.
Marketplace update starts catalog-only work.
The sync actor owns incompatible request queues and compatible waiter sets.
See [SPEC-017](SPEC-017-plugin-marketplace-catalogs.md) for source rules and durable state.

## Output and Exit Codes

- default output: human readable text
Expand Down
136 changes: 136 additions & 0 deletions docs/spec/SPEC-017-plugin-marketplace-catalogs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# SPEC-017: Plugin Marketplace Catalogs

Source PRDs: `PRD-004` (CLI-015), `PRD-002`

This spec extends [SPEC-004](SPEC-004-cli-contract.md).
Use the [engineering glossary](GLOSSARY.md) for shared terms.
Issue [#2185](https://github.com/netclaw-dev/netclaw/issues/2185) tracks this capability.

## Product Boundary

The Agent Plugins 1.0.0 standard defines a package, not a catalog.
Netclaw uses a subset of the [Claude marketplace catalog shape](https://code.claude.com/docs/en/plugin-marketplaces) for the first catalog release.
This choice does not make Netclaw a Claude Code plugin runtime.
Package import still selects Agent Plugins 1.0.0 before host compatibility formats.
Catalog registration is an operator choice, not a curated trust mark.

The first release reads `.claude-plugin/marketplace.json` from a public GitHub repository.
The catalog file supplies a name and a `plugins` array.
Each supported entry supplies a name and a source.
The daemon records the catalog name as metadata.
The operator's marketplace ID remains the durable command key.

The supported source forms are a contained `./` path in the catalog repository and a public GitHub source object.
A GitHub source object uses `"source": "github"`, supplies `repo`, and can supply an exact `sha`.
An object without `sha` follows the repository default branch.
An object with `ref` remains unsupported in this release because a ref can name either a branch or a tag.
Other source forms remain visible as unsupported entries with explicit notices.
The install command rejects an unsupported entry before it writes a plugin source.

For example, `./plugins/review` is a contained catalog path.
The daemon rejects `../review` because it escapes the catalog root.
For example, a GitHub object with `repo` and `sha` can select an exact commit.
An object with `ref` reports an unsupported source notice and cannot install.
For example, `review@team` selects one entry in the `team` catalog.
The daemon rejects an unqualified `review`, even when only one catalog contains it.

## Authority and Durable State

The daemon owns marketplace ID validation, GitHub reference resolution, catalog checks, and all durable writes.
The CLI owns only the authenticated request, confirmation, and result presentation.
The sync actor owns one active pass and queued incompatible requests as actor-local state.
The catalog participant owns call-local fetch and parse state.

`SkillFeeds.Marketplaces` stores public catalog source configuration.
The schema must accept its canonical fields and reject unknown fields.
The daemon stores one atomic validated snapshot per marketplace under a managed path.
The snapshot records its source ID, Git commit, catalog name, entries, and notices.
The daemon reads only the last validated snapshot for search and show.

An installed catalog plugin remains a `ManagedPluginSource` with a distinct source ID.
The source stores its marketplace ID and entry name as optional origin metadata.
The catalog entry name remains separate from the installed package manifest name.
The package receipt remains the source of truth for the installed revision.
A catalog refresh never rewrites an installed source location or package receipt.
A marketplace removal leaves installed sources intact.

For example, catalog registration stores a catalog snapshot but creates no package receipt.
A catalog entry cannot grant a shell tool, MCP server, hook, or subagent authority.
The daemon reports ignored executable catalog fields as notices.
The installed package path still passes Git acquisition, archive checks, and the content scanner.

## Ordered Command Flow

This flow is schematic. It omits ordinary transport setup and the final response format.

```text
marketplace add -> daemon authorization
validate marketplace ID and public GitHub repository
fetch one bounded Git revision
validate marketplace.json and each source path
write an atomic snapshot
persist the canonical source configuration
return catalog status; publish no skill

catalog install -> daemon authorization -> durable snapshot lookup
require one qualified and installable entry
resolve its public GitHub package source
persist the managed plugin source with catalog origin
wait for the daemon to apply the new source configuration
request a source-scoped pass through the existing sync actor
acquire, scan, publish, and verify only that package

skill sync -> existing sync actor -> complete pass
sync each enabled private skill feed
sync each enabled installed Git plugin
refresh each registered catalog snapshot
refresh the skill inventory once after package work
report each source result and the inventory result
```

Marketplace add fetches and validates the catalog before source configuration persistence.
An invalid catalog does not enter `SkillFeeds.Marketplaces`.
A snapshot write failure returns an error and prevents source configuration persistence.
An unused snapshot cannot grant authority if a later config write fails.

## Pass Scope and Failure Rules

`netclaw skill sync` requests the complete pass.
`netclaw plugin update --all` requests a plugin-only pass.
A named plugin command requests a source-scoped package pass.
`netclaw plugin marketplace update <id>` requests a catalog-only pass.

The actor coalesces requests only when their scopes and retry policies match.
It queues an incompatible request behind the active pass.
A named plugin request never joins a complete pass as its own result.
The actor keeps one scheduler, one pass owner, and one final inventory owner.
Catalog-only work does not refresh the skill inventory.

A failed catalog refresh reports a catalog source failure and keeps the last valid snapshot.
It does not stop private feed or installed package work in a complete pass.
The command does not silently present a stale snapshot as current.
An initial add with no valid snapshot fails before catalog configuration persistence.

For example, a catalog HTTP 500 leaves a prior catalog commit available for search.
The source row reports the HTTP failure and the prior snapshot revision.
A malformed JSON update also keeps the prior snapshot but reports validation failure.

The result source kind distinguishes `server-feed`, `git-plugin`, and `catalog`.
Catalog rows report the snapshot commit and entry count without changing existing count meanings.
The wire response adds catalog fields and preserves feed and plugin fields.

## Implementation Sequence and Proof

1. Add canonical marketplace config, schema fields, paths, and snapshot checks.
2. Add a bounded public GitHub catalog reader that reuses package transport and archive checks.
3. Add a catalog participant and explicit request scopes to the existing sync actor.
4. Add authenticated catalog routes and a thin CLI presentation layer.
5. Add qualified catalog install and source origin metadata.
6. Update the operational system skill, CLI help, website issue, and relevant eval cases.

Tests must prove invalid source data fails before config persistence.
Tests must prove a persisted package source matches the runtime path and reference rules.
Config load and save tests must preserve marketplace sources and installed package origins.
Actor tests must prove incompatible requests do not coalesce.
Integration tests must prove a catalog failure does not block a healthy feed.
Smoke tests must prove add installs nothing and explicit install publishes one package.
Loading