Computers · Program API v1.2.0
    Preparing search index...

    Program files

    Programs are GM-authored asynchronous JavaScript files, available in VANTAGE and FERRO. In Configuration → Files & Menu → New Program, enter a filename and edit the source. Save does not execute it. Edit opens the code editor again; rename, move, duplicate, shortcuts and folder duplication work like other files.

    All Programs always execute as admin on the designated online GM's client, with GM permissions, even with that GM's computer window closed. The player keeps control of the computer and answers its prompts. Without an online GM, the computer displays Service unavailable. before any script code executes. The editor states this behavior explicitly; there is no execution-permissions checkbox. Existing Programs follow the same rule regardless of their former admin flag. Syntax errors disable Save.

    An offline-GM Program error also shows the controller a Foundry error notification: Service unavailable. Programs can only run while a GM is online. This applies to both launches and a GM disconnecting during execution. Repeated session updates do not repeat the notification for the same failed run.

    In VANTAGE, select the Program and press Enter or click its row. In FERRO:

    ./diagnostics --full
    /documents/diagnostics "two words"
    diagnostics

    The last form searches /bin; create that folder and place Programs there to make them available by bare name. Built-in commands take precedence over bare Program names. Explicit paths select files directly. Names are case-sensitive; quote names and arguments containing spaces. Tab completes executable names in /bin and ordinary paths. Programs do not currently participate in shell pipes or output redirection. FERRO shows a dedicated Program screen and returns to the existing shell with Escape.

    Scripts can use normal JavaScript, top-level await, and Foundry globals, like script macros. Helpers are supplied as terminal, program, audio, save and load.

    Helper Result
    terminal.write(text) Immediately write at the cursor.
    terminal.writeLine(text = "") Write, then start a new line.
    await terminal.type(text, {delay: 30, sound: true}) Reveal characters over time; delay is milliseconds per character.
    await terminal.typeLine(text = "", options) Type, then start a new line.
    terminal.clear() Clear the transcript and reset the cursor.
    terminal.clearLine() Clear the current row and return to its beginning.
    await terminal.sleep(ms) Cancellable pause, from zero to one hour.
    await terminal.input(label) Read a line from the current controller.
    await terminal.confirm(label) Present Yes/No and return a boolean.
    await terminal.choose(label, choices) Present 1–8 string labels and return the selected label.
    audio.cue(name) Shared key, output, success or lockout sound.
    await save.computer(key, value) Persist a JSON value on this computer; return the saved value.
    load.computer(key) Read this computer's value synchronously; missing keys return undefined.
    await save.world(key, value) Persist a JSON value shared by every computer in this Foundry world.
    load.world(key) Read the locally synchronized world value synchronously; missing keys return undefined.

    Use await for each typewriter, sleep, and prompt call, in sequence. Immediate output is buffered into socket batches. Input supports normal editing and paste; choices support arrow keys, Enter and clicking. All viewers see the same prompts and answers. These prompts are public gameplay input, not secret credentials.

    program.args contains FERRO arguments (an empty array for VANTAGE). program.computer is the JournalEntry, program.file is the JournalEntryPage, program.os is vantage or ferro, and program.signal is the run's AbortSignal. program.controller is the Foundry User operating the computer. Document actions use the executing GM's permissions. In every Program, game.user is the GM; use program.controller.id when targeting the operating player. Foundry dialogs, canvas operations and other client-local UI occur on the executing client's screen; terminal helpers remain shared with the player and viewers.

    VANTAGE's previous OS identifier, terminal, remains accepted in saved computer configuration. Reads normalize it to vantage; creating a computer or saving its configuration writes vantage. This does not reset files, access progress, or power. Programs and integrations comparing the OS name must change "terminal" to "vantage". The terminal helper object keeps its name and works in both OSes.

    A Program can grant ownership of a prepared handout using its GM permissions:

    if (await terminal.confirm("Retrieve the handout?")) {
    const handout = game.journal.get("YOUR_HANDOUT_ID");
    if (!handout) throw new Error("Handout unavailable");
    await handout.update({ [`ownership.${program.controller.id}`]: CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER });
    terminal.writeLine("Handout retrieved.");
    }

    Output is plain text. \n starts a new line at column zero; \r returns to column zero without erasing the row; \r\n is one newline. Tabs advance to the next four-column stop. Long rows wrap at 64 columns, and the latest 200 rows are retained. Page Up/Down scroll the transcript. Each output call accepts up to 8192 characters (writeLine/typeLine reserve one for the appended newline). Input is limited to 1024 characters, prompt labels to 512, and choice labels to 128. Typewriter delays are bounded to 1–1000ms. Program source is limited to 65536 characters.

    terminal.clear();
    await terminal.typeLine("ESTABLISHING UPLINK...", { delay: 35 });
    await terminal.sleep(700);

    for (let percent = 0; percent <= 100; percent += 10) {
    terminal.clearLine();
    terminal.write(`Downloading: ${percent}%`);
    await terminal.sleep(150);
    }
    terminal.writeLine();

    if (await terminal.confirm("Decrypt transmission?")) {
    await terminal.typeLine("\nTRANSMISSION RECOVERED\nUse the east entrance.");
    audio.cue("success");
    } else {
    terminal.writeLine("Transmission retained.");
    }

    Computer keys are shared by every Program on the same computer. World keys are shared by every Program on every computer in the current Foundry world. They are not scoped to a player or Program file. Use distinctive keys to avoid collisions. Keys start with a letter and contain up to 64 letters, digits, underscores or hyphens. Values are copied through JSON serialization; null is a stored value, and undefined, functions, cyclic values and BigInt cannot be saved as values. Each serialized value is limited to 8192 characters and each store to 65536.

    const visits = load.computer("visits") ?? 0;
    await save.computer("visits", visits + 1);
    terminal.writeLine(`Visits to this computer: ${load.computer("visits")}`);

    await save.world("relayOnline", true);
    terminal.writeLine(`Shared relay: ${load.world("relayOnline") ? "online" : "offline"}`);

    Saves replace the value at their key and retain other keys. Loads return independent copies, so changing a loaded object does not save it. Values survive relaunches, power cycles, closing windows and server restarts. They do not resume JavaScript. Computer data belongs to its JournalEntry, so deleting that computer deletes its data. World data remains independently of any computer. These are public gameplay values, not secret storage.

    Computer saves use the executing GM's document permissions. World saves write directly on the executing GM, using one queue across computers to preserve unrelated keys. There is no separate world-save socket relay. Every Program can use world saves. Await every save; errors reject its Promise. Queued writes recheck the run, control and file access before dispatch. A document write already dispatched may still commit after cancellation. Loads read the executing client's current copy; another client's write becomes visible when synchronization arrives. A successful awaited save is locally visible before the helper returns. Concurrent load/change/save sequences are not atomic; the last accepted save wins.

    One session-level Program service selects the designated online GM as executor at launch. That client reads the saved page source and executes it once. Source code is never sent in a socket request. The coordinator validates executor identity and output against the controller's control, power, document access and folder gates; other clients only render serializable output. Typewriter text and start time travel once, with local animation and rate-limited typing sounds. Sounds use Foundry Interface audio and local Computers mute/volume. Joining midway receives the current transcript and prompt without executing code or replaying old cues.

    Escape stops and returns to the launching menu/shell. Completion or error leaves the output visible until Escape. Taking control, losing control/connection, closing the controller window, power-off, or losing access cancels the runner's helpers. Losing or replacing the designated GM ends a running Program with Service unavailable. It never moves a running script to a new GM or replays it when the service returns. Reopening or transferring control never resumes or reruns JavaScript. Each new launch starts fresh. Source edits affect the next launch; appearance changes do not restart a running Program. Source, names and file metadata persist; transcripts, prompts and animations do not. Values explicitly saved through save.computer or save.world persist independently of the run.

    Programs are trusted macro-like code, not a sandbox. All Programs execute with GM permissions. Foundry actions already performed cannot be rolled back when a run stops. Helpers and program.signal cooperate with cancellation; arbitrary timers, listeners and external async operations need the author's own cleanup. A synchronous infinite loop can block the browser and cannot be stopped by Escape. Do not launch code you do not trust. FERRO's filesystem commands cannot edit, move or delete Program sources; a shell cp makes an ordinary text copy, not a new executable. Native Foundry document permissions still apply.

    A Program is an ordinary text JournalEntryPage with flags.computers.program = true. When editing an existing Program, its header has Foundry's Copy UUID button: left-click copies the page UUID, right-click copies its ID, and dragging creates a document reference. New Programs gain this button when reopened after saving. Its escaped source lives once in text.content, inside a preformatted span. No custom page subtype or Macro document is created. Its file projection adds executable: true; scripts themselves do not travel through the module socket. Computer state is a JSON-encoded dictionary in the JournalEntry's flags.computers.programData, avoiding recursive flag merges of saved objects. World state is one hidden Object setting, computers.programData, with world scope.

    The existing registerTerminalProgram API remains the synchronous, deterministic screen/action contract for installed VANTAGE extensions. The Add program button for those extensions is separate from New Program files. Program execution and shared text state live under scripts/programs/, independent of CRT rendering.