# 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 %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.zerowork.io/using-zerowork/using-building-blocks/write-javascript/imports-and-package-management.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
