Skip to content

Reads, writes, copies, moves, removes, inspects and searches files in the project workspace. Paths are relative to the workspace of the run, which is the project folder on the agent. Text files are read and written in the JVM default charset (UTF-8 on current Java versions). When a script runs through a Reflex server, a file or folder that is not on the agent yet is first fetched from the project's files on the server, `Fl.remove` also deletes it there, and the files a run leaves behind (except `.lua` files) are copied back to the project when the run ends. A script run directly on the agent (for example with the CLI) only uses its local project folder. By default paths are not confined to the workspace: `..` is followed, and so are absolute paths when the script runs directly on the agent. An agent started with `agent.files.confineToWorkspace=true` (`AGENT_FILES_CONFINE_TO_WORKSPACE`) rejects absolute paths and anything that leaves the workspace, symlinks included, in every file function of the libraries.

Functions

  • mkdir self Creates a directory including any missing parent directories.
  • getFiles table Lists the files directly inside a directory, most recently modified first.
  • beingWritten boolean Tells whether a file is still growing: reads its size, waits 500 ms, reads it again and returns `true` when the size changed.
  • remove self Deletes a file, or a directory together with everything in it.
  • copy self Copies one file.
  • exists boolean Checks whether a file or directory exists.
  • move self Moves a file or directory to a new path.
  • rename self Renames a file or directory inside its current directory.
  • stat FileStat Returns metadata of a file or directory: type, size in bytes and timestamps.
  • find table Searches a directory recursively and returns every file and directory found below it (not the directory itself), sorted alphabetically.
  • glob table Returns the files and directories whose path matches a glob pattern, searching the whole workspace or the directory given as first argument.
  • read table Reads a text file and returns its lines as a list.
  • write self Writes text to a file and creates missing parent directories.
Fl.mkdir(folderDir: string)→self

Creates a directory including any missing parent directories. Does nothing when it exists.

Parameters

folderDir string
Directory to create, relative to the workspace

Returns

self chainable The library itself, so calls can be chained.
Fl.getFiles(location: string)→table

Lists the files directly inside a directory, most recently modified first. Subdirectories are not listed and the search is not recursive: use `Fl.find` or `Fl.glob` for that. Every entry is the path of the file relative to the workspace, so it starts with the directory you passed (`./report.csv` for `.`, `exports/report.csv` for `exports`). A directory that does not exist gives an empty list.

Parameters

location string
Directory to list, relative to the workspace

Returns

table List of file paths (strings), newest first

Example runs in CI

Fl.write("exports/a.txt", "a")
Fl.write("exports/b.txt", "b")
for _, file in ipairs(Fl.getFiles("exports")) do
print(file)
end
Fl.beingWritten(file: string)→boolean

Tells whether a file is still growing: reads its size, waits 500 ms, reads it again and returns `true` when the size changed. Use it to wait until a download or export has finished before reading the file. Blocks the script for 500 ms and throws when the file does not exist.

Parameters

file string
File to check, relative to the workspace

Returns

boolean true when the size changed during the 500 ms
Fl.remove(fileLua: string)→self

Deletes a file, or a directory together with everything in it. When the script runs through a Reflex server the file or directory is also deleted from the project's files on the server. Throws an error when the path does not exist.

Parameters

fileLua string
File or directory to delete, relative to the workspace

Returns

self chainable The library itself, so calls can be chained.

Example runs in CI

Fl.write("scratch/tmp.txt", "temporary")
Fl.remove("scratch")
print(Fl.exists("scratch"))
Fl.copy(source: string, target: string)→self

Copies one file. The target is overwritten when it exists and its missing parent directories are created. Directories cannot be copied: the call throws `Failed to copy file`, as it does when the source is missing.

Parameters

source string
File to copy, relative to the workspace
target string
Path of the copy, relative to the workspace

Returns

self chainable The library itself, so calls can be chained.
Fl.exists(location: string)→boolean

Checks whether a file or directory exists. When the script runs through a Reflex server, a path that is not on the agent yet is looked up in the project's files on the server and fetched, so project files count as existing.

Parameters

location string
File or directory, relative to the workspace

Returns

boolean
Fl.move(source: string, target: string)→self

Moves a file or directory to a new path. Missing parent directories of the target are created and an existing target file is replaced. Throws `Source path not found` when the source does not exist. To change only the name, use `Fl.rename`.

Parameters

source string
File or directory to move, relative to the workspace
target string
New path, relative to the workspace

Returns

self chainable The library itself, so calls can be chained.
Fl.rename(source: string, newName: string)→self

Renames a file or directory inside its current directory. An existing item with the new name is replaced. Throws when the new name contains a directory part or is absolute, and when the source does not exist. To move something to another directory, use `Fl.move`.

Parameters

source string
File or directory to rename, relative to the workspace
newName string
New name only, without any directory, like `new.txt`

