Repository files navigation

uvtask

imageimageimageActions statusPyPIDownloads

A task runner for pyproject.toml scripts, optimized for the uv workflow.

Define commands once in TOML, run them with uvx uvtask from any machine — zero runtime dependencies in your project.

Editor sidebar for VS Code and Cursor: aiopy/vscode-uvtask.

Why uvtask

  • Run anywhere with uvx — no install required; zero runtime dependencies
  • Scripts live in pyproject.toml — under [tool.run-script] or [tool.uvtask.run-script]
  • Compose pipelines without shell glue — chain commands by referencing other script names
  • Pre/post hooks — Composer-style (pre-test / post-test) or NPM-style (pretest / posttest)
  • uv-like CLI — colored output, structured help, and typo suggestions for unknown commands
  • Forward arguments safely — extra CLI args pass through to the underlying command, including JSON and values with spaces

Pick uvtask when you want npm/composer-style project scripts in Python, with a CLI that feels at home next to uv, ruff, and ty.

Quick Start

1. Add scripts to pyproject.toml:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"check = ["lint", "test"]

2. Run them:

uvx uvtask check
uvx uvtask test -k integration # args forwarded to pytest

For daily use, install once with uv tool install uvtask, then run uvtask directly.

Editor extension (VS Code & Cursor)

List and run scripts from the sidebar with the vscode-uvtask extension (aiopy.uvtask):

# Cursor
cursor --install-extension aiopy.uvtask
# VS Code
code --install-extension aiopy.uvtask

Or install a .vsix from Releases via Extensions: Install from VSIX…. Full steps: install guide.

Features

Configuration formats

Scripts support several TOML shapes:

[tool.run-script]
# Simple stringformat = "uv run ruff format ."# With description (shown in help)lint = { command = "uv run ruff check .", description = "Check code quality" }
# Multiple commands run in sequencecheck = ["lint", "test"]
# Multiline commandsdeploy = """echo 'Building...'uv buildecho 'Done!'"""

You can also nest scripts under [tool.uvtask.run-script] if you prefer a namespaced layout.

See this repository's pyproject.toml for a full real-world script catalog.

Command composition

Reference other script names to build pipelines without repeating shell commands:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"static = ["lint", "test"]
all = ["static"]

Running uvx uvtask all executes lint then test.

Hooks

Define hooks that run automatically before and after a command:

[tool.run-script]
pre-test = "echo 'Setting up...'"test = "uv run pytest"post-test = "echo 'Cleaning up...'"

Both naming styles are supported:

StylePre-hookPost-hook
Composerpre-testpost-test
NPMpretestposttest

Skip hooks when needed:

uvx uvtask --no-hooks test

Arguments

Extra arguments after the command name are forwarded to the underlying script:

uvx uvtask test -k integration -x
uvx uvtask celery-call example -k '{"kwarg": "value"}'

Values with spaces and JSON are quoted correctly for the shell on both Unix and Windows.

Namespaced commands

Use colons to group related commands:

[tool.run-script]
static-analysis = { command = ["static-analysis:linter", "static-analysis:types"], description = "Run all static analysis checks" }
"static-analysis:linter" = "uv run ruff check .""static-analysis:types" = "uv run ty check ."

CLI reference

FlagPurpose
-q / --quietSuppress stdout (stackable)
-v / --verboseShow command and exit codes (stackable)
--no-hooks / --ignore-scriptsSkip pre/post hooks
--colorControl color output: auto, always, or never
help [command]Show per-command documentation from description
-V / --versionPrint version
-h / --helpPrint general help

Security model

uvtask runs the shell strings written in pyproject.toml, so a project's manifest is trusted exactly like a Makefile or an npm script: opening a repository is safe, running one of its commands is not. Treat uvx uvtask <command> in an unfamiliar repository the same way you would treat npm run.

Everything else is treated as untrusted:

  • Forwarded arguments are quoted before reaching the shell — shlex.join on Unix, and list2cmdline plus caret-escaping of cmd.exe metacharacters on Windows — so a value like foo&whoami is passed through as data rather than run as a second command.
  • Script names and descriptions are stripped of terminal control characters before display, so a manifest cannot rewrite what you see in --help or uvtask help <command>.
  • Malformed scripts are rejected rather than coerced. A table without a command key, a non-string command, a circular reference, or a reference graph that expands past 512 commands fails with a config error instead of being handed to the shell.

Hooks are skipped only by --no-hooks / --ignore-scripts placed before the command name. The same flag after the command name is forwarded to the underlying script, so arguments meant for a child process cannot silently bypass a guard hook.

Comparison

ToolBest foruvtask difference
uv run / uvxOne-off tool invocationsNamed, documented project commands with hooks and composition
Poe the PoetRich task runner with templatingZero deps, uvx-native, uv-styled CLI
Hatch scriptsHatch-managed projectsTool-agnostic pyproject.toml config, works with any uv project
npm / Composer scriptsJS / PHP ecosystemsSame mental model, Python-native

Development

Run the development version from a local checkout:

uvx -q --no-cache --from $PWD uvtask

Common project tasks:

uvx uvtask static-analysis
uvx uvtask test

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © uvtask contributors


Note: uvtask is an independent, third-party project — not an official Astral tool. It is inspired by and designed to work seamlessly with Astral's excellent tools (uv, ruff, ty). We're grateful for the work the Astral team does for the Python ecosystem.

About

An extremely fast Python task runner.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

