Tea — a strongly typed scripting language
Tea
DocsPlaygroundExamplesReferenceCommunity
GitHub

Tea Language

A strongly typed scripting language for native tools.

© 2026 Tea Language.

Agent documentationContributingCommunityRepository

Get Started

InstallationGetting StartedCLI

Tutorial

Build a CLI with Tea1. Display your first task2. Display several tasks3. Track task status4. Accept commands5. Read saved tasks6. Add tasks7. Complete tasks8. Organize and test9. Build your executable

Language

SyntaxTypesFunctionsStructsGenericsPattern MatchingError HandlingModulesTesting

Project

Code StyleContributing

Read as Markdown

Tutorial

Accept commands

Read arguments, filter tasks, and explain how to use the tool.

Chapter 4 of 9 · Build a CLI with Tea

Choose a chapter
  1. Build a CLI with Tea
  2. 1. Display your first task
  3. 2. Display several tasks
  4. 3. Track task status
  5. 4. Accept commands
  6. 5. Read saved tasks
  7. 6. Add tasks
  8. 7. Complete tasks
  9. 8. Organize and test
  10. 9. Build your executable

Start with chapter 3's task structs and formatting function. The next version will show open tasks by default, include completed tasks with list --all, and explain itself with --help.

Read the arguments

Add this import at the top of tasks.tea:

use args from "std.args"

use imports a module under a local name. std.args is part of Tea's standard library; you don't need to install a package. Call its functions through the alias, such as args.all().

Move the program's executable statements into def run() -> Void ... end and call run() at the bottom of the file. Void means the function doesn't return a useful value. Keep the struct and format_task above it.

At the beginning of run, read the arguments and supply a default command:

var argv = args.all()
if @len(argv) == 0
  argv = ["list"]
end

Running tea tasks.tea list --all gives this program ["list", "--all"]. The program name isn't included. Checking the length before using an index avoids reading a missing element.

Separate selecting from displaying

Add the show_tasks function from the complete checkpoint below after format_task. Move the sample list into the list branch of run.

The display function takes a List[Task] and a Bool. Its condition is show_all || !task.done: show a task if all tasks were requested, or if this task is not done. || means “or” and ! negates a boolean.

Increment the task number on every iteration, including tasks you hide. That makes a displayed number keep referring to the same task when the view changes. The separate shown counter lets us distinguish an empty view from an empty collection.

Validate the command

The complete run function handles --help and list. It rejects extra or unrecognized arguments, rather than quietly ignoring them. && means “and”; conditions joined by && and || short-circuit, so a length check can guard access to argv[1].

The help function writes usage to standard output. The usage function writes an error to standard error with @eprintln, then calls @exit(2). We'll reserve status 2 for usage errors and 1 for data or file failures. Finishing normally means success, status 0.

Run both views

tea tasks.tea list
1. [ ] Learn Tea
2. [ ] Build a CLI
tea tasks.tea list --all
1. [ ] Learn Tea
2. [ ] Build a CLI
3. [x] Take a break

Running tea tasks.tea gives the first view too. Try tea tasks.tea -- --help to see the supported command shapes. The first -- separates Tea's own options from your script's arguments; it is not included in args.all(). Use it when the first argument for your script starts with a hyphen. tea tasks.tea list --unknown reports an error rather than running a different command.

Script exit status

The current Tea launcher reports a failed script as status 1 and adds a “program exited with status” message, even when the script calls @exit(2). The native executable you'll build in chapter 9 preserves the program's status codes.

Complete checkpoint

Complete tasks.tea for this chapter

tasks.tea

use args from "std.args"

struct Task {
  title: String
  done: Bool
}

def format_task(task: Task, number: Int) -> String
  const marker = if(task.done) "[x]" else "[ ]"
  `${number}. ${marker} ${task.title}`
end

def show_tasks(tasks: List[Task], show_all: Bool) -> Void
  if @len(tasks) == 0
    @println("No tasks yet.")
    return
  end

  var number = 1
  var shown = 0

  for task in tasks
    if show_all ||!task.done
      @println(format_task(task, number))
      shown = shown + 1
    end

    number = number + 1
  end

  if shown == 0
    @println("No open tasks.")
  end
end

def help() -> Void
  @println("Usage: tasks [list [--all] | --help]")
  @println("Tasks are numbered from 1. Listing shows open tasks; --all includes completed tasks.")
end

def usage() -> Void
  @eprintln("tasks: invalid arguments; run tasks --help for usage")
  @exit(2)
end

def run() -> Void
  var argv = args.all()

  if @len(argv) == 0
    argv = ["list"]
  end

  if argv[0] == "--help" && @len(argv) == 1
    help()
    return
  end

  if argv[0] == "list"
    if @len(argv) > 2 ||(@len(argv) == 2 && argv[1] != "--all")
      usage()
      return
    end

    const tasks = [
      Task(title: "Learn Tea", done: false),
      Task(title: "Build a CLI", done: false),
      Task(title: "Take a break", done: true)
    ]
    show_tasks(tasks, @len(argv) == 2)
    return
  end

  usage()
end

run()

Try it yourself

Add a count command that prints the number of sample tasks. Keep it separate from the display function.

Hint

Check the first argument and the total argument count before running the command. Reuse @len(tasks) from chapter 2. For this exercise you can move the sample list above the command branches; the main walkthrough keeps it in the list branch until the next chapter replaces it with file loading.

You now have a small command-line interface. The sample data still resets each time the program runs; next we'll load it from a file.

Read more: command-line argument helpers, imports.

Previous3. Track task statusNext5. Read saved tasks