Skip to content

Latest commit

 

History

History
163 lines (116 loc) · 8.35 KB

File metadata and controls

163 lines (116 loc) · 8.35 KB
name java-cli-script
description Create zero-dependency, single-file executable Java scripts for system-wide use via PATH. Use when asked to create a single-file Java shell script, system utility, PATH-installed Java tool, or shebang-launched Java program without the .java extension. Triggers on "Java script", "Java utility", "PATH script", "system script", or requests for single-file Java programs installed in /usr/local/bin or similar PATH directories. Not for multi-file Java applications — use java-cli-app for those.

Create or maintain a zero-dependency, single-file executable Java script using $ARGUMENTS. Apply all rules below strictly.

Purpose

These are self-contained, single-file Java scripts installed in a PATH directory (e.g., /usr/local/bin) and invoked like any shell command — no project structure, no build tool, no .java extension.

Composition

  • Follow /java-conventions for all generic Java 25 style, syntax, naming, methods, streams, exceptions, and comments
  • The rules below specialize /java-conventions for single-file source-mode scripts; when this skill is silent on a topic, /java-conventions applies

Single-File Constraint

  • Everything lives in one file — all logic, records, enums, sealed types, and helper methods
  • If the script grows beyond what fits comfortably in a single file, suggest switching to the java-cli-app skill instead
  • No multi-file source-code programs — this skill is strictly for single-file scripts

Build

  • Use Java 25 source-file mode — no build tool, no compilation step
  • Never use the .java extension — scripts are executable files with a lowercase filename and a shebang
  • Never use --enable-preview in the shebang — Java 25 is a GA release, all features used here are standard
  • The shebang line is always:
#!/usr/bin/env -S java --source 25
  • Save with a short, descriptive, lowercase command name (e.g., jsonformat, portcheck, sysinfo)
  • Never use dashes in filenames (e.g., hello-world) — Java source-file mode does not support them. Use camelCase instead (e.g., helloWorld)
  • Mark executable: chmod +x scriptname
  • Install by copying or symlinking to a PATH directory: cp scriptname /usr/local/bin/ or ln -s $(pwd)/scriptname /usr/local/bin/scriptname

Script Structure

A minimal script:

#!/usr/bin/env -S java --source 25

void main(String... args) throws Exception {
    // ...
}

Naming Convention

  • The script's filename is its application name — use it consistently in all output: version strings, help text, usage lines, and error messages
  • Derive the application name dynamically at startup using MethodHandles.lookup().lookupClass().getName() — in source-file mode, the class name matches the filename, so there is nothing to hardcode or keep in sync

Version Management

  • Suggest maintaining a String version = "YYYY-MM-DD.N"; instance variable (e.g., String version = "2026-02-20.1";)
  • On every change, update the date to the current date and increase the last number
  • Print the script name and version on startup as the first line of output (e.g., IO.println(name + " " + version);)
  • Support -version flag to print the version — only for scripts that accept additional arguments

Main Method Conventions

  • Use void main(String... args) throws Exception — PATH-installed scripts always accept arguments; declaring throws Exception keeps the code clean by avoiding try-catch boilerplate for checked exceptions like IOException
  • Parse arguments manually for simple scripts with few flags
  • For scripts with multiple options or complex argument handling, use the /zargs skill to generate an enum-based argument parser — it remains zero-dependency and single-file

Argument Handling

  • Scripts with no additional arguments: do not introduce -help or -version flags — the script should just do its work
  • Scripts with additional arguments: support both -help and -version flags
  • Use clear, descriptive error messages for invalid input
  • Exit with code 0 on success, non-zero on failure

Script with additional arguments — include -help and -version:

String name = MethodHandles.lookup().lookupClass().getName();
String version = "2026-02-20.1";

void main(String... args) throws Exception {
    IO.println(name + " " + version);
    if (args.length == 0 || args[0].equals("-help")) {
        IO.println("""
                Usage: %s <input> [options]
                  -help     Show this help
                  -version  Show version
                """.formatted(name));
        return;
    }
    if (args[0].equals("-version")) {
        return;
    }
    // ...
}

Convenience Scripts for an Existing java-cli-app or JAR

When asked for an executable convenience script for a /java-cli-app project or an existing JAR, do not replicate the application's logic in the script. The script stays a single-file source script whose main calls into the JAR's classes. Instead:

  • Keep source-file mode and add the JAR to the classpath in the same shebang — both are required so the source file compiles and can resolve the app's classes:
#!/usr/bin/env -S java --source 25 -cp /path/to/app.jar
  • This is the one sanctioned exception to the "never add classpath entries to the shebang" rule under Zero Dependencies — it applies only when wrapping an existing app, never for standalone scripts
  • Delegate to the existing functionality by calling the app's public API. Only fall back to invoking its main(String[]) when no other entry point exists — calling main directly re-runs the app's startup banner and argument parsing
  • If the existing code is not callable from a single-file launcher (e.g., no accessible entry point, tightly coupled logic), recommend refactoring the java-cli-app to expose a public API or executable entry point rather than duplicating its behavior in the script
  • Never copy or paraphrase the application's logic into the convenience script

Zero Dependencies

  • Only use the java.base module and standard JDK modules
  • Never add external JARs or classpath entries to the shebang
  • Use java.net.http.HttpClient for HTTP, javax.crypto for crypto, java.nio.file for file I/O
  • If a task genuinely requires external libraries or multiple files, suggest using the java-cli-app skill with zb instead

Code Style

Generic Java style comes from /java-conventions. The rules below are script-specific specializations.

  • Do not create classes or interfaces — use unnamed classes with top-level methods, records, enums, and sealed types only
  • No package declaration
  • Use IO.println() for printing (or IO::println as method reference) — never System.out.println() and never System.Logger (scripts use stdout directly)
  • For multi-line output, use a single IO.println with a text block — never multiple IO.println calls for consecutive lines

stdin / stdout / stderr

  • Read from stdin when no file argument is given — support piping (e.g., cat file | scriptname)
  • Write normal output to stdout
  • Write errors and diagnostics to stderr using System.err.println()
  • For colored terminal output (status messages, errors, warnings), use the /zcl skill — it adds ANSI color support with zero dependencies
  • Support both piped input and file arguments where applicable

Configuration (Optional)

  • For scripts that need external configuration (properties files, environment overrides), use the /zcfg skill to add zero-dependency configuration loading
  • Most scripts should prefer command-line arguments over configuration files — only use /zcfg when persistent settings are genuinely needed

Companion Libraries

The following zero-dependency libraries can be embedded directly into single-file scripts. They may need to be cloned first:

  • zcl — colored terminal output: git clone https://github.com/AdamBien/zcl
  • zargs — enum-based argument parsing pattern: https://github.com/AdamBien/zeeds/blob/main/zargs (no repo to clone — the /zargs skill generates the code directly)
  • zcfg — configuration loading: git clone https://github.com/AdamBien/zcfg

Installation Guidance

When presenting the finished script, always include installation instructions:

# Make executable
chmod +x scriptname

# Install system-wide (pick one)
sudo cp scriptname /usr/local/bin/
# or symlink for development
sudo ln -s $(pwd)/scriptname /usr/local/bin/scriptname