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 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
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()Open bookmark input 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()