Imports and Package Management (upcoming)
Available in version 1.1.72 (not yet live).
Use standard JavaScript imports like import dayjs from "[email protected]"
or use the zw
API zw.import()
for custom options to install (if needed) and import packages during a TaskBot run. ZeroWork installs missing packages and makes them available to your code.
Manage packages with zw.packages.list()
, zw.packages.uninstall()
, and zw.packages.uninstallAll()
.
API zw.import()
, zw.packages.*
// @zw-run-locally
// Use desktop automation in ZeroWork
import { execSync } from "child_process";
import { keyboard } from "@computer-use/nut-js";
// Open an editor depending on OS
if (process.platform === "win32") {
execSync("start notepad");
} else if (process.platform === "darwin") {
execSync(`osascript -e 'tell application "TextEdit" to activate' -e 'tell application "TextEdit" to make new document'`);
} else {
execSync("gedit || xed || kate || nano", { shell: "/bin/bash" });
}
// Wait briefly for the editor window to appear
await zw.delay({ min: 2_000 });
// Type into the editor
await keyboard.type("Hello from ZeroWork desktop automation!");
1. Imports
Standard Imports
You can use standard ESM or CommonJS syntax. Any such import installs the package if needed, then loads it.
// @zw-run-locally
// ESM default import
import dayjs from "[email protected]";
await zw.log("now", dayjs().toISOString());
// ESM subpath import
import chunk from "lodash/chunk";
await zw.log("chunked result", chunk([1, 2, 3, 4], 2));
// CommonJS require
const _ = require("[email protected]");
await zw.log("uniq result", _.uniq([1, 1, 2, 3]));
// Import from Git (HTTPS only)
import lodash from "git+https://github.com/lodash/lodash.git";
Notes
Installed packages are available to any TaskBot by default. To scope per TaskBot, use
zw.import()
with{ isolate: true }
(see details further below).If a package isn’t used for one week, it's removed automatically. To change this window or prevent automatic uninstall, use
zw.import()
withuninstallIfUnusedFor
(see details further below).Subpaths like
"lodash/chunk"
and Git repositories are supported.You can disable auto-imports by adding a comment
// @zw-disable-auto-import
// @zw-disable-auto-import import dayjs from "[email protected]"; // throws error
Imports with zw.import()
zw.import()
Use zw.import()
when you need options or want to import several packages at once.
At a Glance
async
await zw.import(options: ImportOptions, pkg: string | string[] | { [key: string]: string })
→ installs if needed and returns the loaded module(s)
// Reference only — not runnable in Write JS
type ImportOptions = {
uninstallIfUnusedFor: number | null; // hours; default 168; null = never uninstall
isolate: boolean; // scope to this TaskBot only; default false
preferDefault: boolean; // advanced; default false
};
Examples
Single package with options
// @zw-run-locally
const dayjs = await zw.import(
{ isolate: true, uninstallIfUnusedFor: 300 },
"[email protected]"
);
Multiple packages as an array
// @zw-run-locally
const [dayjs, lodash] = await zw.import(
{ isolate: true },
["[email protected]", "[email protected]"]
);
Named mapping as an object
// @zw-run-locally
const { time, dash } = await zw.import(
{ uninstallIfUnusedFor: null },
{ time: "[email protected]", dash: "[email protected]" }
);
Installing from Git (HTTPS only)
// @zw-run-locally
const gitLodash = await zw.import(
{ uninstallIfUnusedFor: null },
"git+https://github.com/lodash/lodash.git"
);
Use default options by passing an empty object {}
// @zw-run-locally
const lodash = await zw.import({}, "[email protected]");
Import Option Details
uninstallIfUnusedFor
(default:168
) Defaults to 168 hours (one week). Set tonull
to keep the package on the device indefinitely.isolate
(default:false
) Whentrue
, the package is scoped to this TaskBot only and isn’t visible in other TaskBots. Useful when different TaskBots need different versions of the same package. Note: If the TaskBot is deleted, the package isn’t removed automatically; you can manage it later withzw.packages.*
(see details further below).preferDefault
(default:true
) — advanced Returnsmodule.default
when present (otherwise the module object). This only changes the return shape; it doesn’t affect how the module is resolved.// @zw-run-locally // In most cases, leave preferDefault as is (true). const mysql = await zw.import({}, "mysql2@latest/promise"); // works const { chalk } = await zw.import({}, { chalk: "chalk@4" }); // works
Supported Package Inputs
You can pass any of the following to zw.import()
as the second argument.
String
"lodash"
,"[email protected]"
,"lodash/chunk"
,"https://github.com/user/repo.git#main"
or"git+https://github.com/user/repo.git#main"
Array of strings
["[email protected]", "[email protected]"]
Object mapping
{ util: "[email protected]", time: "[email protected]" }
Invalid inputs that are rejected
Tarball URLs, local file paths, and directories
Non-HTTPS Git URLs
Special Package Types
Built-in Packages
These packages are pre-bundled and resolve without installation.
Node core modules such as
fs
,os
, etc.axios
pinned to^1.6.6
playwright
pinned to^1.45.0
They don’t appear in zw.packages.list()
, and you can’t change their versions or uninstall them.
// @zw-run-locally
import * as fs from "fs"; // pre-bundled; no installation occurs
import axios from "axios"; // resolves to axios@^1.6.6
// Version spec is ignored; resolves to pinned playwright@^1.45.0
import playwright from "[email protected]";
Pure ESM Packages
ZeroWork isn’t pure ESM yet. Pure ESM packages aren’t supported and will throw an error similar to:
This package appears to be pure ESM. Pure ESM packages are currently not supported. To confirm, check the package documentation.
Workarounds for pure ESM packages
Use a maintained non-ESM fork if available. For example,
cacheable-lookup
is a pure ESM module but a maintained fork is available:@esm2cjs/cacheable-lookup
.Pin a non-ESM version if the project provides one. For example, the latest
chalk
version is pure ESM but its documentation recommends pinning to v4chalk@4
for a CommonJS build.If the package is pure ESM with no CommonJS fork or version, you will have to find an alternative.
Native Packages
Packages that include native code (C/C++ or other system-level bindings) may not work reliably. They often require a compatible Node version, build tools, or system libraries. This typically affects modules that talk directly to the operating system or hardware — for example node-window-manager
, robotjs
, or ffi-napi
. Advanced users with the right setup might get them working, but they aren’t guaranteed to install or run on all systems.
Reusing References
Import once and reuse the reference, as is generally best practice. The package won’t reinstall if it’s already installed, but package parsing and resolution still add overhead.
// @zw-run-locally
// 1. Anti-pattern: import inside the loop (re-parses/resolves each time)
for (let i = 0; i < 1_000_000; i++) {
await zw.log((await zw.import({}, "[email protected]"))().toISOString());
}
// 2. Recommended: reuse a local reference
const dayjs = await zw.import({}, "[email protected]");
for (let i = 0; i < 1_000_000; i++) {
await zw.log(dayjs().toISOString());
}
// 3. Optional (micro-optimization): reuse across Write JS blocks via run state
const cachedDayjs = await zw.import({}, "[email protected]");
const state = zw.state.access();
if (!state.cachedResolvedImports) {
state.cachedResolvedImports = {};
}
state.cachedResolvedImports.dayjs = cachedDayjs;
// In a later building block:
await zw.log(zw.state.access().cachedResolvedImports.dayjs().toISOString());
2. Package Management
At a Glance
async
await zw.packages.list()
→ returnsstring[]
of package IDs.async
await zw.packages.uninstall(id: string)
→ removes one.async
await zw.packages.uninstallAll()
→ removes all user-managed packages.
What’s a Package ID?
A package ID uniquely identifies an installed package instance. It’s an opaque string made of an optional isolation prefix (<taskbotId>_
), a lower-cased, sanitized package name, @
, and a version tag (<semver>
, latest
, git
, or a commit hash). It may differ from your original import string (subpaths aren’t included; characters are normalized).
Examples
[email protected]
[email protected]
[email protected] // isolated to a specific TaskBot (prefix includes its ID)
repo_name@git // Git source without a specific commit
repo_name@9f3a2c4 // Git source pinned to a commit
Use these IDs with zw.packages.uninstall(id)
.
Examples
List packages
// @zw-run-locally
const ids = await zw.packages.list();
await zw.log("packages", ids);
Uninstall by package name/version
// @zw-run-locally
const ids = await zw.packages.list();
const target = ids.find(id => id.includes("[email protected]"));
if (target) await zw.packages.uninstall(target);
Uninstall all for a specific TaskBot
// @zw-run-locally
const myTaskBotId = 12345;
const ids = await zw.packages.list();
const targets = ids.filter(id => id.startsWith(`${myTaskBotId.toString()}_`));
for (const target of targets) {
await zw.packages.uninstall(target);
}
Uninstall all
// @zw-run-locally
await zw.packages.uninstallAll();
3. Local and Browser Execution
Package installation and management run locally only. Standard import
/require
, zw.import()
, and zw.packages.*
are supported only when the Write JS block runs outside the browser context. Enable the Run locally checkbox in the block UI, or add the comment // @zw-run-locally
.
You can still use imported packages in the browser by exposing functions from a local block (see below).
How to Use Imported Packages in the Browser
Import locally in one block, expose a function, then call it from a later block that runs in the browser.
// Write JS Block A — runs locally
// @zw-run-locally
import lodash from "lodash";
// Illustrative for simplicity (but prefer pattern below)
const context = zw.browserContext.getContext();
await context.exposeFunction("exposedChunkFn", (arr) => lodash.chunk(arr, 2));
// Better pattern for context continuity
// Ensure function is exposed on any (re)launch
await zw.browserContext.setDefaults({
onContextReady: async (context) => {
await context.exposeFunction("exposedChunkFn", (arr) => lodash.chunk(arr, 2));
}
});
// Write JS Block B — runs in the browser
const result = await exposedChunkFn([1, 2, 3, 4]); // [[1,2],[3,4]]
await zw.log("exposedChunkFn result", result);
Functions exposed via context.exposeFunction()
always become asynchronous in the browser, even if they were defined as synchronous. Always call them with await
.
Last updated
Was this helpful?