Skip to content

Scripts

A script in Reminix is a command your team can run safely, from the app, the CLI, your own code or an AI agent. It has a name, an owner, and the JSON Schema of its inputs, which becomes the form people fill in and the input agents send. Its code lives in versions: every change is a new version, and runs use the one that’s published.

A script's page in the app: Unlock an account, its description and tag, then the run form made from its inputs, with a Customer email field, an Environment field set to production, and a Run button.A script's page in the app: Unlock an account, its description and tag, then the run form made from its inputs, with a Customer email field, an Environment field set to production, and a Run button.A script's page in the app: Unlock an account, its description and tag, then the run form made from its inputs, with a Customer email field, an Environment field set to production, and a Run button.A script's page in the app: Unlock an account, its description and tag, then the run form made from its inputs, with a Customer email field, an Environment field set to production, and a Run button.

A script is a command your team already runs, such as a Python program, a shell script or a Node CLI, in its own folder. To publish it, run reminix publish in that folder. Reminix reads the folder and works out how to run it:

The folder has Reminix runs
pyproject.toml with one [project.scripts] entry that entry
pyproject.toml with a package that has __main__.py python -m <package>
package.json with one bin, else scripts.start, else main node <bin>, npm start, node <main>
one .py, .js, .mjs, .cjs, .ts, .mts or .sh file that file
several, one of them main.py, __main__.py, index.js, index.mjs, index.ts, main.sh or run.sh that one
no such file, but one executable file with no extension that starts with #! that file

The script is named after pyproject.toml or package.json, else the folder. Its dependencies install from requirements.txt (else pyproject.toml) and package.json. For a script with inputs, give the command with a {name} where each input goes:

Terminal window
reminix publish --plan # what Reminix found; uploads nothing
reminix publish --command "python unlock.py {email}"

When the folder doesn’t say how it runs, or says two things (two scripts and no main.py), publish stops and names what it found: pass --command, or customize it with a reminix.json (or ask your agent to write one).

Add a reminix.json beside the code when you want more than the defaults: a form with labels, examples and types for its inputs, a description and tags, the hosts and secrets it may use, a longer timeout, or how to run and install anything the folder can’t say (Ruby, Go, a custom install). Every field.

{
"slug": "unlock-account",
"name": "Unlock an account",
"description": "Lets a customer sign in again after too many attempts.",
"command": "python unlock.py {email}",
"inputSchema": {
"type": "object",
"required": ["email"],
"properties": {
"email": {
"type": "string",
"title": "Customer email",
"examples": ["jane@example.com"]
}
}
}
}
# unlock.py: the result goes to standard output, the log to standard error.
import json, sys
email = sys.argv[1]
print(f"unlocking {email}", file=sys.stderr)
status = unlock(email) # your code, calling your app's admin API
print(json.dumps({"unlocked": email, "status": status}))

reminix init "python unlock.py {email}" writes this reminix.json for you, with a string input for each {name}, and a README.md. It never writes code. Then describe each input’s title, type and description: they become the form people fill in and the input agents send.

Add "tags": ["support", "accounts"] to reminix.json (up to 10, lowercase) and fill in the README.md. The script’s page shows the README, and both help your team find it.

Reminix runs the command directly, never through a shell, so an input can’t become another command.

  • Words are split on spaces. '…' and "…" keep spaces inside one word. Characters such as $, |, > and * are ordinary text. For a pipeline, write sh -c '…' or put it in a .sh file. - The first word is the program: python, node, sh, or a file in the folder such as ./tool. - {name} is an input. A word that is only {name} becomes one argument, or one per item for a list. Inside a word, as in --email={email}, the value is put in place. - [ … ] is an optional part. Reminix leaves it out when an input in it is empty: missing, null, "", false or an empty list. A true/false input adds no argument of its own, so [--dry-run {dryRun}] adds --dry-run only when dryRun is true. One optional part can’t hold another. - An input outside [ … ] has to appear in required.

Two optional keys on an input help people run your script:

  • default fills the field in. Use it for a value that’s right most of the time. - examples shows the first example as a hint in an empty field, with a Use example link that fills it in. Use it to show the shape of a value without choosing one for the person.
"email": {
"type": "string",
"title": "Customer email",
"examples": ["jane@example.com"]
}

A file input can ship a sample file instead: see Ship a sample file.

Your code gets its inputs three ways, so use whichever suits it:

  • in the command, as above;
  • as environment variables named REMINIX_INPUT_ and the input’s name in capitals, so email is REMINIX_INPUT_EMAIL and an input named accountId would be REMINIX_INPUT_ACCOUNT_ID. Strings arrive as they are, other values as JSON;
  • all together as one JSON object, on standard input and in the file named by $REMINIX_INPUTS. Nested objects and lists are easiest to read this way.
#!/bin/sh
# unlock.sh, run with "command": "sh unlock.sh"
set -e
email=$(jq -r .email "$REMINIX_INPUTS")
echo "unlocking $email" >&2
jq -n --arg email "$email" '{email: $email}' |
curl -sf https://api.yourapp.com/admin/unlock -H "Content-Type: application/json" -d @-

Standard output is the run’s result. Reminix keeps it as JSON when it parses as JSON and as text otherwise, up to 1 MB. Exit code 0 means the run succeeded. Any other exit code fails it, and the last lines of standard error become its error. Standard error is the run’s log, which the run’s page shows while it runs.

When you upload a version, Reminix installs its dependencies once: a requirements.txt with pip, else a pyproject.toml with pip install ., and a package.json with npm (npm ci when there’s a lock file; dev dependencies are left out). Only the package registries are reachable during the install. To install some other way, set "build" in reminix.json to your own command, which runs with sh -c in the folder. If the install fails, the upload fails and shows its output. Runs never install anything, so they start fast.

Every run gets a fresh machine with Python 3.12, Node 22 (which runs .ts files directly), sh, curl, jq and pdftotext. It has 1/4 of a CPU, 1 GB of memory and 4 GB of disk. Nothing carries over from one run to the next.

By default a script has no network. To call an API, list its hosts, and the secrets to attach to requests to them:

{
"hosts": ["api.yourapp.com"],
"secrets": {
"ADMIN_KEY": {
"hosts": ["api.yourapp.com"],
"header": "Authorization",
"value": "Bearer {secret}"
}
}
}
# No key in the code: Reminix adds the Authorization header on the way out.
import json, os, urllib.request
request = urllib.request.Request(
"https://api.yourapp.com/admin/unlock",
data=json.dumps({"email": os.environ["REMINIX_INPUT_EMAIL"]}).encode(),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(request) as res:
print(res.read().decode())
  • Requests go out only to hosts, over HTTPS. A request to any other host gets an HTTP 403 at once, with a body that says why, and every request is a line in the run’s log. Python’s urllib and requests, curl and Node’s fetch work as they are. - Reminix attaches each secret only to its hosts, and only to those the secret’s value in the run’s environment allows (Secrets). The code never holds the value. One caution: an API that echoes your credentials back in its response would reveal them to the code, so don’t send secrets to one.
  • value defaults to {secret}. Use it to add a scheme, as in "Bearer {secret}". - $REMINIX_ENVIRONMENT is the name of the run’s environment, such as production or staging. Use it to pick an API’s test or live base URL, and list both hosts in hosts. - To read a secret as a plain value, list it in "env": ["NAME"], and the code gets it as the environment variable NAME. That’s allowed only for a secret whose owner turned on “Let scripts read the value itself”.

The script’s page shows each version’s hosts and secrets, so whoever publishes can see what it can reach.

Terminal window
reminix publish ./unlock-account --draft # uploads a draft version
reminix publish ./unlock-account # uploads a version and publishes it

The CLI packs the folder as it is: the files git tracks, plus new ones .gitignore doesn’t exclude, but never node_modules, .venv, venv, __pycache__, .deps, .git or .env. The packed folder can be up to 10 MB.

The first publish creates the script. A draft changes nothing until a version is published, by the script’s owner or a workspace owner or admin. An agent’s publish becomes a request that a person approves. Publishing an older version again rolls back. See Governance for who can use a script, runs that need approval, and a second person for publishing.

To publish from your own code instead, with a key or token that has scripts:write, see Upload a version and Publish a version under Endpoints. You pack the folder yourself there, as a base64 .tar.gz.

Reminix never deletes a script. When you no longer need one, archive it: on the script’s page, select Archive script. An archived script leaves the Scripts list and can’t be run, and its schedules and triggers pause. Its runs, versions and files stay, so you can always see what it did and what it used. Tick Archived on the Scripts page to find it again, and select Restore to bring it back.

The script’s owner, or a workspace owner or admin, can archive and restore it. Reminix refuses to archive a script while one of its runs is going or waiting for approval. From the CLI: reminix scripts archive <slug> and reminix scripts restore <slug>.

Once a version is published, anyone in the workspace who may use the script can run it:

  • In the app: open it under Scripts. Its input schema is a form, and Run shows the output and the log.
  • From the CLI: reminix run unlock-account --input '{"email":"jane@example.com"}' prints the output. Add --log to print the log too.
  • From an agent: through the MCP server, every published script is a tool with the script’s own name (unlock_account) that takes its inputs.
  • In another environment: with two or more, each of these can name one. See Environments.
  • From your code: reminix.run("unlock-account", { email: "jane@example.com" }) with the TypeScript SDK, or reminix.run("unlock-account", {"email": "jane@example.com"}) in Python. See Runs.