uvtask

imageimageimageActions statusPyPIDownloads

A task runner for pyproject.toml scripts, optimized for the uv workflow.

Define commands once in TOML, run them with uvx uvtask from any machine — zero runtime dependencies in your project.

Editor sidebar for VS Code and Cursor: aiopy/vscode-uvtask.

Why uvtask

  • Run anywhere with uvx — no install required; zero runtime dependencies
  • Scripts live in pyproject.toml — under [tool.run-script] or [tool.uvtask.run-script]
  • Compose pipelines without shell glue — chain commands by referencing other script names
  • Pre/post hooks — Composer-style (pre-test / post-test) or NPM-style (pretest / posttest)
  • uv-like CLI — colored output, structured help, and typo suggestions for unknown commands
  • Forward arguments safely — extra CLI args pass through to the underlying command, including JSON and values with spaces

Pick uvtask when you want npm/composer-style project scripts in Python, with a CLI that feels at home next to uv, ruff, and ty.

Quick Start

1. Add scripts to pyproject.toml:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"check = ["lint", "test"]

2. Run them:

uvx uvtask check
uvx uvtask test -k integration # args forwarded to pytest

For daily use, install once with uv tool install uvtask, then run uvtask directly.

Editor extension (VS Code & Cursor)

List and run scripts from the sidebar with the vscode-uvtask extension (aiopy.uvtask):

# Cursor
cursor --install-extension aiopy.uvtask
# VS Code
code --install-extension aiopy.uvtask

Or install a .vsix from Releases via Extensions: Install from VSIX…. Full steps: install guide.

Features

Configuration formats

Scripts support several TOML shapes:

[tool.run-script]
# Simple stringformat = "uv run ruff format ."# With description (shown in help)lint = { command = "uv run ruff check .", description = "Check code quality" }
# Multiple commands run in sequencecheck = ["lint", "test"]
# Multiline commandsdeploy = """echo 'Building...'uv buildecho 'Done!'"""

You can also nest scripts under [tool.uvtask.run-script] if you prefer a namespaced layout.

See this repository's pyproject.toml for a full real-world script catalog.

Command composition

Reference other script names to build pipelines without repeating shell commands:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"static = ["lint", "test"]
all = ["static"]

Running uvx uvtask all executes lint then test.

Hooks

Define hooks that run automatically before and after a command:

[tool.run-script]
pre-test = "echo 'Setting up...'"test = "uv run pytest"post-test = "echo 'Cleaning up...'"

Both naming styles are supported:

StylePre-hookPost-hook
Composerpre-testpost-test
NPMpretestposttest

Skip hooks when needed:

uvx uvtask --no-hooks test

Arguments

Extra arguments after the command name are forwarded to the underlying script:

uvx uvtask test -k integration -x
uvx uvtask celery-call example -k '{"kwarg": "value"}'

Values with spaces and JSON are quoted correctly for the shell on both Unix and Windows.

Namespaced commands

Use colons to group related commands:

[tool.run-script]
static-analysis = { command = ["static-analysis:linter", "static-analysis:types"], description = "Run all static analysis checks" }
"static-analysis:linter" = "uv run ruff check .""static-analysis:types" = "uv run ty check ."

CLI reference

FlagPurpose
-q / --quietSuppress stdout (stackable)
-v / --verboseShow command and exit codes (stackable)
--no-hooks / --ignore-scriptsSkip pre/post hooks
--colorControl color output: auto, always, or never
help [command]Show per-command documentation from description
-V / --versionPrint version
-h / --helpPrint general help

Security model

uvtask runs the shell strings written in pyproject.toml, so a project's manifest is trusted exactly like a Makefile or an npm script: opening a repository is safe, running one of its commands is not. Treat uvx uvtask <command> in an unfamiliar repository the same way you would treat npm run.

Everything else is treated as untrusted:

  • Forwarded arguments are quoted before reaching the shell — shlex.join on Unix, and list2cmdline plus caret-escaping of cmd.exe metacharacters on Windows — so a value like foo&whoami is passed through as data rather than run as a second command.
  • Script names and descriptions are stripped of terminal control characters before display, so a manifest cannot rewrite what you see in --help or uvtask help <command>.
  • Malformed scripts are rejected rather than coerced. A table without a command key, a non-string command, a circular reference, or a reference graph that expands past 512 commands fails with a config error instead of being handed to the shell.

Hooks are skipped only by --no-hooks / --ignore-scripts placed before the command name. The same flag after the command name is forwarded to the underlying script, so arguments meant for a child process cannot silently bypass a guard hook.

Comparison

ToolBest foruvtask difference
uv run / uvxOne-off tool invocationsNamed, documented project commands with hooks and composition
Poe the PoetRich task runner with templatingZero deps, uvx-native, uv-styled CLI
Hatch scriptsHatch-managed projectsTool-agnostic pyproject.toml config, works with any uv project
npm / Composer scriptsJS / PHP ecosystemsSame mental model, Python-native

Development

Run the development version from a local checkout:

uvx -q --no-cache --from $PWD uvtask

Common project tasks:

uvx uvtask static-analysis
uvx uvtask test

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © uvtask contributors


Note: uvtask is an independent, third-party project — not an official Astral tool. It is inspired by and designed to work seamlessly with Astral's excellent tools (uv, ruff, ty). We're grateful for the work the Astral team does for the Python ecosystem.

About

An extremely fast Python task runner.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

