Card Save
Turn an LLM's JSON card into a reusable library entry in one step
- card
- saved_as
- report
The whole point of the Kinburg pack's card system is that you describe a character or entity once and reuse it forever. The missing piece, historically, was the boring one: getting a card into the library. Before Card Save existed you'd have to route an LLM's JSON output through a JSON-extract node and hand-wire a dozen fields into a Character Card. This node closes that loop in one step.
Here's the flow the pack is built around: Grammar Presets → Local LLM (GGUF) (grammar_override) + a photo → Card Save. The grammar forces the vision model to emit a card-shaped JSON, and Card Save parses it and files it in the Card Presets library. No JSON dancing, no 12-wire choreography - the grammar's keys already mirror the card fields 1:1.
The inputs that matter:
json_string- the card JSON. Wire an LLM output here or paste. Prose around the JSON is tolerated - the first{…}is parsed, so a chatty model's preamble won't break it.card_type-auto(detects character vs entity from the keys), or forcecharacter/entityif auto guesses wrong. Auto's rule: a name+description with no character attributes is an entity, otherwise a character.save_as- the preset name. Empty = use the JSON's ownnamefield. Empty there too = render only, don't save. Re-saving the same name overwrites.tags- comma-separated labels for filtering the library later.
The outputs:
card- the rendered Markdown block, ready to feed Context Collector in the same run.saved_as- the name actually used, or empty if nothing was saved.report- a human line saying what happened ("saved 'Vasya' (character…)", or "not saved (no name in JSON…)").
The design detail worth knowing: the node never raises. Bad or empty JSON yields an empty card and an explanatory report instead of breaking the graph - which matters because LLM output is the messiest thing you'll ever wire into a node. And the resolved name is used both as the library key and the card heading, so an explicit save_as (the real name the model couldn't read off the photo) drives the block too.
Install via ComfyUI Manager (search "Kinburg-Nodes") or:
cd ComfyUI/custom_nodes
git clone https://github.com/Kinburg/Kinburg-Nodes
then restart. The node itself has no dependencies; the workflow it's built for needs the pack's Local LLM (GGUF) node working, which means llama-cpp-python (installed automatically by install.py, matched to your torch's CUDA version) and a vision-capable GGUF + mmproj of your own. A one-author personal pack, but maintained to a rare standard - 95 nodes, per-package docs, 1395 automated checks.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| json_string | STRING | The card JSON — wire a Local LLM (GGUF) output constrained by a Grammar Presets grammar, or paste. Prose around the JSON is tolerated (first {…} is parsed). | |
| card_type | COMBO | auto | auto = detect from the JSON keys (a name+description with no character attributes → entity, else character). Force it if auto guesses wrong. |
| save_asopt | STRING | Preset name for the library. Empty = use the JSON's own 'name' field. Empty here AND in the JSON = render only, don't save. Re-saving the same name overwrites it. | |
| tagsopt | STRING | Comma-separated tags to filter the library by in Card Presets (e.g. 'heroes, medieval'). Empty = leave existing tags untouched (edit/clear them in Card Presets → Manage). |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| card | STRING | — |
| saved_as | STRING | — |
| report | STRING | — |