Skip to content

Latest commit

Β 

History

40 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ›°οΈ StarCLI

A command-line interface for executing Starlark scripts with rich module support, interactive mode, and web server capabilities.

About

StarCLI is a versatile tool that provides a convenient environment for running Starlark scripts from the command line. Starlark is a dialect of Python designed for configuration, extensibility, and embedding. StarCLI extends Starlark with additional modules and utilities to make it more powerful for various automation and scripting tasks.

Features

  • Multiple Execution Modes:

    • REPL (Read-Eval-Print Loop) for interactive script development
    • Direct code execution from command line arguments
    • Script file execution
    • Web server mode that creates a Starbox environment for HTTP requests
  • Configurable Environment:

    • Load custom modules
    • Control recursion and global variable reassignment
    • Set include paths for module loading
    • Configure logging levels
  • Rich Module Support:

    • Standard library modules (json, math, time, etc.)
    • File system operations
    • Network and HTTP requests
    • Regular expressions
    • Base64 encoding/decoding
    • Email functionality
    • LLM (Language Model) integration
    • Templating, data formats & utilities (Liquid, TOML/YAML, QR codes, TOTP, emoji, in-memory cache)
    • And many more

Installation

Prebuilt archives

Download the archive for your system and checksums.txt from the same GitHub Release. No Go installation is needed to run a release binary. Starting with v0.1.3, each archive includes the README, license, and security policy, including the supported execution boundary and private vulnerability reporting instructions.

System Architecture Archive suffix
Linux amd64 / arm64 linux_amd64.tar.gz / linux_arm64.tar.gz
macOS Intel / Apple Silicon macOS_amd64.tar.gz / macOS_arm64.tar.gz
Windows x64 / ARM64 windows_amd64.zip / windows_arm64.zip
  1. Compute the archive's SHA-256 (sha256sum FILE on Linux, shasum -a 256 FILE on macOS, or Get-FileHash FILE -Algorithm SHA256 in PowerShell) and compare it with the matching filename in checksums.txt.
  2. Extract the archive. Keep its README and license; copy starcli (or starcli.exe) into a directory on your PATH.
  3. Run starcli --version and starcli -c 'print(6 * 7)'. The version must match the downloaded release and the script must print 42.

Before upgrading, keep a copy of the currently installed binary and record its version. Stop running CLI processes, replace the binary, and repeat the checks above. To roll back, stop those processes and restore the saved binary. This rehearses binary replacement; backing up script-owned files or databases is the host application's responsibility.

The release workflow verifies all six archive layouts and checksums, then runs installation, upgrade from v0.1.2, the CLI end-to-end suite, and binary rollback on all six native targets. It publishes those same archives after the checks pass and adds GitHub artifact attestations. For releases produced by this workflow, verify provenance with gh attestation verify FILE --repo 1set/starcli. Older releases may not include attestations. Binaries are not Apple notarized or Windows Authenticode signed.

From Source

Clone the repository and build from source:

git clone https://github.com/1set/starcli.git
cd starcli
make build

Docker

Build the static Linux binary before building the image. The image runs as UID 65532, includes system CA certificates, and uses the binary's embedded time-zone data. Arguments go directly to StarCLI, which also receives stop signals.

# Build and test the linux/amd64 image (also from an Apple Silicon host)
make build_linux
docker build --platform linux/amd64 -t starcli .
STARCLI_TEST_IMAGE=starcli go test ./e2e -run TestContainer -count=1

# Run in interactive mode
docker run --rm --platform linux/amd64 -it starcli

# Run a specific script
docker run --rm --platform linux/amd64 --read-only \
  --cap-drop=ALL --security-opt=no-new-privileges \
  --memory=256m --cpus=1 --pids-limit=64 \
  --mount "type=bind,src=$(pwd),dst=/scripts,readonly" \
  starcli --caps safe -I /scripts /scripts/your-script.star

