npx skills add celigo/ai --skill writing-scripts
quick-brown-foxxx/myai
writing-scripts
>- ALWAYS LOAD THIS SKILL WHEN CREATING ANY STANDALONE SINGLE-FILE SCRIPT, AUTOMATION, OR TINY CLI TOOL FOR ANY LANGUAGE OR ECOSYSTEM. Do not create scripts directly — use this skill first. Single-file script patterns: when to use scripts vs projects, script layout, script body structure, CLI conventions, graceful shutdown, and ecosystem-specific examples for Python and TypeScript/Node.
Installation
npx skills add quick-brown-foxxx/myai --skill writing-scripts
Similar popular skills
Related neighbors and high-traction skills in the same topics — useful to compare before installing.
Guidance for distinctive, intentional visual design when building new UI or reshaping an existi…
866.4K installsBrowser automation CLI for AI agents. Use when the user needs to interact with websites, includ…
810.4K installsReview UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "chec…
617.3K installsBuild, deploy, evaluate, optimize, fine-tune, and manage Microsoft Foundry agents, models, and …
576.5K installsDebug Azure production issues on Azure using AppLens, Azure Monitor, resource health, and safe …
568.9K installsAlso in this package
Other skills from quick-brown-foxxx/myai · top by installs.
npx skills add quick-brown-foxxx/myai
More details
Agent compatibility
Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.
Also listed on
Alternate registries and mirrors of this skill.
Repository health
master
Skill metadata
Parsed from SKILL.md frontmatter.
More metadata
- focus
- single-file-scripts
- tags
- scripts, cli, automation
Package contents
Files included with this skill beyond the listing page.
-
skill md
SKILL.md11,054 B -
docs
SUMMARY.md411 B
History
- First seen on skills.sh
- First recorded snapshot · 8 installs
SKILL.md
Writing Scripts
Prerequisites
Load engineering-principles before this skill. It provides the foundation: strict types, validation at boundaries, explicit errors, and principle-over-prescription (ceremony scales with task size). This skill adds concrete script patterns: the script-or-project decision, layout conventions, body structure, and CLI/signal handling defaults.
When to Use a Single Script vs Full Project
| Single Script | Full Project |
|---|---|
| One task, one file | Multiple features |
| No tests needed | Tests required |
| Templating / generation / automation | Application with UI or API |
| Run directly from source | Run via task runner or build step |
| Dependencies inline or in a minimal header | Dependencies in project manifest |
| Under ~500 lines | Will grow beyond ~500 lines |
If criteria lean toward "Full Project," start with a project layout instead. See setting-up-projects.
Script Layout
Supporting files live alongside the single script. Keep a minimal tool-config manifest with only linter and type checker settings — no project metadata or dependency declarations (those live inline in the script).
app/
├── script # Self-contained, directly executable
├── template.ext # Templates (if generating text)
├── schema.ext # Validation schema (if validating configs)
├── configs/ # Configuration files (if multiple needed)
├── tool-config # Linter + type checker config only (no project section)
└── .gitignore
Script Body Structure
Organize every script in four clearly separated sections:
# =============================================================================
# Constants & Types
# =============================================================================
(all type definitions, constants, configuration defaults)
# =============================================================================
# Utils & Helpers
# =============================================================================
(small self-contained helpers that not contain any business logic)
# =============================================================================
# Business Logic
# =============================================================================
(pure functions: load, transform, validate, process — no I/O at the bottom,
no CLI/presentation at the top)
# =============================================================================
# CLI Interface
# =============================================================================
(thin CLI adapter: parse input → call business logic → format output → exit)
The CLI section is a thin adapter: parse arguments, call one business function or a small pipeline, map the result to stdout/stderr and exit code. Do not spread argument parsing, I/O, and business logic across the file.
CLI Conventions
- Always support
-hin addition to--help. Most CLI frameworks
default to --help-only. Override to also accept -h.
- Use the ecosystem's good CLI library: typer for Python, commander
or yargs for Node, etc. Use the standard-library argument parser only when external dependencies are impossible.
- Map errors to exit codes explicitly. Expected failures from business
logic produce non-zero exit codes with a user-readable message to stderr.
- Keep the CLI adapter thin — it decodes input, calls core, and formats
output. No business logic in argument handlers.
Graceful Shutdown
Scripts must not dump tracebacks on Ctrl+C. Catch the interrupt signal and exit cleanly:
- Unix convention: exit code 130 (128 + signal 2 for SIGINT)
- No traceback: the user pressed Ctrl+C intentionally — show nothing
or a brief "Interrupted" message
- Subprocess cleanup: if the script spawns subprocesses, use process
groups so Ctrl+C kills the entire tree, not just the parent
If a subprocess runs a long-lived tool, escalate: friendly signal first (SIGTERM), wait with timeout, force kill (SIGKILL) if unresponsive. See setting-up-projects for detailed shutdown patterns.
Ecosystem Examples
Python
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.14"
# dependencies = [
# "typer>=0.12.0",
# "rusty-results>=1.1.1",
# ]
# ///
import sys
from pathlib import Path
from typing import Final
import typer
from rusty_results import Result, Ok, Err
# =============================================================================
# Constants & Types
# =============================================================================
TEMPLATE_PATH: Final[Path] = Path(__file__).parent / "template.html"
# =============================================================================
# Helpers
# =============================================================================
def _run(command: list[str], cwd: Path) -> None:
subprocess.run(command, cwd=cwd, check=True)
# =============================================================================
# Business Logic
# =============================================================================
def load_config(path: Path) -> Result[str, str]:
if not path.exists():
return Err(f"Config not found: {path}")
return Ok(path.read_text())
def process(config: str) -> Result[str, str]:
return Ok(config.upper())
def do_work() -> Result[str, str]:
config = load_config(Path("config.toml"))
if config.is_err:
return Err(config.unwrap_err())
return process(config.unwrap())
# =============================================================================
# CLI Interface
# =============================================================================
app = typer.Typer(
help="Description",
add_completion=False,
context_settings={"help_option_names": ["-h", "--help"]},
)
@app.command()
def main_command() -> None:
result = do_work()
if result.is_err:
typer.echo(f"Error: {result.unwrap_err()}", err=True)
sys.exit(1)
typer.echo(result.unwrap())
if __name__ == "__main__":
try:
app()
except KeyboardInterrupt:
sys.exit(130)
Python tool config (pyproject.toml, no [project] section):
[tool.pyrigt]
typeCheckingMode = "strict"
reportAny = "error"
[tool.ruff]
line-length = 120
[tool.ruff.lint]
extend-select = ["E", "F", "I", "N", "UP", "S", "B", "A", "C4", "RUF"]
- Use PEP 723 inline metadata for dependencies (
# /// scriptblock). - Execute with
uv run --scriptor./script.py(requires the shebang). - Use typer for CLI (stdlib argparse only for no-dependency scripts).
- See
writing-python-codefor full Python type and error handling rules.
TypeScript / Bun
#!/usr/bin/env bun
// Bun auto-installs dependencies on first run — no package.json needed.
// Just import what you need.
import { program } from "commander";
import { ok, err, Result } from "neverthrow";
import { z } from "zod";
import { readFileSync, existsSync } from "node:fs";
import { resolve } from "node:path";
// =============================================================================
// Constants & Types
// =============================================================================
const TEMPLATE_PATH = resolve(import.meta.dir, "template.html");
const ConfigSchema = z.object({
name: z.string(),
});
type Config = z.infer<typeof ConfigSchema>;
// =============================================================================
// Business Logic
// =============================================================================
function loadConfig(path: string): Result<Config, string> {
if (!existsSync(path)) {
return err(`Config not found: ${path}`);
}
const raw = readFileSync(path, "utf-8");
const parsed = ConfigSchema.safeParse(JSON.parse(raw));
if (!parsed.success) {
return err(`Invalid config: ${parsed.error.message}`);
}
return ok(parsed.data);
}
function process(config: Config): Result<string, string> {
return ok(config.name.toUpperCase());
}
function doWork(): Result<string, string> {
const config = loadConfig("config.json");
if (config.isErr()) {
return err(config.error);
}
return process(config.value);
}
// =============================================================================
// CLI Interface
// =============================================================================
program
.name("script")
.description("Description")
.helpOption("-h, --help", "Show help")
.action(() => {
const result = doWork();
if (result.isErr()) {
console.error(`Error: ${result.error}`);
process.exit(1);
}
console.log(result.value);
});
program.parse();
Bun tool config (tsconfig.json, optional — only needed for IDE type checking):
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"types": ["bun-types"]
}
}
- Use Bun as the default runtime — zero-config TypeScript, auto-installs
npm dependencies on first run, no package.json or build step needed.
- Use commander or yargs for CLI parsing.
- Use a runtime schema validator (e.g., zod, valibot) for validation at the
file/config boundary — this example uses zod.
- Use neverthrow or a custom Result type for expected errors.
- Use
import.meta.dir(Bun built-in) instead of__dirnamefor script-relative paths. - For scripts that must run under Node instead, use tsx or
node myscript.ts with a minimal package.json. Verify current Node native type-stripping limitations (erasable syntax only, no tsconfig-dependent behavior) before choosing it for a script.
These defaults apply to single-file scripts only. Maintained application projects should follow setting-up-typescript-projects for their own runtime, package-manager, and schema decisions.
Handoff
- Use
setting-up-projectswhen the script grows beyond ~500 lines or needs
tests and a full project layout.
- Use
writing-python-code(Python) for type system, error handling, and
async patterns that apply to scripts too.
Related myai Skills
engineering-principles— Parent skill. Language-agnostic philosophy.setting-up-projects— When a script outgrows single-file scope.