uvtask

imageimageimageActions statusPyPIDownloads

A task runner for pyproject.toml scripts, optimized for the uv workflow.

Define commands once in TOML, run them with uvx uvtask from any machine — zero runtime dependencies in your project.

Editor sidebar for VS Code and Cursor: aiopy/vscode-uvtask.

Why uvtask

  • Run anywhere with uvx — no install required; zero runtime dependencies
  • Scripts live in pyproject.toml — under [tool.run-script] or [tool.uvtask.run-script]
  • Compose pipelines without shell glue — chain commands by referencing other script names
  • Pre/post hooks — Composer-style (pre-test / post-test) or NPM-style (pretest / posttest)
  • uv-like CLI — colored output, structured help, and typo suggestions for unknown commands
  • Forward arguments safely — extra CLI args pass through to the underlying command, including JSON and values with spaces

Pick uvtask when you want npm/composer-style project scripts in Python, with a CLI that feels at home next to uv, ruff, and ty.

Quick Start

1. Add scripts to pyproject.toml:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"check = ["lint", "test"]

2. Run them:

uvx uvtask check
uvx uvtask test -k integration # args forwarded to pytest

For daily use, install once with uv tool install uvtask, then run uvtask directly.

Editor extension (VS Code & Cursor)

List and run scripts from the sidebar with the vscode-uvtask extension (aiopy.uvtask):

# Cursor
cursor --install-extension aiopy.uvtask
# VS Code
code --install-extension aiopy.uvtask

Or install a .vsix from Releases via Extensions: Install from VSIX…. Full steps: install guide.

Features

Configuration formats

Scripts support several TOML shapes:

[tool.run-script]
# Simple stringformat = "uv run ruff format ."# With description (shown in help)lint = { command = "uv run ruff check .", description = "Check code quality" }
# Multiple commands run in sequencecheck = ["lint", "test"]
# Multiline commandsdeploy = """echo 'Building...'uv buildecho 'Done!'"""

You can also nest scripts under [tool.uvtask.run-script] if you prefer a namespaced layout.

See this repository's pyproject.toml for a full real-world script catalog.

Command composition

Reference other script names to build pipelines without repeating shell commands:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"static = ["lint", "test"]
all = ["static"]

Running uvx uvtask all executes lint then test.

Hooks

Define hooks that run automatically before and after a command:

[tool.run-script]
pre-test = "echo 'Setting up...'"test = "uv run pytest"post-test = "echo 'Cleaning up...'"

Both naming styles are supported:

StylePre-hookPost-hook
Composerpre-testpost-test
NPMpretestposttest

Skip hooks when needed:

uvx uvtask --no-hooks test

Arguments

Extra arguments after the command name are forwarded to the underlying script:

uvx uvtask test -k integration -x
uvx uvtask celery-call example -k '{"kwarg": "value"}'

Values with spaces and JSON are quoted correctly for the shell on both Unix and Windows.

Namespaced commands

Use colons to group related commands:

[tool.run-script]
static-analysis = { command = ["static-analysis:linter", "static-analysis:types"], description = "Run all static analysis checks" }
"static-analysis:linter" = "uv run ruff check .""static-analysis:types" = "uv run ty check ."

CLI reference

FlagPurpose
-q / --quietSuppress stdout (stackable)
-v / --verboseShow command and exit codes (stackable)
--no-hooks / --ignore-scriptsSkip pre/post hooks
--colorControl color output: auto, always, or never
help [command]Show per-command documentation from description
-V / --versionPrint version
-h / --helpPrint general help

Security model

uvtask runs the shell strings written in pyproject.toml, so a project's manifest is trusted exactly like a Makefile or an npm script: opening a repository is safe, running one of its commands is not. Treat uvx uvtask <command> in an unfamiliar repository the same way you would treat npm run.

Everything else is treated as untrusted:

  • Forwarded arguments are quoted before reaching the shell — shlex.join on Unix, and list2cmdline plus caret-escaping of cmd.exe metacharacters on Windows — so a value like foo&whoami is passed through as data rather than run as a second command.
  • Script names and descriptions are stripped of terminal control characters before display, so a manifest cannot rewrite what you see in --help or uvtask help <command>.
  • Malformed scripts are rejected rather than coerced. A table without a command key, a non-string command, a circular reference, or a reference graph that expands past 512 commands fails with a config error instead of being handed to the shell.

Hooks are skipped only by --no-hooks / --ignore-scripts placed before the command name. The same flag after the command name is forwarded to the underlying script, so arguments meant for a child process cannot silently bypass a guard hook.

Comparison

ToolBest foruvtask difference
uv run / uvxOne-off tool invocationsNamed, documented project commands with hooks and composition
Poe the PoetRich task runner with templatingZero deps, uvx-native, uv-styled CLI
Hatch scriptsHatch-managed projectsTool-agnostic pyproject.toml config, works with any uv project
npm / Composer scriptsJS / PHP ecosystemsSame mental model, Python-native

Development

Run the development version from a local checkout:

uvx -q --no-cache --from $PWD uvtask

Common project tasks:

uvx uvtask static-analysis
uvx uvtask test

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © uvtask contributors


Note: uvtask is an independent, third-party project — not an official Astral tool. It is inspired by and designed to work seamlessly with Astral's excellent tools (uv, ruff, ty). We're grateful for the work the Astral team does for the Python ecosystem.

About

An extremely fast Python task runner.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

uvtask

