fjs is the FunctionalScript command-line tool. It is invoked directly via Node:
node ./fjs/module.mjs <command> [args]
or via npx without a global install:
npx functionalscript <command> [args]
npx resolves the package name, not the bin name: fjs is an unrelated package on npm, so npx fjs … would run somebody else's code.
or, once the package is installed globally:
fjs <command> [args]
Browse the website CLI reference for commands, aliases and copyable examples.
| Command | Description | Documentation |
|---|---|---|
test | Run the FunctionalScript test suite | emergent_testing |
compile | Compile a FunctionalScript module to JavaScript or JSON; with no arguments, check every .f.js | compiler |
deps | Check syntax AST formation for files named module.f.js or module.f.mjs | deps |
cas | Content-addressable storage operations (add, get, list) | cas |
mcp | Run an MCP server over stdio exposing the CAS and Evo as tools | mcp |
ci | Generate the GitHub Actions CI and npm publishing workflows | ci |
web | Serve a directory over HTTP | web |
run | Run a FunctionalScript module as a program | below |
help | Print available commands |
Some commands also accept a short alias (fjs t for fjs test, and so on). fjs help prints them; this documentation uses the full names throughout.
fjs compile <input> <output>
The output is the language its extension declares, matched by the longest suffix first:
| Output | What it holds |
|---|---|
.json | a tree, a shared value written where each reference reaches it, refusing a value JSON cannot spell — undefined, a bigint, NaN, an infinity, a function — rather than approximate it |
.edag.data.js, .edag.data.mjs | the program's EDAG, the graph of what it computes, as a DataJS document |
.data.js, .data.mjs | the value as a DataJS module, sharing preserved by naming reused values as consts |
any other .js or .mjs | the program as a FunctionalScript module, written from the graph rather than the value, so it holds a function too |
.rs | a generated Rust module calling the nanvm-lib API |
The JavaScript names are nested rather than disjoint — a DataJS document is a JavaScript module, and so is the EDAG's — so the longest suffix is what picks the narrowest writer the name asks for, and .f.js needs no rule of its own: it is a JavaScript module, and every output the compiler writes is FunctionalScript. Any other extension is refused: it names no language, and writing one the name does not declare is the silent substitution this route exists to avoid. Imports are resolved and inlined in every case. See compiler/README.md for the accepted subset.
fjs compile
With no arguments the command is a check: every .f.js under the current directory is compiled the same way and nothing is written, so an authored .f.js — the extension that promises the current compiler accepts it — is held to that promise. Each refused file is reported as a compile of it would report it, then a count: .f.js: 3 checked, 2 refused and exit 1, or .f.js: 3 checked and exit 0. CI runs it once per revision (spec: checking every .f.js).
fjs ci
fjs ci runs the built-in CI generator from fjs/ci/module.f.mjs, writing .github/workflows/gen.ci.yml and .github/workflows/gen.npm-publish.yml. It is the standard entry point for projects that want FunctionalScript's default workflows. Projects with custom CI setup code should keep using fjs run <custom-ci-module>, so their module can call ci(setup) with its own extra runtime steps.
fjs web [root] [port]
Serves root (default .) over HTTP on port (default 8080), mapping each request path to a file under it — enough to open the pages this repository generates in a browser, where a file:// URL has no origin. It binds loopback, so what it serves stays on the machine it runs on. Both arguments are positional; port becomes --port, and --host becomes possible at all, once fjs/cli has named options.
fjs web # serve the working directory on http://127.0.0.1:8080/
fjs web docs 3000 # serve ./docs on http://127.0.0.1:3000/
Details, and the list of what it deliberately does not do, are in web.
fjs run <module> [args...]
fjs run dynamically imports <module>, checks its main export with isMain and runs it through dispatch, both from cli:
isMain(v.main)
? dispatch(v.main)({ ...options, args })
: errorExit(`${file}: not a program — no exported \`main\` function or command table`)
A module intended to be run with fjs run must export a named main constant of type Main (from cli/types.ts): either a NodeProgram or a Commands table. A program is the simple case:
import type { NodeProgram } from '../effects/node/types.ts'
export const main: NodeProgram = options => {
// options.args — command-line arguments passed after the module path
...
}
A module whose interface is a set of subcommands exports the table itself, and fjs run routes the first argument to the command it names, with help listing them — no dispatch wrapper needed:
import type { NodeOp } from '../effects/node/types.ts'
import type { Commands } from '../cli/types.ts'
export const main: Commands<NodeOp> = [
{ names: ['hello'], description: 'Say hello', handler: options => { ... } },
]
This mirrors:
export const proof— the convention for proof/test modules.mainentry-point naming from C, C++, and Rust.fjs/module.f.mjsitself, which usesexport const main.
Any arguments after <module> are forwarded to a program main via options.args:
fjs run ./my-tool.f.mjs foo bar # options.args === ['foo', 'bar']
A command table consumes the first of them as the command name, and the handler it names gets the rest:
fjs run ./my-tool.f.mjs hello foo bar # hello's options.args === ['foo', 'bar']
fjs/module.mjs — Node.js entry point (runs main via the node runner)
fjs/module.f.mjs — FunctionalScript command dispatcher (Commands list + dispatch)
fjs/cli/module.f.mjs — generic dispatch primitive (Command/Commands types in fjs/cli/types.ts)