โจ Your scripts. Documented. Runnable. In one Markdown file.
fsr is the script runner that lives in your docs โ not buried in JSON. ๐
You know the drill ๐ค: you clone a repo, there's a README.md that vaguely gestures at some scripts, and a package.json full of cryptic one-liners. Nobody knows what to run. Nobody knows what anything does. You Slack someone. They don't know either. Someone runs the wrong thing. Staging goes down at 9am. โ ๏ธ
fsr fixes this. It collapses your documentation AND your scripts into a single file โ fscripts.md. It's Markdown. It's human-readable. And it actually runs. ๐
npm i -g fscr๐ก Package name:
fscr. Command:fsr. Yes, on purpose. ๐
npm i -g fscr # ๐ฆ install fsr globally
fsr generate # โจ imports your package.json scripts โ fscripts.md
fsr # ๐ฏ interactive picker โ choose and runThat's it. Your package.json scripts are now documented, readable, and runnable from a single Markdown file. No more mystery. No more Slack messages. No more broken staging environments. ๐
fscripts.md is your new source of truth. ๐
It's a plain Markdown file where:
- ๐ Each script is a heading
- ๐ฌ The description is prose right below it
โถ๏ธ The actual command lives in a code block- ๐ฐ The docs and the runner are the same file โ always in sync
## start:web๐ Starts the local dev server on port 3000. Run this first.
node server.js๐๏ธ Full production build. Cleans dist/, compiles, minifies. Takes ~2 min.
npm run clean && npm run compile && npm run minifyfscripts.md isn't just for bash. You can write full executable JavaScript right in your Markdown: ๐ฅ
## db:seed
๐ฑ Seeds the local database with test fixtures. Wipes existing data first โ don't run on prod.
```javascript
import { seed } from "./db/seed.js";
await seed({ wipe: true });
```Real Node.js. Executed directly. Documented inline. No extra files. ๐ฅ
| command | what it does |
|---|---|
โก fsr |
interactive picker โ shows all scripts + plugins, just pick and run |
๐๏ธ fsr start |
browse scripts by category |
๐ fsr list |
fuzzy search across all tasks |
fsr run [task] |
run a specific named task directly |
๐ fsr run-s t1 t2 |
run tasks sequentially, one after another |
โก fsr run-p t1 t2 |
run tasks in parallel, at the same time |
โจ fsr generate |
import package.json scripts โ fscripts.md |
๐ fsr toc |
generate / update Table of Contents in fscripts.md |
๐ฆ fsr scripts |
interactive picker for package.json scripts only |
๐ fsr encrypt |
password-protect secret file(s) |
๐ fsr decrypt |
decrypt secret file(s) |
๐ fsr encryption |
interactive encrypt/decrypt menu |
๐ fsr bump |
bump version in package.json + beautify it |
โฌ๏ธ fsr upgrade |
upgrade all packages (respects ignore-upgrade list) |
๐ฉบ fsr doctor |
run diagnostics & health check |
๐ป fsr completion |
shell completions for bash, zsh, fish, powershell |
๐ฟ fsr branch |
validate you're not on dev, create a new branch |
๐งน fsr clear |
clear recent task history |
๐ฌ fsr commit |
stage + commit with AI-generated conventional commit messages |
๐ fsr plugins |
list all installed plugins (built-in and npm fscr-plugin-*) |
๐
--env <name>(alias-e) is a global flag that works with every command above. It restricts the visible and runnable tasks to a specific environment profile.
Run multiple scripts at the same time ๐:
fsr run-p start:web start:desktop start:apiOr one after another ๐:
fsr run-s lint test buildNo extra config. No setup. Just works. โ
Keep secrets safe without a third-party vault ๐:
fsr encrypt # ๐ password-protect any file(s)
fsr decrypt # ๐ unlock them when you need themPerfect for .env files, credentials, config secrets. Built right in. No extra tools needed. ๐ก๏ธ
fsr has a plugin system ๐งฉ. Plugins hook into execution โ fire logic before or after any script runs.
Perfect for:
- ๐งน Clearing cache before a build
- ๐ฌ Sending notifications after deploy
- ๐ Logging execution time
- ๐ Anything your workflow needs
Plugins show up right alongside built-in commands in the interactive picker. No extra steps. No ceremony. ๐
Drop a folder into lib/plugins/ with an index.js that exports a default plugin object:
// lib/plugins/my-plugin/index.js
export default {
name: "my-plugin",
version: "1.0.0",
description: "Does something great",
async init(context) {
// Hook into lifecycle events
context.registerHook("post-task", async ({ taskName, duration, success }) => {
context.logger.info(`[my-plugin] ${taskName} finished in ${duration}ms`);
});
// Register new fsr commands
context.registerCommand({
name: "my-cmd",
description: "My custom command",
handler: async (options, ctx) => {
ctx.logger.success("Hello from my-plugin!");
}
});
},
// Optional: appear in the interactive picker
async run(ctx) {
ctx.logger.success("Running my-plugin interactively");
}
};Drop a plugin into .fsr/plugins/<name>/index.js in your project root โ no config, no install step:
my-repo/
.fsr/
plugins/
cache-cleaner/
index.js โ same export contract as built-in plugins
Useful for repo-specific automation you don't want to publish. Committed to the repo alongside your code.
Any npm package named fscr-plugin-* is auto-discovered at startup โ no config needed. Just install it:
npm i fscr-plugin-notifyfsr picks it up automatically on next run. Same plugin contract as built-in plugins.
Plugins get persistent key-value storage scoped to their name, written to .fscr/<plugin-name>/storage.json:
async init(context) {
// Read previously saved data (returns {} if nothing saved yet)
const data = context.getStorage();
context.registerHook("post-task", async ({ taskName }) => {
data.runs = (data.runs || 0) + 1;
context.setStorage(data); // persists to disk
});
}| hook | when it fires |
|---|---|
pre-task |
before any fsr command or plugin run starts |
post-task |
after any fsr command or plugin run completes successfully |
post-command |
after any plugin-registered command completes |
task-error |
when a command or plugin run throws |
fsr pluginsShows all loaded plugins with their source ([builtin], [local], or [npm]) and description.
Your fscripts.md grows fast ๐. Keep it navigable:
fsr tocGenerates and updates a Table of Contents at the top of your fscripts.md. Auto-linked. Always current. โ
Something broken? Run the doc ๐ฅ:
fsr doctor # ๐ check system health
fsr doctor --fix # ๐ง auto-fix where possible
fsr doctor --json # ๐ output results as JSON (great for CI)
fsr doctor --verbose # ๐ฌ show everythingpackage.json scripts have four problems:
| problem | reality |
|---|---|
| ๐ต Invisible | cryptic one-liners with zero explanation |
| ๐๏ธ Isolated | docs live somewhere else โ or nowhere at all |
| ๐ง Inflexible | no prose, no full scripts, no real structure |
| ๐ต๏ธ A black box | every new teammate asks the exact same questions |
fsr doesn't replace package.json. It makes it irrelevant as an interface. ๐
You write in Markdown. Humans read it. fsr runs it.
# ๐๏ธ Group Name
## script-name
Description of what this does, when to run it, any gotchas. โ๏ธ
```bash
your-command --here
```// ๐จ full Node.js โ executed directly
import { something } from "./lib/index.js";
await something();- ๐๏ธ Groups = top-level
#headings โถ๏ธ Tasks =##headings (the script name)- ๐ฌ Docs = prose between the heading and code block
- ๐ฅ Runner = the code block itself (
bashorjavascript) - ๐ Env boundary =
## [env:name]heading โ tags all subsequent tasks in the group with that environment profile (see environment profiles)
Different environments โ same fscripts.md. Use ## [env:name] headers inside any group to tag tasks to a specific deployment target, then pass --env <name> to surface only those tasks.
Add an ## [env:name] heading anywhere inside a # group. Every ## task that follows it (until the next ## [env:โฆ] header or the next # group) inherits that environment tag:
# Deployment
## [env:staging]
## deploy:api
Deploy the API to the staging cluster.
```bash
node deploy.js --env staging
```
## deploy:web
```bash
node web.js --env staging
```
## [env:production]
## deploy:api
Deploy the API to the production cluster.
```bash
node deploy.js --env production
```Pass --env <name> (alias -e) to any fsr command. Only tasks tagged with that profile are shown or executed. Tasks defined before any ## [env:โฆ] header (i.e. with no profile) are excluded when --env is set.
fsr --env staging # ๐ฏ interactive picker โ staging tasks only
fsr start --env staging # ๐๏ธ browse staging tasks by category
fsr list --env production # ๐ fuzzy-search production tasks
fsr run deploy:api --env staging # โถ๏ธ run the staging variant of deploy:api
fsr run-s deploy:api deploy:web --env staging # ๐ sequential, staging only
fsr run-p deploy:api deploy:web --env staging # โก parallel, staging onlyThe --env flag is global: it works on every command that reads fscripts.md.
--prod and --dev are convenience shortcuts that are resolved before yargs parses the command. They map to --env production and --env development respectively:
fsr --prod # equivalent to: fsr --env production
fsr --dev # equivalent to: fsr --env development
fsr run deploy:api --prod # runs deploy:api with env=productionThese shortcuts also normalise the prod / dev shortnames when used with --env:
fsr --env prod # resolved to: fsr --env production
fsr --env dev # resolved to: fsr --env development| scenario | result |
|---|---|
No --env flag |
all tasks returned (env-tagged and plain alike) |
--env staging |
only tasks under ## [env:staging] blocks |
New # group heading |
env resets to none for tasks in that group |
| Same task name in two env sections | both preserved; --env picks the right one |
--env unknown |
empty result โ no tasks, no error |
# Deploy
## build
Build the project (no env tag โ always available).
```bash
npm run buildPush image to staging registry.
docker push registry/myapp:stagingPush image to production registry.
docker push registry/myapp:production
```bash
fsr run push --env staging # runs: docker push registry/myapp:staging
fsr run push --env production # runs: docker push registry/myapp:production
fsr |
raw package.json |
Makefile | nx / turbo | |
|---|---|---|---|---|
| ๐ Human-readable docs | โ | โ | ๐ฌ | โ |
| โ | โ | โ | โ | |
| ๐จ Full JS script blocks | โ | โ | โ | โ |
| โก Parallel execution | โ | โ | ||
| ๐ Built-in encryption | โ | โ | โ | โ |
| ๐ฎ Interactive picker | โ | โ | โ | โ |
| ๐ Plugin system (npm-based) | โ | โ | โ | โ |
| ๐ Environment profiles | โ | โ | โ | |
| ๐ฆ Zero config | โ | โ | โ | โ |
| ๐ 3-command onboarding | โ | โ | โ | โ |
- ๐ Run
fsr generateon any existing repo โ instantfscripts.mdfrom yourpackage.json - ๐ฏ Just type
fsrโ the interactive picker means you never have to memorize a command name - ๐จ Use
javascriptblocks for scripts that need real logic โ imports, async, conditionals - ๐ Build a plugin to clear cache before every run โ set it once, forget it forever
- ๐ฆ Publish a plugin as
fscr-plugin-<name>on npm โ anyone who installs it gets it auto-loaded - ๐ Run
fsr tocafter adding new scripts โ keeps yourfscripts.mdnavigable - ๐ฉบ Run
fsr doctorwhen something feels off โ it'll tell you what's wrong - ๐ฌ Run
fsr committo stage and commit with AI-written conventional commit messages - ๐ Use
## [env:staging]/## [env:production]sections andfsr --env stagingin CI โ onefscripts.md, zero duplicated scripts
MIT ๐
โก Built by the Freedcamp team. Battle-tested on our own repos before unleashing it on yours. We eat our own cooking. ๐ณ