Skip to content
Closed
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3927,6 +3927,7 @@ credited it to the wrong command.)
| `model substituted` | The server ran a **different checkpoint** than you asked for and billed for what ran. Warned by default; `--fail-on-substitution` turns it into a refusal on the estimate, before any spend. | [Silent model substitution](#-silent-model-substitution) |
| `The server reported: …` | The orchestrator recorded an account of what happened, and what follows is the server's own words. The CLI does not interpret them and does not know whether the failure is retryable; the only thing it changes is that invisible and direction-reversing characters are removed before the text reaches your terminal (`--json` is unfiltered). Printed on the `generate` error and by `civitai workflows get`. | [What the server says went wrong](#what-the-server-says-went-wrong) |
| `An indented line under a row is what the server recorded` | The same record, on `civitai workflows list`: the indented lines beneath a workflow's row are the server's own words for that workflow, wrapped but never abbreviated (the invisible characters above are removed, and wrapping collapses runs of whitespace and breaks a token longer than the line — no words are dropped). The indent is not decoration — it keeps server text out of the column a real row starts in, so a message cannot pose as a workflow of yours. It holds for the line breaks the CLI makes: the CLI wraps to a fixed 79 columns and never asks how wide your terminal is, so in a **narrower** terminal — or with wide (CJK) characters — your terminal re-wraps and the overflow can still reach column zero. | [What the server says went wrong](#what-the-server-says-went-wrong) |
| `prompt: …` / `negative: …` | In `civitai images search --meta` and `civitai images get`: generation prompts are rendered with continuation lines indented so a server string cannot impersonate CLI output headers. Unlike workflow failure reasons, prompts are deliberately **not** soft-wrapped by the CLI — wrapping would collapse whitespace runs and split tokens, altering prompt weights and syntax. In terminals narrower than an emitted line, the terminal's own soft-wrap still occurs and the overflow can reach column zero. | [Command reference](#command-reference) |
| `the orchestrator often supplies no failure reason, so it may not say why` | The same failure with **no** account recorded — a real, measured case, not a CLI limitation. Neither `civitai workflows get <id>` nor `civitai workflows list` will say why either. | [What the server says went wrong](#what-the-server-says-went-wrong) |

### Everything else
Expand Down
32 changes: 17 additions & 15 deletions internal/cmd/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,17 +243,18 @@ func printImageList(cmd *cobra.Command, items []civitai.ImageItem) {
fmt.Fprintln(tw, "ID\tUPLOADER\tBASE MODEL\tSIZE\tNSFW\tHEARTS\tCOMMENTS\tURL")
for _, im := range items {
fmt.Fprintf(tw, "%d\t%s\t%s\t%dx%d\t%s\t%d\t%d\t%s\n",
im.ID, orDash(safeTerm(im.Username.String())), orDash(truncate(safeTerm(im.BaseModel), 24)),
im.Width, im.Height, orDash(safeTerm(im.NSFWLevel)),
im.Stats.HeartCount, im.Stats.CommentCount, safeTerm(im.URL))
im.ID, orDash(safeTermSingle(im.Username.String())), orDash(truncate(safeTermSingle(im.BaseModel), 24)),
im.Width, im.Height, orDash(safeTermSingle(im.NSFWLevel)),
im.Stats.HeartCount, im.Stats.CommentCount, safeTermSingle(im.URL))
}
_ = tw.Flush()
}

// printImageListMeta renders each image as an indented detail block instead of
// the compact table, so it can carry the generation metadata (prompt, settings)
// that --meta requests. Every server-origin string is routed through safeTerm —
// prompts are attacker-controlled user text and can carry ANSI/control bytes.
// that --meta requests. Every server-origin string is routed through safeTerm or
// safeTermSingle — prompts are indented multi-line text, while inline metadata and
// table columns are sanitized to single lines to prevent output forgery (#552).
func printImageListMeta(cmd *cobra.Command, items []civitai.ImageItem) {
out := cmd.OutOrStdout()
if len(items) == 0 {
Expand All @@ -267,12 +268,13 @@ func printImageListMeta(cmd *cobra.Command, items []civitai.ImageItem) {

// printImageMetaBlock renders one image as an indented detail block carrying its
// generation metadata (prompt, settings, and the resources "recipe"). Every
// server-origin string is routed through safeTerm — prompts, resource names and
// hashes are attacker-controlled user text that can carry ANSI/control bytes.
// server-origin string is routed through safeTerm or safeTermSingle — prompts are
// indented multi-line text, while inline metadata (model, sampler, resources,
// hashes, URL) is sanitized to single lines to prevent output forgery (#552).
// Shared by `images search --meta` and `images get`.
func printImageMetaBlock(out io.Writer, im civitai.ImageItem) {
fmt.Fprintf(out, "%d [%s] %dx%d by %s\n",
im.ID, orDash(safeTerm(im.NSFWLevel)), im.Width, im.Height, orDash(safeTerm(im.Username.String())))
im.ID, orDash(safeTermSingle(im.NSFWLevel)), im.Width, im.Height, orDash(safeTermSingle(im.Username.String())))
m, state := im.ParseMeta()
switch state {
case civitai.MetaAbsent:
Expand All @@ -285,9 +287,9 @@ func printImageMetaBlock(out io.Writer, im civitai.ImageItem) {
fmt.Fprintln(out, " meta: (unrecognized format)")
default: // civitai.MetaOK
fmt.Fprintf(out, " model: %s sampler: %s cfg: %s steps: %s seed: %s\n",
orDash(safeTerm(m.Model)), orDash(safeTerm(m.Sampler)),
orDash(safeTerm(m.CfgScaleString())), orDash(safeTerm(m.StepsString())),
orDash(safeTerm(m.SeedString())))
orDash(safeTermSingle(m.Model)), orDash(safeTermSingle(m.Sampler)),
orDash(safeTermSingle(m.CfgScaleString())), orDash(safeTermSingle(m.StepsString())),
orDash(safeTermSingle(m.SeedString())))
if strings.TrimSpace(m.Prompt) != "" {
fmt.Fprintf(out, " prompt: %s\n", indentContinuation(safeTerm(m.Prompt), " "))
}
Expand All @@ -296,7 +298,7 @@ func printImageMetaBlock(out io.Writer, im civitai.ImageItem) {
}
printImageResources(out, m)
}
fmt.Fprintf(out, " url: %s\n", safeTerm(im.URL))
fmt.Fprintf(out, " url: %s\n", safeTermSingle(im.URL))
}

// printImageResources renders the meta.resources reproduction recipe — one line
Expand All @@ -312,12 +314,12 @@ func printImageResources(out io.Writer, m civitai.ImageMeta) {
fmt.Fprintln(out, " resources:")
for _, r := range rs {
line := fmt.Sprintf(" - [%s] %s",
orDash(safeTerm(strings.TrimSpace(r.Type))), orDash(safeTerm(strings.TrimSpace(r.Name))))
orDash(safeTermSingle(strings.TrimSpace(r.Type))), orDash(safeTermSingle(strings.TrimSpace(r.Name))))
if w := strings.TrimSpace(r.WeightString()); w != "" {
line += " weight " + safeTerm(w)
line += " weight " + safeTermSingle(w)
}
if h := strings.TrimSpace(m.ResolveHash(r)); h != "" {
line += " hash " + safeTerm(h)
line += " hash " + safeTermSingle(h)
}
fmt.Fprintln(out, line)
}
Expand Down
Loading
Loading