# Serve a host-selected script; publish only on the host's loopback interface
docker run --rm --platform linux/amd64 --read-only \
  --cap-drop=ALL --security-opt=no-new-privileges \
  --memory=256m --cpus=1 --pids-limit=64 \
  -p 127.0.0.1:8080:8080 starcli --caps safe \
  --web-host 0.0.0.0 --web 8080 -c 'response.set_text("ready")'

Mounted scripts must be readable by UID 65532. Mount a separate writable directory when a trusted script needs persistent data. These examples are for host-selected scripts; the image is not an untrusted multi-tenant execution service. The container no longer uses /root, port 80, or a shell command as its default entrypoint. Override --entrypoint explicitly if you need a shell.

Usage

$ ./starcli -h
Usage of ./starcli:
      --allow-cmd               allow ANY host command, gum subprocesses, and runtime environment mutation (trusted scripts only)
      --allow-fs                widen a restrictive tier with filesystem modules (file, path)
      --allow-net               widen a restrictive tier with network modules (http, net, email, llm)
      --caps string             capability tier: open (default net/fs) | full | network | safe; or env STAR_CAPS
      --check                   syntax/resolve check the script (-c or file) without running it
  -c, --code string             Starlark code to execute
  -C, --config string           config file to load
      --dangerously-allow-all   DANGER: open everything β€” network + filesystem + host command execution of ANY command. Use only with fully trusted scripts.
  -g, --globalreassign          allow reassigning global variables in Starlark code (default true)
  -I, --include string          grant load() this directory (default: CWD only with filesystem capability)
  -i, --interactive             enter interactive mode after executing
  -l, --log string              log level: debug, info, warn, error, dpanic, panic, fatal (default "info")
      --log-file string         append the script's log module output to this file
      --log-format string       log file format: console (human) or json (machine) (default "console")
      --max-output uint         max top-level output entries per run (0=unlimited)
      --max-steps uint          max Starlark execution steps per run, guards runaway loops (0=unlimited)
  -m, --module strings          allowed modules to preload and load (default [args,atom,base64,cache,cmd,csv,email,emoji,file,go_idiomatic,gum,hashlib,http,json,liquid,llm,log,markdown,math,net,path,qrcode,random,re,regex,runtime,serial,sqlite,stats,string,struct,sys,time,toml,totp,web,yaml])
  -o, --output string           output printer: none,stdout,stderr,basic,lineno,since,auto (default "auto")
      --record string           record the complete session output (stdout+stderr) to this transcript file
  -r, --recursion               allow recursion in Starlark code
  -V, --version                 print version & build information
  -w, --web uint16              run web server on specified port, it provides request and response structs for Starlark code to use

Capabilities & sandboxing

By default StarCLI runs open, granting network and filesystem access. Choose the capabilities needed by host-selected scripts with --caps (or the STAR_CAPS env var). Module access follows this grant:

tier loadable modules
(default) open network and filesystem; cmd loads with execution disabled; gum and runtime require --allow-cmd
--caps full network and filesystem; cmd, gum, and runtime require --allow-cmd
--caps network safe + network (http, net, email, llm)
--caps safe pure / log / process only (math, json, sys, markdown, …)

From a restrictive tier the granular flags widen the grant: --allow-net, --allow-fs, and --allow-cmd. A module is classified by the union of everything it can do, so the dual-capability modules β€” web (HTTP + static_dir) and sqlite (DB + remote connect_remote) β€” need both --allow-net and --allow-fs (or --caps full).

Local script imports follow a separate directory grant. The default open/full posture and --allow-fs include the working directory. --caps safe and --caps network do not implicitly expose it. An explicit -I directory grants load() access to that directory without enabling the file module. Parent traversal, absolute load names, and symlinks escaping the root are rejected; symlinks must be relative and remain inside the root. This is an intentional restriction compared with the previous os.DirFS behavior.