imageimageimageActions statusPyPIDownloads

A task runner for pyproject.toml scripts, optimized for the uv workflow.

Define commands once in TOML, run them with uvx uvtask from any machine — zero runtime dependencies in your project.

Editor sidebar for VS Code and Cursor: aiopy/vscode-uvtask.

Why uvtask

  • Run anywhere with uvx — no install required; zero runtime dependencies
  • Scripts live in pyproject.toml — under [tool.run-script] or [tool.uvtask.run-script]
  • Compose pipelines without shell glue — chain commands by referencing other script names
  • Pre/post hooks — Composer-style (pre-test / post-test) or NPM-style (pretest / posttest)
  • uv-like CLI — colored output, structured help, and typo suggestions for unknown commands
  • Forward arguments safely — extra CLI args pass through to the underlying command, including JSON and values with spaces

Pick uvtask when you want npm/composer-style project scripts in Python, with a CLI that feels at home next to uv, ruff, and ty.

Quick Start

1. Add scripts to pyproject.toml:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"check = ["lint", "test"]

2. Run them:

uvx uvtask check
uvx uvtask test -k integration # args forwarded to pytest

For daily use, install once with uv tool install uvtask, then run uvtask directly.

Editor extension (VS Code & Cursor)

List and run scripts from the sidebar with the vscode-uvtask extension (aiopy.uvtask):

# Cursor
cursor --install-extension aiopy.uvtask
# VS Code
code --install-extension aiopy.uvtask

Or install a .vsix from Releases via Extensions: Install from VSIX…. Full steps: install guide.

Features

Configuration formats

Scripts support several TOML shapes:

[tool.run-script]
# Simple stringformat = "uv run ruff format ."# With description (shown in help)lint = { command = "uv run ruff check .", description = "Check code quality" }
# Multiple commands run in sequencecheck = ["lint", "test"]
# Multiline commandsdeploy = """echo 'Building...'uv buildecho 'Done!'"""

You can also nest scripts under [tool.uvtask.run-script] if you prefer a namespaced layout.

See this repository's pyproject.toml for a full real-world script catalog.

Command composition

Reference other script names to build pipelines without repeating shell commands:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"static = ["lint", "test"]
all = ["static"]

Running uvx uvtask all executes lint then test.

Hooks

Define hooks that run automatically before and after a command:

[tool.run-script]
pre-test = "echo 'Setting up...'"test = "uv run pytest"post-test = "echo 'Cleaning up...'"

Both naming styles are supported:

StylePre-hookPost-hook
Composerpre-testpost-test
NPMpretestposttest

Skip hooks when needed:

uvx uvtask --no-hooks test

Arguments

Extra arguments after the command name are forwarded to the underlying script:

uvx uvtask test -k integration -x
uvx uvtask celery-call example -k '{"kwarg": "value"}'

Values with spaces and JSON are quoted correctly for the shell on both Unix and Windows.

Namespaced commands

Use colons to group related commands:

[tool.run-script]
static-analysis = { command = ["static-analysis:linter", "static-analysis:types"], description = "Run all static analysis checks" }
"static-analysis:linter" = "uv run ruff check .""static-analysis:types" = "uv run ty check ."

CLI reference

FlagPurpose
-q / --quietSuppress stdout (stackable)
-v / --verboseShow command and exit codes (stackable)
--no-hooks / --ignore-scriptsSkip pre/post hooks
--colorControl color output: auto, always, or never
help [command]Show per-command documentation from description
-V / --versionPrint version
-h / --helpPrint general help

Security model

uvtask runs the shell strings written in pyproject.toml, so a project's manifest is trusted exactly like a Makefile or an npm script: opening a repository is safe, running one of its commands is not. Treat uvx uvtask <command> in an unfamiliar repository the same way you would treat npm run.

Everything else is treated as untrusted:

  • Forwarded arguments are quoted before reaching the shell — shlex.join on Unix, and list2cmdline plus caret-escaping of cmd.exe metacharacters on Windows — so a value like foo&whoami is passed through as data rather than run as a second command.
  • Script names and descriptions are stripped of terminal control characters before display, so a manifest cannot rewrite what you see in --help or uvtask help <command>.
  • Malformed scripts are rejected rather than coerced. A table without a command key, a non-string command, a circular reference, or a reference graph that expands past 512 commands fails with a config error instead of being handed to the shell.

Hooks are skipped only by --no-hooks / --ignore-scripts placed before the command name. The same flag after the command name is forwarded to the underlying script, so arguments meant for a child process cannot silently bypass a guard hook.

Comparison

ToolBest foruvtask difference
uv run / uvxOne-off tool invocationsNamed, documented project commands with hooks and composition
Poe the PoetRich task runner with templatingZero deps, uvx-native, uv-styled CLI
Hatch scriptsHatch-managed projectsTool-agnostic pyproject.toml config, works with any uv project
npm / Composer scriptsJS / PHP ecosystemsSame mental model, Python-native

Development

Run the development version from a local checkout:

uvx -q --no-cache --from $PWD uvtask

Common project tasks:

uvx uvtask static-analysis
uvx uvtask test

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © uvtask contributors


Note: uvtask is an independent, third-party project — not an official Astral tool. It is inspired by and designed to work seamlessly with Astral's excellent tools (uv, ruff, ty). We're grateful for the work the Astral team does for the Python ecosystem.

About

An extremely fast Python task runner.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

uvtask

