Section 17 already defined what a registry does when the same id@version is republished; nothing defined what a *consumer* does. That is where namespace conflict actually bites — in the catalog, after installing from two registries. Identity is now registry-scoped: an id names a package within a registry, the way a path names a file within a repository. A local-first format with no signing, no federation and no central authority cannot enforce global uniqueness, and an unenforceable guarantee is worse than none — it invites consumers to conflate two packages that merely share a name. A qualified `<registry>:<id>` reference distinguishes them, and `:` is now barred from ids so the separator stays available. Installing the same id from two registries is therefore not a conflict. The catalog is namespaced by registry and keeps both. Ownership is registry policy, not package data. An optional `registry.yaml` names a registry and records namespace claims. Those claims are explicitly descriptive — a filesystem registry cannot authenticate a publisher, and `publish` says so rather than implying it checked. Keeping the claim out of packages leaves artifacts free of unverifiable assertions of authority, and means package semantics do not change when a hosted registry appears later. Spec: 3.2 (registry-scoped identity, qualified references), 17 (immutability scoped to a registry), 20.1 and 20.2 (new), 18 (registry-manifest validation), 21 (qualified references, reserved `local` name). Reference CLI: parse_reference, check_registry_name, read_registry_manifest, registry_name, namespace_policy; registry_package_path and catalog_package_path split; resolve_installed reports ambiguity and returns the source registry; iter_catalog; `add --as`; closed-namespace warning on publish. Tests 11 -> 21. The catalog layout changed. An existing catalog is detected and reported with instructions rather than failing as "package not found". Signing, trust scoring and federation remain non-goals and were not touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
80 lines
2.7 KiB
Markdown
80 lines
2.7 KiB
Markdown
# canned-prompts reference CLI
|
|
|
|
This is intentionally a **small reference implementation**, not the intended final architecture.
|
|
|
|
It demonstrates seven verbs:
|
|
|
|
```text
|
|
add PATH
|
|
search QUERY
|
|
show ID
|
|
resolve ID --set key=value
|
|
render ID --set key=value
|
|
install ID [--version VERSION]
|
|
publish PATH
|
|
```
|
|
|
|
The implementation uses a local catalog plus a filesystem registry and performs no model calls.
|
|
|
|
`resolve` and `render` are separate because the specification separates them
|
|
(§ 5.1): resolution decides each value and may be non-deterministic, rendering
|
|
substitutes and always is. `resolve` prints where every value came from —
|
|
supplied, default, or fallback — before any prompt is produced.
|
|
|
|
## Stores
|
|
|
|
Default locations:
|
|
|
|
```text
|
|
~/.canned-prompts/catalog
|
|
~/.canned-prompts/registry
|
|
```
|
|
|
|
A **registry** stores packages flat, because an id is unambiguous within one
|
|
registry:
|
|
|
|
```text
|
|
<registry>/<id path>/<version>/...
|
|
```
|
|
|
|
A **catalog** is namespaced by registry, because identity is registry-scoped
|
|
(§ 3.2) and the same id may be installed from more than one place:
|
|
|
|
```text
|
|
<catalog>/<registry name>/<id path>/<version>/...
|
|
```
|
|
|
|
For example:
|
|
|
|
```text
|
|
~/.canned-prompts/catalog/house/practice/pqrst-estimate/0.1.0/
|
|
~/.canned-prompts/catalog/local/practice/pqrst-estimate/0.1.0/
|
|
```
|
|
|
|
A registry's name comes from its optional `registry.yaml`, and otherwise from
|
|
its directory basename. `add` takes a package from a path rather than a
|
|
registry, so it files it under `local` (override with `--as`).
|
|
|
|
Commands that take an ID accept a bare id or a qualified `<registry>:<id>`.
|
|
A bare id installed from more than one registry is reported as ambiguous
|
|
rather than resolved by guessing.
|
|
|
|
## Design choices
|
|
|
|
- YAML manifest via PyYAML.
|
|
- `{{ name }}` template substitution only.
|
|
- No arbitrary expression/code execution.
|
|
- Published versions are immutable by default.
|
|
- `install` copies from registry to catalog.
|
|
- `add` copies a package directly to catalog.
|
|
- `search`, `show`, `resolve`, and `render` operate on catalog packages, and
|
|
print qualified `<registry>:<id>` references.
|
|
- An optional `registry.yaml` names a registry and records namespace claims.
|
|
`publish` warns when a namespace is declared `closed` — it cannot
|
|
authenticate a publisher, and says so rather than implying it checked.
|
|
- Static input defaults are applied; **derived** defaults (§ 6.1) are not. This
|
|
tool never calls a model, so a derived default is satisfied only by its
|
|
static fallback `value`. Without one, `resolve` reports the input as
|
|
unresolved and `render` refuses rather than substituting empty text.
|
|
|
|
Use this implementation to challenge the format. Replace it once real usage reveals the right architecture.
|