# Imports and Package Management

Use standard JavaScript imports like `import dayjs from "dayjs@1.11.11"` 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.*`

```javascript
// @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.

```javascript
// @zw-run-locally

// ESM default import
import dayjs from "dayjs@1.11.11";
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("lodash@4.17.21");
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 time window or prevent automatic uninstall, use `zw.import()` with `uninstallIfUnusedFor` (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`

  ```javascript
  // @zw-disable-auto-import

  import dayjs from "dayjs@1.11.11"; // throws error
  ```

***

### Imports with `zw.import()`&#x20;

Use `zw.import()` when you need options or want to import several packages at once.

#### **At a Glance**

* <mark style="color:$info;">async</mark> \
  `await zw.import(pkg: string | string[] | { [key: string]: string }, options?: ImportOptions)` \
  → installs if needed and returns the loaded module(s)

```typescript
// Reference only — not runnable in Write JS
type ImportOptions = {
  uninstallIfUnusedFor?: number | null; // hours; default 168 (1 week); null = never uninstall
  isolate?: boolean;                    // scope to this TaskBot only; default false
  preferDefault?: boolean;              // advanced; default true
};
```

#### **Examples**

**Single package with options**

```javascript
// @zw-run-locally
const dayjs = await zw.import(
  "dayjs@1.11.11",
  { isolate: true, uninstallIfUnusedFor: 300 }
);
```

**Multiple packages as an array**

```javascript
// @zw-run-locally
const [dayjs, lodash] = await zw.import(
  ["dayjs@1.11.11", "lodash@4.17.21"]
);
```

**Named mapping as an object**

```javascript
// @zw-run-locally
const { time, dash } = await zw.import(
  { time: "dayjs@1.11.11", dash: "lodash@4.17.21" }
);
```

**Installing from Git (HTTPS only)**

```javascript
// @zw-run-locally
const gitLodash = await zw.import(
  "git+https://github.com/lodash/lodash.git"
);
```

**Never uninstall**

```javascript
// @zw-run-locally
const lodash = await zw.import(
   "lodash@4.17.21",
   { uninstallIfUnusedFor: null }
);
```

#### **Import Option Details**

* **`uninstallIfUnusedFor` (default: `168`)**\
  Defaults to 168 hours (**one week**). Set to `null` to keep the package on the device indefinitely.
* **`isolate` (default: `false`)**\
  When `true`, 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 with `zw.packages.*` (see details further below).
* **`preferDefault` (default: `true`)** — *advanced*\
  Returns `module.default` when present (otherwise the module object). This only changes the return shape; it doesn’t affect how the module is resolved.

  ```javascript
  // @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 first argument (package input).

* **String**\
  `"lodash"`, `"lodash@4.17.21"`, `"lodash/chunk"`,\
  `"https://github.com/user/repo.git#main"` or `"git+https://github.com/user/repo.git#main"`
* **Array of strings**\
  `["dayjs@1.11.11", "lodash@4.17.21"]`
* **Object mapping**\
  `{ util: "lodash@4.17.21", time: "dayjs@1.11.11" }`

**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.

```javascript
// @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 "playwright@1.46.0";
```

#### 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 v4 `chalk@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.

```javascript
// @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("dayjs@1.11.11"))().toISOString());
}

// 2. Recommended: reuse a local reference
const dayjs = await zw.import("dayjs@1.11.11");
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("dayjs@1.11.11");
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

* <mark style="color:$info;">async</mark> \
  `await zw.packages.list()` \
  → returns `string[]` of **package IDs**.
* <mark style="color:$info;">async</mark> \
  `await zw.packages.uninstall(id: string)` \
  → removes one.
* <mark style="color:$info;">async</mark> \
  `await zw.packages.uninstallAll()` \
  → removes all user-managed packages.

{% hint style="info" %}
Built-ins (`axios@^1.6.6`, `playwright@^1.45.0`, and Node core modules like `os`, `fs`, etc.) do not appear in `list()` and cannot be uninstalled.
{% endhint %}

#### 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**

```typescript
lodash@4.17.21
dayjs@1.11.11
12345_dayjs@1.11.0   // 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**

```javascript
// @zw-run-locally

const ids = await zw.packages.list();
await zw.log("packages", ids);
```

**Uninstall by matching name/version in the package ID**

```javascript
// @zw-run-locally

const ids = await zw.packages.list();
const target = ids.find(id => id.includes("lodash@4.17.21"));
if (target) await zw.packages.uninstall(target);
```

**Uninstall all for a specific TaskBot**

```javascript
// @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**

```javascript
// @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`.&#x20;

{% hint style="success" %}
You can still ***use*** imported packages in the browser by exposing functions from a local block (see below).
{% endhint %}

### 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.

```javascript
// 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));
  }
});
```

```javascript
// 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);
```

{% hint style="warning" %}
Functions exposed via `context.exposeFunction()` always become **asynchronous** in the browser, even if they were defined as synchronous. Always call them with `await`.
{% endhint %}
