Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ukiryu/ukiryu

Repository files navigation

Ukiryu

RubyGems Version License Build

Ukiryu: Open Definitions For CLI Tools

The "OpenAPI" for Command Line Interfaces.

Purpose

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.

Origin

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.

Why Ukiryu?

  • 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

Features

  • 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

What Makes CLIs Unpredictable?

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?

The Ukiryu Solution

Problem Traditional CLI Ukiryu Solution

Argument fragility

tool -o output -f input file.txt

tool input=input.txt output=output.txt

Output parsing

grep "success" output.txt

result.success? (boolean)

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

tool --help

ukiryu describe tool command

Why "OpenAPI for CLIs"?

OpenAPI provides the following benefits for REST APIs by:

  1. Standardizing - Machine-readable schema definitions

  2. Documenting - Auto-generated interactive documentation

  3. Type-Safe - Request/response validation

  4. Versioning - Multiple API versions coexisting

Ukiryu provides the same benefits for CLI tools by:

  • Schema: YAML profiles replace grepping --help

  • Docs: ukiryu describe replaces man pages

  • Types: Named parameters replace positional args

  • Versions: inkscape/1.0.yaml and inkscape/0.92.yaml coexist

Use Cases

  • 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

What is Ukiryu?

Ukiryu is a framework that turns CLI tools into well-defined, versioned APIs through declarative YAML profiles.

Key Concepts

  • 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 imagemagick package provides the convert, identify, and mogrify tools * Both imagemagick-6 and imagemagick-7 implement the convert interface * BusyBox provides many tools (ls, cat, gzip) as one executable

Architecture: Packages, Tools, and Interfaces

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    │              │
│  └─────────────────────┘  └─────────────────────┘              │
│                                                                   │
└───────────────────────────────────────────────────────────────────┘

What is a Package?

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.

What is a Tool?

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: last

What is an Interface?

An 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 interface

How Invocation Patterns Differ

Different implementations of the same interface have different invocation patterns:

ImageMagick 6.x:

convert input.png -resize 50% output.png
# "convert" is a standalone executable

ImageMagick 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.png

Identity Detection

How 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:

  • Allowed: Version patterns, help text, command availability

  • NOT allowed: Checksums, package manager queries, external APIs

This ensures tools work regardless of how they were installed.

Example: BusyBox Multiplexing

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/busybox

How 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.gz

When version detection shows "BusyBox v1.35.0", Ukiryu knows this is the BusyBox implementation, not GNU gzip.

Architecture

┌───────────────────────────────────────────────────────────────────┐
│                                                                   │
│  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)                  │
└───────────────────────────────────────────────────────────────────┘

Tool Profile Schema

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'

Tool, Command, Action, and Routing

Ukiryu organizes CLI functionality into a hierarchical structure similar to REST API resources.

Tool

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

Command

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

Action

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 names

With 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
})

Version routing

Routing maps command names to their implementations, enabling:

  1. Tool aliases: Multiple tools implementing the same interface

  2. Version routing: Different versions for different behaviors (using Versionian)

  3. Multi-level hierarchies: Parent/child command relationships

About Versionian

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, between for 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"
  1. Specifies which Versionian scheme to use for version comparison.

  2. Exact version match - only version 1.35 matches this rule.

  3. Semantic version range - matches any version >= 1.35.

Version scheme types

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

How version routing works

When a tool version is detected (e.g., tar (GNU tar) 1.35), Ukiryu:

  1. Parses the detected version using the specified version_scheme

  2. Compares it against the versions array rules (in order)

  3. Selects the first matching rule’s file value

  4. 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_gnu

Default Command Resolution

When 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=1

Complete Example: Inkscape Export

Here’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}"
end

Generated command:

inkscape --batch-process --export-filename=/Users/user/drawing.png --export-type=png --export-width=1024 --dpi=300 /Users/user/drawing.svg

Configuration Methods

Ukiryu supports three complementary configuration methods with clear precedence:

Configuration precedence (highest to lowest):
  1. Environment variables (UKIRYU_*)

  2. CLI parameters

  3. Ruby API parameters

  4. Profile defaults ===

Method 1: Environment Variables (Highest Precedence)

# 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

Method 2: CLI Parameters (Medium Precedence)

# Per-command configuration
ukiryu exec inkscape export \
  inputs=drawing.svg \
  output=drawing.png \
  timeout=120

Method 3: Ruby API (Lowest Precedence)

# 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)

Precedence Example

# 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 ENV

Installation

Add this line to your application’s Gemfile:

gem 'ukiryu'

And then execute:

bundle install

Or install it yourself as:

gem install ukiryu

Quick Start

For CLI Tool Users

Make your CLI "intelligent" with a bundled Ukiryu definition:

mytool --definition > mytool-1.0.yaml
mytool --validate-definition mytool-1.0.yaml

Your tool now has: * Auto-generated ukiryu describe mytool documentation * Type-safe parameter validation * Platform-aware command formatting * Structured error responses

For Ruby Developers

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}"

For DevOps Engineers

# .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=png

Core concepts

Ukiryu 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.

The declarative API model

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.svg

Ukiryu’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: -quality

Architecture overview

Ukiryu’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                     │   │
│  └─────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

How Ukiryu processes a command

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           │
└─────────────────────────────────────────────────────────────────────┘

Crafting a YAML profile

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: unix

Profile structure explained

The 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 name

Version 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: --verbose

Using a custom profile

Load 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 \
  --verbose

Contributing to the official register

The official Ukiryu register is a curated collection of YAML profiles for popular CLI tools. Contributing helps the community by making tools more accessible.

Submitting a new tool profile

  1. Prepare your profile: Create tools/<toolname>/<version>.yaml following 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 ----

  2. Test with the executable: [source,bash] ---- ukiryu validate --definition tools/mytool/1.0.yaml --executable ----

  3. Submit a pull request: The register is at https://github.com/ukiryu/register Submit PRs to the v1 branch.

Profile requirements

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

Directory structure for new tools

tools/
└── mytool/
    ├── 1.0.yaml           # Version-specific profile
    ├── 1.1.yaml           # Another version
    └── index.yaml         # Optional: version routing

For 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 interface

Validation checklist

Before 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 all

The register ecosystem

The 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

Key terminology

Term Definition

Tool

A CLI executable with a specific command interface (e.g., convert, ffmpeg)

Interface

An abstract contract that multiple tools can implement (e.g., convert interface)

Profile

YAML file defining tool behavior for specific platforms and shells

Command

An operation a tool performs (e.g., convert export, ffmpeg transcode)

Argument

Positional parameter (e.g., input.png)

Option

Named parameter with value (e.g., --resize 50%)

Flag

Boolean switch (e.g., --verbose)

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

Distributed Definitions

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.

Loading Definitions from Files

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

Loading Definitions from Strings

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.

CLI Usage

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=value

Definition Source Tracking

Tools 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 +0800

For 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..."

Validation Modes

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::DefinitionValidationError for missing required fields

  • :lenient - Prints warnings but continues loading

  • :none - Skips all validation (useful for testing)

Error handling

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}"
end

Definition Caching

Definitions 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)

Definition Source Classes

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

Example: Bundling Definitions with Tools

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')

Automatic Definition Discovery

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.

Discovery Priority

Definitions are searched in the following priority order (highest to lowest):

  1. User definitions - ~/.local/share/ukiryu/definitions/

  2. Tool-bundled paths - Auto-discovered from PATH

  3. Local system - /usr/local/share/ukiryu/definitions/

  4. System - /usr/share/ukiryu/definitions/

When multiple definitions exist for the same tool, the highest priority definition is used.

XDG Compliance

Ukiryu respects XDG environment variables:

# Override user data directory
export XDG_DATA_HOME=/custom/data

# Override system data directories
export XDG_DATA_DIRS=/path1:/path2

Default values: * XDG_DATA_HOME: ~/.local/share (if not set) * XDG_DATA_DIRS: /usr/local/share:/usr/share (if not set)

Tool-Bundled Definition Detection

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/

Directory Structure

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.yaml

Each tool has its own subdirectory, with version-specific YAML files.

Finding Definitions Programmatically

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"]

Definition Metadata

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_definition

Source Type Priority

Source 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.

Version Sorting

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)

CLI Commands for Definitions

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
List Definitions
$ 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
Show Paths
$ 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)
Definition Info
$ 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
Add Definition
$ 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 successfully
Remove Definition
$ ukiryu definitions remove mytool

Removing user definition:
  Path: ~/.local/share/ukiryu/definitions/mytool/1.0.yaml

✓ Definition removed successfully

CLI Reference

List Available Tools

ukiryu list

Get Tool Information

ukiryu info inkscape

Shows: * Tool metadata (name, version, homepage) * Version detection method * Search paths for your platform * Available profiles with execution mode

Describe Commands

ukiryu describe inkscape export

Shows: * 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

Execute Commands

ukiryu exec inkscape export \
  inputs=drawing.svg \
  output=drawing.png \
  format=png

System Information

ukiryu system

Shows: * Available shells on your system * All supported shells * Platform detection

Manage Definitions

List All Definitions

ukiryu definitions list

Shows all discovered definitions with source type and path.

Show Definition Paths

ukiryu definitions paths

Shows all search paths in priority order.

Get Definition Information

ukiryu definitions info inkscape

Shows detailed information about a specific definition.

Add Definition

ukiryu definitions add ./mytool-1.0.yaml

Installs a definition file to the user definitions directory.

Remove Definition

ukiryu definitions remove mytool

Removes a user definition.

Validate Definition File

ukiryu validate --definition ./mytool.yaml

Validates a definition file without installing it.

Initialize New Definition

ukiryu init mytool

Creates a new tool definition from a template.

ukiryu init --list-types

Lists available template types (basic, cli_tool, converter).

ukiryu init mytool --type cli_tool --output mytool.yaml

Creates a CLI tool definition and saves to file.

Developer Experience

Error Suggestions

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 information

Template Generation

Ukiryu 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.yaml

Template Types

Basic Template

Minimal 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]
CLI Tool Template

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 information
Converter Template

File 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 format

Customizing Templates

ukiryu init mytool \
  --type cli_tool \
  --version "2.0" \
  --platforms linux windows \
  --purpose "image processing" \
  --output mytool.yaml

Template 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)

Interactive Mode (Opt-In)

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.yaml

To enable interactive prompts, add one of these gems to your Gemfile:

# For rich prompts (recommended)
gem 'tty-prompt'

# Or use highline
gem 'highline'

Advanced Features

Version Constraints

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

Tool Aliases

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:convert

List all aliases:

$ ukiryu alias list

Tool Aliases:
  magick                 → imagemagick
  gs7                    → ghostscript/7.0
  convert                → imagemagick:convert

Total: 3 aliases

Resolve 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.yaml

Remove an alias:

ukiryu alias remove magick

Definition Caching

Ukiryu 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 clear

Cache 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

Definition Resolution

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: 1

Resolve 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.1

Priority icons: * - User definitions * - Bundled definitions * - Local system definitions * - System definitions * - Register definitions

Definition Composition

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 command

Shadowing and Override Behavior

When multiple definitions exist for the same tool, the highest priority definition is used:

  1. User definitions (~/.local/share/ukiryu/definitions/)

  2. Tool-bundled definitions

  3. Local system definitions (/usr/local/share/ukiryu/definitions/)

  4. System definitions (/usr/share/ukiryu/definitions/)

  5. 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 export

Key Features

Structured Responses

No 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"

Type Safety

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!
})

Platform Adaptation

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.png

Version Management

Support 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.yaml

Headless Mode

GUI 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/CD

Check headless support:

ukiryu info inkscape | grep "Execution Mode"
# Shows: Execution Mode: headless

Error handling

Structured 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"
end

Exit Code Semantics

Exit codes are documented in the profile:

exit_codes:
  standard:
    "0": "Success"
    "1": "File not found"
    "3": "Initialization error"
  custom:
    "2": "Export failed (see stderr for details)"

Ecosystem Tools

Ukiryu provides tools for validating, linting, and documenting tool definitions.

Definition Validation

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 json

The --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}" }
end

Definition Linting

Check 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 rules

Programmatic 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?
end

Linting 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

Documentation Generation

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 asciidoc

Programmatic usage:

docs = Ukiryu::Definition::DocumentationGenerator.generate(
  definition,
  format: :markdown
)

puts docs
# Outputs:
# # imagemagick
#
# Version: 7.1
#
# ## Commands
#
# ### `convert`
# Convert between image formats...

Documentation

Comprehensive documentation is available at:

Development

Running Tests

bundle exec rspec

Docker Multi-platform Testing

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/register

The 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.

Schema Validation

bundle exec rspec spec/ukiryu/schema_validator_spec.rb

GitHub Actions

Tests run on: * Ubuntu latest * macOS latest

Contributing

  1. Fork the repository

  2. Create your feature branch

  3. Write tests for your changes

  4. Ensure all tests pass

  5. Submit a pull request

See CONTRIBUTING for details.

License

The content is available as open source under the terms of the Ribose BSD 2-Clause License.

Copyright Ribose.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors 2

  •  
  •