imageimageimageActions statusPyPIDownloads

A task runner for pyproject.toml scripts, optimized for the uv workflow.

Define commands once in TOML, run them with uvx uvtask from any machine — zero runtime dependencies in your project.

Editor sidebar for VS Code and Cursor: aiopy/vscode-uvtask.

Why uvtask

  • Run anywhere with uvx — no install required; zero runtime dependencies
  • Scripts live in pyproject.toml — under [tool.run-script] or [tool.uvtask.run-script]
  • Compose pipelines without shell glue — chain commands by referencing other script names
  • Pre/post hooks — Composer-style (pre-test / post-test) or NPM-style (pretest / posttest)
  • uv-like CLI — colored output, structured help, and typo suggestions for unknown commands
  • Forward arguments safely — extra CLI args pass through to the underlying command, including JSON and values with spaces

Pick uvtask when you want npm/composer-style project scripts in Python, with a CLI that feels at home next to uv, ruff, and ty.

Quick Start

1. Add scripts to pyproject.toml:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"check = ["lint", "test"]

2. Run them:

uvx uvtask check
uvx uvtask test -k integration # args forwarded to pytest

For daily use, install once with uv tool install uvtask, then run uvtask directly.

Editor extension (VS Code & Cursor)

List and run scripts from the sidebar with the vscode-uvtask extension (aiopy.uvtask):

# Cursor
cursor --install-extension aiopy.uvtask
# VS Code
code --install-extension aiopy.uvtask

Or install a .vsix from Releases via Extensions: Install from VSIX…. Full steps: install guide.

Features

Configuration formats

Scripts support several TOML shapes:

[tool.run-script]
# Simple stringformat = "uv run ruff format ."# With description (shown in help)lint = { command = "uv run ruff check .", description = "Check code quality" }
# Multiple commands run in sequencecheck = ["lint", "test"]
# Multiline commandsdeploy = """echo 'Building...'uv buildecho 'Done!'"""

You can also nest scripts under [tool.uvtask.run-script] if you prefer a namespaced layout.

See this repository's pyproject.toml for a full real-world script catalog.

Command composition

Reference other script names to build pipelines without repeating shell commands:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"static = ["lint", "test"]
all = ["static"]

Running uvx uvtask all executes lint then test.

Hooks

Define hooks that run automatically before and after a command:

[tool.run-script]
pre-test = "echo 'Setting up...'"test = "uv run pytest"post-test = "echo 'Cleaning up...'"

Both naming styles are supported:

StylePre-hookPost-hook
Composerpre-testpost-test
NPMpretestposttest

Skip hooks when needed:

uvx uvtask --no-hooks test

Arguments

Extra arguments after the command name are forwarded to the underlying script:

uvx uvtask test -k integration -x
uvx uvtask celery-call example -k '{"kwarg": "value"}'

Values with spaces and JSON are quoted correctly for the shell on both Unix and Windows.

Namespaced commands

Use colons to group related commands:

[tool.run-script]
static-analysis = { command = ["static-analysis:linter", "static-analysis:types"], description = "Run all static analysis checks" }
"static-analysis:linter" = "uv run ruff check .""static-analysis:types" = "uv run ty check ."

CLI reference

FlagPurpose
-q / --quietSuppress stdout (stackable)
-v / --verboseShow command and exit codes (stackable)
--no-hooks / --ignore-scriptsSkip pre/post hooks
--colorControl color output: auto, always, or never
help [command]Show per-command documentation from description
-V / --versionPrint version
-h / --helpPrint general help

Security model

uvtask runs the shell strings written in pyproject.toml, so a project's manifest is trusted exactly like a Makefile or an npm script: opening a repository is safe, running one of its commands is not. Treat uvx uvtask <command> in an unfamiliar repository the same way you would treat npm run.

Everything else is treated as untrusted:

  • Forwarded arguments are quoted before reaching the shell — shlex.join on Unix, and list2cmdline plus caret-escaping of cmd.exe metacharacters on Windows — so a value like foo&whoami is passed through as data rather than run as a second command.
  • Script names and descriptions are stripped of terminal control characters before display, so a manifest cannot rewrite what you see in --help or uvtask help <command>.
  • Malformed scripts are rejected rather than coerced. A table without a command key, a non-string command, a circular reference, or a reference graph that expands past 512 commands fails with a config error instead of being handed to the shell.

Hooks are skipped only by --no-hooks / --ignore-scripts placed before the command name. The same flag after the command name is forwarded to the underlying script, so arguments meant for a child process cannot silently bypass a guard hook.

Comparison

ToolBest foruvtask difference
uv run / uvxOne-off tool invocationsNamed, documented project commands with hooks and composition
Poe the PoetRich task runner with templatingZero deps, uvx-native, uv-styled CLI
Hatch scriptsHatch-managed projectsTool-agnostic pyproject.toml config, works with any uv project
npm / Composer scriptsJS / PHP ecosystemsSame mental model, Python-native

Development

Run the development version from a local checkout:

uvx -q --no-cache --from $PWD uvtask

Common project tasks:

uvx uvtask static-analysis
uvx uvtask test

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © uvtask contributors


Note: uvtask is an independent, third-party project — not an official Astral tool. It is inspired by and designed to work seamlessly with Astral's excellent tools (uv, ruff, ty). We're grateful for the work the Astral team does for the Python ecosystem.

About

An extremely fast Python task runner.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

uvtask

imageimageimageActions statusPyPIDownloads

