jjui exposes a Lua API for custom workflows and UI automation.
For startup configuration wiring (setup(config), config.action, config.bind), see config.lua .
For LuaLS autocomplete and basic type information, run:
This installs generated jjui Lua API metadata into your config directory. See config.lua for details.
Define a Lua action in config.toml and bind it:
Helper functions and the revisions, revset, and context tables are available both as top-level globals and under the jjui namespace — flash(...) and jjui.flash(...) are the same, and revisions.edit() and jjui.revisions.edit() are the same.
Other built-in action scopes (ui, git, diff, oplog, bookmarks, undo, redo, choose, input, command_history, file_search) are only available under the jjui namespace — e.g. jjui.git.push(), not git.push().
Built-in action wrappers are additionally available under jjui.builtin.*, which forces built-in resolution and ignores any user-defined overrides for those action names.
Read-only access to the currently highlighted or selected item.
Function Returns Notes context.change_id()string Available for revisions and files context.commit_id()string Available for revisions, files, and commits context.file()string Only when a file is selected in the details view context.operation_id()string Only in the oplog context.checked_files()string[] Files checked in the details view context.checked_change_ids()string[] Checked revisions or files context.checked_commit_ids()string[] Checked revisions, files, or commits
Function Returns Notes revisions.current()string change_id of the highlighted revisionrevisions.checked()string[] change_id of every checked revisionrevisions.refresh({ keep_selections?, selected_revision? })— Yields until the revision list finishes loading revisions.navigate({ by?, page?, target?, to?, fallback?, ensureView?, allowStream? })— target: "parent", "child", or "working"
Function Returns revset.current()Active revset expression string revset.default()Default revset from config
Function Returns Notes jj(...args)(output, err)Runs synchronously; returns output string and error jj_async(...args)— Yields until the command completes jj_interactive(...args)— Opens an interactive terminal for the command
Args can be passed as individual strings or as a single table of strings.
Function Returns Notes flash(text)— Show a short status message flash({ text, error?, sticky? })— error = true styles as error; sticky = true keeps it visiblechoose(option1, option2, ...)string or nil Show a picker; returns the selected value or nil if cancelled choose({ options, title?, filter?, ordered? })string or nil Table form input({ title?, prompt? })string or nil Show a text input dialog; returns the entered text or nil set_theme(name)— Switch the active theme at runtime copy_to_clipboard(text)(true, nil) or (nil, err)Copy to system clipboard split_lines(text, keep_empty?)string[] Split on newlines; strips \r; skips empty lines by default exec_shell(command)— Run a shell command line; yields until done
set_theme(name) changes the currently active theme immediately. This is exposed to Lua for custom workflows and plugins, but it is not wired to the UI by default, so you need to call it from your own action, binding, or startup logic.
These pause the script until a matching UI event arrives.
Function Returns Notes wait_close()bool Waits for any modal or view to close; returns true if it was applied wait_refresh()— Waits for the revision list to finish refreshing
Every built-in action is exposed as a Lua function via jjui.<scope>.<action>(). These dispatch the action into the UI and yield until it is handled, so they compose naturally with other async steps.
In every scope that has a cancel() action, close() is available as an alias — e.g. jjui.git.close() is the same as jjui.git.cancel().
Actions that take a single required string arg accept it positionally:
revset . set ({ value = " trunk() " })
Function Notes revisions.absorb()Run jj absorb on the current revision revisions.new()Create a new revision revisions.commit()revisions.describe()Open external editor to edit description revisions.edit()revisions.force_edit()revisions.diff()revisions.diff_edit()revisions.split()revisions.split_parallel()revisions.apply({ force? })revisions.force_apply()revisions.toggle_select()revisions.refresh()revisions.move_up() / move_down()revisions.page_up() / page_down()revisions.jump_to_parent()revisions.jump_to_children()revisions.jump_to_working_copy()revisions.ace_jump()revisions.quick_search_next() / _prev() / _clear()revisions.open_abandon()Enter abandon mode revisions.open_details()Open the details view revisions.open_duplicate()Enter duplicate mode revisions.open_evolog()Open the evolution log revisions.open_inline_describe()Start inline describe revisions.open_rebase()Enter rebase mode revisions.open_revert()Enter revert mode revisions.open_set_bookmark({ value? })Open bookmark input, optionally prefilled revisions.open_set_parents()Enter set-parents mode revisions.open_squash()Enter squash mode
Function Args revisions.rebase.set_source({ source })"revision", "branch", or "source" (descendants)revisions.rebase.set_target({ target })"onto", "after", "before", or "insert"revisions.rebase.skip_emptied()Toggle revisions.rebase.apply({ force? })revisions.rebase.force_apply()revisions.rebase.cancel()revisions.rebase.target_picker()revisions.rebase.ace_jump()revisions.rebase.jump_to_working_copy()
Function Args revisions.squash.interactive()Toggle revisions.squash.keep_emptied()Toggle revisions.squash.use_destination_msg()Toggle revisions.squash.apply({ force? })revisions.squash.force_apply()revisions.squash.cancel()revisions.squash.target_picker()revisions.squash.ace_jump()revisions.squash.jump_to_working_copy()
Function Args revisions.duplicate.set_target({ target })"onto", "after", "before", or "insert"revisions.duplicate.apply({ force? })revisions.duplicate.force_apply()revisions.duplicate.cancel()revisions.duplicate.target_picker()revisions.duplicate.ace_jump()revisions.duplicate.jump_to_working_copy()
Function Args revisions.revert.set_target({ target })"onto", "after", "before", or "insert"revisions.revert.apply({ force? })revisions.revert.force_apply()revisions.revert.cancel()revisions.revert.target_picker()
Function revisions.abandon.toggle_select()revisions.abandon.select_descendants()revisions.abandon.apply({ force? })revisions.abandon.force_apply()revisions.abandon.cancel()revisions.abandon.ace_jump()revisions.abandon.jump_to_working_copy()
Function revisions.set_parents.toggle_select()revisions.set_parents.apply()revisions.set_parents.cancel()revisions.set_parents.ace_jump()revisions.set_parents.jump_to_working_copy()
Function Notes revisions.inline_describe.accept({ force? })Apply the description revisions.inline_describe.force_accept()Apply the description with force revisions.inline_describe.new_line()Insert a line break revisions.inline_describe.editor()Open external editor revisions.inline_describe.cancel()Cancel without saving
Function Args revisions.details.select_file(file)Required string (positional) revisions.details.toggle_select()revisions.details.squash()revisions.details.absorb()revisions.details.split()revisions.details.split_parallel()revisions.details.restore()revisions.details.diff()revisions.details.refresh()revisions.details.revisions_changing_file()revisions.details.move_up() / move_down()revisions.details.page_up() / page_down()revisions.details.cancel() / revisions.details.quit()
Shown when a details operation (e.g. squash) affects multiple revisions and needs confirmation.
Function revisions.details.confirmation.apply({ force? })revisions.details.confirmation.force_apply()revisions.details.confirmation.cancel()revisions.details.confirmation.next()revisions.details.confirmation.prev()
Function revisions.evolog.restore()revisions.evolog.apply({ force? })revisions.evolog.diff()revisions.evolog.move_up() / move_down()revisions.evolog.page_up() / page_down()revisions.evolog.cancel() / revisions.evolog.quit()
Function revisions.ace_jump.apply()revisions.ace_jump.cancel()
Shared target picker used by rebase, squash, duplicate, and revert.
Function revisions.target_picker.move_up() / move_down()revisions.target_picker.apply({ force? })revisions.target_picker.force_apply()revisions.target_picker.cancel()revisions.target_picker.autocomplete() / autocomplete_back()
Function revisions.set_bookmark.apply()revisions.set_bookmark.cancel()revisions.set_bookmark.autocomplete() / autocomplete_back()
Function Args revset.set(value)Required string (positional) revset.reset()revset.edit({ clear? })Open the revset editor revset.apply()revset.cancel()revset.autocomplete() / autocomplete_back()Cycle completion suggestions revset.move_up() / revset.move_down()Navigate completion list
Also available as a top-level global — ui.cancel() and jjui.ui.cancel() are the same.
Function Notes jjui.ui.cancel()jjui.ui.quit()jjui.ui.suspend()jjui.ui.open_bookmarks()jjui.ui.open_git()jjui.ui.open_oplog()jjui.ui.open_undo()jjui.ui.open_redo()jjui.ui.open_revset()jjui.ui.open_command_history()jjui.ui.file_search_toggle()jjui.ui.quick_search()jjui.ui.expand_status()jjui.ui.preview_toggle()jjui.ui.preview_toggle_bottom()jjui.ui.preview_expand() / jjui.ui.preview_shrink()jjui.ui.preview_scroll_down() / jjui.ui.preview_scroll_up()jjui.ui.preview_half_page_down() / jjui.ui.preview_half_page_up()jjui.ui.preview.show(content)Required string (positional) — display content in the preview panel jjui.ui.exec_jj()Open the jj command prompt jjui.ui.exec_shell()Open the shell command prompt
Function jjui.oplog.restore()jjui.oplog.revert()jjui.oplog.diff()jjui.oplog.move_up() / jjui.oplog.move_down()jjui.oplog.page_up() / jjui.oplog.page_down()jjui.oplog.close() / jjui.oplog.quit()
Function jjui.bookmarks.cycle_remotes() / jjui.bookmarks.cycle_remotes_back()jjui.bookmarks.filter()jjui.bookmarks.bookmark_delete()jjui.bookmarks.bookmark_forget()jjui.bookmarks.bookmark_move()jjui.bookmarks.bookmark_track()jjui.bookmarks.bookmark_untrack()jjui.bookmarks.move_up() / jjui.bookmarks.move_down()jjui.bookmarks.page_up() / jjui.bookmarks.page_down()jjui.bookmarks.apply() / jjui.bookmarks.cancel() / jjui.bookmarks.quit()
Function jjui.git.push()jjui.git.fetch()jjui.git.filter()jjui.git.cycle_remotes() / jjui.git.cycle_remotes_back()jjui.git.move_up() / jjui.git.move_down()jjui.git.page_up() / jjui.git.page_down()jjui.git.apply() / jjui.git.cancel() / jjui.git.quit()
Also available as a top-level global — diff.show(content) and jjui.diff.show(content) are the same.
Function jjui.diff.scroll_up() / jjui.diff.scroll_down()jjui.diff.page_up() / jjui.diff.page_down()jjui.diff.half_page_up() / jjui.diff.half_page_down()jjui.diff.move_top() / jjui.diff.move_bottom()jjui.diff.left() / jjui.diff.right()jjui.diff.show(content)jjui.diff.toggle_wrap()
Function jjui.undo.apply() / jjui.undo.cancel()jjui.undo.prev() / jjui.undo.next()jjui.redo.apply() / jjui.redo.cancel()jjui.redo.prev() / jjui.redo.next()
Function jjui.choose.apply()jjui.choose.cancel()jjui.choose.move_up() / jjui.choose.move_down()
Function jjui.input.apply()jjui.input.cancel()
Function jjui.command_history.move_up() / jjui.command_history.move_down()jjui.command_history.close()jjui.command_history.delete_selected()
Function jjui.file_search.move_up() / jjui.file_search.move_down()jjui.file_search.page_up() / jjui.file_search.page_down()jjui.file_search.preview_half_page_up() / jjui.file_search.preview_half_page_down()jjui.file_search.toggle()jjui.file_search.edit()jjui.file_search.apply() / jjui.file_search.cancel()