Modules#

An overview of the packages that make up nur. The API reference below is generated automatically from the source docstrings.

CLI (nur.cli)#

Typer command-line interface: discovers project tasks and either runs one directly or launches the TUI.

CLI package for the project.

nur.cli.format_list(registry)[source]#

Render the discovered tasks grouped by provider prefix.

Parameters:

registry (Registry)

Return type:

str

nur.cli.main(argv=None)[source]#

Entry point: parse args with Typer, returning an int instead of exiting.

Typer/Click raise SystemExit for help, version, and every command exit; catching it here keeps main’s int contract so it stays usable from tests and embedders. Passthrough args are handed to the command via ctx.obj.

Parameters:

argv (list[str] | None)

Return type:

int

nur.cli.split_passthrough(argv)[source]#

Split argv on the first -- into task args and passthrough args.

Parameters:

argv (list[str])

Return type:

tuple[list[str], list[str]]

Discovery (nur.core.discovery)#

Walks the working directory and builds the task registry from every supported source file.

Registry (nur.core.registry)#

Holds discovered tasks and resolves a query to a single task, with ambiguity and close-match handling.

exception nur.core.registry.AmbiguousTaskError(name, candidates)[source]#

Bases: ResolutionError

Parameters:
Return type:

None

exception nur.core.registry.ResolutionError[source]#

Bases: Exception

Base class for name-resolution failures.

exception nur.core.registry.UnknownTaskError(query, suggestions)[source]#

Bases: ResolutionError

Parameters:
Return type:

None

Execution (nur.core.execution)#

Runs a resolved task’s command, propagating its exit code.

class nur.core.execution.ProcessRunner[source]#

Bases: object

Runs a command with combined output captured and streamed line-by-line.

Blocking; intended to be driven from a background thread by the TUI.

nur.core.execution.run_direct(task, extra_args, cwd)[source]#

Run a task with inherited stdio; return the child’s exit code.

A natively-parsed provider (npm/PDM/poe) can surface a task whose runner binary is not installed. Rather than letting FileNotFoundError escape as a traceback, report a controlled error and return 127.

Parameters:
Return type:

int

Shell (nur.core.shell)#

Quotes literal arguments for the shell that runs shell tasks.

nur.core.shell.quote(s)[source]#

Return a shell-escaped version of the string s.

nur.core.shell.quote_for_cmd(argument)[source]#

Quote one argument for cmd.exe and the MSVC argv parser, only if needed.

Plain tokens stay bare, since cmd.exe built-ins such as echo print quotes verbatim. Anything with whitespace, quotes, or cmd.exe operators such as & or > is double-quoted so it stays one literal token. cmd.exe expands %NAME% even inside quotes, so each % is emitted as ^% between quoted pieces: the caret keeps it literal, and the quote characters around it stop the text from naming a real variable.

Parameters:

argument (str)

Return type:

str

Models (nur.core.models)#

Core data types shared across discovery, the registry, and execution.

class nur.core.models.Provider(*args, **kwargs)[source]#

Bases: Protocol

class nur.core.models.Task(name: 'str', prefix: 'str', argv_base: 'tuple[str, ...]', description: 'str | None' = None, definition: 'str' = '', source_file: 'str' = '', passthrough_prefix: 'tuple[str, ...]' = (), run_in_shell: 'bool' = False)[source]#

Bases: object

Parameters:

Providers (nur.core.providers)#

One module per supported task source (make, npm, deno, composer, just, taskfile, pre-commit, pdm, poe, tox, mise, cargo-make, moon, xc, VS Code tasks, nox, mask, invoke, rake, grunt, gulp, ant, turbo, nx, workspace).

nur.core.providers.npm.resolve_pm(cwd)[source]#

Return the package manager owning cwd (npm when unknown).

Parameters:

cwd (Path)

Return type:

str

nur.core.providers.just.parse_justfile(text, source_file='justfile')[source]#

Extract recipes from justfile text without executing anything.

Deliberately does NOT shell out to just --dump: a pure text parse needs no just binary on PATH and cannot run recipe bodies. Descriptions come from the comment immediately preceding a recipe (skipping any attribute lines) or from an explicit [doc('...')] attribute. Grammar fidelity is intentionally partial: imports, string interpolation, and computed names are not resolved (see docs/adr/0001).

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