A task runner for pyproject.toml scripts, optimized for the uv workflow.

Define commands once in TOML, run them with uvx uvtask from any machine — zero runtime dependencies in your project.

Editor sidebar for VS Code and Cursor: aiopy/vscode-uvtask.

Why uvtask

  • Run anywhere with uvx — no install required; zero runtime dependencies
  • Scripts live in pyproject.toml — under [tool.run-script] or [tool.uvtask.run-script]
  • Compose pipelines without shell glue — chain commands by referencing other script names
  • Pre/post hooks — Composer-style (pre-test / post-test) or NPM-style (pretest / posttest)
  • uv-like CLI — colored output, structured help, and typo suggestions for unknown commands
  • Forward arguments safely — extra CLI args pass through to the underlying command, including JSON and values with spaces

Pick uvtask when you want npm/composer-style project scripts in Python, with a CLI that feels at home next to uv, ruff, and ty.

Quick Start

1. Add scripts to pyproject.toml:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"check = ["lint", "test"]

2. Run them:

uvx uvtask check
uvx uvtask test -k integration # args forwarded to pytest

For daily use, install once with uv tool install uvtask, then run uvtask directly.

Editor extension (VS Code & Cursor)

List and run scripts from the sidebar with the vscode-uvtask extension (aiopy.uvtask):

# Cursor
cursor --install-extension aiopy.uvtask
# VS Code
code --install-extension aiopy.uvtask

Or install a .vsix from Releases via Extensions: Install from VSIX…. Full steps: install guide.

Features

Configuration formats

Scripts support several TOML shapes:

[tool.run-script]
# Simple stringformat = "uv run ruff format ."# With description (shown in help)lint = { command = "uv run ruff check .", description = "Check code quality" }
# Multiple commands run in sequencecheck = ["lint", "test"]
# Multiline commandsdeploy = """echo 'Building...'uv buildecho 'Done!'"""

You can also nest scripts under [tool.uvtask.run-script] if you prefer a namespaced layout.

See this repository's pyproject.toml for a full real-world script catalog.

Command composition

Reference other script names to build pipelines without repeating shell commands:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"static = ["lint", "test"]
all = ["static"]

Running uvx uvtask all executes lint then test.

Hooks

Define hooks that run automatically before and after a command:

[tool.run-script]
pre-test = "echo 'Setting up...'"test = "uv run pytest"post-test = "echo 'Cleaning up...'"

Both naming styles are supported:

StylePre-hookPost-hook
Composerpre-testpost-test
NPMpretestposttest

Skip hooks when needed:

uvx uvtask --no-hooks test

Arguments

Extra arguments after the command name are forwarded to the underlying script:

uvx uvtask test -k integration -x
uvx uvtask celery-call example -k '{"kwarg": "value"}'

Values with spaces and JSON are quoted correctly for the shell on both Unix and Windows.

Namespaced commands

Use colons to group related commands:

[tool.run-script]
static-analysis = { command = ["static-analysis:linter", "static-analysis:types"], description = "Run all static analysis checks" }
"static-analysis:linter" = "uv run ruff check .""static-analysis:types" = "uv run ty check ."

CLI reference

FlagPurpose
-q / --quietSuppress stdout (stackable)
-v / --verboseShow command and exit codes (stackable)
--no-hooks / --ignore-scriptsSkip pre/post hooks
--colorControl color output: auto, always, or never
help [command]Show per-command documentation from description
-V / --versionPrint version
-h / --helpPrint general help

Security model

uvtask runs the shell strings written in pyproject.toml, so a project's manifest is trusted exactly like a Makefile or an npm script: opening a repository is safe, running one of its commands is not. Treat uvx uvtask <command> in an unfamiliar repository the same way you would treat npm run.

Everything else is treated as untrusted:

  • Forwarded arguments are quoted before reaching the shell — shlex.join on Unix, and list2cmdline plus caret-escaping of cmd.exe metacharacters on Windows — so a value like foo&whoami is passed through as data rather than run as a second command.
  • Script names and descriptions are stripped of terminal control characters before display, so a manifest cannot rewrite what you see in --help or uvtask help <command>.
  • Malformed scripts are rejected rather than coerced. A table without a command key, a non-string command, a circular reference, or a reference graph that expands past 512 commands fails with a config error instead of being handed to the shell.

Hooks are skipped only by --no-hooks / --ignore-scripts placed before the command name. The same flag after the command name is forwarded to the underlying script, so arguments meant for a child process cannot silently bypass a guard hook.

Comparison

ToolBest foruvtask difference
uv run / uvxOne-off tool invocationsNamed, documented project commands with hooks and composition
Poe the PoetRich task runner with templatingZero deps, uvx-native, uv-styled CLI
Hatch scriptsHatch-managed projectsTool-agnostic pyproject.toml config, works with any uv project
npm / Composer scriptsJS / PHP ecosystemsSame mental model, Python-native

Development

Run the development version from a local checkout:

uvx -q --no-cache --from $PWD uvtask

Common project tasks:

uvx uvtask static-analysis
uvx uvtask test

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © uvtask contributors


Note: uvtask is an independent, third-party project — not an official Astral tool. It is inspired by and designed to work seamlessly with Astral's excellent tools (uv, ruff, ty). We're grateful for the work the Astral team does for the Python ecosystem.

About

An extremely fast Python task runner.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

uvtask

imageimageimageActions statusPyPIDownloads

