Ukiryu is a framework for defining command-line interfaces as declarative APIs.
It makes the following possible:
-
Declarative definition of command-line interfaces, options, parameters and typed arguments
-
Harmonized definitions across platforms and shell environments
-
Unified action interface across versioned and variant command interfaces
-
Structured input and output handling, with type safety and validation
The Ukiryu framework has the following components:
-
Register: A collection of tool profiles encoded in YAML, organized by tool name and version
-
Schema: A formal definition of the structure and types used in tool profiles
-
Runtime: A platform-adaptive Ruby library that provides a harmonized API interface for CLI commands.
The name 浮流 "ukiryu" (lit. "Floating Flow") is inspired by 天浮橋 "ame-no-ukihashi" (lit. "Floating Bridge of Heaven"), the mythical bridge that connects heaven and earth.
The ame-no-ukihashi is described in Japanese mythology as the place where the God Izanagi and Goddess Izanami stood at creating the Japanese archipelago.
The pronunciation of "ukiryu" ("yoo-kee-rhoo") is the English transliteration of the Kanji characters 「浮流」, read in Hiragana as 「うきりゅう」.
In the view that a command line tool is a vessel for performing tasks, Ukiryu provides the harmonized definitions and interfaces that make cross-platform CLI tool integration predictable and reliable. serves as the flexible flow that guides its path through different environments.
-
Platform-adaptive: Commands work seamlessly across macOS, Linux, and Windows
-
Shell-aware: Proper quoting and escaping for bash, zsh, fish, PowerShell, and cmd
-
Versioned: Support multiple tool versions with distinct interfaces
-
Interface-capable: Different implementations of the same command interface
-
Declarative: Tool behavior defined in YAML profiles, not code
-
Type-safe: Parameter validation with automatic type coercion
-
Schema-Driven: Define CLI behavior in declarative YAML profiles
-
Type-Safe: Automatic parameter validation and type coercion
-
Platform-Aware: Automatic adaptation across macOS, Linux, and Windows
-
Structured Results: Rich response objects instead of parsing stdout/stderr
-
Versioned APIs: Support multiple tool versions with compatibility matrices
-
Intelligent Discovery: Self-documenting commands with built-in introspection
Without Ukiryu, CLI tools suffer from:
-
Argument Fragility: Positional args break when order changes
-
Output Parsing Hell: Grepping unstructured text is brittle
-
Platform Divergence: Same command differs on macOS vs Linux
-
Version Drift: Tools change behavior between versions
-
Error Ambiguity: Is exit code 1 a timeout or validation error?
-
Discovery Friction: Which flags exist? What’s the syntax?
| Problem | Traditional CLI | Ukiryu Solution |
|---|---|---|
Argument fragility |
|
|
Output parsing |
|
|
Platform divergence |
Different quoting per OS |
Same code works everywhere |
Version drift |
Breaking changes silently break scripts |
Version-specific YAML profiles |
Error ambiguity |
Exit code 1 = ??? |
TimeoutError, ValidationError, etc. |
Discovery friction |
|
|
OpenAPI provides the following benefits for REST APIs by:
-
Standardizing - Machine-readable schema definitions
-
Documenting - Auto-generated interactive documentation
-
Type-Safe - Request/response validation
-
Versioning - Multiple API versions coexisting
Ukiryu provides the same benefits for CLI tools by:
-
Schema: YAML profiles replace grepping --help
-
Docs:
ukiryu describereplaces man pages -
Types: Named parameters replace positional args
-
Versions:
inkscape/1.0.yamlandinkscape/0.92.yamlcoexist
-
CLI Developers: Distribute "intelligent man pages" with your CLI
-
Tool Users: Get deterministic, documented command behavior
-
CI/CD: Run commands with structured error handling
-
Libraries: Wrap CLI tools in type-safe Ruby interfaces *DevOps: Manage tool versions and compatibility automatically
Ukiryu is a framework that turns CLI tools into well-defined, versioned APIs through declarative YAML profiles.
-
Package: A distribution unit (e.g.,
imagemagick,busybox,coreutils) -
Tool: A CLI command you run (e.g.,
convert,identify,ls,gzip) -
Interface: An abstract API contract that multiple tools can implement
-
Command: An operation a tool performs
-
Profile: YAML file defining tool behavior across platforms/versions
|
Tip
|
One package can provide multiple tools, and one interface can be implemented by multiple different tools. For example:
* The |
Ukiryu’s architecture is built on a clear separation between distribution units (packages), executable commands (tools), and their abstract contracts (interfaces).
.Package, Tool, and Interface Relationships
┌──────────────────────────────────────────────────────────────────┐
│ │
│ PACKAGE (Distribution Unit) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ imagemagick-7.1 (Homebrew, MacPorts, apt, etc.) │ │
│ │ │ │
│ │ Provides multiple tools: │ │
│ │ ├─ magick (main executable) │ │
│ │ ├─ convert (tool) │ │
│ │ ├─ identify (tool) │ │
│ │ └─ mogrify (tool) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ INTERFACE (Abstract Contract) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ convert interface │ │
│ │ │ │
│ │ Defines: │ │
│ │ ├─ Image format conversion capabilities │ │
│ │ ├─ Common options: resize, quality, strip │ │
│ │ └─ Input/output file arguments │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │ │
│ │ implements │ implements │
│ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ imagemagick-6 │ │ imagemagick-7 │ │
│ │ convert tool │ │ convert tool │ │
│ │ │ │ │ │
│ │ Invocation: │ │ Invocation: │ │
│ │ convert input.png │ │ magick convert │ │
│ │ output.jpg │ │ input.png │ │
│ │ │ │ output.jpg │ │
│ │ standalone: true │ │ subcommand: true │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────┘A package is a distribution unit - how software is installed and distributed:
-
Homebrew formula:
brew install imagemagick -
MacPorts port:
sudo port install ImageMagick7 -
apt package:
apt install imagemagick -
Chocolatey package:
choco install imagemagick
|
Warning
|
Ukiryu does NOT define packages in YAML. Package management is the responsibility of the system administrator or CI/CD configuration. Ukiryu defines tools, not packages. |
A tool is a CLI command you can run - an executable with a specific interface:
-
convert- Image conversion tool -
identify- Image information tool -
ls- List directory contents -
gzip- Compression tool
One YAML file defines one tool:
# register/tools/convert/6.9.yaml
name: convert
version: "6.9"
display_name: ImageMagick 6.x Convert
implements: [convert] # Declares which interface this provides
version_detection:
command: -version
pattern: 'Version: ImageMagick ([\d.]+)'
profiles:
- name: unix
platforms: [macos, linux]
shells: [bash, zsh, fish, sh]
commands:
- name: convert
standalone_executable: true
description: Convert between image formats
arguments:
- name: inputs
type: file
variadic: true
- name: output
type: file
position: lastAn interface is an abstract API contract that defines:
-
What operations can be performed
-
What parameters are accepted
-
what behavior is expected
Interfaces are typically not explicitly declared in YAML - they emerge from the tool’s command definition. However, tools can declare which interfaces they implement:
# convert tool declares it implements the "convert" interface
implements: [convert]
# A "pdf2svg" tool might also implement the convert interface
implements: [convert]Multiple tools can implement the same interface:
# imagemagick/6.9.yaml
name: convert
version: "6.9"
implements: [convert]
# imagemagick/7.1.yaml
name: convert
version: "7.1"
implements: [convert]
# graphicsmagick/1.3.yaml
name: gm
version: "1.3"
implements: [convert] # GraphicsMagick's gm convert implements same interfaceDifferent implementations of the same interface have different invocation patterns:
ImageMagick 6.x:
convert input.png -resize 50% output.png
# "convert" is a standalone executableImageMagick 7.x:
magick convert input.png -resize 50% output.png
# "convert" is a subcommand of "magick"The YAML profiles capture these differences:
# IM6: standalone executable
- name: convert
standalone_executable: true
# Invocation: /usr/bin/convert input.png output.png
# IM7: subcommand
- name: convert
subcommand: convert
# Invocation: /usr/bin/magick convert input.png output.pngHow does Ukiryu know which implementation it’s using? Through identity detection - self-contained verification mechanisms intrinsic to the executable itself:
Version Detection (primary mechanism):
version_detection:
command: -version
pattern: 'Version: ImageMagick ([\d.]+)'
modern_threshold: '7.0'|
Important
|
Identity detection must be self-contained - it only uses information from the executable itself:
This ensures tools work regardless of how they were installed. |
BusyBox provides many Unix commands as one executable, using symlinks or applets:
# These all point to the same BusyBox binary:
/bin/ls → /bin/busybox
/bin/cat → /bin/busybox
/bin/gzip → /bin/busyboxHow do we know it’s BusyBox and not the real gzip?
# busybox/gzip.yaml
name: gzip
version: "1.35"
implements: [gzip]
version_detection:
command: --help
# BusyBox's gzip identifies itself in help output
pattern: 'BusyBox v([\d.]+)'
profiles:
- name: default
commands:
- name: gzip
subcommand: gzip
# Invocation: busybox gzip -d file.gzWhen version detection shows "BusyBox v1.35.0", Ukiryu knows this is the BusyBox implementation, not GNU gzip.
┌───────────────────────────────────────────────────────────────────┐
│ │
│ User Code (Ruby / CLI) │
│ ├─ tool.execute(:inkscape, { inputs: [...] }) │
│ └─ ukiryu exec inkscape export inputs=... │
│ │
└─────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────▼─────────────────────────────────────┐
│ Ukiryu Framework │
│ ├─ Register (loads YAML profiles) │
│ ├─ Tool (selects profile, detects version) │
│ ├─ CommandBuilder (formats arguments for shell) │
│ ├─ Executor (runs commands, captures output) │
│ └─ Shell Layer (platform-specific quoting/escaping) │
│ │
└─────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────▼─────────────────────────────────────┐
│ YAML Tool Profiles (Declarative API Definition) │
│ ├─ tools/inkscape/1.0.yaml (Modern Inkscape) │
│ ├─ tools/inkscape/0.92.yaml (Legacy Inkscape) │
│ └─ tools/imagemagick/7.1.yaml (ImageMagick 7.1) │
└───────────────────────────────────────────────────────────────────┘A tool profile is a YAML file that defines a CLI’s complete API surface:
# tools/inkscape/1.0.yaml
---
ukiryu_schema: "1.2"
# Tool Metadata
name: inkscape
version: "1.0"
$self: https://www.ukiryu.com/register/1.0/inkscape/1.0
# Version Detection
version_detection:
command: "--version"
pattern: "Inkscape (\\d+\\.\\d+)"
modern_threshold: "1.0"
# Search Paths (platform-specific)
search_paths:
macos:
- "/Applications/Inkscape.app/Contents/MacOS/inkscape"
linux:
- "/usr/bin/inkscape"
windows:
- "C:/Program Files/Inkscape/bin/inkscape.exe"
# Platform/Shell/Version Profiles
profiles:
- name: modern_unix
platforms: [macos, linux]
shells: [bash, zsh, fish, sh]
version: ">= 1.0"
option_style: double_dash_equals
# Commands (API Endpoints)
commands:
- name: export
description: Export document to different format
# Environment variables to apply for this command
use_env_vars: [headless]
# Request Parameters
arguments:
- name: inputs
type: file
variadic: true
position: last
min: 1
description: Input file(s)
options:
- name: output
type: file
cli: --export-filename
format: double_dash_equals
required: true
description: Output filename
- name: format
type: symbol
cli: --export-type
format: double_dash_equals
values: [svg, png, pdf, eps, ps]
description: Output format
flags:
- name: batch_process
cli: --batch-process
position_constraint: prefix
default: true
description: Close GUI after processing
# Exit Codes (API Response Status)
exit_codes:
standard:
"0": "Success"
"1": "File not found or cannot be opened"
"3": "Initialization error"
custom:
"2": "Export failed (see stderr for details)"
# Response Format
parse_output:
type: hash
pattern: 'key:\\s*value'Ukiryu organizes CLI functionality into a hierarchical structure similar to REST API resources.
A tool represents a command-line utility (e.g., inkscape, imagemagick).
# Get a tool by name
tool = Ukiryu::Tool.get(:inkscape)Has metadata: name, version, homepage, aliases
Detects version: Uses version_detection from profile
*Finds executable: Searches search_paths for the tool
*Loads profile*: Selects appropriate YAML profile based on version
A command is a major operation a tool performs (e.g., export, convert, query).
commands:
- name: export
description: Export document to different format
usage: inkscape [OPTIONS] input1.svg [input2.svg ...]# Execute a command
result = tool.execute(:export, { inputs: ['drawing.svg'], output: 'drawing.png' })Has parameters: arguments, options, flags
Returns result: Structured response object
May have execution mode: e.g., headless for GUI tools
An action is a specific operation within a command, used for complex CLIs with nested commands.
# Git has nested commands
commands:
- name: remote
description: Manage set of tracked repositories
- name: branch
description: Manage, create, delete, list branch namesWith routing:
routing:
remote: git-remote # Maps "tool remote" to git-remote command
branch: git-branch # Maps "tool branch" to git-branch command# Actions are executed via the command name
git_tool = Ukiryu::Tool.get(:git)
# Execute 'git remote add' action
git_tool.execute(:remote, :add, {
name: 'origin',
url: 'https://github.com/user/repo.git'
})
# Execute 'git branch delete' action
git_tool.execute(:branch, :delete, {
branch: 'feature-branch',
force: true
})Routing maps command names to their implementations, enabling:
-
Tool aliases: Multiple tools implementing the same interface
-
Version routing: Different versions for different behaviors (using Versionian)
-
Multi-level hierarchies: Parent/child command relationships
Ukiryu uses the Versionian library for version parsing, comparison, and range matching.
Versionian provides:
-
Built-in schemes: Semantic (SemVer), Calendar versioning (CalVer), SoloVer, WendtVer
-
Version range matching:
equals,after,before,betweenfor version constraints -
Extensible: Custom version schemes via declarative YAML or Ruby
In the register’s implementation index files, Versionian’s syntax is used:
# tools/tar/gnu/index.yaml
implementations:
- name: gnu
version_scheme: semantic (1)
versions:
- equals: "1.35" (2)
file: "1.35.yaml"
- after: "1.35" (3)
file: "1.35.yaml"-
Specifies which Versionian scheme to use for version comparison.
-
Exact version match - only version 1.35 matches this rule.
-
Semantic version range - matches any version >= 1.35.
The version_scheme field in implementation indexes specifies which Versionian scheme to use:
-
semantic- MAJOR.MINOR.PATCH (e.g., 1.35.0, 2.0.0) -
calver- YYYY.MM.DD (e.g., 2024.01.17, 2024.03.15) -
solover- N[+|-]postfix (e.g., 5+hotfix, 5-beta) -
wendtver- MAJOR.MINOR.PATCH.BUILD
When a tool version is detected (e.g., tar (GNU tar) 1.35), Ukiryu:
-
Parses the detected version using the specified
version_scheme -
Compares it against the
versionsarray rules (in order) -
Selects the first matching rule’s
filevalue -
Loads the corresponding YAML profile
Versionian range types used in implementation indexes:
-
equals: "1.35"- Exact version match -
after: "1.35"- Matches any version >= 1.35 (inclusive) -
before: "2.0"- Matches any version < 2.0 (exclusive) -
between: "1.35", "2.0"- Matches versions in inclusive range [1.35, 2.0]
Example flow:
# Detected version: GNU tar 1.35.0
# version_scheme: semantic
# Parsed: [1, 35, 0]
# Check rules in order:
# 1. equals: "1.35" → "1.35.0" != "1.35" → no match
# 2. after: "1.35" → "1.35.0" > "1.35" → MATCH!
# Result: Load tools/tar/1.35.yaml# Ping has platform-specific implementations
tools:
ping: # Abstract interface
implements: ping
ping_bsd: # BSD ping implementation
implements: ping
platforms: [freebsd, openbsd, netbsd]
ping_gnu: # GNU ping implementation
implements: ping
platforms: [linux]# Ukiryu automatically routes to correct implementation
tool = Ukiryu::Tool.get(:ping)
# On FreeBSD: uses ping_bsd
# On Linux: uses ping_gnuWhen a tool’s name matches its command name, you can omit the command:
# Both syntaxes work when tool.name == command.name
tool.execute(:ping, { host: '127.0.1.1', count: 1 })
tool.execute(:ping, :ping, { host: '127.0.1.1', count: 1 })Similarly in CLI:
ukiryu exec ping host=127.0.1.1 count=1
ukiryu exec ping ping host=127.0.1.1 count=1Here’s a complete example showing all concepts:
require 'ukiryu'
# 1. Get the tool
tool = Ukiryu::Tool.get(:inkscape)
# 2. Check availability
unless tool.available?
puts "Inkscape not installed"
exit 1
end
# 3. Execute the export command
result = tool.execute(:export, {
inputs: ['~/drawing.svg'],
output: '~/drawing.png',
format: :png,
dpi: 300,
width: 1024,
height: 768
})
# 4. Handle the result
if result.success?
puts "✓ Export successful"
puts " Duration: #{result.metadata.formatted_duration}"
puts " Command: #{result.command_info.full_command}"
else
puts "✗ Export failed"
puts " Exit code: #{result.output.exit_status}"
puts " Error: #{result.output.stderr}"
endGenerated command:
inkscape --batch-process --export-filename=/Users/user/drawing.png --export-type=png --export-width=1024 --dpi=300 /Users/user/drawing.svgUkiryu supports three complementary configuration methods with clear precedence:
-
Environment variables (
UKIRYU_*) -
CLI parameters
-
Ruby API parameters
-
Profile defaults ===
# Global configuration
export UKIRYU_REGISTER=/path/to/register
export UKIRYU_TIMEOUT=180
export UKIRYU_DEBUG=true
# All commands use these settings
ukiryu exec inkscape export inputs=drawing.svg output=drawing.png# Per-command configuration
ukiryu exec inkscape export \
inputs=drawing.svg \
output=drawing.png \
timeout=120# Programmatic configuration
Ukiryu::Register.default_register_path = '/path/to/register'
Ukiryu::Config.debug = true
tool = Ukiryu::Tool.get(:inkscape)
result = tool.execute(:export, params, timeout: 60)# Profile default: 90 seconds
# Ruby API parameter: 60 seconds
# CLI parameter: 120 seconds
# ENV variable: 180 seconds (WINS!)
ENV['UKIRYU_TIMEOUT'] = '180'
tool = Ukiryu::Tool.get(:inkscape)
result = tool.execute(:export, {
inputs: ['drawing.svg'],
output: 'drawing.png'
}, timeout: 60)
# Result: Uses 180 second timeout from ENVAdd this line to your application’s Gemfile:
gem 'ukiryu'And then execute:
bundle installOr install it yourself as:
gem install ukiryuMake your CLI "intelligent" with a bundled Ukiryu definition:
mytool --definition > mytool-1.0.yaml
mytool --validate-definition mytool-1.0.yamlYour tool now has:
* Auto-generated ukiryu describe mytool documentation
* Type-safe parameter validation
* Platform-aware command formatting
* Structured error responses
require 'ukiryu'
# Configure register
Ukiryu::Register.default_register_path = 'path/to/register'
# Get a tool
tool = Ukiryu::Tool.get(:inkscape)
# Execute with type safety
result = tool.execute(:export, {
inputs: ['drawing.svg'],
output: 'drawing.png',
format: :png, # Symbol - validated against values list
dpi: 300, # Integer - range checked
width: 1024 # Integer - range checked
})
puts result.success? ? "Success!" : "Failed: #{result.stderr}"# .github/workflows/convert.yml
name: Convert Images
on: [push]
jobs:
convert:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Ukiryu
run: gem install ukiryu
- name: Convert SVG to PNG
env:
UKIRYU_REGISTER: ./register
run: |
ukiryu exec inkscape export \
inputs=drawing.svg \
output=drawing.png \
format=pngUkiryu transforms how developers interact with command-line tools by replacing fragile, hardcoded command strings with a declarative, type-safe API. This section explains the core concepts that make this possible.
Traditional CLI usage involves crafting command strings with proper escaping, quoting, and platform-specific syntax:
# Traditional approach - fragile and error-prone
convert input.png -resize 50% -quality 85 output.jpg
ffmpeg -i video.mp4 -ss 00:01:00 -t 30 -c:v libx264 output.mp4
inkscape --export-filename=output.png --export-type=png input.svgUkiryu’s declarative model lets you describe what you want, not how to invoke it:
# Declarative approach - portable and type-safe
tool = Ukiryu::Tool.get(:imagemagick)
result = tool.execute(:convert, {
inputs: ['input.png'],
resize: '50%',
quality: 85,
output: 'output.jpg'
})The YAML profile captures all the knowledge about how to invoke the tool:
# imagemagick/7.1.yaml
commands:
- name: convert
description: Convert between image formats
arguments:
- name: inputs
type: file
variadic: true
- name: output
type: file
position: last
options:
- name: resize
type: string
cli: -resize
- name: quality
type: integer
range: 0..100
cli: -qualityUkiryu’s architecture separates concerns into distinct layers that work together to provide a unified CLI experience:
.Architecture layers
┌─────────────────────────────────────────────────────────────────────┐
│ Developer Code │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Ruby API │ CLI │ │
│ │ tool.execute(:cmd, params) │ ukiryu exec tool cmd key=val │ │
│ └─────────────────────────────────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Ukiryu Framework │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Tool layer │ │
│ │ ├─ Tool.get(:name) - Load tool profile │ │
│ │ ├─ tool.available? - Check executable exists │ │
│ │ └─ tool.version - Detected version │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ Command layer │ │
│ │ ├─ build_args(params) - Validate and format parameters │ │
│ │ ├─ execute(params, env) - Run command │ │
│ │ └─ result parsing - Structured output │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ Shell layer │ │
│ │ ├─ format_path(path) - Platform path formatting │ │
│ │ ├─ escape_arg(arg) - Shell-specific escaping │ │
│ │ └─ join_args(args) - Argument joining │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ Executor layer │ │
│ │ ├─ Open3.popen3 - Process spawn │ │
│ │ ├─ capture(output) - Stdout/stderr capture │ │
│ │ └─ timing - Execution metadata │ │
│ └─────────────────────────────────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ YAML Tool Profiles │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Metadata │ │
│ │ name, version, homepage, aliases │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ Version detection │ │
│ │ command, pattern, modern_threshold │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ Search paths │ │
│ │ Platform-specific executable locations │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ Profiles (platform/shell combinations) │ │
│ │ commands with arguments, options, flags │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘When you call tool.execute(:convert, params), Ukiryu performs these steps:
.Step 1: Tool selection
┌─────────────────────────────────────────┐
│ Ukiryu::Tool.get(:convert) │
│ │
│ 1. Load convert/*.yaml from register │
│ 2. Detect installed version │
│ - Run: convert --version │
│ - Parse: Version: ImageMagick 7.1.0 │
│ 3. Select matching profile │
│ - 7.1.0 matches "7.1" version │
│ 4. Find executable │
│ - Check /usr/bin/convert │
│ - Check /opt/homebrew/bin/convert │
└────────────────┬────────────────────────┘
│
▼
.Step 2: Command building
┌─────────────────────────────────────────┐
│ CommandBuilder.build_args(params) │
│ │
│ params = { │
│ inputs: ['a.png', 'b.png'], │
│ resize: '50%', │
│ quality: 85, │
│ output: 'c.jpg' │
│ } │
│ │
│ 1. Validate parameter types │
│ - inputs: Array[file] ✓ │
│ - resize: String ✓ │
│ - quality: Integer (0..100) ✓ │
│ - output: File ✓ │
│ 2. Validate required fields │
│ - output: required ✓ │
│ 3. Format for shell │
│ -args = ['-resize', '50%', ...] │
└────────────────┬────────────────────────┘
│
▼
.Step 3: Shell formatting
┌─────────────────────────────────────────┐
│ Shell.format_command(args, options) │
│ │
│ On bash (linux/macos): │
│ "convert" "-resize" "50%" "-quality" │
│ "85" "a.png" "b.png" "c.jpg" │
│ │
│ On PowerShell (windows): │
│ "convert" "-resize", "50%", "-quality", │
│ "85", "a.png", "b.png", "c.jpg" │
│ │
│ On cmd (windows): │
│ convert -resize 50% -quality 85 │
│ a.png b.png c.jpg │
└────────────────┬────────────────────────┘
│
▼
.Step 4: Execution
┌─────────────────────────────────────────┐
│ Executor.execute(command, env) │
│ │
│ 1. Spawn process │
│ Open3.popen3("convert ...") │
│ 2. Capture output │
│ stdout: "", stderr: "" │
│ 3. Wait for completion │
│ exit_code: 0 │
│ 4. Return Result object │
│ result.success? # => true │
└─────────────────────────────────────────────────────────────────────┘To define a CLI tool for Ukiryu, you create a YAML file that describes its interface.
Here’s a complete example of a fictional myprocessor tool:
# tools/myprocessor/1.0.yaml
---
ukiryu_schema: '1.0'
# Tool metadata
name: myprocessor
display_name: MyProcessor
homepage: https://example.com/myprocessor
version: '1.0'
description: Batch file processor for data transformation
# Version detection
version_detection:
command: --version
pattern: 'MyProcessor version ([\d.]+)'
modern_threshold: '1.0'
# Where to find the executable on each platform
search_paths:
macos:
- /usr/local/bin/myprocessor
- /opt/homebrew/bin/myprocessor
linux:
- /usr/bin/myprocessor
- /usr/local/bin/myprocessor
windows:
- C:/Program Files/MyProcessor/bin/myprocessor.exe
# Profiles: platform/shell combinations
profiles:
- name: unix
platforms: [macos, linux]
shells: [bash, zsh, fish, sh]
option_style: double_dash_space
commands:
- name: process
description: Process input files
arguments:
- name: inputs
type: file
variadic: true
min: 1
description: Input files to process
- name: output
type: file
position: last
description: Output file or directory
options:
- name: format
type: string
cli: --format
values: [json, xml, csv, yaml]
description: Output format
default: json
- name: verbose
type: boolean
cli: --verbose
short: -v
description: Enable verbose output
default: false
- name: workers
type: integer
cli: --workers
range: 1..16
description: Number of parallel workers
default: 4
- name: validate
description: Validate input files
arguments:
- name: inputs
type: file
variadic: true
min: 1
options:
- name: strict
type: boolean
cli: --strict
description: Enable strict validation
default: false
- name: windows
platforms: [windows]
shells: [powershell, cmd]
option_style: double_dash_space
inherits: unixThe YAML profile has these key sections:
Metadata (required):
name: myprocessor # Tool identifier (used in API calls)
version: '1.0' # Version string
display_name: MyProcessor # Human-readable nameVersion detection (recommended):
version_detection:
command: --version # Command to run
pattern: 'version ([\d.]+)' # Regex to extract version
modern_threshold: '1.0' # Minimum version considered "modern"Search paths (recommended):
search_paths:
macos: ['/usr/local/bin/myprocessor']
linux: ['/usr/bin/myprocessor']
windows: ['C:/Program Files/MyProcessor/bin/myprocessor.exe']Commands (required):
commands:
- name: process # Command name (used in execute(:process))
description: What the command does
arguments: # Positional parameters
- name: inputs
type: file
variadi: true # Accept multiple files
options: # Named parameters
- name: format
type: string
cli: --format # CLI flag
values: [json, xml, csv] # Valid values
flags: # Boolean switches
- name: verbose
type: boolean
cli: --verboseLoad and use your custom profile:
require 'ukiryu'
# Load from file
tool = Ukiryu::Tool.load('tools/myprocessor/1.0.yaml')
# Check availability
puts tool.available? ? "Ready!" : "Not found"
# Execute commands
result = tool.execute(:process, {
inputs: ['file1.txt', 'file2.txt'],
output: 'output.json',
format: 'json',
verbose: true
})
puts result.success? ? "Done!" : "Error: #{result.output.stderr}"Or from the CLI:
ukiryu exec -d tools/myprocessor/1.0.yaml myprocessor process \
inputs=file1.txt,file2.txt \
output=output.json \
format=json \
--verboseThe official Ukiryu register is a curated collection of YAML profiles for popular CLI tools. Contributing helps the community by making tools more accessible.
-
Prepare your profile: Create
tools/<toolname>/<version>.yamlfollowing the v1 schema. Validate locally: [source,bash] ---- gem install ukiryu ukiryu validate --definition tools/mytool/1.0.yaml ukiryu lint file tools/mytool/1.0.yaml ---- -
Test with the executable: [source,bash] ---- ukiryu validate --definition tools/mytool/1.0.yaml --executable ----
-
Submit a pull request: The register is at https://github.com/ukiryu/register Submit PRs to the
v1branch.
For inclusion in the official register, profiles must:
-
Conform to the v1 schema (
ukiryu_schema: '1.0') -
Have complete metadata (name, version, display_name)
-
Include version detection with accurate pattern
-
Define search paths for all supported platforms
-
Cover major platforms (macOS, Linux, Windows)
-
Support common shells (bash, zsh, fish at minimum)
-
Define all major commands with arguments, options, and flags
-
Pass all validation and linting checks
-
Include smoke tests for basic functionality
tools/
└── mytool/
├── 1.0.yaml # Version-specific profile
├── 1.1.yaml # Another version
└── index.yaml # Optional: version routingFor tools with multiple implementations (GNU, BusyBox, BSD):
tools/
└── tar/
├── gnu/
│ ├── 1.35.yaml
│ ├── 1.36.1.yaml
│ └── index.yaml # GNU implementation routing
├── busybox/
│ ├── 1.36.1.yaml
│ └── index.yaml # BusyBox implementation routing
└── index.yaml # Abstract tar interfaceBefore submitting, verify your profile:
# 1. Schema validation
ukiryu validate --definition tools/mytool/1.0.yaml
# 2. Linting
ukiryu lint file tools/mytool/1.0.yaml
# 3. Executable testing (if tool is installed)
ukiryu validate --definition tools/mytool/1.0.yaml --executable
# 4. Check all YAML files in the register
cd /path/to/register && ukiryu validate allThe Ukiryu ecosystem consists of three repositories that work together:
┌─────────────────────────────────────────────────────────────────────┐
│ Ukiryu Ecosystem │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│
│ │ ukiryu/ │ │ register/ │ │ schemas/ ││
│ │ ukiryu │────▶│ register │◀────│ schemas ││
│ │ (Runtime) │ │ (Profiles) │ │ (Schema) ││
│ └─────────────────┘ └─────────────────┘ └─────────────────┘│
│ │ │ │ │
│ │ Loads YAML │ Contains YAML │ Defines │
│ │ Executes commands │ Profiles │ Schema │
│ │ Validates input │ Documents CLI │ Validates │
│ └──────────────────────┴───────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
ukiryu/ Ruby gem - The runtime engine
- lib/ukiryu/ Core framework code
- bin/ukiryu CLI executable
- spec/ Tests
register/ GitHub repo - YAML profiles
- tools/ Tool profiles (imagemagick/, ffmpeg/, etc.)
- interfaces/ Interface definitions
- .github/workflows/ Validation CI/CD
schemas/ GitHub repo - JSON Schema
- v1/
- tool.schema.yaml Tool profile schema
- interface.schema.yaml Interface definition schema| Term | Definition |
|---|---|
Tool |
A CLI executable with a specific command interface (e.g., |
Interface |
An abstract contract that multiple tools can implement (e.g., |
Profile |
YAML file defining tool behavior for specific platforms and shells |
Command |
An operation a tool performs (e.g., |
Argument |
Positional parameter (e.g., |
Option |
Named parameter with value (e.g., |
Flag |
Boolean switch (e.g., |
Profile |
Platform/shell combination (e.g., "unix bash", "windows powershell") |
Register |
Collection of YAML tool profiles |
Schema |
Formal definition of YAML structure and validation rules |
Ukiryu supports loading tool definitions from external files, enabling tool authors to distribute definitions independently from the central register. This allows tools to be distributed with their own definitions, versioned alongside the tool itself.
Load a tool definition from a local file:
require 'ukiryu'
# Load from file path
tool = Ukiryu::Tool.load('path/to/mytool.yaml')
# Execute using the loaded definition
result = tool.execute(:command, { param: 'value' })The Tool.load method (also available as Tool.from_file) loads a YAML definition from the filesystem:
-
Validates the YAML structure
-
Checks required fields (name, version, profiles)
-
Caches the definition based on file path and modification time
-
Tracks file metadata for cache invalidation
Load a tool definition from a YAML string:
require 'ukiryu'
yaml_string = <<~YAML
name: mytool
version: "1.0"
profiles:
- name: default
platforms: [linux, macos]
shells: [bash, zsh]
commands:
- name: process
description: Process files
YAML
# Load from string
tool = Ukiryu::Tool.load_from_string(yaml_string)
# Execute using the loaded definition
result = tool.execute(:process, { inputs: ['file.txt'] })The Tool.load_from_string method (also available as Tool.from_definition) loads a definition from a YAML string.
Use the --definition (or -d) flag to load a definition from a file:
# Execute using a local definition file
ukiryu exec --definition ./mytool.yaml mytool command param=value
# Short form
ukiryu exec -d ./mytool.yaml mytool command param=valueTools loaded from definitions track their source:
tool = Ukiryu::Tool.load('path/to/tool.yaml')
# Get information about the definition source
tool.definition_source # => #<Ukiryu::Definition::Sources::FileSource>
tool.definition_path # => "/absolute/path/to/tool.yaml"
tool.definition_mtime # => 2024-01-15 10:30:00 +0800For string-based definitions:
tool = Ukiryu::Tool.load_from_string(yaml_string)
# String sources have content hash instead of path/mtime
tool.definition_source # => #<Ukiryu::Definition::Sources::StringSource>
tool.definition_path # => nil (not applicable for string sources)
tool.definition_source.content_hash # => "a1b2c3d4..."Definition loading supports three validation modes:
# Strict mode (default) - raises errors for invalid definitions
tool = Ukiryu::Tool.load('tool.yaml', validation: :strict)
# Lenient mode - warns but continues
tool = Ukiryu::Tool.load('tool.yaml', validation: :lenient)
# None mode - skips validation entirely
tool = Ukiryu::Tool.load('tool.yaml', validation: :none)-
:strict (default) - Raises
Ukiryu::Errors::DefinitionValidationErrorfor missing required fields -
:lenient - Prints warnings but continues loading
-
:none - Skips all validation (useful for testing)
The definition loading system provides specific error types in the Ukiryu::Errors namespace:
begin
tool = Ukiryu::Tool.load('tool.yaml')
rescue Ukiryu::Errors::DefinitionNotFoundError => e
# File does not exist
puts "Definition file not found: #{e.message}"
rescue Ukiryu::Errors::DefinitionLoadError => e
# Invalid YAML or file read error
puts "Failed to load definition: #{e.message}"
rescue Ukiryu::Errors::DefinitionValidationError => e
# Valid YAML but fails validation rules
puts "Definition validation failed: #{e.message}"
endDefinitions are cached automatically based on:
-
FileSource: File path SHA256 hash + modification time
-
StringSource: Content SHA256 hash
Clear the cache:
# Clear all cached definitions
Ukiryu::Tool.clear_definition_cache
# Clear specific source (if you have the source object)
source = Ukiryu::Definition::Sources::FileSource.new('tool.yaml')
Ukiryu::Definition::Loader.clear_cache(source)The definition loading system uses an abstract source pattern:
-
Source - Abstract base class for all definition sources
-
FileSource - Loads from filesystem paths with modification time tracking
-
StringSource - Loads from YAML strings with content hashing
-
BundledSource - (Planned) Loads from tool-bundled locations
-
RegisterSource - (Planned) Loads from central register
Each source provides:
* load - Returns the YAML content
* cache_key - Unique identifier for caching
* source_type - Symbol identifying the source type
Tool authors can bundle definitions with their tools:
# Tool installation directory structure
/opt/mytool/
├── bin/mytool
├── share/ukiryu/
│ └── 1.0.yaml # Tool definition
└── lib/Users can then load the bundled definition:
# Load from bundled path
tool = Ukiryu::Tool.load('/opt/mytool/share/ukiryu/1.0.yaml')Ukiryu can automatically discover tool definitions in standard filesystem locations following the XDG Base Directory Specification. This allows tools and users to install definitions in well-known locations without manual configuration.
Definitions are searched in the following priority order (highest to lowest):
-
User definitions -
~/.local/share/ukiryu/definitions/ -
Tool-bundled paths - Auto-discovered from PATH
-
Local system -
/usr/local/share/ukiryu/definitions/ -
System -
/usr/share/ukiryu/definitions/
When multiple definitions exist for the same tool, the highest priority definition is used.
Ukiryu respects XDG environment variables:
# Override user data directory
export XDG_DATA_HOME=/custom/data
# Override system data directories
export XDG_DATA_DIRS=/path1:/path2Default values:
* XDG_DATA_HOME: ~/.local/share (if not set)
* XDG_DATA_DIRS: /usr/local/share:/usr/share (if not set)
When tools are installed, Ukiryu automatically detects bundled definitions:
# Standard installation structure
/usr/local/
├── bin/
│ └── mytool # Tool executable
├── share/
│ └── ukiryu/
│ └── 1.0.yaml # Tool definition (auto-discovered)
└── lib/Ukiryu also detects definitions in /opt installations:
/opt/mytool/
├── bin/mytool
├── ukiryu/
│ └── 1.0.yaml # Auto-discovered from /opt
└── lib/Definitions follow a structured directory layout:
~/.local/share/ukiryu/definitions/
├── inkscape/
│ ├── 1.0.yaml
│ └── 0.92.yaml
├── imagemagick/
│ ├── 7.1.yaml
│ └── 6.0.yaml
└── mytool/
└── 1.5.yamlEach tool has its own subdirectory, with version-specific YAML files.
require 'ukiryu'
# Discover all available definitions
definitions = Ukiryu::Definition::Discovery.discover
# Get definitions for a specific tool
inkscape_defs = Ukiryu::Definition::Discovery.definitions_for('inkscape')
# Find the best definition (highest priority, latest version)
metadata = Ukiryu::Definition::Discovery.find('inkscape')
# Find a specific version
metadata = Ukiryu::Definition::Discovery.find('inkscape', '1.0')
# List all available tool names
tools = Ukiryu::Definition::Discovery.available_tools
# => ["inkscape", "imagemagick", "mytool"]Each discovered definition has associated metadata:
metadata = Ukiryu::Definition::Discovery.find('inkscape')
# Basic information
metadata.name # => "inkscape"
metadata.version # => "1.0"
metadata.path # => "/home/user/.local/share/ukiryu/definitions/inkscape/1.0.yaml"
metadata.source_type # => :user (or :bundled, :local_system, :system)
# File information
metadata.exists? # => true
metadata.mtime # => 2024-01-15 10:30:00 +0800
# Load the full definition
definition = metadata.load_definitionSource types determine definition priority:
# Priority values (lower = higher priority)
:user => 1 # User definitions (~/.local/share)
:bundled => 2 # Tool-bundled paths
:local_system => 3 # /usr/local/share
:system => 4 # /usr/share
:register => 5 # Central register (planned)When sorting definitions, higher priority comes first, then higher versions.
Definitions are sorted by version within each priority level:
# For the same tool in the same location:
# 2.0.yaml comes before 1.0.yaml
# 1.10.yaml comes before 1.9.yaml (semantic versioning)Ukiryu provides CLI commands for managing definitions:
# List all discovered definitions
ukiryu definitions list
# Show where definitions are searched
ukiryu definitions paths
# Get information about a specific definition
ukiryu definitions info inkscape
# Install a definition to user directory
ukiryu definitions add ./mytool-1.0.yaml
# Remove a user definition
ukiryu definitions remove mytool$ ukiryu definitions list
Available Definitions:
inkscape/1.0 (user) - ~/.local/share/ukiryu/definitions/inkscape/1.0.yaml
inkscape/0.92 (system) - /usr/share/ukiryu/definitions/inkscape/0.92.yaml
imagemagick/7.1 (bundled) - /usr/local/share/ukiryu/definitions/imagemagick/7.1.yaml$ ukiryu definitions paths
Definition Search Paths:
1. ~/.local/share/ukiryu/definitions (user)
2. /usr/local/share/ukiryu/definitions (local_system)
3. /usr/share/ukiryu/definitions (system)$ ukiryu definitions info inkscape
Tool: inkscape
Version: 1.0
Source: user
Path: ~/.local/share/ukiryu/definitions/inkscape/1.0.yaml
File exists: Yes
Modified: 2024-01-15 10:30:00 +0800$ ukiryu definitions add ./mytool-1.0.yaml
Installing definition to user directory:
Source: ./mytool-1.0.yaml
Target: ~/.local/share/ukiryu/definitions/mytool/1.0.yaml
✓ Definition installed successfullyukiryu info inkscapeShows: * Tool metadata (name, version, homepage) * Version detection method * Search paths for your platform * Available profiles with execution mode
ukiryu describe inkscape exportShows: * Command description and usage * All arguments with types and constraints * All options with types and formats * All flags with defaults * Exit codes and their meanings
ukiryu systemShows: * Available shells on your system * All supported shells * Platform detection
ukiryu definitions listShows all discovered definitions with source type and path.
ukiryu definitions info inkscapeShows detailed information about a specific definition.
ukiryu definitions add ./mytool-1.0.yamlInstalls a definition file to the user definitions directory.
ukiryu validate --definition ./mytool.yamlValidates a definition file without installing it.
Ukiryu provides intelligent error suggestions to help you fix definition issues quickly:
$ ukiryu validate --definition mytool.yaml
✗ Validation failed
- Unknown field: 'platfoms'
Did you mean 'platforms'?
- Missing required field: 'name'
Add a tool name: `name: mytool`
- Missing required field: 'profiles'
Add at least one profile with platform and shell informationUkiryu includes templates for common tool types to help you get started quickly:
# Basic template (minimal)
ukiryu init mytool --type basic --output mytool.yaml
# CLI tool template (with help/version commands)
ukiryu init mycli --type cli_tool --output mycli.yaml
# Converter template (for file conversion tools)
ukiryu init imgconv --type converter --output imgconv.yamlMinimal template with required fields only:
name: mytool
version: "1.0"
$schema: https://ukiryu.com/schema/1.2
profiles:
- name: default
platforms: [linux, macos]
shells: [bash, zsh]Standard CLI tool with common commands:
name: mytool
version: "1.0"
$schema: https://ukiryu.com/schema/1.2
display_name: Mytool
description: A CLI tool for processing data
homepage: https://example.com/mytool
version_detection:
command: "--version"
pattern: "(\\d+\\.\\d+)"
modern_threshold: "1.0"
search_paths:
macos:
- "/usr/local/bin/mytool"
- "/opt/homebrew/bin/mytool"
linux:
- "/usr/bin/mytool"
- "/usr/local/bin/mytool"
profiles:
- name: default
platforms: [linux, macos]
shells: [bash, zsh]
option_style: double_dash_equals
commands:
- name: help
description: Show help information
- name: version
description: Show version informationFile conversion tool with extensive options:
name: imagick
version: "1.0"
$schema: https://ukiryu.com/schema/1.2
display_name: Imagick
description: File conversion tool for various formats
profiles:
- name: default
platforms: [linux, macos]
shells: [bash, zsh]
# Reusable environment variable sets
env_var_sets:
headless:
- name: DISPLAY
value: ''
platforms: [macos, linux]
description: Disable display for headless operation
commands:
- name: convert
description: Convert files between formats
use_env_vars: [headless]
arguments:
- name: inputs
type: file
variadic: true
min: 1
description: Input file(s)
options:
- name: output
type: file
cli: --output
required: true
description: Output file path
- name: format
type: symbol
cli: --format
values: [png, jpg, pdf, svg]
description: Output formatukiryu init mytool \
--type cli_tool \
--version "2.0" \
--platforms linux windows \
--purpose "image processing" \
--output mytool.yamlTemplate variables:
* tool_name - The tool name (required)
* version - Tool version (default: "1.0")
* platforms - Target platforms (default: [linux, macos])
* shells - Target shells (default: [bash, zsh])
* purpose - Tool description (for cli_tool)
* file_types - File types description (for converter)
When highline or tty-prompt gems are available, Ukiryu provides interactive prompts:
$ ukiryu init
Select template type:
1) Basic: Minimal template with required fields only
2) CLI Tool: Standard CLI tool with common commands
3) Converter: File conversion tool with extensive options
Selection [1-3] [Basic]:
Tool name: [mytool]:
Version: [1.0]: 2.0
Save to file: [mytool.yaml]: /path/to/mytool.yaml
✓ Definition saved to: /path/to/mytool.yaml
Next steps:
1. Review and edit the definition
2. Validate: ukiryu validate --definition /path/to/mytool.yaml
3. Install: ukiryu definitions add /path/to/mytool.yamlTo enable interactive prompts, add one of these gems to your Gemfile:
# For rich prompts (recommended)
gem 'tty-prompt'
# Or use highline
gem 'highline'Ukiryu supports semantic versioning constraints when selecting tool definitions:
# Use specific version
tool = Ukiryu::Tool.get(:inkscape, version: '1.0')
# Use version constraint (pessimistic)
# Any 1.x version, but less than 2.0
constraint = '~> 1.0'
# Find available versions
versions = Ukiryu::Definition::Discovery
.definitions_for('inkscape')
.map(&:version)
# Resolve constraint
selected = Ukiryu::Definition::VersionResolver.resolve(
constraint,
versions
)Supported constraint operators:
* 1.0 - exact version
* >= 1.0 - minimum version (inclusive)
* > 1.0 - minimum version (exclusive)
* ⇐ 2.0 - maximum version (inclusive)
* < 2.0 - maximum version (exclusive)
* ~> 1.2 - pessimistic version constraint (>= 1.2.0, < 1.3.0)
* >= 1.0, < 2.0 - range constraint
Create shortcuts for frequently used tools:
# Simple alias
ukiryu alias add magick imagemagick
# Versioned alias
ukiryu alias add gs7 ghostscript
# Command alias
ukiryu alias add convert imagemagick:convertList all aliases:
$ ukiryu alias list
Tool Aliases:
magick → imagemagick
gs7 → ghostscript/7.0
convert → imagemagick:convert
Total: 3 aliasesResolve an alias:
$ ukiryu alias resolve magick
Alias: magick
Resolves to:
Tool: imagemagick
Definition lookup:
✓ Found: imagemagick/7.1 (bundled)
Path: /usr/local/share/ukiryu/definitions/imagemagick/7.1.yamlRemove an alias:
ukiryu alias remove magickUkiryu automatically caches tool definitions for performance:
# View cache information
ukiryu cache info
Status: Active
Entries: 5
TTL: 300 seconds
Refresh Strategy: lazy
# Show detailed statistics
ukiryu cache stats
# Clear the cache
ukiryu cache clearCache behavior: * Lazy mode (default) - Checks staleness on each access, refreshes if needed * Eager mode - Background thread periodically refreshes all cache * Never mode - Disables auto-refresh, manual clear only
See which definition would be used for a tool:
$ ukiryu resolve inkscape
Resolution for: inkscape
Available Definitions (2):
★ 1.0 (user) Priority: 1
Path: ~/.local/share/ukiryu/definitions/inkscape/1.0.yaml
Mtime: 2024-01-15 10:30:00 +0800
□ 0.92 (system) Priority: 4
Path: /usr/share/ukiryu/definitions/inkscape/0.92.yaml
Mtime: 2023-12-01 15:20:00 +0800
Selected Definition (highest priority):
✓ inkscape/1.0
Source: user
Path: ~/.local/share/ukiryu/definitions/inkscape/1.0.yaml
Priority: 1Resolve with version constraint:
$ ukiryu resolve imagemagick ">= 7.0"
Resolution for: imagemagick
Available Definitions (3):
★ 7.1 (bundled) Priority: 2
★ 7.0 (system) Priority: 4
□ 6.0 (system) Priority: 4
Selected Definition (constraint: >= 7.0):
✓ imagemagick/7.1Priority icons:
* ★ - User definitions
* ◆ - Bundled definitions
* ■ - Local system definitions
* □ - System definitions
* △ - Register definitions
Definition composition allows building complex definitions from simpler ones:
name: mytool
version: "2.0"
# Include common profiles from another definition
includes:
- tool: common_tools
profile: default
commands: [help, version]
profiles:
- name: default
platforms: [linux, macos]
shells: [bash, zsh]
# Commands from includes are merged here
# Add tool-specific commands
commands:
- name: mycommand
description: My custom commandWhen multiple definitions exist for the same tool, the highest priority definition is used:
-
User definitions (
~/.local/share/ukiryu/definitions/) -
Tool-bundled definitions
-
Local system definitions (
/usr/local/share/ukiryu/definitions/) -
System definitions (
/usr/share/ukiryu/definitions/) -
Register definitions
To explicitly use a specific definition source:
# Use only system definitions
ukiryu exec --system inkscape export
# Use specific definition file
ukiryu exec --definition /path/to/inkscape.yaml inkscape exportNo more parsing stdout/stderr:
result = tool.execute(:export, params)
# Rich result object
result.command_info.executable # Full path
result.command_info.full_command # Complete command
result.output.success? # Boolean
result.output.stdout # Stripped stdout
result.output.stderr # Stripped stderr
result.metadata.duration # Float seconds
result.metadata.formatted_duration # "500ms"Parameters are validated against the schema:
# Valid
tool.execute(:export, {
format: :png, # Must be in values list
dpi: 300, # Must be in range
width: 1024 # Must be integer
})
# Invalid - raises Ukiryu::TypeError
tool.execute(:export, {
format: :unsupported, # Type error!
dpi: -5 # Range error!
})Same code works everywhere:
# Works on macOS, Linux, Windows
tool.execute(:export, {
inputs: ['~/drawing.svg'], # Tilde expansion
output: ['~/drawing.png'],
format: :png
})
# Paths are automatically formatted:
# macOS: /Users/user/drawing.png
# Windows: C:\Users\user\drawing.pngSupport multiple tool versions with automatic selection:
register/
├── tools/
│ ├── inkscape/
│ │ ├── 1.0.yaml # Modern Inkscape
│ │ └── 0.92.yaml # Legacy Inkscape
│ └── imagemagick/
│ ├── 7.1.yaml # Current ImageMagick
│ └── 6.0.yaml # Old ImageMagick# Automatically selects correct profile
tool = Ukiryu::Tool.get(:inkscape)
# If Inkscape 1.3+ detected → uses 1.0.yaml
# If Inkscape 0.9.x detected → uses 0.92.yamlGUI applications run without windows on servers:
# Automatically runs in headless mode
tool = Ukiryu::Tool.get(:inkscape)
result = tool.execute(:export, {
inputs: ['drawing.svg'],
output: 'drawing.png'
})
# No GUI window appears! Perfect for servers and CI/CDCheck headless support:
ukiryu info inkscape | grep "Execution Mode"
# Shows: Execution Mode: headlessStructured exceptions in the Ukiryu::Errors namespace instead of magic exit codes:
begin
result = tool.execute(:export, params)
rescue Ukiryu::Errors::ToolNotFoundError => e
puts "Tool not found: #{e.tool_name}"
puts "Available tools: #{Ukiryu::Register.tools.join(', ')}"
rescue Ukiryu::Errors::ExecutionError => e
puts "Command failed!"
puts "Exit code: #{e.result.exit_status}"
puts "Stderr: #{e.result.stderr}"
puts "Stdout: #{e.result.stdout}"
rescue Ukiryu::Errors::TimeoutError => e
puts "Command timed out after #{e.timeout}s"
endUkiryu provides tools for validating, linting, and documenting tool definitions.
Validate definitions against JSON Schema and structural rules:
# Validate a single file
ukiryu validate file path/to/tool.yaml
# Validate all definitions in register
ukiryu validate all
# Validate with custom schema
ukiryu validate file path/to/tool.yaml --schema path/to/schema.json
# Test executable against actual tool (smoke test)
ukiryu validate file path/to/tool.yaml --executable
# Strict mode (warnings as errors)
ukiryu validate file path/to/tool.yaml --strict
# JSON output for CI/CD
ukiryu validate file path/to/tool.yaml --format jsonThe --executable flag performs a smoke test against the actual tool:
-
Checks if the tool executable can be found
-
Tests version detection (if defined)
-
Tests basic command execution
Programmatic usage:
result = Ukiryu::Definition::DefinitionValidator.validate_file('tool.yaml')
if result.valid?
puts "✓ Valid"
else
puts "✗ Invalid"
result.errors.each { |e| puts " - #{e}" }
end
# Check for warnings
if result.has_warnings?
result.warnings.each { |w| puts " Warning: #{w}" }
endCheck definitions for best practices and potential issues:
# Lint a definition file
ukiryu lint file path/to/tool.yaml
# Lint all definitions
ukiryu lint all
# List all linting rules
ukiryu lint rulesProgrammatic usage:
result = Ukiryu::Definition::DefinitionLinter.lint_file('tool.yaml')
# Access issues by severity
result.errors.each { |e| puts "ERROR: #{e.message}" }
result.warnings.each { |w| puts "WARNING: #{w.message}" }
# Get suggestions for fixes
result.issues.each do |issue|
puts "#{issue.message}"
puts " Suggestion: #{issue.suggestion}" if issue.has_suggestion?
endLinting rules include:
-
Naming conventions - Tool and command name format checks
-
Completeness - Missing descriptions, homepages, version detection
-
Security - Dangerous shell commands, unvalidated user input
-
Best practices - Redundant profiles, missing platform specifications
Generate human-readable documentation from definitions:
# Generate docs for a tool
ukiryu docs generate imagemagick
# Generate docs to file
ukiryu docs generate imagemagick --output imagemagick.md
# Generate docs for all tools
ukiryu docs generate-all --output-dir docs/
# Choose format (markdown or asciidoc)
ukiryu docs generate imagemagick --format asciidocProgrammatic usage:
docs = Ukiryu::Definition::DocumentationGenerator.generate(
definition,
format: :markdown
)
puts docs
# Outputs:
# # imagemagick
#
# Version: 7.1
#
# ## Commands
#
# ### `convert`
# Convert between image formats...Comprehensive documentation is available at:
Ukiryu provides a Docker-based test runner for cross-platform validation. The
docker-test-all.sh script runs the full test suite across multiple Linux
distributions to ensure compatibility.
# Test all platforms (alpine, ubuntu, debian)
./docker-test-all.sh
# Test specific platforms only
./docker-test-all.sh alpine ubuntu
# Force rebuild Docker images
./docker-test-all.sh --build
# Verbose output
./docker-test-all.sh --verbose debian
# Use custom register path
./docker-test-all.sh --register /path/to/registerThe script tests Ukiryu across:
-
Alpine Linux - Minimal musl libc distribution
-
Ubuntu - Debian-based with glibc
-
Debian - Standard Debian environment
Each platform is tested in an isolated Docker container with the register mounted read-only. Test results are summarized at the end with timing information.
Docker test logs are written to /tmp/docker-test-*.log for detailed
inspection of any failures.
-
Fork the repository
-
Create your feature branch
-
Write tests for your changes
-
Ensure all tests pass
-
Submit a pull request
See CONTRIBUTING for details.
-
ukiryu/register - Tool profile register
-
ukiryu/schemas - Profile validation schema
-
lutaml/versionian - Version parsing and comparison library