# Custom Local JavaScript transforms

Write text-in/text-out JavaScript transforms that run locally in Nibit.

Custom Local JavaScript transforms let you write small text-in/text-out scripts for formatting, cleanup, extraction, or repetitive edits that do not need AI.

They run locally: on Android they execute on-device, and in the web dashboard the **Try it** panel runs in your browser. Nibit stores and syncs the JavaScript source for Cloud Pro users; compiled bytecode and execution logs stay local.

## Plan availability

| Capability | Free | Local Pro | Cloud Pro |
|---|---:|---:|---:|
| Use built-in local transforms | ✅ | ✅ | ✅ |
| View/copy built-in equivalent JavaScript where available | ✅ | ✅ | ✅ |
| Fork built-ins into editable custom Local JavaScript | — | ✅ | ✅ |
| Create and run custom Local JavaScript on Android | — | ✅ | ✅ |
| Import/export `.nibit-transform` bundles on Android | — | ✅ | ✅ |
| Create, test, import/export, and sync Local JavaScript from the web dashboard | — | — | ✅ |
| Create and run custom AI prompt transforms | — | — | ✅ |

## Authoring contract

Each transform defines an async function named `transform`:

```js
async function transform(input, nibit) {
  return input.trim()
}
```

- `input` is the text Nibit is transforming.
- `nibit` contains the local APIs listed below.
- Return a **string**. Returning anything else is treated as an error.
- Throwing an error, rejecting a Promise, timing out, or returning a non-string preserves the original input.
- Source is limited to **128 KB**.

## Available APIs

All v1 APIs are async-safe, even when the current implementation can return immediately.

### `nibit.clipboard.read()`

Reads the current clipboard text when available.

```js
async function transform(input, nibit) {
  const clip = await nibit.clipboard.read()
  return `${input}\n\nClipboard: ${clip ?? ''}`
}
```

### `nibit.context.currentApp()`

Returns context about the current app when Nibit can provide it. In the web dashboard test runner, this comes from the **Current app** test field.

```js
async function transform(input, nibit) {
  const app = await nibit.context.currentApp()
  nibit.log(`Current app: ${app ?? 'unknown'}`)
  return input
}
```

### `nibit.log()` and `console.*`

Use logs while testing or troubleshooting. Recent logs are stored locally with execution metadata, but Nibit does not store raw input or output by default.

```js
async function transform(input, nibit) {
  console.log('Starting cleanup')
  nibit.log(`Input length: ${input.length}`)
  return input.replace(/\s+/g, ' ').trim()
}
```

## Examples

### Normalize whitespace

```js
async function transform(input, nibit) {
  nibit.log('Normalizing whitespace')
  return input.replace(/\s+/g, ' ').trim()
}
```

### Keep only email addresses

```js
async function transform(input, nibit) {
  const emails = input.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi) ?? []
  return [...new Set(emails)].join('\n')
}
```

### Add a prefix to every non-empty line

```js
async function transform(input, nibit) {
  return input
    .split('\n')
    .map(line => line.trim())
    .filter(Boolean)
    .map(line => `- ${line}`)
    .join('\n')
}
```

## What is not available in v1

Custom transforms do **not** get APIs for network requests, filesystem access, launching apps, or arbitrary Android/browser integration. Keep transforms focused on text processing.

## Privacy and safety

- Local JavaScript source is the canonical saved form.
- Cloud Pro sync and backups store source plus safe metadata.
- Bytecode caches are local only.
- Logs are local rolling history.
- Raw input and output are not stored in execution logs by default.
- If a transform fails, Nibit leaves the original input unchanged.
