Files
loom-cli/.loom/cart/current/claude-marmalade.md
T
jeffryandClaude Opus 5 c51655f626 marmalade opens: a CLI shape, and reachable has no home under external
Jeff's notes arrived as jeff-osprey.md, into a cart that had already converted.
Moved unedited to loom-marmalade.md: cart allows two files and gives the test —
what happens to this file when the person changes — and jeff-… dies with the
person, so it is a person and not a stake. loom renamed itself for this exact
reason last round and said that if Jeff wrote here he would write into that file.
The shape predicted it.

His subcommand grouping exposes something osprey got wrong. pull, check and
reachable were logged as one family and they are two: external is what somebody
else published and we depend on, published is our surface and somebody else's
dependency. reachable looks at .loom/published/ and therefore has no home under
external — so it becomes loomctl published check, and check means one thing in
both nouns.

external list is the first command that must know what a host is, and it is worth
it: the origin path stops being parsed because the tool builds the URL, and it is
discovery, which the log records as the thing a fetcher cannot do. Asked as a
Select with a lean toward git itself as the one API every host already speaks,
with a fallback so silence still builds something.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris
2026-09-07 13:58:46 -04:00

136 lines
6.7 KiB
Markdown

# claude — cart `marmalade`
**`osprey` converted, so this is a new round.** *Its name is meaningless and I
picked it arbitrarily; change it if you like.*
---
## Two mechanical things before the design
**Your text arrived as `jeff-osprey.md` and I have moved it, unedited, to
`loom-marmalade.md`.** *`cart` allows two files and gives the test:* **what
happens to this file when the person changes?** *`jeff-…` dies with Jeff, so it is
a person and not a stake* — **and loom renamed itself to `loom` last round for
exactly this reason**, *saying that if you wrote here you would write into that
file.* **Not a correction of you; the shape predicted it and the shape was right.**
**And it landed in a cart that had converted**, *which is fine — it opened the next
one.* **Nothing travelled silently:** *everything below is reintroduced with a
fresh reason, and the design that survived `osprey` is in
[`.loom/event-log.md`](../../event-log.md) rather than in a file I am carrying
forward.*
## `loomctl`, and the noun that is missing
**Subcommands, yes.** *And your grouping exposes something the log got wrong.*
**`osprey` named three commands — `pull`, `check`, `reachable` — as if they were
one family. They are two.**
```
loomctl external add | check things somebody else published, that we depend on
loomctl published check things we published, that somebody else depends on
```
> **`reachable` is not an `external` operation at all.** *It looks at
> `.loom/published/`, which is our surface and not anybody's dependency.* **Under
> your grouping it has no home, and that is the grouping doing its job.**
*`publication.md` already draws this line — **published is a change of kind** — and
the CLI having a noun for each side is the same distinction wearing a verb.*
**I would drop the word `reachable` and make it `loomctl published check`**, *so
that `check` means one thing in both nouns: **ask somebody else whether what we
believe is still true.*** *Against an external it asks the publisher; against our
own, it asks as a stranger.*
## `external list` is the first command that needs to know what a host is
**Everything in `osprey` is host-agnostic on purpose.** *A conditional `GET`
behaves identically on gitea and GitHub — we measured it.* **`list` is not like
that.** *Enumerating `.loom/published/` means asking a service what is in a
directory, and every host answers differently or not at all.*
**That is the real cost of the command, and I think it is worth paying**, because
of what it buys:
- **The origin path stops being parsed.** *You type a repository and a document
name; the tool builds the URL and writes it to the lock.* **The problem that ate
two passes of `osprey` disappears by never arising.**
- **It is discovery**, *which the log records as the thing a fetcher cannot do*
**and your `gitea.md` story is exactly the failure it fixes.** *I could not have
pulled that document, because I did not know it existed.*
> **You said there is no registry yet, so you are the registry.** *`external list`
> is the smallest thing that stops that being true.*
## Select: what does `external list` talk to?
- **each host's REST API** — *gitea `/api/v1/repos/{o}/{r}/contents/.loom/published`,
GitHub `/repos/{o}/{r}/contents/…`, one adapter per host*
- **git itself** — *a treeless or shallow fetch, then `ls-tree` the published
directory; the same protocol everywhere, no adapters, no API tokens*
- **a published index** — *the convention grows a file listing the surface, and
`list` is a fetch like every other command*
*My lean is **git itself**, and I want to be honest that it is not obviously
right.* **git is the one API every one of these hosts already speaks**, *and we
have working ssh here* — **so `list` needs no host adapter and no second auth
story**, *which collapses most of your `auth` section.* **The costs are real:** *it
is heavier than one request, it needs git credentials rather than a read token,
and it fetches more than it needs to answer.*
**Fallback if unanswered:** *I build it against git, behind a small seam, and log
that the REST adapter is the fallback if the git path is too slow or too
credentialed for a public repository.*
## Auth: kubeconfig-shaped is right, and it is not only credentials
**Your instinct holds for a reason the specimen missed.** *`osprey` assumed one
token because it assumed one host.* **The moment there are several, the per-host
thing is not just a secret — it is *how you talk to this host at all***: *its API
shape, whether ssh is on a strange port, which transport `check` should use.*
**That is a config, and kubeconfig is the right silhouette:** *named contexts, one
current, everything addressable by name.*
**Two things I would carry forward from `osprey` into it:**
**A token cannot be asked what it may do** — *gitea's `/api/v1/repos/…` reports the
**account's** rights, not the token's* — **so the name is the only record of its
scope**, and the config should make naming unavoidable rather than optional.
**And it lives in `~`, never in a repository.** *A file that holds hosts and
tokens together will be tempting to commit the first time two people need the same
host list.* **Splitting it — a committed host list, a private credential file — is
the obvious answer and I have not thought it through**, *so I am naming it rather
than deciding it.*
## Affirm: subcommands now, plugins not yet?
**I would not build a plugin boundary before `beads` exists.**
*`kubectl` and `gh` both grew plugins after they had users and after somebody
outside wanted in.* **Asserting the seam now means guessing where it goes**, *and
`osprey` just spent a round on what a confident guess costs* — **it is the same
argument that killed `init`: a boundary nobody has felt asserts something nobody
decided.**
**Fallback if unanswered:** *subcommands only, with the internals arranged so that
`external` and `published` do not share state.* **If that stays true, the plugin
seam is discovered rather than designed.**
## Question: does `loomctl` document itself the way loom asks everyone else to?
**Your first requirement — legible to an AI or a human — is loom's own thesis
pointed at loom.** *So the test is available: **this repository has a
`.loom/published/`, and `loomctl published check` can be run against it.***
*I think that is the acceptance test for the whole tool, and it is nicer than any
unit test I would otherwise write.* **The tool's first real use is on itself, and
its second is `homelab-cluster`'s `gitea.md`** — *the document I needed and could
not read.*
**Fallback if unanswered:** *I write `.loom/published/` for `loomctl` as I build,
and treat `--help` as a rendering of it rather than a second source of truth.*