nur.core.providers.task.parse_taskfile(text, source_file='Taskfile.yml')[source]#

Parse a Taskfile’s YAML text without executing anything.

Deliberately does NOT run task --list: go-task evaluates dynamic sh: variables while loading/compiling the file, so listing could run repository-controlled commands during discovery. Reading the YAML directly is safe, at the cost of not resolving includes: or generated tasks.

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

nur.core.providers.pre_commit.parse_pre_commit_config(text, source_file='.pre-commit-config.yaml')[source]#

Parse a pre-commit config without invoking pre-commit.

Only repos[].hooks[] declares hooks. In particular, this deliberately does not walk arbitrary YAML mappings, whose id keys can be unrelated configuration.

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

class nur.core.providers.tox.ToxProvider[source]#

Bases: object

Task provider for tox test automation configurations.

detect(cwd)[source]#

Check whether a supported tox configuration exists in the directory.

Parameters:

cwd (Path)

Return type:

bool

discover(cwd)[source]#

Discover runnable tox environments as tasks from configuration.

Parameters:

cwd (Path)

Return type:

list[Task]

nur.core.providers.moon.parse_moon(text, source_file='moon.yml')[source]#

Parse a moon project’s moon.yml text without executing anything.

Deliberately does NOT run moon query tasks: enumerating tasks that way boots moon’s toolchain and resolves inherited config, which can run repository-controlled commands during discovery. Reading the YAML directly is safe, at the cost of not resolving inherited tasks from .moon/ or project-qualified <project>:<task> addressing across the monorepo.

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

nur.core.providers.xc.parse_xc(text, source_file='README.md')[source]#

Parse xc tasks out of a markdown text without executing anything.

Deliberately does NOT run xc -s: xc executes task scripts, and listing should never be able to run repository-controlled commands. The grammar is documented at https://xcfile.dev/task-syntax/task-list/.

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

class nur.core.providers.vscode.VsCodeProvider[source]#

Bases: object

Task provider for VS Code’s version 2.0.0 tasks configuration.

detect(cwd)[source]#

Check whether a supported VS Code tasks file exists.

Parameters:

cwd (Path)

Return type:

bool

discover(cwd)[source]#

Discover shell and process tasks nur can run as VS Code would.

Parameters:

cwd (Path)

Return type:

list[Task]

nur.core.providers.nox.parse_noxfile(text, source_file='noxfile.py')[source]#

Extract nox sessions from text by AST parsing, never importing it.

nox --list imports and executes noxfile.py to build its registry, so discovery reads @nox.session-decorated functions from the syntax tree instead. It only trusts straight-line noxfiles: module code may use imports, plain statements (assignment, del, expression, pass, def, class), and if/try blocks made of those, where a raise ends its branch. Sessions register only from top-level def statements. Anything else (loops, with, match, assert, except*, a star import from another module) makes nox’s registry depend on control flow nur does not follow, so nothing is listed rather than a session nox may not register. python=[...] and @nox.parametrize variants are surfaced under their base name (nox -s <name> still runs every variant).

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

Discover Invoke tasks by parsing Python, without importing project code.

nur.core.providers.invoke.parse_tasks(text, source_file='tasks.py')[source]#

Read top-level decorated functions and literal names/aliases.

Imports are tracked in source order, never executed. Nested/conditional definitions, custom decorators, computed metadata and Collection wiring are not resolved. Task bodies and pre/post hooks remain Invoke’s concern.

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

nur.core.providers.rake.parse_rakefile(text, source_file='Rakefile')[source]#

Extract literal task candidates without evaluating Ruby behavior.

Direct task/multitask declarations and literal namespaces are supported. Parentheses and handler-free begin wrappers are transparent. Other scopes are opaque. Parser errors skip the file; Ruby semantic validity, execution order, method overrides and successful task registration are not inferred.

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

Discover Turborepo tasks by parsing turbo.json, without running turbo.

nur.core.providers.turbo.parse_turbo(text, source_file='turbo.json')[source]#

Parse a workspace root’s turbo.json text without executing anything.

