Using scripts inside Agent Skills: one-off commands, bundled scripts, and agentic design

When you build an Agent Skill, you often need the agent to run code that doesn't live inside the skill's own instruction file. The Agent Skills specification gives you two ways to do that: one-off commands that invoke existing tools directly, and bundled scripts in a scripts/ directory that carry their own dependencies. This guide covers both approaches, shows you how to reference scripts from SKILL.md, and explains how to design scripts that agents can use reliably.
What it does
A skill can tell an agent to run a shell command. That command can be a one-liner that calls an existing package manager tool (like npx or uvx), or it can point to a script you bundle inside the skill's scripts/ directory. The agent resolves relative paths from the skill directory root, so you never need absolute paths. For scripts that need dependencies, several languages support inline dependency declarations, meaning the script is self-contained and the agent can run it with a single command.
Before you start
- You need a
SKILL.mdfile at the root of your skill directory. - For one-off commands that use package runners, the runner must be installed in the agent's environment. The source lists these runners and their prerequisites:
uvxrequires a separate install ofuv.pipxrequires a separate install (available viaapt install pipxorbrew install pipx).npxships with npm, which ships with Node.js.bunxships with Bun.deno runships with Deno.go runis built into the Go toolchain.
- For self-contained scripts, the corresponding runtime (Python with
uvorpipx, Deno, Bun, Ruby with Bundler) must be available. - The agent operates in a non-interactive shell. Scripts must not require TTY input.
One-off commands

When an existing package already does what you need, you can reference it directly in your SKILL.md instructions without a scripts/ directory. Many ecosystems provide tools that auto-resolve dependencies at runtime.
uvx
uvx runs Python packages in isolated environments with aggressive caching. It ships with uv.
uvx ruff@0.8.0 check .
uvx black@24.10.0 .
- Not bundled with Python, requires a separate install.
- Fast. Caches aggressively so repeat runs are near-instant.
pipx
pipx runs Python packages in isolated environments. Available via OS package managers (apt install pipx, brew install pipx).
pipx run 'black==24.10.0' .
pipx run 'ruff==0.8.0' check .
- Not bundled with Python, requires a separate install.
- A mature alternative to
uvx. Whileuvxhas become the standard recommendation,pipxremains a reliable option with broader OS package manager availability.
npx
npx runs npm packages, downloading them on demand. It ships with npm (which ships with Node.js).
npx eslint@9 --fix .
npx create-vite@6 my-app
- Bundled with Node.js, no extra install needed.
- Downloads the package, runs it, and caches it for future use.
- Pin versions with
npx package@versionfor reproducibility.
bunx
bunx is Bun's equivalent of npx. It ships with Bun.
bunx eslint@9 --fix .
bunx create-vite@6 my-app
- Drop-in replacement for
npxin Bun-based environments. - Only appropriate when the user's environment has Bun rather than Node.js.
deno run
deno run runs scripts directly from URLs or specifiers. It ships with Deno.
deno run npm:create-vite@6 my-app
deno run --allow-read npm:eslint@9 -- --fix .
- Permission flags (
--allow-read, etc.) are required for filesystem/network access. - Use
--to separate Deno flags from the tool's own flags.
go run
go run compiles and runs Go packages directly. It is built into the go command.
go run golang.org/x/tools/cmd/goimports@v0.28.0 .
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0 run
- Built into Go, no extra tooling needed.
- Pin versions or use
@latestto make the command explicit.
Tips for one-off commands in skills:
- Pin versions (e.g.,
npx eslint@9.0.0) so the command behaves the same over time. - State prerequisites in your
SKILL.md(e.g., "Requires Node.js 18+") rather than assuming the agent's environment has them. For runtime-level requirements, use thecompatibilityfrontmatter field. - Move complex commands into scripts. A one-off command works well when you're invoking a tool with a few flags. When a command grows complex enough that it's hard to get right on the first try, a tested script in
scripts/is more reliable.
Referencing scripts from SKILL.md
Use relative paths from the skill directory root to reference bundled files. The agent resolves these paths automatically, no absolute paths needed.
List available scripts in your SKILL.md so the agent knows they exist:
## Available scripts
- **`scripts/validate.sh`** — Validates configuration files
- **`scripts/process.py`** — Processes input data
Then instruct the agent to run them:
## Workflow
1. Run the validation script:
```bash
bash scripts/validate.sh "$INPUT_FILE"
```
2. Process the results:
```bash
python3 scripts/process.py --input results.json
```
The same relative-path convention works in support files like references/*.md, script execution paths (in code blocks) are relative to the skill directory root, because the agent runs commands from there.
Self-contained scripts
When you need reusable logic, bundle a script in scripts/ that declares its own dependencies inline. The agent can run the script with a single command, no separate manifest file or install step required.
Several languages support inline dependency declarations:
Python (PEP 723)
PEP 723 defines a standard format for inline script metadata. Declare dependencies in a TOML block inside # /// markers:
# /// script
# dependencies = [
# "beautifulsoup4",
# ]
# ///
from bs4 import BeautifulSoup
html = '<html><body><h1>Welcome</h1><p class="info">This is a test.</p></body></html>'
print(BeautifulSoup(html, "html.parser").select_one("p.info").get_text())
Run with uv (recommended):
uv run scripts/extract.py
uv run creates an isolated environment, installs the declared dependencies, and runs the script. pipx (pipx run scripts/extract.py) also supports PEP 723.
- Pin versions with PEP 508 specifiers:
"beautifulsoup4>=4.12,<5". - Use
requires-pythonto constrain the Python version. - Use
uv lock --scriptto create a lockfile for full reproducibility.
Deno
Deno's npm: and jsr: import specifiers make every script self-contained by default:
#!/usr/bin/env -S deno run
import * as cheerio from "npm:cheerio@1.0.0";
const html = `<html><body><h1>Welcome</h1><p class="info">This is a test.</p></body></html>`;
const $ = cheerio.load(html);
console.log($("p.info").text());
deno run scripts/extract.ts
- Use
npm:for npm packages,jsr:for Deno-native packages. - Version specifiers follow semver:
@1.0.0(exact),@^1.0.0(compatible). - Dependencies are cached globally. Use
--reloadto force re-fetch. - Packages with native addons (node-gyp) may not work, packages that ship pre-built binaries work best.
Bun
Bun auto-installs missing packages at runtime when no node_modules directory is found. Pin versions directly in the import path:
#!/usr/bin/env bun
import * as cheerio from "cheerio@1.0.0";
const html = `<html><body><h1>Welcome</h1><p class="info">This is a test.</p></body></html>`;
const $ = cheerio.load(html);
console.log($("p.info").text());
bun run scripts/extract.ts
- No
package.jsonornode_modulesneeded. TypeScript works natively. - Packages are cached globally. First run downloads; subsequent runs are near-instant.
- If a
node_modulesdirectory exists anywhere up the directory tree, auto-install is disabled and Bun falls back to standard Node.js resolution.
Ruby (bundler/inline)
Bundler ships with Ruby since 2.6. Use bundler/inline to declare gems directly in the script:
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'nokogiri'
end
html = '<html><body><h1>Welcome</h1><p class="info">This is a test.</p></body></html>'
doc = Nokogiri::HTML(html)
puts doc.at_css('p.info').text
ruby scripts/extract.rb
- Pin versions explicitly (
gem 'nokogiri', '~> 1.16'), there is no lockfile. - An existing
GemfileorBUNDLE_GEMFILEenv var in the working directory can interfere.
Designing scripts for agentic use

When an agent runs your script, it reads stdout and stderr to decide what to do next. A few design choices make scripts dramatically easier for agents to use.
Avoid interactive prompts
This is a hard requirement of the agent execution environment. Agents operate in non-interactive shells, they cannot respond to TTY prompts, password dialogs, or confirmation menus. A script that blocks on interactive input will hang indefinitely.
Accept all input via command-line flags, environment variables, or stdin:
# Bad: hangs waiting for input
$ python scripts/deploy.py
Target environment: _
# Good: clear error with guidance
$ python scripts/deploy.py
Error: --env is required. Options: development, staging, production.
Usage: python scripts/deploy.py --env staging --tag v1.2.3
Document usage with --help
--help output is the primary way an agent learns your script's interface. Include a brief description, available flags, and usage examples:
Usage: scripts/process.py [OPTIONS] INPUT_FILE
Process input data and produce a summary report.
Options:
--format FORMAT Output format: json, csv, table (default: json)
--output FILE Write output to FILE instead of stdout
--verbose Print progress to stderr
Examples:
scripts/process.py data.csv
scripts/process.py --format csv --output report.csv data.csv
Keep it concise, the output enters the agent's context window alongside everything else it's working with.
Write helpful error messages
When an agent gets an error, the message directly shapes its next attempt. An opaque "Error: invalid input" wastes a turn. Instead, say what went wrong, what was expected, and what to try:
Error: --format must be one of: json, csv, table.
Received: "xml"
Use structured output
Prefer structured formats, JSON, CSV, TSV, over free-form text. Structured formats can be consumed by both the agent and standard tools (jq, cut, awk), making your script composable in pipelines.
# Whitespace-aligned — hard to parse programmatically
NAME STATUS CREATED
my-service running 2025-01-15
# Delimited — unambiguous field boundaries
{"name": "my-service", "status": "running", "created": "2025-01-15"}
Separate data from diagnostics: send structured data to stdout and progress messages, warnings, and other diagnostics to stderr. This lets the agent capture clean, parseable output while still having access to diagnostic information when needed.
Further considerations
- Idempotency. Agents may retry commands. "Create if not exists" is safer than "create and fail on duplicate."
- Input constraints. Reject ambiguous input with a clear error rather than guessing. Use enums and closed sets where possible.
- Dry-run support. For destructive or stateful operations, a
--dry-runflag lets the agent preview what will happen. - Meaningful exit codes. Use distinct exit codes for different failure types (not found, invalid arguments, auth failure) and document them in your
--helpoutput so the agent knows what each code means. - Safe defaults. Consider whether destructive operations should require explicit confirmation flags (
--confirm,--force) or other safeguards appropriate to the risk level. - Predictable output size. Many agent harnesses automatically truncate tool output beyond a threshold (e.g., 10-30K characters), potentially losing critical information. If your script might produce large output, default to a summary or a reasonable limit, and support flags like
--offsetso the agent can request more information when needed. Alternatively, if output is large and not amenable to pagination, require agents to pass an--outputflag that specifies either an output file or-to explicitly opt in to stdout.
When not to use it
If your skill's logic is simple enough to fit entirely in SKILL.md instructions without running external code, you don't need scripts. One-off commands are best for invoking well-known tools with a few flags. For anything more complex, a bundled script is more reliable. The source also notes that if a command grows complex enough that it's hard to get right on the first try, a tested script in scripts/ is more reliable.
Limits and gotchas
- The agent operates in a non-interactive shell. Interactive prompts will hang the script indefinitely.
- Many agent harnesses automatically truncate tool output beyond a threshold (e.g., 10-30K characters). Plan for this with summaries, pagination, or output files.
- For Ruby scripts using
bundler/inline, an existingGemfileorBUNDLE_GEMFILEenvironment variable in the working directory can interfere. - For Deno, packages with native addons (node-gyp) may not work. Packages that ship pre-built binaries work best.
- For Bun, if a
node_modulesdirectory exists anywhere up the directory tree, auto-install is disabled and Bun falls back to standard Node.js resolution. - For one-off commands, the required runner must be installed in the agent's environment. State prerequisites in
SKILL.mdrather than assuming they exist.
What pairs with this
- The Agent Skills specification covers the
compatibilityfrontmatter field for declaring runtime-level requirements. - The SKILL.md format describes the full structure of a skill file.
- The scripts directory page (this guide) is the primary reference for bundling executable scripts.
The #1 AI Newsletter
The most important ai updates, guides, and fixes — one weekly email.
No spam, unsubscribe anytime. Privacy policy