From e88aa236c7efdb344ffe490a8037555bf2a0a357 Mon Sep 17 00:00:00 2001 From: prode Date: Sun, 20 Sep 2026 19:04:02 -0300 Subject: [PATCH] docs(skill): price ui new, not the group it belongs to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ui` became a group in PR #79 — `ui new` generates at Pro Tools pricing, `ui list` and `ui show` read the account back for nothing. The rename reached the command surface, the README and the wiki, and missed every place that names `ui` as a price: the harness block, the Pro Tools line in the entry skill, three lines of `references/costs.md`, and one in the interface skill. Those are what an agent reads before deciding whether to run something. Left alone they say the group costs twenty to forty generations a call, so the two commands that cost nothing are the ones an agent would avoid. A test now refuses a price quoted against a name nobody can type: every backticked command on a Pro Tools line has to be a leaf command of the CLI. Reverting one of the lines above fails it with "the harness block prices 'ui', which is not a command", which is what makes a third rename a red test rather than another page left behind. plans/the-cost-tables-name-ui-new.md. Verified with scc validate --checks --pr: 1725 passed, 58 skipped, coverage 95.28%, ruff check and format clean, artifacts clean. --- .claude/skills/pixellab-cli-assets/SKILL.md | 2 +- .../pixellab-cli-assets/references/costs.md | 6 +-- .../skills/pixellab-cli-interface/SKILL.md | 2 +- plans/the-cost-tables-name-ui-new.md | 51 +++++++++++++++++++ src/pixellab_cli/commands/interface.py | 2 +- src/pixellab_cli/harness.py | 2 +- .../skill/pixellab-cli-assets/SKILL.md | 2 +- .../pixellab-cli-assets/references/costs.md | 6 +-- .../skill/pixellab-cli-interface/SKILL.md | 2 +- tests/test_skill.py | 40 +++++++++++++++ 10 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 plans/the-cost-tables-name-ui-new.md diff --git a/.claude/skills/pixellab-cli-assets/SKILL.md b/.claude/skills/pixellab-cli-assets/SKILL.md index 6bb2ad6..9933c5c 100644 --- a/.claude/skills/pixellab-cli-assets/SKILL.md +++ b/.claude/skills/pixellab-cli-assets/SKILL.md @@ -113,7 +113,7 @@ recovering a stopped run, and the free local tooling every category uses. - Free or cheap, reach for them freely: `balance`, `ledger`, `character list|show|sheet`, `object list`, `recipe list`, every `image` command, every `export` command, `config show|path`. -- **Pro Tools, twenty to forty generations a call:** `object new`, `ui`, `inpaint`, +- **Pro Tools, twenty to forty generations a call:** `object new`, `ui new`, `inpaint`, `tiles variants`, `character state`, `outfit`, `portrait`, `sprite` with more than one `--style`, and `edit` with more than one image. They say so before calling. Never run one without agreement. diff --git a/.claude/skills/pixellab-cli-assets/references/costs.md b/.claude/skills/pixellab-cli-assets/references/costs.md index dd8217f..b5e1c18 100644 --- a/.claude/skills/pixellab-cli-assets/references/costs.md +++ b/.claude/skills/pixellab-cli-assets/references/costs.md @@ -16,7 +16,7 @@ public pricing; the tool records what each call actually reported. | Base | ~1 | `sprite`, `animate`, `character animate` (per direction), `edit` with one image, `tiles isometric`, `tiles prop` | | Character | ~3–4 | `character new`, `rotate` | | Tilesets | ~3 | `tiles terrain`, `tiles platform` | -| Pro Tools | 20–40 | `object new`, `ui`, `inpaint`, `tiles variants`, `character state`, `outfit`, `portrait`, `interpolate`, `sprite` with several `--style`, `edit` with several images or `--match` | +| Pro Tools | 20–40 | `object new`, `ui new`, `inpaint`, `tiles variants`, `character state`, `outfit`, `portrait`, `interpolate`, `sprite` with several `--style`, `edit` with several images or `--match` | | Fonts | 25 fixed | `font` | | By generation time | 1–6 | `animate` above sixteen frames, which is `animate-pixminimax`: beta, tier 1 and above, and the one route whose estimate here is rough | | fal | unpriced | `art concept`, `art anchor`, `art boxart`, `art edit` — fal reports no usage and publishes no price, so the ledger records these as unknown rather than as a number nobody checked | @@ -70,7 +70,7 @@ Interpolate knows both ends and fills the middle, on a Pro route, at most 128 pe and picks the count itself. If they described where the motion ends as concretely as where it starts — shut to open, car to robot — it is interpolate. -**`ui` against `sprite --style`.** A panel is one picture holding a whole layout, for Pro +**`ui new` against `sprite --style`.** A panel is one picture holding a whole layout, for Pro pricing. Several small repeated pieces — inventory slots, icon buttons — come back as separate sprites from a style reference, which is usually what was actually wanted. @@ -103,5 +103,5 @@ conversion. `pixellab-cli-images` carries the full decision. - `animate` and `character animate`: at most 256 per side, and width times height times frames may not exceed 524,288 — so 256x256 stops at eight frames. - `inpaint`: 32 to 512 per side. -- `ui`: 192 to 688 per axis, the maximum depending on the aspect. +- `ui new`: 192 to 688 per axis, the maximum depending on the aspect. - `sprite` with several `--style`: no size at all — it is read off the style images. diff --git a/.claude/skills/pixellab-cli-interface/SKILL.md b/.claude/skills/pixellab-cli-interface/SKILL.md index 3f5c599..0285f64 100644 --- a/.claude/skills/pixellab-cli-interface/SKILL.md +++ b/.claude/skills/pixellab-cli-interface/SKILL.md @@ -54,7 +54,7 @@ With neither `--element` nor `--piece` the result is one full-canvas rounded pan ## A panel, or one element -`ui` makes a **panel** by default, and moves to the single-element route +`ui new` makes a **panel** by default, and moves to the single-element route (`generate-ui-v2`) on either of the two signals that mean a panel was never possible or never intended: diff --git a/plans/the-cost-tables-name-ui-new.md b/plans/the-cost-tables-name-ui-new.md new file mode 100644 index 0000000..53968ce --- /dev/null +++ b/plans/the-cost-tables-name-ui-new.md @@ -0,0 +1,51 @@ +--- +autonomy: auto +ci: wait +--- + +# The cost tables name ui new + +`ui` became a group in PR #79 — `ui new` generates, `ui list` and `ui show` read the +account back — and the rename reached the command help, the README and the wiki but not +the places that name `ui` as a price. The harness block and the cost reference still +list `ui` among the Pro Tools routes, which now reads as though the group costs twenty +to forty generations a call when two of its three commands are free. + +## Why + +The harness block is read every session by an agent that may never load a skill, and +`references/costs.md` is what an agent consults before quoting a price. Both now name a +command that does not exist as a paid route. An agent that believes `ui list` costs +thirty generations will not run it, and reading the account back for nothing is the +whole point of the command it will avoid. + +## Paths + +- `src/pixellab_cli/harness.py` +- `src/pixellab_cli/skill/pixellab-cli-assets/SKILL.md` +- `src/pixellab_cli/skill/pixellab-cli-assets/references/costs.md` +- `src/pixellab_cli/skill/pixellab-cli-interface/SKILL.md` +- `.claude/skills/` — the installed copies of the same files + +- `src/pixellab_cli/commands/interface.py` — one docstring naming the command rather than the group + +## References + +- `specs/interface-portrait-and-font/` — R1.5 and R1.6, which made `ui` a group +- `specs/agent-skill/` — R5.2, the entry skill carrying the rules that cost money if missed + +## Out of scope + +- Any change to what the commands do. This is what they are called and what they cost. +- The generating form's own price, which is unchanged at Pro Tools. + +## Tasks + +- [x] 1.1 (Unit) Name `ui new` wherever a price is quoted, and sync the installed copies +- [x] 1.2 (Unit) Refuse a price list that names a group rather than a command + _Reason nothing would catch a third rename leaving a price behind_ + +## Done when + +- No file under `src/` or `.claude/skills/` quotes a price for `ui` rather than `ui new` +- `pixellab-cli setup` writes a harness block naming `ui new` diff --git a/src/pixellab_cli/commands/interface.py b/src/pixellab_cli/commands/interface.py index c18a883..9d77eff 100644 --- a/src/pixellab_cli/commands/interface.py +++ b/src/pixellab_cli/commands/interface.py @@ -375,7 +375,7 @@ def _choose_ui_route( ) -> str: """A panel unless a panel was never possible or never intended (R1.3). - The default is unchanged, because a panel is what `ui` has always made. The two + The default is unchanged, because a panel is what this command has always made. The two signals that move it are the ones a panel route cannot answer: a size beneath its floor, and a concept image it has no slot for. """ diff --git a/src/pixellab_cli/harness.py b/src/pixellab_cli/harness.py index 40c8323..8b3d770 100644 --- a/src/pixellab_cli/harness.py +++ b/src/pixellab_cli/harness.py @@ -290,7 +290,7 @@ def block_body(references: Path | None) -> str: started from, and what the subject has cost. A `--start-pose` from the wrong character is accepted by the route, charged per frame per direction, and comes back wrong. -- **Pro Tools cost twenty to forty generations a call:** `object new`, `ui`, `inpaint`, +- **Pro Tools cost twenty to forty generations a call:** `object new`, `ui new`, `inpaint`, `tiles variants`, `character state`, `outfit`, `sprite` with more than one `--style`, and `edit` with more than one image. - **A failed generation is charged.** `pixellab-cli ledger` lists what was spent, and diff --git a/src/pixellab_cli/skill/pixellab-cli-assets/SKILL.md b/src/pixellab_cli/skill/pixellab-cli-assets/SKILL.md index 6bb2ad6..9933c5c 100644 --- a/src/pixellab_cli/skill/pixellab-cli-assets/SKILL.md +++ b/src/pixellab_cli/skill/pixellab-cli-assets/SKILL.md @@ -113,7 +113,7 @@ recovering a stopped run, and the free local tooling every category uses. - Free or cheap, reach for them freely: `balance`, `ledger`, `character list|show|sheet`, `object list`, `recipe list`, every `image` command, every `export` command, `config show|path`. -- **Pro Tools, twenty to forty generations a call:** `object new`, `ui`, `inpaint`, +- **Pro Tools, twenty to forty generations a call:** `object new`, `ui new`, `inpaint`, `tiles variants`, `character state`, `outfit`, `portrait`, `sprite` with more than one `--style`, and `edit` with more than one image. They say so before calling. Never run one without agreement. diff --git a/src/pixellab_cli/skill/pixellab-cli-assets/references/costs.md b/src/pixellab_cli/skill/pixellab-cli-assets/references/costs.md index dd8217f..b5e1c18 100644 --- a/src/pixellab_cli/skill/pixellab-cli-assets/references/costs.md +++ b/src/pixellab_cli/skill/pixellab-cli-assets/references/costs.md @@ -16,7 +16,7 @@ public pricing; the tool records what each call actually reported. | Base | ~1 | `sprite`, `animate`, `character animate` (per direction), `edit` with one image, `tiles isometric`, `tiles prop` | | Character | ~3–4 | `character new`, `rotate` | | Tilesets | ~3 | `tiles terrain`, `tiles platform` | -| Pro Tools | 20–40 | `object new`, `ui`, `inpaint`, `tiles variants`, `character state`, `outfit`, `portrait`, `interpolate`, `sprite` with several `--style`, `edit` with several images or `--match` | +| Pro Tools | 20–40 | `object new`, `ui new`, `inpaint`, `tiles variants`, `character state`, `outfit`, `portrait`, `interpolate`, `sprite` with several `--style`, `edit` with several images or `--match` | | Fonts | 25 fixed | `font` | | By generation time | 1–6 | `animate` above sixteen frames, which is `animate-pixminimax`: beta, tier 1 and above, and the one route whose estimate here is rough | | fal | unpriced | `art concept`, `art anchor`, `art boxart`, `art edit` — fal reports no usage and publishes no price, so the ledger records these as unknown rather than as a number nobody checked | @@ -70,7 +70,7 @@ Interpolate knows both ends and fills the middle, on a Pro route, at most 128 pe and picks the count itself. If they described where the motion ends as concretely as where it starts — shut to open, car to robot — it is interpolate. -**`ui` against `sprite --style`.** A panel is one picture holding a whole layout, for Pro +**`ui new` against `sprite --style`.** A panel is one picture holding a whole layout, for Pro pricing. Several small repeated pieces — inventory slots, icon buttons — come back as separate sprites from a style reference, which is usually what was actually wanted. @@ -103,5 +103,5 @@ conversion. `pixellab-cli-images` carries the full decision. - `animate` and `character animate`: at most 256 per side, and width times height times frames may not exceed 524,288 — so 256x256 stops at eight frames. - `inpaint`: 32 to 512 per side. -- `ui`: 192 to 688 per axis, the maximum depending on the aspect. +- `ui new`: 192 to 688 per axis, the maximum depending on the aspect. - `sprite` with several `--style`: no size at all — it is read off the style images. diff --git a/src/pixellab_cli/skill/pixellab-cli-interface/SKILL.md b/src/pixellab_cli/skill/pixellab-cli-interface/SKILL.md index 3f5c599..0285f64 100644 --- a/src/pixellab_cli/skill/pixellab-cli-interface/SKILL.md +++ b/src/pixellab_cli/skill/pixellab-cli-interface/SKILL.md @@ -54,7 +54,7 @@ With neither `--element` nor `--piece` the result is one full-canvas rounded pan ## A panel, or one element -`ui` makes a **panel** by default, and moves to the single-element route +`ui new` makes a **panel** by default, and moves to the single-element route (`generate-ui-v2`) on either of the two signals that mean a panel was never possible or never intended: diff --git a/tests/test_skill.py b/tests/test_skill.py index 3f02ca2..fad0240 100644 --- a/tests/test_skill.py +++ b/tests/test_skill.py @@ -280,3 +280,43 @@ def test_every_packaged_skill_is_named_here(self): from pixellab_cli.harness import packaged_skills assert set(packaged_skills()) == set(ALL_SKILLS) + + +class TestAPriceNamesACommand: + """`ui` was listed as a Pro Tools route, and then `ui` became a group whose other + two commands are free. Nothing caught it, because a group's name reads as a command + everywhere else in this file. A price is quoted against what a person types, so + every name in a price list has to be a leaf command.""" + + PRICED = re.compile(r"Pro Tools[^\n|]*[:|]([^\n]*)") + + def priced_names(self, body: str) -> set[str]: + """Every backticked command named on a line that quotes the Pro Tools price.""" + named: set[str] = set() + for line in self.PRICED.findall(body): + for quoted in re.findall(r"`([^`]+)`", line): + # `sprite` with several `--style` is one entry in two spans: a flag is + # not a command and is not what this is checking. + if not quoted.startswith("-"): + named.add(quoted) + return named + + def sources(self) -> dict[str, str]: + from pixellab_cli.harness import block_body + + return { + "the harness block": block_body(None), + "the entry skill": entry_text(), + "the cost reference": (REFERENCES / "costs.md").read_text(encoding="utf-8"), + } + + def test_each_source_quotes_a_price_against_something(self): + """A regex that matches nothing would pass every assertion below.""" + for where, body in self.sources().items(): + assert self.priced_names(body), f"no priced command found in {where}" + + def test_every_priced_name_is_a_command_somebody_can_type(self): + commands = cli_commands() + for where, body in self.sources().items(): + for name in sorted(self.priced_names(body)): + assert name in commands, f"{where} prices {name!r}, which is not a command"