A task runner for pyproject.toml scripts, optimized for the uv workflow.

Define commands once in TOML, run them with uvx uvtask from any machine — zero runtime dependencies in your project.

Editor sidebar for VS Code and Cursor: aiopy/vscode-uvtask.

Why uvtask

  • Run anywhere with uvx — no install required; zero runtime dependencies
  • Scripts live in pyproject.toml — under [tool.run-script] or [tool.uvtask.run-script]
  • Compose pipelines without shell glue — chain commands by referencing other script names
  • Pre/post hooks — Composer-style (pre-test / post-test) or NPM-style (pretest / posttest)
  • uv-like CLI — colored output, structured help, and typo suggestions for unknown commands
  • Forward arguments safely — extra CLI args pass through to the underlying command, including JSON and values with spaces

Pick uvtask when you want npm/composer-style project scripts in Python, with a CLI that feels at home next to uv, ruff, and ty.

Quick Start

1. Add scripts to pyproject.toml:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"check = ["lint", "test"]

2. Run them:

uvx uvtask check
uvx uvtask test -k integration # args forwarded to pytest

For daily use, install once with uv tool install uvtask, then run uvtask directly.

Editor extension (VS Code & Cursor)

List and run scripts from the sidebar with the vscode-uvtask extension (aiopy.uvtask):

# Cursor
cursor --install-extension aiopy.uvtask
# VS Code
code --install-extension aiopy.uvtask

Or install a .vsix from Releases via Extensions: Install from VSIX…. Full steps: install guide.

Features

Configuration formats

Scripts support several TOML shapes:

[tool.run-script]
# Simple stringformat = "uv run ruff format ."# With description (shown in help)lint = { command = "uv run ruff check .", description = "Check code quality" }
# Multiple commands run in sequencecheck = ["lint", "test"]
# Multiline commandsdeploy = """echo 'Building...'uv buildecho 'Done!'"""

You can also nest scripts under [tool.uvtask.run-script] if you prefer a namespaced layout.

See this repository's pyproject.toml for a full real-world script catalog.

Command composition

Reference other script names to build pipelines without repeating shell commands:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"static = ["lint", "test"]
all = ["static"]

Running uvx uvtask all executes lint then test.

Hooks

Define hooks that run automatically before and after a command:

[tool.run-script]
pre-test = "echo 'Setting up...'"test = "uv run pytest"post-test = "echo 'Cleaning up...'"

Both naming styles are supported:

StylePre-hookPost-hook
Composerpre-testpost-test
NPMpretestposttest

Skip hooks when needed:

uvx uvtask --no-hooks test

Arguments

Extra arguments after the command name are forwarded to the underlying script:

uvx uvtask test -k integration -x
uvx uvtask celery-call example -k '{"kwarg": "value"}'

Values with spaces and JSON are quoted correctly for the shell on both Unix and Windows.

Namespaced commands

Use colons to group related commands:

[tool.run-script]
static-analysis = { command = ["static-analysis:linter", "static-analysis:types"], description = "Run all static analysis checks" }
"static-analysis:linter" = "uv run ruff check .""static-analysis:types" = "uv run ty check ."

CLI reference

FlagPurpose
-q / --quietSuppress stdout (stackable)
-v / --verboseShow command and exit codes (stackable)
--no-hooks / --ignore-scriptsSkip pre/post hooks
--colorControl color output: auto, always, or never
help [command]Show per-command documentation from description
-V / --versionPrint version
-h / --helpPrint general help

Security model

uvtask runs the shell strings written in pyproject.toml, so a project's manifest is trusted exactly like a Makefile or an npm script: opening a repository is safe, running one of its commands is not. Treat uvx uvtask <command> in an unfamiliar repository the same way you would treat npm run.

Everything else is treated as untrusted:

  • Forwarded arguments are quoted before reaching the shell — shlex.join on Unix, and list2cmdline plus caret-escaping of cmd.exe metacharacters on Windows — so a value like foo&whoami is passed through as data rather than run as a second command.
  • Script names and descriptions are stripped of terminal control characters before display, so a manifest cannot rewrite what you see in --help or uvtask help <command>.
  • Malformed scripts are rejected rather than coerced. A table without a command key, a non-string command, a circular reference, or a reference graph that expands past 512 commands fails with a config error instead of being handed to the shell.

Hooks are skipped only by --no-hooks / --ignore-scripts placed before the command name. The same flag after the command name is forwarded to the underlying script, so arguments meant for a child process cannot silently bypass a guard hook.

Comparison

ToolBest foruvtask difference
uv run / uvxOne-off tool invocationsNamed, documented project commands with hooks and composition
Poe the PoetRich task runner with templatingZero deps, uvx-native, uv-styled CLI
Hatch scriptsHatch-managed projectsTool-agnostic pyproject.toml config, works with any uv project
npm / Composer scriptsJS / PHP ecosystemsSame mental model, Python-native

Development

Run the development version from a local checkout:

uvx -q --no-cache --from $PWD uvtask

Common project tasks:

uvx uvtask static-analysis
uvx uvtask test

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © uvtask contributors


Note: uvtask is an independent, third-party project — not an official Astral tool. It is inspired by and designed to work seamlessly with Astral's excellent tools (uv, ruff, ty). We're grateful for the work the Astral team does for the Python ecosystem.

About

An extremely fast Python task runner.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

uvtask

imageimageimageActions statusPyPIDownloads

