AGI is the server-side JavaScript runtime used by ArozOS for module scripts (.agi / .js).
Scripts run in an Otto VM with sandboxed access to ArozOS functions.
This document is updated to match the current AGI implementation in mod/agi/agi*.go.
Maintainer note — keep Terminal in sync The Terminal webapp ships an in-app API reference panel that is driven by a separate structured data file:
src/web/Terminal/docs/api.json. Whenever this README is updated (new functions, changed signatures, new library, etc.) that file must also be updated to keep the in-app help accurate. The JSON mirrors this README's section structure — one object per library section, each with afunctionsarray of{ name, sig, desc, ret, example }entries.
3.4 (AgiVersion in agi.go)// Basic response
sendResp("Hello from AGI");
// JSON response
sendJSONResp({ ok: true, time: Date.now() });
// Load a library
if (requirelib("filelib")) {
var files = filelib.glob("user:/Desktop/*");
sendJSONResp(files);
}
BUILD_VERSIONINTERNAL_VERSIONLOADED_MODULESLOADED_STORAGES__FILE__HTTP_RESPHTTP_HEADERUSERNAMEUSERICONUSERQUOTA_TOTALUSERQUOTA_USEDUSER_VROOTSUSER_MODULESexecd child)BUILD_VERSION: Current system build versionINTERNAL_VERSION: Internal version numberLOADED_MODULES: Array of loaded system modulesLOADED_STORAGES: Array of available storage pools__FILE__: Current script file pathHTTP_RESP: Response content (automatically set)HTTP_HEADER: Response content type (automatically set)USERNAME: Current user's usernameUSERICON: Current user's icon pathUSERQUOTA_TOTAL: User's total storage quotaUSERQUOTA_USED: User's used storage quotaUSER_VROOTS: User's accessible virtual root pathsUSER_MODULES: User's accessible modulesEXECUTION_ID: UUIDv4 that uniquely identifies this script invocation — useful for correlating log lines across concurrent executionsPARENT_DETACHED (true)PARENT_PAYLOAD (string payload)user:/, tmp:/, extuuid:/....CanRead / CanWrite).sendResp(content)Sets HTTP_RESP.
sendResp("done");
echo(content)Appends text to current HTTP_RESP.
echo("Hello ");
echo("World");
sendOK()Sets response to ok.
sendOK();
sendJSONResp(objectOrJsonString)Sets HTTP_HEADER = application/json and writes JSON response.
sendJSONResp({ success: true, items: [1, 2, 3] });
newDBTableIfNotExists(tableName)newDBTableIfNotExists("my_table");
DBTableExists(tableName)if (DBTableExists("my_table")) sendOK();
writeDBItem(tableName, key, value)writeDBItem("my_table", "theme", "dark");
readDBItem(tableName, key)var v = readDBItem("my_table", "theme");
listDBTable(tableName)Returns key-value object.
var kv = listDBTable("my_table");
sendJSONResp(kv);
deleteDBItem(tableName, key)deleteDBItem("my_table", "theme");
dropDBTable(tableName)dropDBTable("my_table");
registerModule(jsonConfigString)Registers a module from JSON config.
registerModule(JSON.stringify({
Name: "MyApp",
Desc: "Example module",
Group: "Utilities",
IconPath: "icon.png",
Version: "1.0",
StartDir: "index.html",
SupportFW: true,
LaunchFWDir: "index.html"
}));
addNightlyTask(scriptPath)Adds a valid AGI script to nightly task list.
addNightlyTask("MyApp/nightly.agi");
includes(scriptName)Loads and executes another script relative to current script directory.
includes("helpers.js");
delay(ms)Sleeps for milliseconds.
delay(500);
exit()Stops script execution.
if (!userIsAdmin()) exit();
execd(scriptName, payload)Executes another script asynchronously.
execd("worker.agi", JSON.stringify({ job: "thumbs" }));
requirepkg(...) -> deprecated in AGI v3execpkg(...) -> deprecated in AGI v3decodeVirtualPath(...) -> deprecateddecodeAbsoluteVirtualPath(...) -> deprecatedencodeRealPath(...) -> deprecatedpathCanWrite(vpath)if (pathCanWrite("user:/Documents")) sendOK();
getUserPermissionGroup()Returns JSON string.
var group = JSON.parse(getUserPermissionGroup());
userIsAdmin()if (!userIsAdmin()) sendResp("admin only");
userExists(username) (admin only)if (userExists("alice")) echo("exists");
createUser(username, password, defaultGroup) (admin only)createUser("alice", "StrongPass", "default");
removeUser(username) (admin only)removeUser("alice");
editUser(...)Currently stubbed and always returns false.
Use requirelib(libName).
requirelib("filelib");
Registered library IDs:
filelibimagelibhttpshareiotappdatasysinfoziplib (includes 7z support via ziplib.extract7zFile, ziplib.list7zFileContents, etc.)sqlite (SQLite database access — not available on linux/mipsle or windows/arm/386)llm (OpenAI / Anthropic LLM chat: text & file based, with pricing & quota)cnn (CXNNAIO vision inference: classification, detection, segmentation, pose, oriented detection, face analysis)sharedspace (multi-user collaboration spaces: texts / images / files / documents, ACLs, persistence)meetroom (MeetRoom control: create / end meetings, attendance - requires MeetRoom module access)office (ArozOS Office suite: .pptx / .xlsx / .docx converters + native zip container pack/unpack)ffmpeg (only when ffmpeg exists on host)Special case:
requirelib("websocket") is injected only in HTTP request context.Load:
requirelib("filelib");
filelib.writeFile(vpath, content)filelib.writeFile("user:/notes.txt", "hello");
filelib.readFile(vpath)var t = filelib.readFile("user:/notes.txt");
filelib.deleteFile(vpath)filelib.deleteFile("user:/notes.txt");
filelib.walk(vpath, mode)mode: all, file, folder
var allFiles = filelib.walk("user:/", "file");
filelib.glob(pattern, sortMode)sortMode supports default and user modes from FS sort settings.
var list = filelib.glob("user:/Desktop/*.jpg", "default");
filelib.aglob(pattern, sortMode)Advanced glob.
var list = filelib.aglob("user:/Desktop/**/*.png", "default");
filelib.readdir(vpath, sortMode)Returns array of objects: {Filename, Filepath, Ext, Filesize, Modtime, IsDir}.
var entries = filelib.readdir("user:/Desktop", "default");
filelib.filesize(vpath)var size = filelib.filesize("user:/movie.mp4");
filelib.fileExists(vpath)if (filelib.fileExists("user:/a.txt")) sendOK();
filelib.isDir(vpath)if (filelib.isDir("user:/Desktop")) sendOK();
filelib.mkdir(vpath)filelib.mkdir("user:/newfolder");
filelib.md5(vpath)var hash = filelib.md5("user:/a.txt");
filelib.mtime(vpath, parseToUnix)parseToUnix=true returns Unix timestamp; otherwise formatted string.
var ts = filelib.mtime("user:/a.txt", true);
filelib.rootName(vpath)Returns storage root display name.
var root = filelib.rootName("user:/Desktop/a.txt");
Load:
requirelib("imagelib");
imagelib.getImageDimension(vpath)Returns [width, height].
var dim = imagelib.getImageDimension("user:/img.jpg");
imagelib.resizeImage(src, dest, width, height)imagelib.resizeImage("user:/img.jpg", "user:/img_small.jpg", 800, 600);
imagelib.resizeImageBase64(src, width, height, format)Returns data URL string.
var b64 = imagelib.resizeImageBase64("user:/img.jpg", 320, 240, "jpeg");
imagelib.cropImage(src, dest, x, y, width, height)imagelib.cropImage("user:/img.jpg", "user:/crop.jpg", 10, 10, 200, 200);
imagelib.loadThumbString(vpath)Returns cached thumbnail base64 string.
var thumb = imagelib.loadThumbString("user:/img.jpg");
imagelib.hasExif(vpath)if (imagelib.hasExif("user:/img.jpg")) echo("has exif");
imagelib.getExif(vpath)Returns JSON string.
var exif = JSON.parse(imagelib.getExif("user:/img.jpg"));
Load:
requirelib("http");
http.get(url)var body = http.get("https://example.com");
http.post(url, jsonString)var body = http.post("https://example.com/api", JSON.stringify({a:1}));
http.head(url, headerKey)headerKey: returns JSON string of all headers.headerKey: returns JSON string of that header value.var headers = JSON.parse(http.head("https://example.com"));
http.getCode(url)Returns status code.
var code = http.getCode("https://example.com");
http.download(url, destDirVpath, filenameOptional)Downloads into destination directory.
http.download("https://example.com/a.zip", "user:/Downloads", "a.zip");
http.getb64(url)var raw = http.getb64("https://example.com/logo.png");
http.redirect(targetUrl, statusCode)Default status code is 307 when omitted.
http.redirect("https://example.com/new", 302);
Load:
requirelib("share");
share.shareFile(vpath, timeoutSec)timeoutSec=0 means no auto-expire.
var uuid = share.shareFile("user:/report.pdf", 3600);
share.removeShare(shareUUID)share.removeShare(uuid);
share.checkShareExists(shareUUID)if (share.checkShareExists(uuid)) sendOK();
share.fileIsShared(vpath)if (share.fileIsShared("user:/report.pdf")) sendOK();
share.getFileShareUUID(vpath)var sid = share.getFileShareUUID("user:/report.pdf");
share.checkSharePermission(shareUUID)var perm = share.checkSharePermission(uuid);
Load:
requirelib("iot");
iot.ready()if (!iot.ready()) sendResp("iot unavailable");
iot.scan()Returns scanned device array.
var devices = iot.scan();
iot.list()Returns cached device array.
var devices = iot.list();
iot.connect(deviceId, username, password, token)iot.connect("dev-1", "admin", "pass", "");
iot.status(deviceId)Returns parsed status object.
var s = iot.status("dev-1");
iot.exec(deviceId, endpointName, payloadObject)Returns parsed result object or false.
var resp = iot.exec("dev-1", "toggle", { value: true });
iot.disconnect(deviceId)iot.disconnect("dev-1");
iot.iconTag(deviceId)var tag = iot.iconTag("dev-1");
Load:
requirelib("appdata");
appdata.readFile(relativePathFromWebRoot)var conf = appdata.readFile("MyApp/config.json");
appdata.listDir(relativeDirFromWebRoot)Returns relative path array.
var files = appdata.listDir("MyApp");
appdata.getModuleList()Returns parsed module list array.
var mods = appdata.getModuleList();
Load:
requirelib("sysinfo");
sysinfo.getCPUUsage()Returns CPU usage percent.
var cpu = sysinfo.getCPUUsage();
sysinfo.getRAMUsage()Returns {used, total, percent}.
var ram = sysinfo.getRAMUsage();
sysinfo.getNetworkUsage()Returns {rxRate, txRate, rxTotal, txTotal} in bytes / bytes per second.
var net = sysinfo.getNetworkUsage();
sysinfo.getDiskInfo()Returns logical disk info array.
var disks = sysinfo.getDiskInfo();
Load:
requirelib("ziplib");
ziplib.extractZipFile(src, destDir)ziplib.extractZipFile("user:/a.zip", "user:/out/");
ziplib.createZipFile(sourcesArrayOrString, outputZip)ziplib.createZipFile(["user:/a.txt", "user:/b.txt"], "user:/bundle.zip");
ziplib.createTarFile(sourcesArrayOrString, outputTar)ziplib.createTarFile(["user:/folder"], "user:/bundle.tar");
ziplib.extractTarFile(srcTar, destDir)ziplib.extractTarFile("user:/bundle.tar", "user:/out/");
ziplib.createTarGzFile(sourcesArrayOrString, outputTarGz)ziplib.createTarGzFile(["user:/folder"], "user:/bundle.tar.gz");
ziplib.extractTarGzFile(srcTarGz, destDir)ziplib.extractTarGzFile("user:/bundle.tar.gz", "user:/out/");
ziplib.createGzFile(srcFile, outputGz)ziplib.createGzFile("user:/a.log", "user:/a.log.gz");
ziplib.extractGzFile(srcGz, outputFile)ziplib.extractGzFile("user:/a.log.gz", "user:/a.log");
ziplib.isValidZipFile(vpath)Checks whether archive format is recognizable.
var ok = ziplib.isValidZipFile("user:/a.zip");
ziplib.listZipFileContents(zipPath)Returns JSON tree string.
var tree = JSON.parse(ziplib.listZipFileContents("user:/a.zip"));
ziplib.listZipFileDir(zipPath, dirPathInZip)Returns immediate child names array.
var items = ziplib.listZipFileDir("user:/a.zip", "docs");
ziplib.getFileFromZip(zipPath, filePathInZip)Extracts one file to tmp:/ and returns that virtual path.
var tmp = ziplib.getFileFromZip("user:/a.zip", "docs/readme.txt");
ziplib.getCompressFileType(vpath)Returns one of zip, 7z, tar, tar.gz, gz, unknown.
var t = ziplib.getCompressFileType("user:/a.tgz");
ziplib.extractAnyFile(srcArchive, destDir)Auto-detects format and extracts.
ziplib.extractAnyFile("user:/archive.any", "user:/out/");
ziplib.createAnyZipFile(sourcesArrayOrString, outputPath, format)format: zip, tar, tar.gz (tgz), gz.
ziplib.createAnyZipFile(["user:/folder"], "user:/bundle.tar.gz", "tar.gz");
The following functions are registered alongside the standard ziplib functions and
operate on .7z archives. Load ziplib with requirelib("ziplib") as normal.
ziplib.extract7zFile(src, destDir) → boolExtracts all files from a 7z archive to destDir.
requirelib("ziplib");
ziplib.extract7zFile("user:/archive.7z", "user:/out/");
ziplib.list7zFileDir(src, dirPathIn7z) → string[]Lists immediate children of a directory inside a 7z archive.
Directories are returned with a trailing /.
requirelib("ziplib");
var items = ziplib.list7zFileDir("user:/archive.7z", "docs");
ziplib.list7zFileContents(src) → string (JSON tree)Returns the full contents of a 7z archive as a JSON tree (same schema as
listZipFileContents).
requirelib("ziplib");
var tree = JSON.parse(ziplib.list7zFileContents("user:/archive.7z"));
ziplib.getFileFrom7z(src, filePathIn7z) → string (vpath)Extracts a single file from a 7z archive to tmp:/ and returns its virtual path.
requirelib("ziplib");
var tmp = ziplib.getFileFrom7z("user:/archive.7z", "docs/readme.txt");
ziplib.extractPartial7z(src, paths, destDir) → boolExtracts selected files or folders from a 7z archive.
paths may be a JS array or a JSON string array.
For folder selections the parent prefix is stripped; for file selections the file
is placed flat in destDir (matching extractPartialZip semantics).
requirelib("ziplib");
ziplib.extractPartial7z("user:/archive.7z", ["docs/", "README.md"], "user:/out/");
ziplib.get7zFileInfo(src) → string (JSON)Returns metadata about a 7z archive:
{ fileCount, dirCount, totalUncompressedSize, totalCompressedSize }.
totalCompressedSize is always 0 because 7z uses solid compression.
requirelib("ziplib");
var info = JSON.parse(ziplib.get7zFileInfo("user:/archive.7z"));
console.log(info.fileCount + " files, " + info.totalUncompressedSize + " bytes");
Load:
requirelib("sqlite");
Platform note: the sqlite library is not available on
linux/mipsle,windows/arm, orwindows/386builds (excluded at compile time).
sqlite.open() returns a connection object. All SQL operations go through that
object. Connections are automatically closed when the script exits; you may also
call db.close() explicitly to release the handle early.
sqlite.open(vpath) → dbOpens (or creates) a SQLite database file at the given virtual path.
Throws a SQLiteError on failure.
requirelib("sqlite");
var db = sqlite.open("user:/.appdata/myapp/data.sqlite");
db.exec(sql, params) → {lastInsertId, rowsAffected}Executes a statement that does not return rows (INSERT, UPDATE, DELETE, CREATE …).
params is an optional JS array of bound values.
db.exec("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)");
db.exec("INSERT INTO notes (body) VALUES (?)", ["Hello world"]);
db.query(sql, params) → object[]Executes a SELECT and returns all matching rows as an array of plain objects.
var rows = db.query("SELECT * FROM notes WHERE id > ?", [0]);
rows.forEach(function(r) { console.log(r.id, r.body); });
db.queryRow(sql, params) → object | nullLike db.query() but returns only the first row, or null if no rows matched.
var row = db.queryRow("SELECT * FROM notes WHERE id = ?", [1]);
if (row) sendJSONResp(row);
db.tables() → string[]Returns the names of all user-created tables in the database.
var tables = db.tables();
sendJSONResp(tables);
db.schema(tableName) → object[]Returns column metadata for the table as an array of
{ cid, name, type, notnull, dflt_value, pk } objects (from PRAGMA table_info).
var cols = db.schema("notes");
sendJSONResp(cols);
db.close() → boolCloses the database connection and releases the handle.
db.close();
requirelib("sqlite");
var db = sqlite.open("user:/.appdata/myapp/tasks.sqlite");
db.exec("CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY KEY, title TEXT, done INTEGER DEFAULT 0)");
// Insert
var res = db.exec("INSERT INTO tasks (title) VALUES (?)", ["Buy milk"]);
console.log("new id:", res.lastInsertId);
// Query
var pending = db.query("SELECT * FROM tasks WHERE done = 0");
sendJSONResp(pending);
db.close();
Load:
requirelib("llm");
The llm library connects to any OpenAI-compatible or Anthropic endpoint
configured by an admin in System Settings > AI Integration > AI Model
(the settings tab and its /system/aimodel/... routes kept their original
"AI Model" name; only the requirelib identifier changed from aimodel to
llm). Per-model pricing and an optional token/cost quota are also defined
there. The wire-protocol logic (OpenAI / Anthropic request building and
response parsing) lives in the standalone mod/aiservers/llm Go package.
llm.chat(prompt, options) → stringSends a single-turn text prompt and returns the assistant's reply.
options is an optional object (see Options below).
requirelib("llm");
var reply = llm.chat("What is the capital of France?");
sendResp(reply);
With a system prompt and model override:
requirelib("llm");
var reply = llm.chat("Summarise this in one sentence.", {
system: "You are a concise summariser.",
model: "gpt-4o-mini"
});
sendResp(reply);
llm.chatWithFile(prompt, files, options) → stringLike llm.chat() but attaches one or more virtual-path files to the message.
Images are sent as base64 vision parts; text files are inlined as labelled text.
files may be a single vpath string or an array.
requirelib("llm");
var reply = llm.chatWithFile(
"Describe what you see in this image.",
"user:/Photos/holiday.jpg"
);
sendResp(reply);
llm.request(messages, options) → objectLow-level call. Accepts the full OpenAI-style messages array and returns the
raw response object (including usage and choices).
requirelib("llm");
var resp = llm.request([
{ role: "system", content: "You are helpful." },
{ role: "user", content: "Hi!" },
{ role: "assistant", content: "Hello! How can I help?" },
{ role: "user", content: "Tell me a joke." }
]);
sendResp(resp.choices[0].message.content);
llm.usage() → objectReturns accumulated token / cost metrics across all models.
requirelib("llm");
var u = llm.usage();
sendJSONResp(u);
// { totalTokens, totalCost, totalRequests, perModel: { ... }, currency, ... }
llm.models() → objectReturns the configured default model name and a list of models that have pricing entries defined in System Settings.
requirelib("llm");
var m = llm.models();
sendJSONResp(m);
// { default: "gpt-4o", models: ["gpt-4o", "gpt-4o-mini", ...] }
llm.listModels() → objectQueries the live endpoint for available models (does not consume tokens).
requirelib("llm");
var m = llm.listModels();
sendJSONResp(m.models);
llm.fileParts(files) → object[]Converts virtual-path file(s) into OpenAI-style content parts that can be
embedded in a messages array for llm.request().
Images become image_url data-URI parts; text files become text parts.
requirelib("llm");
var parts = llm.fileParts(["user:/report.txt"]);
var resp = llm.request([
{ role: "user", content: parts }
]);
sendResp(resp.choices[0].message.content);
All functions that accept options support the following fields (all optional):
| Field | Type | Description |
|---|---|---|
model |
string | Override the configured default model |
system |
string | System prompt (prepended as a system role message) |
endpoint |
string | Override the global endpoint URL |
apikey |
string | Override the global API key |
apiFormat |
string | Wire format: "openai" (default) or "anthropic" |
temperature |
number | Sampling temperature |
max_tokens |
number | Maximum tokens to generate |
Load:
requirelib("cnn");
The cnn library connects to an external CXNNAIO vision-inference server
configured by an admin in System Settings > AI Integration > CNN
Inference (endpoint, optional bearer token, request timeout). Every
function reads its input image from a virtual file path (the calling user
must have read access); the returned object is the server's own response
envelope (object, model, created, image, timing_ms, data, ...),
so it matches the CXNNAIO API documentation field-for-field.
cnn.classify(file, options) → objectImage classification (default model mobilenet-v2).
requirelib("cnn");
var r = cnn.classify("user:/Photos/cat.jpg", { top_k: 3 });
sendJSONResp(r.data); // [{ label, index, score }, ...]
cnn.detect(file, options) → objectObject detection (default model yolo11n).
requirelib("cnn");
var r = cnn.detect("user:/Photos/street.jpg", { score_threshold: 0.3, render: true });
sendJSONResp(r.data); // [{ label, class_id, score, box:{x1,y1,x2,y2} }, ...]
// r.rendered_image is a data URI PNG when render:true was set
cnn.segment(file, options) → objectInstance segmentation (yolo11n-seg). Each item carries a per-instance,
box-cropped mask (mask.data is a base64 PNG).
cnn.pose(file, options) → objectPose estimation (yolo11n-pose), 17 COCO keypoints per detected person.
cnn.oriented(file, options) → objectOriented/rotated-box detection (yolo11n-obb), intended for aerial/top-down imagery.
cnn.faceDetect(file, options) → objectFace detection (default model ultraface-rfb-320).
cnn.faceLandmarks(file, options) → object98-point facial landmarks (pfld). Set options.cropped = true to treat the
whole input image as one face crop instead of detecting faces first.
cnn.faceEmbedding(file, options) → objectL2-normalized 128-d face embedding vector(s) (mbv2facenet).
cnn.faceAttributes(file, options) → objectGender attributes per face (gender-mbv2-0.35). Calls the server's
/v1/faces/gender route (the upstream API doc names this endpoint
/v1/faces/attributes, but the deployed server registers it as gender;
the response object field reads "face.gender").
cnn.faceCompare(fileA, fileB, options) → objectCompares two face photos/crops and returns their cosine similarity. Does not
support options.async (the server has no async variant for this endpoint).
requirelib("cnn");
var r = cnn.faceCompare("user:/a.jpg", "user:/b.jpg", { threshold: 0.5 });
sendResp(r.similarity + " - " + (r.same ? "same person" : "different"));
cnn.analyze(file, tasks, options) → objectRuns several tasks over one image in a single round trip. tasks is an
array ("classify", "detect", "segment", "pose", "oriented",
"faces", "landmarks", "attributes"); options carries an optional
top-level render/async plus per-task parameter blocks keyed by task name.
requirelib("cnn");
var r = cnn.analyze("user:/group.jpg", ["detect", "faces"], {
render: true,
detect: { score_threshold: 0.3 }
});
sendJSONResp(r.results.detect.data);
document_rendered = r.rendered_image; // data URI PNG
cnn.job(id) → objectPolls an async job (see Async below). Returns
{ id, object, status, created, result, error } where status is one of
"queued", "running", "succeeded" or "failed".
cnn.models() → objectLive model registry from the configured server: { object, data: [{ id, object, task, classes, input }, ...] }.
cnn.health() → objectLive server health: { status, version, models_loaded, sessions, uptime_s }.
Every single-image function (classify, detect, segment, pose,
oriented, faceDetect, faceLandmarks, faceEmbedding,
faceAttributes, analyze) accepts options.async = true. When set, the
function returns immediately with a job object instead of blocking:
requirelib("cnn");
var job = cnn.detect("user:/big.jpg", { async: true });
while (job.status === "queued" || job.status === "running") {
delay(500);
job = cnn.job(job.id);
}
sendJSONResp(job.status === "succeeded" ? job.result : job.error);
All single-image functions accept the same options fields, using the
server's own field names so they match the CXNNAIO API documentation
directly (all optional):
| Field | Type | Description |
|---|---|---|
model |
string | Override the server's default model for this task |
score_threshold |
number | Minimum confidence to keep (detect/seg/pose/oriented/faces) |
nms_threshold |
number | IoU suppression threshold (detect/seg/pose/oriented/faces) |
top_k |
number | Number of ranked results (classification) |
max_results |
number | Cap on returned items |
render |
bool | Also return an annotated PNG in rendered_image |
cropped |
bool | Treat the whole input image as one face crop (face endpoints) |
async |
bool | Submit as an async job instead of blocking (see Async jobs) |
cnn.faceCompare uses its own options shape instead: model, threshold,
a_cropped, b_cropped.
Converters between the ArozOS Office suite webapps (src/web/Office/) and
common office file formats. Backed by mod/office (pure Go, no external
dependencies). Word (.docx) and Excel (.xlsx) helpers will join this library
as the Docs and Sheets webapps mature.
Load:
requirelib("office");
office.pptxToPresentation(srcVpath)Parse a PowerPoint .pptx file into the Slides document body schema (see
src/web/Office/common/CONTRACT.md). Returns the body as a JSON string,
or throws on failure. Embedded pictures are inlined as data: URLs; text
boxes, preset shapes, connector lines and tables map to their Slides object
types. Unsupported content (video, native charts, SmartArt) is skipped.
requirelib("office");
var bodyJson = office.pptxToPresentation("user:/Desktop/deck.pptx");
sendJSONResp('{"body":' + bodyJson + '}');
office.presentationToPptx(bodyJson, destVpath)Build a .pptx from a serialized Slides body JSON string and write it to
destVpath. Returns true on success. Image objects must be inlined as
data: URLs and chart objects should carry a client-rendered PNG in
props.png (the Slides webapp does both automatically before calling).
requirelib("office");
if (office.presentationToPptx(data, "user:/Desktop/out.pptx")){
sendResp("OK");
}
office.xlsxToWorkbook(srcVpath)Parse an Excel .xlsx file into the Sheets document body schema. Returns
the body as a JSON string, or throws on failure. Handles values,
formulas (recalculated by the webapp), shared/inline strings, cell styles,
number formats, column widths / row heights, merged cells and frozen panes.
Charts, pivot tables and conditional formatting are skipped. Legacy binary
.xls is rejected with a message asking for .xlsx.
requirelib("office");
var bodyJson = office.xlsxToWorkbook("user:/Desktop/report.xlsx");
sendJSONResp('{"body":' + bodyJson + '}');
office.workbookToXlsx(bodyJson, destVpath)Build a .xlsx from a serialized Sheets body JSON string and write it to
destVpath. Returns true on success. Formulas are written natively so
Excel recalculates them; webapp charts and filters are not exported.
requirelib("office");
if (office.workbookToXlsx(data, "user:/Desktop/out.xlsx")){
sendResp("OK");
}
office.docxToDocument(srcVpath)Parse a Word .docx file into the Docs document body schema. Returns the
body as a JSON string, or throws on failure. Handles paragraphs,
heading/title styles, alignment, inline formatting (bold/italic/underline/
strikethrough, color, size), hyperlinks, lists, tables, embedded images
(inlined as data: URLs), header/footer text and page geometry. Tracked
changes, footnotes and text boxes are ignored. Legacy binary .doc is
rejected with a message asking for .docx.
requirelib("office");
var bodyJson = office.docxToDocument("user:/Desktop/report.docx");
sendJSONResp('{"body":' + bodyJson + '}');
office.documentToDocx(bodyJson, destVpath)Build a .docx from a serialized Docs body JSON string and write it to
destVpath. Returns true on success. Images must be inlined as data:
URLs (the Docs webapp does this automatically before calling).
requirelib("office");
if (office.documentToDocx(data, "user:/Desktop/out.docx")){
sendResp("OK");
}
office.packToFile(envelopeJson, destVpath)Write an Office suite native file (.doca / .xlsa / .ppta) as a zip
container: document.json plus deduplicated binary assets/. Media data
URLs and legacy media?file= links inside the envelope become embedded
assets, so the file stays portable when copied to another machine. Returns
true on success.
office.unpackFromFile(srcVpath)Read a native Office suite file and return its envelope JSON string
with embedded assets re-inlined as data: URLs. Legacy plain-JSON
documents pass through unchanged, so old files keep opening (and are
upgraded to the container format on their next save).
office.unpackToWorkdir(srcVpath, workdirBase)Read a native Office suite container and return its envelope JSON string
with binary assets extracted into <workdirBase>/<doc-hash>/ and referenced
by media?file= links instead of inline base64 - so the JSON stays small
even for video-heavy documents (the Office webapps use
user:/.appdata/Office/cache as the working directory). Legacy plain-JSON
documents pass through unchanged.
requirelib("office");
var envelope = office.unpackToWorkdir("user:/Documents/deck.ppta", "user:/.appdata/Office/cache");
sendJSONResp('{"envelope":' + envelope + '}');
Load:
requirelib("ffmpeg");
Note: library exists only when host has ffmpeg installed.
ffmpeg.convert(input, output, compression)Generic conversion.
ffmpeg.convert("user:/in.mov", "user:/out.mp4", 0);
ffmpeg.audioConvert(input, output, sampleRate, progressFile)ffmpeg.audioConvert("user:/in.wav", "user:/out.mp3", 44100, "tmp:/audio_progress.json");
ffmpeg.imageConvert(input, output, scaleFactor, compressionRate)ffmpeg.imageConvert("user:/in.png", "user:/out.jpg", 0.5, 80);
ffmpeg.videoConvert(input, output, resolution, compressionRate, progressFile)ffmpeg.videoConvert("user:/in.mp4", "user:/out.mp4", "720p", 55, "tmp:/video_progress.json");
ffmpeg.convertWithProgress(input, output, progressFile)ffmpeg.convertWithProgress("user:/in.mp4", "user:/out.gif", "tmp:/conv_progress.json");
The websocket library upgrades the current HTTP connection to a WebSocket session.
It is only available in script paths reached via a live HTTP request context
(standard InterfaceHandler or token-handler routes — not execd children).
Load:
requirelib("websocket");
Note on
delay()after upgrade —websocket.upgrade()replaces the globaldelay()with a message-pumping version. While the script sleeps insidedelay(), any queued inbound frames are dispatched towebsocket.onMessage(if set).delay()is therefore the natural yield point in event-driven loops. WhenonMessageisnullthe buffer is left untouched so thatavailable()andread()can still see the frames.
websocket.upgrade(timeoutSec) → boolUpgrades the HTTP connection to WebSocket and starts the background frame reader.
The connection is closed automatically after timeoutSec seconds of idle time
(default 300). Also installs the message-pumping delay() override.
Returns false if the upgrade fails.
requirelib("websocket");
if (!websocket.upgrade(120)) exit();
websocket.send(text) → boolSends a UTF-8 text frame to the client. Returns false if the connection is closed.
websocket.send("Hello from server");
websocket.read(timeoutMs?) → string | null | falseReads the next inbound message from the internal buffer.
| Return value | Meaning |
|---|---|
string |
Message text |
null |
timeoutMs elapsed with no message; connection still open |
false |
Connection is closed |
timeoutMs = 0 or omitted blocks indefinitely until a message arrives or the
connection closes.
// Block until a message arrives or connection closes
var msg = websocket.read();
// Wait at most 5 s; returns null on timeout
var msg = websocket.read(5000);
if (msg === false) { /* connection closed */ }
if (msg === null) { /* timed out, still open */ }
websocket.available() → numberReturns the number of messages currently queued in the inbound buffer. Non-blocking — safe to call on every iteration of a tight loop.
if (websocket.available() > 0) {
var msg = websocket.read();
}
websocket.isClosed() → boolReturns true when the WebSocket connection is no longer active.
while (!websocket.isClosed()) {
websocket.send("tick");
delay(1000);
}
websocket.onMessageAssign a function(msg) callback to receive messages asynchronously.
The handler fires inside delay() on the script's own goroutine — Otto-safe, no
concurrent JS execution.
Message object properties:
| Property | Type | Description |
|---|---|---|
msg.data |
string |
Text payload |
msg.timestamp |
number |
Arrival time (Unix milliseconds) |
msg.type |
number |
Frame type: 1 = text, 2 = binary |
websocket.onMessage = function(msg) {
console.log("Received at " + msg.timestamp + " ms: " + msg.data);
};
Set back to null to stop receiving callbacks and leave messages in the buffer:
websocket.onMessage = null;
websocket.close()Sends a normal-closure frame and closes the connection.
websocket.close();
Simplest pattern. read(timeoutMs) returns null on timeout so the loop can
send a keep-alive or do other work without blocking forever.
requirelib("websocket");
if (!websocket.upgrade(120)) exit();
websocket.send("Connected. Commands: echo <text> | stop");
while (true) {
var msg = websocket.read(30000); // wait up to 30 s
if (msg === false) break; // remote side closed
if (msg === null) { // 30-second idle timeout
websocket.send("Still here.");
continue;
}
msg = msg.trim();
if (msg === "stop") {
websocket.send("Bye!");
break;
} else if (msg.indexOf("echo ") === 0) {
websocket.send(msg.slice(5));
} else if (msg !== "") {
websocket.send("Unknown command: '" + msg + "'");
}
}
websocket.close();
available() polling (Arduino-style)Use when you want to drain all queued frames in one shot each iteration, or when the main loop body does other work regardless of incoming messages.
onMessage must be null (the default) so that delay() does not consume
frames behind your back.
requirelib("websocket");
if (!websocket.upgrade(120)) exit();
websocket.send("available() polling mode.");
while (true) {
if (websocket.isClosed()) break;
var n = websocket.available();
if (n > 0) {
// Drain all waiting frames without blocking
for (var i = 0; i < n; i++) {
var msg = websocket.read(); // data already queued, returns immediately
if (msg === false) break;
msg = msg.trim();
if (msg === "stop") {
websocket.send("Bye!");
websocket.close();
break;
}
websocket.send("Echo: " + msg);
}
} else {
delay(500); // sleep; buffer is untouched because onMessage is null
}
}
onMessage callback with delay() pumpEvent-driven style. The callback fires inside delay() on the script goroutine.
Use a shared variable to hand data from the callback to the main loop.
requirelib("websocket");
if (!websocket.upgrade(120)) exit();
websocket.send("onMessage mode. Commands: echo <text> | stop");
var lastMessage = "";
websocket.onMessage = function(msg) {
// Runs on the script goroutine during delay() — safe to update shared state
lastMessage = msg.data;
};
while (true) {
if (lastMessage !== "") {
var msg = lastMessage.trim();
lastMessage = "";
if (msg === "stop") {
websocket.send("Bye!");
break;
} else if (msg.indexOf("echo ") === 0) {
websocket.send(msg.slice(5));
} else if (msg !== "") {
websocket.send("Unknown command: '" + msg + "'");
}
}
if (websocket.isClosed()) break;
// delay() pumps the inbound channel and fires onMessage for each queued frame
delay(100);
}
websocket.onMessage = null;
websocket.close();
scheduler)Load with: requirelib("scheduler")
Lets a webapp register, check, and remove background scheduled tasks on behalf of the signed-in user. Tasks call a script that lives inside the webapp's own folder — not in user virtual storage.
Prerequisite — the user must have granted cron-job permission to this app first. The recommended flow is:
- Check
scheduler.hasPermission()in a backend.agiscript.- If false, return a signal to the frontend so it can call
ao_module_requestSchedulerPermission()to show the permission dialog.- After permission is granted, register the task from the backend.
scheduler.hasPermission() → boolReturns true when the current user is allowed to create scheduled tasks.
requirelib("scheduler");
if (!scheduler.hasPermission()) {
sendResp("no_permission");
}
scheduler.registered(taskName, appName) → boolReturns true when a task with the given name is already registered for this user+app combination.
requirelib("scheduler");
if (scheduler.registered("MyApp_DailySync", "MyApp")) {
sendResp("already_registered");
}
scheduler.register(taskName, appName, intervalSecs [, description [, scriptName]]) → boolRegisters a new background task. Returns true on success.
| Parameter | Type | Description |
|---|---|---|
taskName |
string | Unique task identifier (max 32 chars) |
appName |
string | Module folder name (must match ./web/<appName>/) |
intervalSecs |
number | Execution interval in seconds |
description |
string | Optional human-readable description |
scriptName |
string | Script filename inside the app folder (default: "cron.agi") |
requirelib("scheduler");
var ok = scheduler.register("MyApp_DailySync", "MyApp", 86400, "Daily maintenance", "cron.agi");
if (!ok) {
sendResp("register_failed");
}
scheduler.unregister(taskName) → boolRemoves a previously registered task. Returns true on success.
requirelib("scheduler");
scheduler.unregister("MyApp_DailySync");
The cron script must reside inside the webapp's own web folder, not in user virtual storage:
./web/MyApp/
init.agi ← module registration
index.html
backend.agi ← called by the frontend to register/query scheduler
cron.agi ← executed by the scheduler at each interval
The scheduler calls cron.agi with the permissions of the user who approved it, so all file-system and database operations are scoped to that user.
Load:
requirelib("sharedspace");
A shared space is an area where multiple different users can share texts, images, files and collaboratively edited documents together - the collaboration backbone behind chat-style apps, document collaboration and MeetRoom meetings. Every space carries:
"open" (default - the random space ID acts as the
capability, anyone who knows it can read and post), "public"
(discoverable via listPublicSpaces, any logged-in user can self-join)
or "private" (members only, invited by the owner or a space admin);owner / admin / member;Web clients get the same feature set over /system/sharedspace/* and a
realtime WebSocket channel (/system/sharedspace/ws), so items and
document patches posted from AGI appear live in connected clients.
MeetRoom binds one space to every meeting room (see the meetroom API), so
this library is also how scripts read a live meeting's chat and post
messages or files into it.
sharedspace.createSpace(name) → objectCreate a new space owned by the calling user. Returns
{ spaceid, name, owner, items, createdat }.
requirelib("sharedspace");
var space = sharedspace.createSpace("Design sync");
sendJSONResp(space);
sharedspace.listMySpaces() → arrayList the spaces owned by the calling user (same fields as createSpace).
sharedspace.getSpaceInfo(spaceid) → objectDescribe a space by ID. Returns { exists: false } for unknown IDs.
sharedspace.addText(spaceid, text) → string | nullPost a text snippet (clipped to 4000 characters) into the space. Returns the
new item ID, or null on failure.
sharedspace.addFile(spaceid, vpath) → string | nullCopy a file from the calling user's storage into the space. Files with a
raster-image extension (png / jpg / jpeg / gif / webp / bmp) are stored as
image items, everything else as file. Returns the new item ID.
requirelib("sharedspace");
var itemid = sharedspace.addFile(spaceid, "user:/Photo/cat.png");
sharedspace.listItems(spaceid) → array | nullChronological list of items:
{ itemid, type, name, text, size, uploader, origin, time }. type is
"text", "image" or "file"; text is only filled for text items.
sharedspace.getText(spaceid, itemid) → string | nullRead the content of a text item.
sharedspace.saveFileTo(spaceid, itemid, destVpath) → boolCopy an image / file item into the calling user's storage.
sharedspace.removeItem(spaceid, itemid) → boolRemove an item. Only the item's uploader, a space admin or the space owner may remove it.
sharedspace.deleteSpace(spaceid) → boolDelete a space and all its items. Space owner or a space admin only.
sharedspace.createSpaceAdvanced(name, options) → object | nullCreate a space with options: { access, persistent, metadata }. Returns the
space descriptor ({ spaceid, name, owner, access, persistent, items, docs,
members, metadata, createdat } - the same shape every listing below uses),
or null when e.g. persistence is disabled by the administrator.
requirelib("sharedspace");
var space = sharedspace.createSpaceAdvanced("Team chat", {
access: "private",
persistent: true,
metadata: { purpose: "chat" }
});
sharedspace.listPublicSpaces() → arrayThe public space directory (descriptors of every "public" space).
sharedspace.listJoinedSpaces() → arrayDescriptors of every space the calling user belongs to (owner, admin or
member). listMySpaces() remains the owned-only subset.
sharedspace.joinSpace(spaceid) / sharedspace.leaveSpace(spaceid) → boolSelf-join a public (or open) space as a member / leave a space. Private
spaces are invite-only, so joinSpace fails on them.
sharedspace.setAccess(spaceid, access) → boolChange the access mode ("open", "public" or "private"). Managers only.
sharedspace.setMeta(spaceid, key, value) / sharedspace.getMeta(spaceid) → bool / objectSet (empty value deletes) or read the space metadata. Writing requires manager rights; reading requires read access.
sharedspace.addMember(spaceid, username, role) → boolInvite a user with role "admin" or "member" (default). Managers only.
sharedspace.removeMember(spaceid, username) → boolRemove a member (managers) or leave (self). The owner cannot be removed.
sharedspace.listMembers(spaceid) → object | nullMember map { username: role, ... }. Members and managers only.
sharedspace.createDoc(spaceid, name) → object | nullCreate a collaborative document. Returns the document snapshot
{ docid, name, creator, revision, content, createdat, updatedat, updatedby }.
sharedspace.listDocs(spaceid) → array | nullDocument snapshots without their content.
sharedspace.getDoc(spaceid, docid) → object | nullOne document with its full content - also the recovery path after an update conflict.
sharedspace.updateDoc(spaceid, docid, baserev, content) → object | nullCompare-and-swap update: the new content replaces the document body only
when baserev matches the document's current revision. Returns
{ ok: true, revision } on success or
{ ok: false, conflict: true, revision } when someone else updated the
document first - re-fetch with getDoc, merge your change and retry.
Debounce saves (~500ms): every accepted revision is one database write, and
live WebSocket clients receive each revision as a patch frame.
requirelib("sharedspace");
var doc = sharedspace.getDoc(spaceid, docid);
var result = sharedspace.updateDoc(spaceid, docid, doc.revision, doc.content + "\nAppended by AGI");
if (result !== null && result.conflict) {
// someone got there first: re-fetch, rebase, retry
}
sharedspace.deleteDoc(spaceid, docid) → boolDelete a document. Creator or space managers only.
Load:
requirelib("meetroom");
Control interface for the MeetRoom video conferencing WebApp. The library is
only injected for users who have access permission to the MeetRoom
module (the same gate as the /system/meetroom/* endpoints). Every meeting
room owns a shared space where the room's chat and file attachments are
mirrored; posting into that space with the sharedspace API delivers the item
into the live meeting chat.
meetroom.createRoom(title, password) → objectCreate a meeting room hosted by the calling user. Both arguments are
optional ("" for an untitled / open room). Returns
{ roomid, displayid, title, host, protected, participants, createdat, spaceid }
where spaceid is the room's shared space.
requirelib("meetroom");
var room = meetroom.createRoom("Weekly standup", "");
sendJSONResp({ invite: room.displayid, space: room.spaceid });
meetroom.getRoomInfo(roomid) → object | nullDescribe a room. Returns { exists: false } for unknown IDs. spaceid is
included only when the calling user is the room's host.
meetroom.getRoomSpace(roomid, password) → string | nullReturn the room's shared space ID after passing the same password check the join endpoint applies. Use this to chat with a meeting you were invited to:
requirelib("meetroom");
requirelib("sharedspace");
var spaceid = meetroom.getRoomSpace("123456789", "roomPassword");
if (spaceid !== null) {
sharedspace.addText(spaceid, "Reminder: meeting notes are due today");
}
meetroom.listMyRooms() → arrayList the live rooms hosted by the calling user (same fields as createRoom).
meetroom.endRoom(roomid) → boolEnd the meeting for everyone. Host only.
meetroom.getAttendance(roomid) → array | nullExport the room's attendance log:
{ username, joinedat, leftat, present } per join (leftat is 0 while the
participant is still connected). Only the host or a currently connected
participant may read it.
requirelib("meetroom");
var log = meetroom.getAttendance("123456789");
if (log !== null) {
var report = "";
for (var i = 0; i < log.length; i++) {
report += log[i].username + "," + log[i].joinedat + "," + log[i].leftat + "\n";
}
if (requirelib("filelib")) {
filelib.writeFile("user:/Desktop/attendance.csv", report);
}
}
A typical webapp has three files that work together to set up a background task.
./web/MyApp/backend.agi — called from the frontend via ao_module_agirun:
requirelib("scheduler");
var APP = "MyApp"; // matches ./web/MyApp/
var TASK = "MyApp_HourlySync";
var action = getPara("action");
if (action === "status") {
// Report current scheduler state and persisted stats
var TABLE = "MyApp/" + USERNAME;
newDBTableIfNotExists(TABLE);
var lastRun = readDBItem(TABLE, "lastRun");
var runCount = parseInt(readDBItem(TABLE, "runCount") || "0", 10);
sendJSONResp({
hasPermission: scheduler.hasPermission(),
registered: scheduler.registered(TASK, APP),
lastRun: lastRun || null,
runCount: runCount
});
} else if (action === "register") {
if (!scheduler.hasPermission()) {
sendResp("no_permission");
} else if (scheduler.registered(TASK, APP)) {
sendResp("already_registered");
} else {
var ok = scheduler.register(TASK, APP, 3600, "Hourly sync for MyApp");
sendResp(ok ? "ok" : "error");
}
} else if (action === "unregister") {
scheduler.unregister(TASK);
sendOK();
} else {
sendJSONResp({error: "unknown action"});
}
./web/MyApp/cron.agi — executed by the scheduler at each interval:
// Runs with the permissions of the user who approved the task.
// USERNAME, EXECUTION_ID and all standard globals are available.
var TABLE = "MyApp/" + USERNAME;
newDBTableIfNotExists(TABLE);
// Persist a timestamp and run counter for the frontend to display
writeDBItem(TABLE, "lastRun", new Date().toISOString());
var count = parseInt(readDBItem(TABLE, "runCount") || "0", 10);
writeDBItem(TABLE, "runCount", String(count + 1));
// EXECUTION_ID is a UUIDv4 unique to this invocation — appears in scheduler logs
console.log("MyApp tick [" + EXECUTION_ID + "] user=" + USERNAME + " run=" + (count + 1));
sendOK();
./web/MyApp/index.html — frontend snippet that requests permission and polls stats:
<script src="../script/ao_module.js"></script>
<script>
var APP = "MyApp";
var TASK = "MyApp_HourlySync";
function checkStatus() {
ao_module_agirun("MyApp/backend.agi", {action: "status"}, function(data) {
document.getElementById('last-run').textContent =
data.lastRun ? new Date(data.lastRun).toLocaleString() : "Never";
document.getElementById('run-count').textContent = data.runCount;
if (!data.registered && data.hasPermission) {
document.getElementById('btn-enable').style.display = '';
}
});
}
function enableScheduler() {
ao_module_requestSchedulerPermission({
appName: APP,
appIcon: APP + "/img/icon.png",
taskName: TASK,
scriptName: "cron.agi", // filename inside ./web/MyApp/
interval: 3600, // seconds
description: "Hourly sync for MyApp."
}, function(result) {
if (result && result.allowed) checkStatus();
});
}
checkStatus();
setInterval(checkStatus, 30000);
</script>
This documentation covers all available AGI APIs with practical examples. For more advanced usage, refer to the existing module implementations in the system.
requirelib("audio") is registered in code but currently has no callable functions.filelib currently does not expose writeBinaryFile / readBinaryFile on the public filelib object.false or null on failure; many also raise AGI runtime errors.userExists, createUser, removeUser), check userIsAdmin() first.