Deliberately does NOT run turbo run (or --dry-run) to enumerate tasks: that boots Turborepo’s daemon and resolves the package graph, which can run repository-controlled code during discovery. Reading the task keys directly is safe, at the cost of not verifying that a member package.json actually implements each script. definition stays empty because turbo.json holds orchestration config (dependsOn, outputs), not the command itself.

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

Discover Nx shared tasks by parsing nx.json, without running nx.

nur.core.providers.nx.parse_nx(text, source_file='nx.json')[source]#

Parse a workspace root’s nx.json text without executing anything.

Deliberately does NOT run nx show projects to enumerate tasks: that loads Nx plugins and infers targets, which can run repository-controlled code during discovery. Reading the targetDefaults keys directly is safe, at the cost of missing per-project-only targets. Only literal target names become tasks: executor keys (@nx/js:tsc) and globs (e2e-ci--**/**) cannot be run via nx run-many -t and are skipped, while array-form defaults (filtered entries for one target) are accepted like plain objects. Results are syntactic candidates, not a promise that every project implements them.

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

Discover Grunt tasks by parsing Gruntfile.js, without running Grunt.

nur.core.providers.grunt.parse_gruntfile(text, source_file='Gruntfile.js')[source]#

Extract literal Grunt task candidates without evaluating JavaScript.

Direct grunt.registerTask / grunt.registerMultiTask calls (and their grunt.task.* aliases) with a literal name are supported, at any nesting level — conditional guards are not evaluated, so guarded declarations are still listed. Task bodies and alias lists stay opaque; plugin-loaded tasks (loadNpmTasks), renamed tasks, external task files, computed/dynamic names, and task:target spellings are skipped. Parser errors skip the file. Results are syntactic task candidates, not a promise that Grunt will register or successfully execute each task.

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

Discover Gulp tasks by parsing gulpfile.js, without running Gulp.

nur.core.providers.gulp.parse_gulpfile(text, source_file='gulpfile.js')[source]#

Extract literal Gulp task candidates without evaluating JavaScript.

Supported: literal gulp.task('name', ...) / bare task('name', ...) calls, CommonJS exports.name / module.exports.name / exports['name'] assignments, and ESM export function / export const / export default / export {a as b} forms. <task>.description string assignments supply optional metadata. Function references (task(build)), series()/parallel() composition, computed keys, object-literal module.exports = {...}, re-exports, and task bodies stay opaque. Conditional guards are not evaluated. Parser errors skip the file. Results are syntactic task candidates, not a promise that Gulp will register or run each task.

Parameters:
  • text (str)

  • source_file (str)

Return type:

list[Task]

Discover Apache Ant targets by parsing build.xml, without running Ant.

nur.core.providers.ant.parse_build_xml(text, source_file='build.xml')[source]#

Extract runnable entry names from build.xml without executing anything.

Deliberately does NOT shell out to ant -p: the file is declarative XML, so <target name=...> attributes can be read directly and a task body (a sequence of XML tasks, not shell commands) never runs during discovery. <extension-point name=...> entries are listed the same way: they are target-like states with a name and an optional description, each running as ant <name>. Descriptions come from the description attribute; every entry is listed, including ones without a description (which ant -p hides as internal). Names are kept verbatim — Ant permits spaces in target names and argv_base passes the name as a single token, so normalizing would change the target’s identity. Only a missing name or an actually empty one is skipped (Ant rejects the latter); anything else, including whitespace-only names, stays listed exactly as Ant accepts it. Names beginning with a hyphen are skipped: Ant parses such arguments as options, so they cannot run via ant <name> (the conventional internal-target form); the check mirrors Ant’s own startsWith("-") on the same verbatim token, so padded variants (`` -init ) stay listed. Only core-namespace ``<target>/<extension-point> elements count: other namespaces are ordinary task/type uses, not entry points. Documents whose root is not an Ant <project> element are skipped: Ant rejects them before installing any target. Task bodies stay opaque, so definition is empty. Imported/included build files, macrodef-defined behaviour, if/unless conditions, depends ordering, and ${property} interpolation are not resolved; names are left literal. Parser errors skip the file. Files with a <!DOCTYPE> declaration are skipped as well: it is the only carrier of <!ENTITY> definitions, whose expansion is unbounded, and genuine Ant build files never need one. Detection is an expat pre-scan that aborts at the first declaration, so no entity is ever expanded during discovery. Results are syntactic task candidates, not a promise that Ant will accept or successfully execute each target.

Byte input is preferred: ElementTree honours the XML encoding declaration only for bytes; a str carrying one is skipped as unparsable. Byte input in an encoding outside the stdlib’s native set (multibyte ones like Shift_JIS, UTF-32, or EBCDIC variants, which Ant accepts) is transcoded to UTF-8 first.

Parameters:
Return type:

list[Task]

Discover workspace member scripts by parsing configs, without traversal tools.

nur.core.providers.workspace.member_scripts(cwd)[source]#

Discover workspace member scripts without executing anything.

Globs come from pnpm-workspace.yaml (packages), package.json (workspaces), and lerna.json (packages, defaulting to packages/* unless useWorkspaces defers to package.json). Matching stays confined to cwd: absolute patterns, escapes, and node_modules/.git never become members, and only directories with their own package.json qualify. Member scripts are the same static JSON the npm provider reads; running delegates to the lockfile-resolved package manager scoped to the member.

Parameters:

cwd (Path)

Return type:

list[Task]

nur.core.providers.mask.parse_mask(text, source_file='maskfile.md', *, windows=False)[source]#

Parse mask commands out of a maskfile text without executing anything.

Mirrors mask’s own parser (https://github.com/jacobdeichert/mask): headings are commands, nested headings are subcommands, the last fenced block under a heading is its script, and the last blockquote its description. A subcommand is named by its path (test lint) and runs as mask test lint. windows selects whether powershell/batch/ cmd blocks count, as they do only in mask’s Windows build.

Parameters:
Return type:

list[Task]

TUI (nur.tui)#

Textual terminal user interface for browsing and running tasks.

class nur.tui.app.HeaderItem(prefix)[source]#

Bases: ListItem

Initialize a Widget.

Parameters:
  • *children – Child widgets.

  • name – The name of the widget.

  • id – The ID of the widget in the DOM.

  • classes – The CSS classes for the widget.

  • disabled – Whether the widget is disabled or not.

  • markup – Enable content markup?

  • prefix (str)

can_focus = False#

Widget may receive focus.

can_focus_children = True#

Widget’s children may receive focus.

class nur.tui.app.NurApp(cwd, scan)[source]#

Bases: App[None]

Create an instance of an app.

Parameters:
  • driver_class – Driver class or None to auto-detect. This will be used by some Textual tools.

  • css_path – Path to CSS or None to use the CSS_PATH class variable. To load multiple CSS files, pass a list of strings or paths which will be loaded in order.

  • watch_css – Reload CSS if the files changed. This is set automatically if you are using textual run with the dev switch.

  • ansi_color – Allow ANSI colors if True, or convert ANSI colors to RGB if False, None to use “ansi” parameter from themes.

  • cwd (Path)

  • scan (Callable[[], Registry])

Raises:

CssPathError – When the supplied CSS path(s) are an unexpected type.

CSS_PATH = 'app.tcss'#

File paths to load CSS from.

BINDINGS = [('q', 'quit', 'quit'), ('escape', 'quit', 'quit'), ('slash', 'focus_filter', 'filter'), ('j', 'next', 'down'), ('k', 'prev', 'up'), ('r', 'run', 'run'), Binding(key='ctrl+c', action='interrupt', description='interrupt', show=True, key_display=None, priority=True, tooltip='', id=None, system=False, group=None), ('question_mark', 'toggle_help', 'help')]#

The default key bindings.

compose()[source]#

Yield child widgets for a container.

This method should be implemented in a subclass.

Return type:

Iterable[Widget]

async action_quit()[source]#

An [action](/guide/actions) to quit the app as soon as possible.

Return type:

None

class nur.tui.app.TaskItem(task)[source]#

Bases: ListItem

Initialize a Widget.

Parameters:
  • *children – Child widgets.

  • name – The name of the widget.

  • id – The ID of the widget in the DOM.

  • classes – The CSS classes for the widget.

  • disabled – Whether the widget is disabled or not.

  • markup – Enable content markup?

  • task (Task)

can_focus = False#

Widget may receive focus.

can_focus_children = True#

Widget’s children may receive focus.