A task runner for pyproject.toml scripts, optimized for the uv workflow.

Define commands once in TOML, run them with uvx uvtask from any machine — zero runtime dependencies in your project.

Editor sidebar for VS Code and Cursor: aiopy/vscode-uvtask.

Why uvtask

  • Run anywhere with uvx — no install required; zero runtime dependencies
  • Scripts live in pyproject.toml — under [tool.run-script] or [tool.uvtask.run-script]
  • Compose pipelines without shell glue — chain commands by referencing other script names
  • Pre/post hooks — Composer-style (pre-test / post-test) or NPM-style (pretest / posttest)
  • uv-like CLI — colored output, structured help, and typo suggestions for unknown commands
  • Forward arguments safely — extra CLI args pass through to the underlying command, including JSON and values with spaces

Pick uvtask when you want npm/composer-style project scripts in Python, with a CLI that feels at home next to uv, ruff, and ty.

Quick Start

1. Add scripts to pyproject.toml:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"check = ["lint", "test"]

2. Run them:

uvx uvtask check
uvx uvtask test -k integration # args forwarded to pytest

For daily use, install once with uv tool install uvtask, then run uvtask directly.

Editor extension (VS Code & Cursor)

List and run scripts from the sidebar with the vscode-uvtask extension (aiopy.uvtask):

# Cursor
cursor --install-extension aiopy.uvtask
# VS Code
code --install-extension aiopy.uvtask

Or install a .vsix from Releases via Extensions: Install from VSIX…. Full steps: install guide.

Features

Configuration formats

Scripts support several TOML shapes:

[tool.run-script]
# Simple stringformat = "uv run ruff format ."# With description (shown in help)lint = { command = "uv run ruff check .", description = "Check code quality" }
# Multiple commands run in sequencecheck = ["lint", "test"]
# Multiline commandsdeploy = """echo 'Building...'uv buildecho 'Done!'"""

You can also nest scripts under [tool.uvtask.run-script] if you prefer a namespaced layout.

See this repository's pyproject.toml for a full real-world script catalog.

Command composition

Reference other script names to build pipelines without repeating shell commands:

[tool.run-script]
lint = "uv run ruff check ."test = "uv run pytest"static = ["lint", "test"]
all = ["static"]

Running uvx uvtask all executes lint then test.

Hooks

Define hooks that run automatically before and after a command:

[tool.run-script]
pre-test = "echo 'Setting up...'"test = "uv run pytest"post-test = "echo 'Cleaning up...'"

Both naming styles are supported:

StylePre-hookPost-hook
Composerpre-testpost-test
NPMpretestposttest

Skip hooks when needed:

uvx uvtask --no-hooks test

Arguments

Extra arguments after the command name are forwarded to the underlying script:

uvx uvtask test -k integration -x
uvx uvtask celery-call example -k '{"kwarg": "value"}'

Values with spaces and JSON are quoted correctly for the shell on both Unix and Windows.

Namespaced commands

Use colons to group related commands:

[tool.run-script]
static-analysis = { command = ["static-analysis:linter", "static-analysis:types"], description = "Run all static analysis checks" }
"static-analysis:linter" = "uv run ruff check .""static-analysis:types" = "uv run ty check ."

CLI reference

FlagPurpose
-q / --quietSuppress stdout (stackable)
-v / --verboseShow command and exit codes (stackable)
--no-hooks / --ignore-scriptsSkip pre/post hooks
--colorControl color output: auto, always, or never
help [command]Show per-command documentation from description
-V / --versionPrint version
-h / --helpPrint general help

Security model

uvtask runs the shell strings written in pyproject.toml, so a project's manifest is trusted exactly like a Makefile or an npm script: opening a repository is safe, running one of its commands is not. Treat uvx uvtask <command> in an unfamiliar repository the same way you would treat npm run.

Everything else is treated as untrusted:

  • Forwarded arguments are quoted before reaching the shell — shlex.join on Unix, and list2cmdline plus caret-escaping of cmd.exe metacharacters on Windows — so a value like foo&whoami is passed through as data rather than run as a second command.
  • Script names and descriptions are stripped of terminal control characters before display, so a manifest cannot rewrite what you see in --help or uvtask help <command>.
  • Malformed scripts are rejected rather than coerced. A table without a command key, a non-string command, a circular reference, or a reference graph that expands past 512 commands fails with a config error instead of being handed to the shell.

Hooks are skipped only by --no-hooks / --ignore-scripts placed before the command name. The same flag after the command name is forwarded to the underlying script, so arguments meant for a child process cannot silently bypass a guard hook.

Comparison

ToolBest foruvtask difference
uv run / uvxOne-off tool invocationsNamed, documented project commands with hooks and composition
Poe the PoetRich task runner with templatingZero deps, uvx-native, uv-styled CLI
Hatch scriptsHatch-managed projectsTool-agnostic pyproject.toml config, works with any uv project
npm / Composer scriptsJS / PHP ecosystemsSame mental model, Python-native

Development

Run the development version from a local checkout:

uvx -q --no-cache --from $PWD uvtask

Common project tasks:

uvx uvtask static-analysis
uvx uvtask test

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © uvtask contributors


Note: uvtask is an independent, third-party project — not an official Astral tool. It is inspired by and designed to work seamlessly with Astral's excellent tools (uv, ruff, ty). We're grateful for the work the Astral team does for the Python ecosystem.

About

An extremely fast Python task runner.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages