---
title: "Read the command line"
audience: "anyone whose terminal tool takes flags"
description: "You'll pass flags to the task list after `--`, read them in a loader and hand them to the page as props. The loader runs once, when the terminal opens."
snippet_caption: "app/routes/page.loader.ts"
snippet: "import type { Ctx } from \"@snapfire/fsr/host\";\n\nexport async function load({ params }: Ctx<\"/\">) {\n  return {\n    name: params.file ?? \"tasks.json\",\n    limit: Number(params.limit ?? \"10\"),\n  };\n}"
---

import { Install } from "@src/ui/Install";

You'll pass flags to the task list after `--`, read them in a loader and hand them to the page as props. The loader runs once, when the terminal opens. It starts from the list you built in [610](https://snapfirers.com/fsr/learn/610-lay-out-a-task-list).

<Install captured="0.x" />

## Everything after `--` is the app's

`fsr dev app --terminal` reads its own flags up to `--`. The rest of the line goes to your app:

```shell-session
$ fsr dev app --terminal -- --file work.json --limit 2
```

The terminal turns that line into `params`, a map of strings:

| You type | `params` gets |
| --- | --- |
| `--file work.json` | `file` is `"work.json"` |
| `--limit=2` | `limit` is `"2"` |
| `--all` with no value after it | `all` is `"true"` |
| `notes.txt` with no flag before it | `"0"` is `"notes.txt"`, the next one `"1"` |

Every value is a string. Convert a number yourself.

## Write the loader

Create `app/routes/page.loader.ts` beside the page:

```ts title="app/routes/page.loader.ts"
import type { Ctx } from "@snapfire/fsr/host";

export async function load({ params }: Ctx<"/">) {
  return {
    name: params.file ?? "tasks.json",
    limit: Number(params.limit ?? "10"),
  };
}
```

This is the same loader a web page has. On the web it runs for each request. In the terminal it runs once, before the first frame. What it returns becomes the root page's props.

## Take the props

The page destructures what the loader returned. `RootProps` from `@generated/client` is the type of those props:

```tsx title="app/routes/page.tsx" {2,13,19,26}
import { useState } from "react";
import type { RootProps } from "@generated/client";
import { Box, Newline, Spacer, Text, useInput } from "ink";

type Task = { title: string; done: boolean };

const START: Task[] = [
  { title: "Write the README", done: true },
  { title: "Tag a release", done: false },
  { title: "Answer the open issues", done: false },
];

export default function Tasks({ name, limit }: RootProps) {
  const [tasks, setTasks] = useState(START);
  const [selected, setSelected] = useState(0);

  useInput((input, key) => {
    if (key.upArrow && selected > 0) setSelected(selected - 1);
    if (key.downArrow && selected < shown.length - 1) setSelected(selected + 1);
    if (input === " ") {
      setTasks(tasks.map((task, index) => (index === selected ? { ...task, done: !task.done } : task)));
    }
  });

  const open = tasks.filter((task) => !task.done).length;
  const shown = tasks.slice(0, limit);

  return (
    <Box flexDirection="column" borderStyle="round" borderColor="cyan" paddingX={1} width={44}>
      <Box>
        <Text bold>Tasks</Text>
        <Text dimColor> {name}</Text>
        <Spacer />
        <Text color={open === 0 ? "green" : "yellow"}>{open} open</Text>
      </Box>
      <Box flexDirection="column" marginY={1}>
        {shown.map((task, index) => (
          <Box key={task.title}>
            <Text color="cyan">{index === selected ? "> " : "  "}</Text>
            <Text bold={index === selected} dimColor={task.done} strikethrough={task.done}>
              {task.title}
            </Text>
          </Box>
        ))}
      </Box>
      {tasks.length > shown.length ? <Text dimColor>{tasks.length - shown.length} more not shown</Text> : null}
      <Text dimColor>
        up and down move, space marks one done
        <Newline />
        Ctrl-C quits
      </Text>
    </Box>
  );
}
```

`shown` is the first `limit` tasks. The rows, the down arrow and the line under the rows read it.

## Run it

With no flags the loader's defaults apply:

```shell-session
$ fsr dev app --terminal
```

```text
╭──────────────────────────────────────────╮
│ Tasks tasks.json                  2 open │
│                                          │
│ > Write the README                       │
│   Tag a release                          │
│   Answer the open issues                 │
│                                          │
│ up and down move, space marks one done   │
│ Ctrl-C quits                             │
╰──────────────────────────────────────────╯
```

Quit and pass both flags:

```shell-session
$ fsr dev app --terminal -- --file work.json --limit 2
```

```text
╭──────────────────────────────────────────╮
│ Tasks work.json                   2 open │
│                                          │
│ > Write the README                       │
│   Tag a release                          │
│                                          │
│ 1 more not shown                         │
│ up and down move, space marks one done   │
│ Ctrl-C quits                             │
╰──────────────────────────────────────────╯
```

`--limit=1` works too and leaves one row with `2 more not shown`. The name does nothing yet. In the next step it picks the file the list is saved to.

Next up: [630. Save to a file](https://snapfirers.com/fsr/learn/630-save-to-a-file).