Host command execution (cmd) is the sharpest tool and is gated on its own. The cmd module loads in the open posture, but run() is disabled β€” it returns an error β€” until you pass --allow-cmd, which enables execution of any command (no allowlist; still argv-only, never a shell). cmd is never granted by a tier, not even full. For a one-flag "trust everything" run there is --dangerously-allow-all β€” it opens network + filesystem + host command execution of any command in a single switch. Use it only with fully trusted scripts.

Set a stricter default for a whole deployment with the env var:

export STAR_CAPS=safe     # default every invocation to the safe tier

Under a restrictive tier, a script that load()s a withheld module fails with a non-zero exit code (4 for a withheld builtin). Execution budgets bound runaway scripts: --max-steps caps Starlark computation steps and --max-output caps a run's result size.

# open by default: network and filesystem modules load
$ ./starcli -c 'load("http", "get"); print(get)'

# sandbox down to safe: a network module is now withheld
$ ./starcli --caps safe -c 'load("http", "get")'       # fails (exit 4)

# from safe, opt back into the network
$ ./starcli --caps safe --allow-net -c 'load("http", "get"); print(get)'

# host command execution needs its own explicit flag (then run() runs anything)
$ ./starcli --allow-cmd -c 'load("cmd", "run"); print(run("go version").stdout)'

# one-flag "trust everything": network + filesystem + run any command
$ ./starcli --dangerously-allow-all script.star

Examples

REPL Mode

Start an interactive REPL session:

$ ./starcli

Execute Starlark Code Directly

Run a single line of Starlark code:

$ ./starcli -c 'print("Hello, World!")'

Execute a Script File

Run a Starlark script file:

$ ./starcli path/to/script.star

Interactive Mode After Execution

Execute code and then enter interactive mode with the environment preserved:

$ ./starcli -c 'greeting = "Hello, World!"' -i

Run as Web Server

Start a web server that executes Starlark code for HTTP requests:

$ ./starcli -w 8080 -c 'def handle_request(request): return {"message": "Hello from Starlark!"}'

Debug Mode

Run with debug-level logging:

$ ./starcli --log debug path/to/script.star

Parse Script Arguments

The args module is an argparse-style parser for the script's own arguments (everything after --). argv[0] is the script name (or -c), like Python's sys.argv; parse_args() parses argv[1:].

load("args", "ArgumentParser")

p = ArgumentParser(description = "greet someone")
p.add_argument("--name", default = "World", help = "who to greet")
p.add_argument("--count", type = int, default = 1)
p.add_argument("--shout", action = "store_true")
p.add_argument("file", help = "input file")

ns = p.parse_args()
print(ns.name, ns.count, ns.shout, ns.file)
$ ./starcli greet.star -- --name Kevin --count 3 --shout in.txt
Kevin 3 True in.txt

Capture Logs to a File

When a script uses the log module, --log-file routes all of its output to a file at the interpreter level (the parent directory is created if needed, and runs append):

load("log", "info", "warn")
info("starting up")
warn("careful now")
$ ./starcli --log-file run.log job.star
$ cat run.log
2026-06-21T17:32:07.373+0800    info    starting up
2026-06-21T17:32:07.373+0800    warn    careful now

Use --log-format json for machine-readable logs (structured fields included):

$ ./starcli --log-file run.log --log-format json job.star
{"level":"info","ts":"2026-06-21T17:32:07.373+0800","msg":"starting up"}

Record a Session

--record saves the complete session output β€” print output, results, REPL interaction and errors β€” to a transcript file (appended, with a timestamped session header), while still showing it live. Handy for replay and review.

REPL prompts and input echoes are included, so terminal transcripts can contain ANSI control sequences. Recording errors produce a non-zero exit status; an existing script error keeps its original exit code. If recording fails during execution, output continues to drain and the failure is reported at shutdown.

$ ./starcli --record session.log job.star      # works in REPL mode too
$ cat session.log

===== starcli session 2026-06-21T17:43:24+08:00 =====
... everything the run printed (stdout + stderr) ...

Read Piped Input

The sys module reads piped data from standard input (the script itself still comes from a file or -c). sys.input() accepts a final line without a newline. All input helpers share one buffered stream: consecutive calls and mixed input() / lines() / read() consume the remaining data without discarding read-ahead. An empty input() at EOF reports EOF. sys.read() returns all remaining stdin; sys.lines() is a lazy iterator (a large stream is not buffered whole); sys.isatty() tells interactive from piped input.

load("sys", "lines")
for line in lines():
    print(line.upper())
$ printf 'foo\nbar\n' | ./starcli upper.star
FOO
BAR

Check Without Running

Syntax- and resolve-check a script without executing it (reports problems as file:line:col: message, non-zero exit on any problem):

$ ./starcli --check path/to/script.star
$ ./starcli --check -c 'print(undefined_name)'
direct.star:1:7: undefined: undefined_name
check: 1 problem(s) found

Build your own CLI

StarCLI is the standard, fully-loaded build. It is assembled from a small reusable core β€” the kit package β€” that you can use to build your own CLI: a few-line Go shell that embeds your Starlark scripts and wires only the modules you need. Your shell and the standard StarCLI construct their runtime through the same path, so they behave identically.

//go:embed app.star
var app string

func main() {
	// embed the script + pick modules + run, in one call
	kit.Run(app, kit.WithModules("json", "math"))
}

See examples/ for runnable demos (a minimal shell, and one wiring a single starpkg module) and the build-your-own quickstart.

Configuration

StarCLI can be configured through a config file (YAML format) using the -C or --config flag:

# Example config.yaml
host_name: MyStarCLIServer

Development

Prerequisites

  • Go 1.25.8 or later (prebuilt binaries do not require a Go installation)

Building

# Build for current platform
make build

# Build for specific platforms
make build_linux
make build_mac
make build_windows

Testing

make test

License

This project is licensed under the MIT License. See the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Contact

For ordinary bugs, questions, or support, please open an issue on GitHub. For vulnerabilities, follow the private reporting instructions in SECURITY.md.

HTTP execution limits

--web PORT serves host-selected, trusted scripts on 127.0.0.1 by default. Use --web-host ADDRESS to explicitly allow another bind address. The defaults are a 1 MiB body (--web-max-body), 16 admitted requests (--web-max-concurrency), and a 15s execution deadline (--web-timeout). All three limits must be positive. Oversized bodies return 413, read failures 400, admission overflow 503, and execution deadlines 504; request cancellation propagates to the interpreter. Transport limits are 16 KiB headers, 5s header read, 10s request read, 30s idle, and execution deadline + 10s response write.

Ctrl-C / SIGTERM stop admission, cancel requests, and drain connections for up to 5s before closing remaining connections. Startup errors return a nonzero CLI status. Embedders can use web.StartContext for the same lifecycle. These cooperative limits do not preempt arbitrary Go builtins or provide process/RSS isolation. This entry point is for controlled scripts; untrusted code and public multi-tenant execution require isolated workers and separate admission review.

Process execution and environment capabilities

gum (including its editor, tmux and spinner paths) and runtime (including putenv, setenv and unsetenv) require --allow-cmd or --dangerously-allow-all. This applies to every tier, including the default open tier, and to both preloaded modules and load() calls. --allow-net, --allow-fs, and --caps full do not imply command execution. Use sys for basic process/platform information without granting these capabilities.

--allow-cmd authorizes arbitrary host commands and their environment; it is not a restricted command allowlist or an isolation boundary. The CLI supports host-selected scripts. Run untrusted scripts in a separate constrained worker.

About

πŸŒŸβœ¨πŸ’« CLI for executing Starlark scripts with rich module support, interactive mode, and web server capabilities.

Resources

Security policy

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages