Module: Toy::Core::CLI

Defined in:
lib/toy/core/cli.rb,
lib/toy/core/cli/new.rb,
lib/toy/core/cli/eval.rb,
lib/toy/core/cli/list.rb,
lib/toy/core/cli/fetch.rb,
lib/toy/core/cli/infer.rb,
lib/toy/core/cli/serve.rb,
lib/toy/core/cli/train.rb,
lib/toy/core/cli/install.rb,
lib/toy/core/cli/describe.rb,
lib/toy/core/cli/manifest.rb,
lib/toy/core/cli/exit_codes.rb

Defined Under Namespace

Classes: Describe, Eval, Fetch, Infer, Install, List, Manifest, New, Serve, Train

Constant Summary collapse

COMMANDS =

Single source of truth. Each entry: the command class + a machine/human description of its surface. The manifest is generated from this; –help reads summaries from it.

{
  "new" => {
    class: New,
    summary: "Scaffold a conventional toy project tree",
    args:  [{ name: "path", required: true, desc: "target dir" }],
    flags: [{ name: "--lib", desc: "scaffold a library-composition project (Gemfile + experiment.rb) instead of an app" },
            { name: "--force", desc: "overwrite a non-empty dir" },
            { name: "--json", desc: "machine output" }]
  },
  "list" => {
    class: List,
    summary: "Find GGUF models in caches + project data/",
    args:  [],
    flags: [{ name: "--json", desc: "machine output" }]
  },
  "describe" => {
    class: Describe,
    summary: "Read GGUF metadata, render the arch-derived Card",
    args:  [{ name: "model", required: true, desc: "path to a .gguf file" }],
    flags: [{ name: "--json", desc: "machine output" }]
  },
  "fetch" => {
    class: Fetch,
    summary: "Download a GGUF from HuggingFace into the cache + data/ symlink",
    args:  [{ name: "hf-repo", required: true, desc: "HF repo id" },
            { name: "file.gguf", required: false, desc: "GGUF filename in the repo" }],
    flags: [{ name: "--json", desc: "machine output" }]
  },
  "install" => {
    class: Install,
    summary: "Build/verify the CPU backend for this project",
    args:  [],
    flags: [{ name: "--json", desc: "machine output" }]
  },
  "infer" => {
    class: Infer,
    summary: "Generate text from a GGUF model (greedy decode)",
    args:  [{ name: "model", required: true, desc: "path to a .gguf file" }],
    flags: [{ name: "--prompt", desc: "prompt text (default \"Once upon a time\")" },
            { name: "--prompt-ids", desc: "space-separated token IDs (tokenizer-less models; overrides --prompt)" },
            { name: "--n", desc: "tokens to generate (default 16)" },
            { name: "--device", desc: "cpu (default) | cuda | metal (macOS)" },
            { name: "--json", desc: "machine output" }]
  },
  "train" => {
    class: Train,
    summary: "Train a model from scratch (records runs/<id>/ + loss curve)",
    args:  [{ name: "recipe", required: true, desc: "'from-scratch'" }],
    flags: [{ name: "--steps", desc: "training steps (default 5)" },
            { name: "--seed", desc: "random-init seed (default 0)" },
            { name: "--arch", desc: "llama (default) | gpt2 (from-scratch, CPU)" },
            { name: "--device", desc: "cpu (default) | cuda | metal (macOS)" },
            { name: "--out", desc: "run dir override (default runs/<id>)" },
            { name: "--json", desc: "machine output" }]
  },
  "eval" => {
    class: Eval,
    summary: "Score a GGUF model (per-token logprobs; `eval lmc` for two-checkpoint LMC)",
    args:  [{ name: "model", required: true, desc: "path to a .gguf file" }],
    flags: [{ name: "--top-k", desc: "top-K logprobs to report (default 5)" },
            { name: "--device", desc: "cpu (default) | cuda | metal (macOS)" },
            { name: "--json", desc: "machine output" }]
  },
  "serve" => {
    class: Serve,
    summary: "Serve a GGUF model over an OpenAI-compatible HTTP API (CPU)",
    args:  [{ name: "model", required: true, desc: "path to a .gguf file" }],
    flags: [{ name: "--port", desc: "TCP port to bind (default 4567)" },
            { name: "--name", desc: "model label in /v1/models (default GGUF basename)" }]
  }
}.freeze
GLOBAL_FLAGS =
[
  { name: "--manifest", desc: "emit the machine-readable command manifest (JSON)" },
  { name: "--help",     desc: "show usage" },
  { name: "--version",  desc: "show the toy version" }
].freeze
EXIT_OK =
0
EXIT_FAILURE =
1
EXIT_BAD_INPUT =
2

Class Method Summary collapse

Class Method Details



150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/toy/core/cli.rb', line 150

def print_usage(io)
  io.puts "usage: toy <command> [args] [flags]"
  io.puts ""
  io.puts "Commands:"
  COMMANDS.each do |cmd_name, entry|
    io.puts format("  %-10s %s", cmd_name, entry[:summary])
  end
  io.puts ""
  io.puts "Global flags:"
  GLOBAL_FLAGS.each do |f|
    io.puts format("  %-12s %s", f[:name], f[:desc])
  end
end

.run(argv) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/toy/core/cli.rb', line 117

def run(argv)
  argv = argv.dup

  # (1) Peel global flags BEFORE subcommand lookup so they work with
  #     no subcommand (e.g. `toy --manifest`, `toy --version`).
  return Manifest.new([]).run if argv.include?("--manifest")
  if argv.include?("--version")
    puts "toy #{Toy::VERSION}"
    return EXIT_OK
  end
  if argv.empty? || argv.first == "--help" || argv.first == "-h"
    print_usage($stdout)
    return argv.empty? ? EXIT_BAD_INPUT : EXIT_OK
  end

  # (2) Shift the subcommand token.
  name = argv.shift

  # (3) Unknown subcommand → usage to stderr + bad-input.
  entry = COMMANDS[name]
  unless entry
    $stderr.puts "toy: unknown command #{name.inspect}"
    print_usage($stderr)
    return EXIT_BAD_INPUT
  end

  # (4) Instantiate the command with remaining argv; #run returns
  #     an Integer exit code. Each command parses its OWN flags.
  entry[:class].new(argv).run
rescue Interrupt
  EXIT_FAILURE
end