Functions, Inputs, and Components
This section lists the helpers available in Lis Novel prompts.
Built-in prompt template functions
wordCount
Counts whitespace-delimited words in a string.
wordCount(text) -> number
Arguments
text- the string to measure. Usemessage,input("Notes"), orscene.fullText()when you need to count prompt content.
Return value The total number of words in text.
Definition Words are separated by whitespace (space, tab, or line break). Punctuation stays attached to the word.
Empty input Returns 0 for empty or whitespace-only strings.
Example 1: Count prompt input
Notes ({wordCount(input("Notes"))} words):
{input("Notes")}
Output 1
Notes (12 words):
Keep the pacing tight and focus on the protagonist's choice.
Example 2: Enforce a limit
{#if isGreaterThan(wordCount(message), 120)}
Keep the response under 120 words.
{#endif}
Output 2
Keep the response under 120 words.
lowercase
Converts a string to all lowercase letters.
lowercase(text) -> string
Arguments
text- the string to transform.
Return value The lowercase version of text.
Empty input Returns an empty string for missing or non-string values.
Example 1: Single input
Label: {lowercase(input("Label"))}
Output 1
Label: draft
Example 2: Conditional match
{#if isEqual(lowercase(scene.subtitle), "flashback")}
Add a flashback note to the response.
{#endif}
Output 2
Add a flashback note to the response.
removeWhitespace
Trims leading and trailing whitespace from one or more inputs.
removeWhitespace(...texts) -> string
Arguments
texts- one or more values to combine and trim. Each value is converted to text, concatenated, and then the outer whitespace is trimmed.
Return value The combined text with leading and trailing whitespace removed.
Definition Whitespace includes spaces, tabs, and line breaks. Interior whitespace is preserved.
Empty input Returns an empty string when no arguments are provided.
Example 1: Single input
Title: {removeWhitespace(" The Last Signal ")}
Output 1
Title: The Last Signal
Example 2: Title + subtitle
{removeWhitespace("Chapter 3", "-", "The Gate Arrival")}
Output 2
Chapter 3-The Gate Arrival
Example 3: Title + subtitle + tag
Slug: {removeWhitespace(" Chapter 3", "-", "The Gate Arrival", ":", "Draft ")}
Output 3
Slug: Chapter 3-The Gate Arrival:Draft
asXml
Serializes a value into structured XML.
asXml(value, tagName?) -> string
Arguments
value- the value to render as XML.tagName- optional wrapper tag name. Defaults todata.
Return value XML text representing the value.
XML passthrough If value is already valid XML, it is returned as-is.
Accepted values
- Strings and numbers (rendered as text content).
- Booleans (
true/false). - Arrays (each item becomes a sibling tag).
- Plain objects (keys become nested tags).
- Null/undefined (renders as an empty string).
Example
{asXml(input("Additional Context"), "context")}
Sample output
<context>Keep the tone warm and close.</context>
asXml details
- Handles nested objects and arrays, turning them into structured XML.
- Sanitizes the optional tag name so it is a valid XML tag.
- Returns valid XML strings as-is without double-wrapping.
- Guards against circular references to prevent infinite loops.
Logic
and
Returns true when every argument is truthy.
and(...values) -> boolean
Arguments
values- one or more values to test.
Return value true when all values are truthy; otherwise false.
Truthiness Empty strings, 0, null, undefined, and false are falsy.
Example 1: Require text and summary
{#if and(scene.hasText, scene.hasSummary)}
This scene is ready for export.
{#endif}
Example 2: Gate a long opening
{#if and(isStartOfText, isGreaterThan(wordCount(message), 40))}
Summarize the opening in two sentences.
{#endif}
or
Returns true when any argument is truthy.
or(...values) -> boolean
Arguments
values- one or more values to test.
Return value true when at least one value is truthy; otherwise false.
Truthiness Empty strings, 0, null, undefined, and false are falsy.
Example 1: Include if there is any content
{#if or(scene.hasSummary, scene.hasText)}
Include this scene in the export.
{#endif}
Example 2: Prefer shorter content
{#if or(isLessThan(wordCount(scene.fullText()), wordCount(scene.summary)), not(scene.hasSummary))}
Use full scene text:
{scene.fullText}
{#else}
Use summary:
{scene.summary}
{#endif}
not
Returns true when the given value is falsy.
not(value) -> boolean
Arguments
value- the value to invert.
Return value true when value is falsy; otherwise false.
Truthiness Empty strings, 0, null, undefined, and false are falsy.
Example 1: Detect empty scene
{#if not(scene.hasText)}
Write a brief summary for this scene.
{#endif}
Example 2: Continue mid-scene
{#if not(isStartOfText)}
Continue from the previous paragraph.
{#endif}
Example 3: Recap when no summary exists
{#if and(isStartOfText, not(scene.hasSummary))}
No scene summary yet. Use the story so far to anchor continuity:
{storySoFar}
{#endif}
ifs
Returns the value for the first condition that is truthy. If none are truthy, it returns the fallback when provided.
ifs(condition, value, ...pairs, fallback?) -> any
Arguments
condition- the first condition to evaluate.value- the value to return when the first condition is truthy.pairs- additionalcondition, valuepairs to evaluate in order.fallback- optional final value to return when no conditions match.
Return value The value for the first truthy condition, or the fallback when supplied.
Pairs Even argument counts are treated as condition/value pairs. Odd counts use the final value as a fallback.
Example 1: Summary vs text fallback
Context: {ifs(scene.hasSummary, "summary-first", scene.hasText, "text-first", "neutral")}
Sample output
Context: summary-first
Example 2: Size buckets from message length
{ifs(
isGreaterThan(wordCount(message), 200), "long",
isGreaterThan(wordCount(message), 60), "medium",
"short"
)}
Comparison
isEqual
Returns true when two values are strictly equal (===).
isEqual(a, b) -> boolean
Arguments
a- the first value to compare.b- the second value to compare.
Return value true when a === b; otherwise false.
Example
{#if isEqual(pov.character, "Mara")}
Use Mara's voice and internal thoughts.
{#endif}
isGreaterThan
Returns true when the first number is greater than the second.
isGreaterThan(a, b) -> boolean
Arguments
a- the number to compare.b- the number to compare against.
Return value true when a > b; otherwise false.
Type Returns false if either argument is not a number.
Example
{#if isGreaterThan(wordCount(act.fullText), 10000)}
Reduce the length of the Act text.
{#endif}
isLessThan
Returns true when the first number is less than the second.
isLessThan(a, b) -> boolean
Arguments
a- the number to compare.b- the number to compare against.
Return value true when a < b; otherwise false.
Type Returns false if either argument is not a number.
Example
{#if isLessThan(wordCount(message), 40)}
Ask for more detail before continuing.
{#endif}
isGreaterOrEqual
Returns true when the first number is greater than or equal to the second.
isGreaterOrEqual(a, b) -> boolean
Arguments
a- the number to compare.b- the number to compare against.
Return value true when a >= b; otherwise false.
Type Returns false if either argument is not a number.
Example
{#if isGreaterOrEqual(wordCount(message), 200)}
Summarize the response before the next step.
{#endif}
isLessOrEqual
Returns true when the first number is less than or equal to the second.
isLessOrEqual(a, b) -> boolean
Arguments
a- the number to compare.b- the number to compare against.
Return value true when a <= b; otherwise false.
Type Returns false if either argument is not a number.
Example
{#if isLessOrEqual(wordCount(message), 15)}
Expand the response with a concrete detail.
{#endif}
Lis Novel custom functions
local temporary storage
local() offers per-render storage that is shared across the main template and any included components.
Requirements and behavior:
- The key must be a string literal (for example,
local("lead")). local("key", value)stores the value and emits nothing.local("key")returns the stored value or nothing if it is missing.- Later writes to the same key overwrite earlier values.
{local("lead", input("Lead"))}
Lead operative: {local("lead")}
without
Removes a substring from text, or filters lore entries out of a content selection.
without(source, removal) -> string
Arguments
source- the text or content selection to filter.removal- the text or lore context to remove.
Return value The filtered text or rendered XML.
Text behavior Removes every occurrence of removal from source and trims the result.
Selection behavior When source is a content selection input, lore entries already present in removal are removed and the remaining selections are returned as XML.
Example 1: Remove lore already in context
{without(input("Additional Context"), lore.context)}
Example 2: Remove a keyword
Context (cleaned):
{without(input("Notes"), "spoiler")}
lastWords
Returns the last N words from a text value or scene.
lastWords(text, count) -> string
Arguments
text- the source text or a scene object.count- the number of words to return.
Return value The final count words from the input.
Scenes When text is a scene, it prepends the scene title and counts words from the scene body only.
Example 1: Scene tail excerpt
Tail excerpt:
{lastWords(scene.fullText, 10)}
Sample output
Tail excerpt:
# Chapter 1: Intro - Scene 1
keep its scraps. At the edge sirens curl neon veins.
Example 2: Text tail excerpt
{lastWords("The city keeps moving; hope is a line in the gutter, and I keep its scraps. At the edge sirens curl neon veins.", 10)}
Sample output
keep its scraps. At the edge sirens curl neon veins.
Prompt inputs
Prompt inputs are available in two ways:
input("Name")retrieves the value by its exact input name.- A camel-cased alias is injected into the context (for example, input name
Qwe Rtybecomes{qweRty}), as long as it does not collide with built-in variables or functions.
Duplicate input names are discouraged. The editor warns when a name is reused, and runtime resolution favors the first matching input.
For custom_content inputs that use dropdown options, Lis Novel resolves the stored option ID to its display label before rendering. Templates see the label value, not the internal option ID.
Defaults and preview
Prompt inputs can define a default value through the Input Preview UI. Defaults are applied when a user has not provided a value yet, and they satisfy required inputs.
Defaults currently apply to:
custom_content(single or multiple values)checkbox
Content selection inputs show a disabled button preview only; they do not accept defaults.
Content selection inputs
content_selection inputs are pre-rendered into XML before the prompt renderer evaluates them:
- Lore selections (entry, type, tag, detail) become rendered lore XML.
full_novel_textandfull_novel_outlineinject the full novel XML content.act,chapter, andsceneselections inject full-text XML for the selected item. Acts include their chapters and scenes, chapters include their scenes, and scenes include their full text (beat-free). Each selection is wrapped in a<novel>root with the containing<act>or<chapter>when applicable; in chapters-only or scenes-only mode,<act>wrappers are omitted, and in scenes-only mode chapter selections omit the<chapter>wrapper.
If you need raw selection data for custom logic, capture it elsewhere. These inputs are already formatted for direct inclusion in prompts.
input
Returns the value for the prompt input with the given name.
input(name) -> any
Arguments
name- the exact prompt input name (case-sensitive).
Return value The stored input value for the matching input name.
Missing input Returns nothing if no input with that name exists.
Example
Notes:
{input("Notes")}
Sample output
Notes:
Keep the voice intimate and focused on sensory detail.
Example
{#if input("Tone")}
Tone: {input("Tone")}
{#endif}
include in Lis Novel
Lis Novel resolves include("PromptName") for component prompts only. Regular prompts are not eligible for inclusion. When a component contains multiple messages, Lis Novel concatenates their contents in position order with a blank line between messages.
Includes are resolved by component name from your prompt library at render time. Included components share the same context (inputs, variables, and local() storage), and components can include other components.
If a component name cannot be resolved, Lis Novel inserts nothing. Recursive includes are blocked to prevent infinite loops.
without and content selections
When without(source, removal) receives a content_selection input as source, it never returns the raw selection array. It removes any lore entries already present in removal, then returns the remaining selections as rendered XML. Any non-lore selections (act, chapter, scene, full novel text or outline) pass through unchanged in the returned XML.