scriptling.console
The scriptling.console library provides a TUI (terminal UI) console. Import the library and use module-level functions to interact with it. There is a single shared TUI instance — no constructor needed.
Note: This library is for TUI use only. For plain stdin/stdout I/O, use the built-in
print()andinput()functions.
Import
import scriptling.console as consoleOverview
The console uses a module-level API. Everything is accessed through the console module directly — there is no Console() constructor. The TUI is a singleton shared across your entire application.
Module-level — TUI control, layout, overlays, callbacks:
console.panel("logs") # get a named panel
console.main_panel() # get the main chat panel
console.create_panel(...) # create a panel
console.add_left(panel) # layout
console.add_right(panel) # layout
console.clear_layout() # layout
console.has_panels() # check layout state
console.styled(color, text) # theme helper
console.set_status(l, r) # status bar
console.spinner_start(text) # overlay
console.set_progress(l, pct) # overlay
console.on_submit(fn) # callback
console.on_escape(fn) # callback
console.run() # event loopPanel — all content operations:
main = console.main_panel()
main.add_message("Hello")
main.stream_start()
main.stream_chunk("chunk")
main.stream_end()
main.clear()
logs = console.panel("logs")
logs.write("entry\n")
logs.set_content("full content")Module Functions
| Function | Description |
|---|---|
panel([name]) |
Get a Panel by name (default: "main") |
main_panel() |
Get the main chat panel |
create_panel([name], **kwargs) |
Create a new panel (independent of layout) |
add_left(panel) |
Add a panel to the left of main |
add_right(panel) |
Add a panel to the right of main |
clear_layout() |
Remove layout tree but keep all panels |
has_panels() |
Return True if multi-panel layout is active |
styled(color, text) |
Apply a color to text; returns the styled string |
set_status(left, right) |
Set both status bar texts |
set_status_left(text) |
Set left status bar text only |
set_status_right(text) |
Set right status bar text only |
set_labels(user, assistant, system) |
Set default role labels; empty string leaves label unchanged |
register_command(name, desc, fn) |
Register a slash command with the TUI |
remove_command(name) |
Remove a registered slash command |
spinner_start([text]) |
Show a spinner (TUI overlay, not per-panel) |
spinner_stop() |
Hide the spinner |
set_progress(label, pct) |
Set progress bar (0.0–1.0, or <0 to clear) |
on_submit(fn) |
Register handler called when user submits input |
on_escape(fn) |
Register a callback for Esc key |
run() |
Start the console event loop (blocks until exit) |
Styling
console.styled(color, text) applies a foreground color to text and returns the styled string.
The color argument accepts a constant, a theme color name string, or a hex color.
Color constants
| Constant | Name string | Description |
|---|---|---|
console.PRIMARY |
"primary" |
Accent color (prompts, highlights) |
console.SECONDARY |
"secondary" |
Secondary accent |
console.ERROR |
"error" |
Error / warning color |
console.DIM |
"dim" |
Muted / hint text |
console.USER |
"user" |
User message text color |
console.TEXT |
"text" |
Normal content text |
# Using constants (recommended)
console.styled(console.PRIMARY, "ScriptlingCoder")
console.styled(console.DIM, "type /exit to quit")
console.styled(console.ERROR, "WARNING: executes AI-generated code")
# Using name strings
console.styled("primary", "ScriptlingCoder")
# Hex colors (#RRGGBB or RRGGBB)
console.styled("#ff6600", "orange text")
console.styled("4eb8c8", "teal text")
# Combine with add_message
main = console.main_panel()
main.add_message(
console.styled(console.PRIMARY, "MyApp") + " — " +
console.styled(console.DIM, "type /exit to quit")
)Output (Main Panel)
main = console.main_panel()
main.add_message("Hello", "world") # adds a message to the main area
main.add_message("result here", label="Tool") # message with a custom label
main.clear() # clears all messagesStreaming
Use streaming to display text as it arrives (e.g. LLM token output).
main = console.main_panel()
main.stream_start() # default label
main.stream_start(label="Assistant") # custom label
for chunk in response_chunks:
main.stream_chunk(chunk)
main.stream_end()Spinner
Spinner is a TUI-level overlay — it’s not tied to any specific panel.
console.spinner_start("Thinking")
result = do_work()
console.spinner_stop()Progress
Progress is a TUI-level overlay — it’s not tied to any specific panel.
console.set_progress("Downloading", 0.5) # 50%
console.set_progress("", -1) # clearStatus bar
console.set_status("myapp", "v1.0") # set both
console.set_status_left("myapp") # left only
console.set_status_right("v1.0") # right onlySlash commands
def cmd_clear(args):
console.main_panel().clear()
console.register_command("clear", "Clear output", cmd_clear)
console.remove_command("clear") # remove when no longer neededEvent loop
Register handlers then call run() to hand control to the event loop:
import scriptling.console as console
main = console.main_panel()
def on_submit(text):
main.stream_start()
main.stream_chunk("You said: " + text)
main.stream_end()
console.set_status("MyApp", "v1.0")
main.add_message(console.styled(console.PRIMARY, "MyApp") + " ready!")
console.on_submit(on_submit)
console.on_escape(lambda: console.spinner_stop())
console.run() # blocks until /exit or Ctrl+CLabels
console.set_labels("You", "Assistant", "") # empty string leaves that label unchangedPanels
The console supports a multi-panel layout with a main chat area in the center, plus optional left and right side panels. Use the two-step builder pattern: create panels first, then add them to the layout.
Layout
┌──────────┬─────────────────────┬──────────────┐
│ Left │ Main Chat │ Right Top │
│ Panel │ ├──────────────┤
│ │ │ Right Bottom │
└──────────┴─────────────────────┴──────────────┘Builder API
Use create_panel to create panels with configuration, then attach them to the layout with add_left, add_right, add_row, and add_column.
console.create_panel(name="", **kwargs)
Create a new panel. Panels exist independently of the layout — they can be added and removed without losing their content.
Parameters:
name(str, optional): Panel identifierwidth(int): Positive = columns, negative = percentage (e.g. -25 = 25%)height(int): Positive = rows, negative = percentage (e.g. -50 = 50%)min_width(int): Minimum width to render; panel hides if narrowerscrollable(bool):True= content scrolls,False= fixed viewporttitle(str): Title shown in the borderno_border(bool):True= hide borderskip_focus(bool):True= exclude from Tab focus cycle
Returns: Panel — a panel instance
console.add_left(panel)
Add a panel to the left of the main panel.
Parameters:
panel(Panel): Panel to add
console.add_right(panel)
Add a panel to the right of the main panel.
Parameters:
panel(Panel): Panel to add
console.clear_layout()
Remove the layout tree but keep all panels and their content. Panels can be re-added with add_left/add_right to toggle layouts.
panel.add_row(child)
Add a child panel as a vertical row (appends top to bottom). A panel cannot mix rows and columns — adding a row to a panel that already has columns is a no-op.
Parameters:
child(Panel): Child panel to add
panel.add_column(child)
Add a child panel as a horizontal column (appends left to right). A panel cannot mix rows and columns — adding a column to a panel that already has rows is a no-op.
Parameters:
child(Panel): Child panel to add
Basic example
# Create panels
logs = console.create_panel("logs", width=-25, min_width=15, scrollable=True, title="Logs")
right = console.create_panel(width=-30, min_width=16)
cpu_panel = console.create_panel("cpu", height=-50, title="CPU")
mem_panel = console.create_panel("mem", height=-50, title="Memory")
# Build layout tree
right.add_row(cpu_panel)
right.add_row(mem_panel)
# Attach to console
console.add_left(logs)
console.add_right(right)Toggle layout
Panels persist across layout changes — their content and scroll state are preserved when you clear and re-add them:
def cmd_layout(args):
if console.has_panels():
console.clear_layout()
else:
console.add_left(logs)
console.add_right(right)
console.register_command("layout", "Toggle panel layout", cmd_layout)Full dashboard example
import scriptling.console as console
import scriptling.runtime as runtime
import time
# Create panels
logs = console.create_panel("logs", width=-25, min_width=15, scrollable=True, title="Logs")
right = console.create_panel(width=-30, min_width=16)
cpu_panel = console.create_panel("cpu", height=-50, title="CPU")
mem_panel = console.create_panel("mem", height=-50, title="Memory")
# Build layout
right.add_row(cpu_panel)
right.add_row(mem_panel)
console.add_left(logs)
console.add_right(right)
# Background task updates the logs panel
shared = runtime.sync.Shared("state", {"count": 0})
def pump_logs():
while True:
state = shared.get()
count = state["count"]
timestamp = time.strftime("%H:%M:%S")
line = console.styled(console.DIM, timestamp) + " Log entry #" + str(count)
logs.write(line + "\n")
state["count"] = count + 1
shared.set(state)
time.sleep(1)
runtime.background("log_pump", "pump_logs")
main = console.main_panel()
console.on_submit(lambda text: main.add_message("Echo: " + text))
console.run()console.panel(name)
Get an existing Panel instance by name. Returns None if the panel doesn’t exist. Defaults to "main" if no name is given.
Parameters:
name(str): Panel name
Returns: Panel or None
logs = console.panel("logs")
if logs:
logs.write("Application started\n")console.has_panels()
Return True if a multi-panel layout is active.
if console.has_panels():
console.clear_layout() # toggle back to single panelPanel class
console.create_panel(name) and console.panel(name) return a Panel instance with its own methods. Panels support both raw text content and message-based content.
Panel methods
| Method | Description |
|---|---|
write(text) |
Append text to the panel |
set_content(text) |
Replace all panel content |
clear() |
Remove all panel content |
set_title(title) |
Set the panel border title |
set_color(color) |
Set border/accent color (name or hex) |
set_scrollable(bool) |
Set whether content scrolls |
add_message(*args, [label=], [role=]) |
Add a message to the panel |
stream_start([label=], [role=]) |
Begin a streaming message |
stream_chunk(text) |
Append a chunk to the current stream |
stream_end() |
Finalise the current stream |
add_row(panel) |
Add a child panel as a vertical row |
add_column(panel) |
Add a child panel as a horizontal column |
scroll_to_top() |
Scroll to top of panel content |
scroll_to_bottom() |
Scroll to bottom of panel content |
size() |
Return [width, height] of the panel |
styled(color, text) |
Apply theme color to text |
write_at(row, col, text) |
Write text at a specific position |
clear_line(row) |
Clear a specific line |
Focus and navigation
- Tab cycles focus through all visible, non-skip-focus panels (left children, main, right children)
- Focused panel border uses the panel’s color; unfocused uses the theme’s dim color
- Call
clear_layout()to reset to single-panel mode at any time - Panels and their content survive
clear_layout()— re-add them withadd_left/add_right
Background tasks
Background tasks can access panels without needing a Console instance:
def pump_logs():
# Look up the panel by name — works from any background task
logs = console.panel("logs")
if logs:
logs.write("Background message\n")Go integration
Register the library with a scriptling interpreter:
import "github.com/paularlott/scriptling/extlibs/console"
console.Register(p) // p is *scriptling.ScriptlingThe TUI is created automatically when first accessed. Use console.TUI() to get the shared *tui.TUI from Go code.
The on_submit callback receives a context.Context that is cancelled when the user presses Esc. Pass this context to any cancellable operations (e.g. streaming AI completions) so they stop immediately on Esc.
See Also
- scriptling.ai.agent.interact — interactive agent loop using this library