Returns

self chainable The library itself, so calls can be chained.
Fl.stat(location: string)→FileStat

Returns metadata of a file or directory: type, size in bytes and timestamps. A path that does not exist is not an error: the table then only has `path` and `exists = false`.

Parameters

location string
File or directory, relative to the workspace

Returns

FileStat Metadata of a file or directory, returned by Fl.stat.
Fields of FileStat
path string
the path relative to the workspace, with forward slashes (the workspace itself is .)
exists boolean
whether the path exists
name string optional
the last part of the path
type string optional
file, directory or other (for example a device)
isFile boolean optional
true for a regular file
isDirectory boolean optional
true for a directory
size number optional
size in bytes (for a directory this is whatever the file system reports, not the size of its content)
modifiedAt string optional
last modification time as an ISO 8601 instant in UTC, for example 2026-01-31T12:30:00.123Z
createdAt string optional
creation time as an ISO 8601 instant in UTC; some file systems report the modification time
accessedAt string optional
last access time as an ISO 8601 instant in UTC
parent string optional
the path of the containing directory relative to the workspace; left out when it lies outside the workspace
All table shapes

Example runs in CI

Fl.write("report.txt", "hello")
local info = Fl.stat("report.txt")
print(info.exists, info.type, info.size, info.modifiedAt)
print(Fl.stat("missing.txt").exists)
Fl.find(root: string)→tableFl.find(root: string, pattern: string)→table

Searches a directory recursively and returns every file and directory found below it (not the directory itself), sorted alphabetically. With a pattern only the matching entries are returned. When `root` is a single file, the result is that file (if it matches). A path that does not exist gives an empty list. The returned paths are relative to the workspace (not to `root`) and always use `/`. The pattern is a glob such as `*.csv` or `reports/**/*.pdf`, matched against the path relative to `root`. A pattern without `/` is also matched against the bare name of every entry, so `*.csv` finds CSV files at any depth, and a leading `**/` also matches entries directly in `root`. `Fl.glob` does the same with the pattern as the main argument.

Parameters

root string
Directory or file to search from, relative to the workspace
pattern string optional
Glob pattern the entries must match, like `*.csv`

Returns

table List of paths (strings) relative to the workspace

Example runs in CI

Fl.write("data/a.csv", "1")
Fl.write("data/nested/b.csv", "2")
Fl.write("data/c.txt", "3")
for _, path in ipairs(Fl.find("data", "*.csv")) do
print(path)
end
Fl.glob(pattern: string)→tableFl.glob(root: string, pattern: string)→table

Returns the files and directories whose path matches a glob pattern, searching the whole workspace or the directory given as first argument. Same as `Fl.find` with a pattern, including how patterns are matched: `*.csv` matches CSV files at any depth. Paths are relative to the workspace, use `/` and are sorted alphabetically.

Parameters

root string
Directory to search from, relative to the workspace
pattern string
Glob pattern like `*.csv` or `reports/**/*.pdf`

Returns

table List of matching paths (strings) relative to the workspace

Example runs in CI

Fl.write("out/one.csv", "1")
Fl.write("out/two.csv", "2")
print(#Fl.glob("*.csv"))
print(#Fl.glob("out", "one.*"))
Fl.read(fileLoc: string)→table

Reads a text file and returns its lines as a list. Line breaks are removed, a final line break does not add an empty entry and an empty file gives an empty list. The whole file is read into memory. Throws `File not found` when the file cannot be read. For structured files use `Json`, `Yaml`, `Csv`, `Xml` or `Xlsx`.

Parameters

fileLoc string
File to read, relative to the workspace

Returns

table List of strings, one per line

Example runs in CI

Fl.write("notes.txt", "first line\nsecond line")
local lines = Fl.read("notes.txt")
print(#lines, lines[1])
print(Fl.exists("notes.txt"))
Fl.write(fileName: string, content: string)→selfFl.write(fileName: string, content: string, append: boolean, newLine: boolean)→self

Writes text to a file and creates missing parent directories. With two arguments the text is appended to the file, not written over it: when the file already has content a line break is inserted first, so every call adds one line and the file never ends with a trailing line break. To replace the content of the file call it with four arguments and `append = false`. Pass exactly 2 or 4 arguments. With 3 arguments the third is ignored and the call appends. Numbers and other values are converted to text.

Parameters

fileName string
File to write, relative to the workspace
content string
Text to write
append boolean optional
true to append to the existing content, false to replace the file
newLine boolean optional
Only used when appending to a file that already has content: true to put a line break before the text

Returns

self chainable The library itself, so calls can be chained.

Example runs in CI

Fl.write("log.txt", "first")
Fl.write("log.txt", "second") -- appended on a new line
Fl.write("log.txt", "only line", false, false) -- replaces the content
print(#Fl.read("log.txt"))