--registry now accepts an http(s):// URL as well as a path, implemented with urllib so reference/ keeps PyYAML as its only dependency. This was the first real test of INTENT principle 10's claim that a hosted registry layers on without changing package semantics. Verified against the running service, almost everything survived the transport unaltered: identity and its ambiguity rules (a bare id in two registries returns 409 over HTTP just as it does locally), immutability of a published id@version (identical content accepted, changed content refused, version bump accepted), strict packaging, validation, and the index. A package published and then installed over HTTP was byte-identical to its source — diff -r clean — and its canonical-fidelity eval still passed after the round trip. One thing did not survive: a URL is not a registry. A filesystem registry IS one registry and section 20.1 names it from its directory; an HTTP service HOSTS SEVERAL behind one base URL. The address therefore cannot name the registry, so it must be named separately — --as when publishing, a qualified reference when installing. Recorded as section 20.4 rather than worked around silently in the client, because the gap is in the specification's list of registry kinds, not in the CLI. Publishing to an HTTP registry without --as fails with that explanation rather than guessing a registry name. Registry responses are treated as untrusted input (section 19): decode_files refuses path traversal, with a test. The wire shape round-trips binary content through base64, also tested. Reference tests 99 -> 105. 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
117 lines
5.1 KiB
Markdown
117 lines
5.1 KiB
Markdown
# canned-prompts reference CLI
|
|
|
|
This is intentionally a **small reference implementation**, not the intended final architecture.
|
|
|
|
It demonstrates nine verbs:
|
|
|
|
```text
|
|
add PATH
|
|
search QUERY
|
|
show ID
|
|
resolve ID --set key=value
|
|
render ID --set key=value
|
|
eval ID
|
|
index [ID]
|
|
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/default
|
|
```
|
|
|
|
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/default/practice/pqrst-estimate/1.0.0/
|
|
~/.canned-prompts/catalog/local/practice/pqrst-estimate/1.0.0/
|
|
```
|
|
|
|
A registry's name comes from its optional `registry.yaml`, and otherwise from
|
|
its directory basename — which is why the default registry directory is called
|
|
`default` and not `registry`: the basename reaches qualified references,
|
|
catalog paths and index rows, and `registry:practice/thing` reads poorly. A
|
|
store left at the old `~/.canned-prompts/registry` is reported with the command
|
|
to move it, rather than a fresh empty one being created beside it. `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.
|
|
- `add`, `publish` and `install` copy only reserved paths and manifest-
|
|
referenced files. A working directory's `.git`, virtualenv or scratch files
|
|
never ship; whatever is left out is named on stderr.
|
|
- Version precedence follows SemVer: a prerelease ranks below its release, and
|
|
`any`, `newest` and `>=` skip prereleases entirely. Only an exact pin
|
|
selects one.
|
|
- `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.
|
|
- `include` defaults are satisfied (inclusion is deterministic); `derive`
|
|
defaults are reported, not run. Inclusion cycles are detected and named.
|
|
- `resolve`, `render` and `eval` warn when an included value will render more
|
|
than once — two inputs including the same thing, or an included package
|
|
inheriting an outer input of the same name. Nothing is deduplicated; the fix
|
|
is a better factoring.
|
|
- `eval` runs the deterministic render checks of any eval declaring the
|
|
`canned-prompts/eval-rubric/v0.1` schema, and reports output criteria as
|
|
declared but not run. Unrecognized schemas are skipped, not rejected. A
|
|
failed render check exits non-zero.
|
|
- `resolve` lists required capabilities and context dependencies, which this
|
|
tool cannot verify, rather than implying it checked them.
|
|
- `add` and `install` name declared prompt dependencies the catalog cannot
|
|
satisfy. They do not fetch them — resolution is the consumer's job (§ 10) —
|
|
but a package that looks installed and cannot render should say so.
|
|
- `add`, `install` and `publish` record an entry in the store's `index.yaml`:
|
|
source, method, first-inclusion date, and the package's declared author,
|
|
source and licence. `index` lists it. Re-adding keeps the original
|
|
`included_at` and updates `last_seen_at`.
|
|
- `--registry` accepts an `http(s)://` URL as well as a path. HTTP uses the
|
|
stdlib, so this stays dependency-light. `CANNED_PROMPTS_PUBLISH_TOKEN`
|
|
supplies the bearer token when publishing.
|
|
- Publishing to an HTTP registry needs `--as NAME`: a URL addresses a service
|
|
that **hosts several registries**, so unlike a directory it does not name one
|
|
(§ 20.4). Installing names it in a qualified reference.
|
|
- 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.
|