# Start a terminal app from the hello-pear-bare template



This is the terminal counterpart to [Start from the hello-pear-electron template](/p2p/getting-started/from-a-template/start-from-hello-pear-electron). Instead of a desktop window, you start from a finished command-line boilerplate and learn how a Bare app wires peer-to-peer updates and replication through a worker—then ship it as a standalone binary.

[`holepunchto/hello-pear-bare`](https://github.com/holepunchto/hello-pear-bare) is Holepunch's official boilerplate for peer-to-peer terminal applications. Where the Electron template embeds [`pear-runtime`](/pear/reference/pear/runtime) into Electron, this template embeds it into [Bare](/bare/reference/bare/modules), the zero-core embeddable JavaScript runtime. The application and runtime compile into a single standalone executable per OS and architecture with no peer dependencies—no Node.js, and no Bare or [Pear CLI](/pear/reference/pear/cli) required on the user's machine.

With this shape you can build CLIs, REPLs, TUIs, long-running services, or transport hooks—anything headless—and ship it with peer-to-peer over-the-air (OTA) updates. The Pear CLI v3 is built on this same architecture: a Bare standalone executable that updates itself peer-to-peer through `pear-runtime`.

<Callout type="info">
  New to peer-to-peer? [Peer-to-peer, demystified](/p2p/explanation/peer-to-peer-demystified) explains how peers find each other and connect directly—no servers—and [How the stack fits together](/p2p/explanation/how-the-stack-fits-together) maps the pieces you'll wire together.
</Callout>

Take this path when you want to distribute a terminal app as a single executable and update it over the air.
If your program is a short-lived command that runs and exits, follow the [hello-pear-daemon template](/p2p/getting-started/from-a-template/start-from-hello-pear-daemon). If you want to build a desktop app instead, follow the [hello-pear-electron template](/p2p/getting-started/from-a-template/start-from-hello-pear-electron).

<Callout type="info">
  **Need the `pear` CLI?** Install it from **[install.pears.com](https://install.pears.com)**, or prefix any command below with `npx`. See [Install & upgrade](/pear/reference/pear/cli#install) for details.
</Callout>

<Callout type="info">
  **New to the CLI?** Run **`pear --menu`** (Pear 3.2.0+) to browse every command from a filterable list and fill in its flags as a form, instead of memorizing them. See [Browse commands with the interactive menu](/pear/how-to/browse-commands-with-the-interactive-menu).
</Callout>

## Clone and run [#clone-and-run]

### 1. Clone the repository [#1-clone-the-repository]

Clone the repository and install dependencies with the following commands:

```bash
git clone https://github.com/holepunchto/hello-pear-bare
cd hello-pear-bare
npm install
```

### 2. Create a valid `upgrade` link [#2-create-a-valid-upgrade-link]

The template ships with a placeholder `upgrade` link in `package.json`. Until you replace it, startup fails with `INVALID_URL`. Create a real link with [`pear touch`](/pear/reference/pear/cli#pear-touch-flags):

```bash
pear touch
```

This prints a link, for example: `pear://qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5o`.

### 3. Set the `upgrade` link in `package.json` [#3-set-the-upgrade-link-in-packagejson]

Set the `upgrade` field in `package.json` to the link you just created:

```json
"upgrade": "pear://qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5o"
```

### 4. Run the app [#4-run-the-app]

`npm start` runs `bare bin.mjs --no-updates`: the process starts in development mode with over-the-air updates disabled, so a live release never swaps the binary while you work.

```bash
npm start
```

To exercise the updater locally, opt back in:

```bash
npm start -- --updates
```

## Map the template [#map-the-template]

| Path                                                                                          | What it is                                                                                                                                                                                                                                                                                                      | Do you edit it?                |
| --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| [`bin.mjs`](https://github.com/holepunchto/hello-pear-bare/blob/main/bin.mjs)                 | The entrypoint—parses CLI flags, resolves the storage path, constructs the `App`, and logs its updater events.                                                                                                                                                                                                  | Yes—your startup and CLI.      |
| [`app.js`](https://github.com/holepunchto/hello-pear-bare/blob/main/app.js)                   | The `App` class (a [`ready-resource`](https://github.com/holepunchto/ready-resource)). Spawns the Bare worker with [`PearRuntime.run`](/pear/reference/pear/runtime#running-workers), wraps its IPC in a [`FramedStream`](https://www.npmjs.com/package/framed-stream), and turns updater messages into events. | Sometimes—app lifecycle.       |
| [`workers/main.js`](https://github.com/holepunchto/hello-pear-bare/blob/main/workers/main.js) | The Bare [worker](/pear/explanation/workers) that owns the peer-to-peer code and the [`pear-runtime`](/pear/reference/pear/runtime) updater. Its source is inlined into your repo.                                                                                                                              | Yes—this is your backend.      |
| [`package.json`](https://github.com/holepunchto/hello-pear-bare/blob/main/package.json)       | App metadata, scripts (including `inline-worker`), the `upgrade` link, the worker's runtime dependencies, and the per-platform `make:*` build targets.                                                                                                                                                          | Yes—branding and release link. |
| [`scripts/make.js`](https://github.com/holepunchto/hello-pear-bare/blob/main/scripts/make.js) | The `npm run make` builder—runs [`bare-build`](https://github.com/holepunchto/bare-build) for the host OS and architecture (or the target in `HOST`), and signs the binary when signing credentials are set.                                                                                                    | Rarely.                        |
| [`test/index.js`](https://github.com/holepunchto/hello-pear-bare/blob/main/test/index.js)     | The [`brittle`](https://github.com/holepunchto/brittle) test entry, run by `npm test`.                                                                                                                                                                                                                          | Yes—your tests.                |

Like the Electron template, the peer-to-peer logic lives in a Bare [worker](/pear/explanation/workers); `bin.mjs` and `app.js` are the host that spawns it and drives updates. There's no renderer or preload bridge—your "frontend" is the terminal.

<Callout type="info">
  `workers/main.js` is an inlined copy of the [`hello-pear-worker`](https://github.com/holepunchto/hello-pear-worker) package's source, and the packages it needs at runtime are listed in the template's own `package.json`. That file is where your peer-to-peer code goes—open [Corestore](/p2p/reference/helpers/corestore) cores, join [Hyperswarm](/p2p/reference/building-blocks/hyperswarm) topics, and run your protocol.

  The cloned `workers/main.js` starts with a comment recording the worker's source URL and version. While that comment is there, `npm run inline-worker` ([`inline-worker-module`](https://www.npmjs.com/package/inline-worker-module) run against the installed `hello-pear-worker`) overwrites the file with the installed version of the worker, installs the worker's runtime dependencies, and merges its `imports` map into `package.json`. Delete the comment before you edit the file: without it the command refuses to overwrite `workers/main.js` and exits with an error, so your changes are safe.
</Callout>

## How it's wired [#how-its-wired]

The template is three layers: `bin.mjs` (entry + CLI), `app.js` (the host that runs the worker and updater), and the Bare [worker](/pear/explanation/workers) that holds your peer-to-peer code.

`bin.mjs` is the Bare entry process. It parses two flags with [`paparam`](https://github.com/holepunchto/paparam)—`--storage` and `--no-updates`—then constructs the `App`:

```js file=<rootDir>/examples/getting-started/hello-pear-bare/bin.mjs#L13-L18 title="bin.mjs"
const cmd = command(
  appName,
  summary(pkg.description),
  flag('--storage <dir>', 'custom storage directory'),
  flag('--no-updates', 'disable OTA updates for this run')
)
```

It resolves a storage directory, then hands the runtime configuration to a `new App(...)`. In development (launched with `bare`) storage is a temporary directory; a packaged binary uses the persistent per-app directory from [`bare-storage`](/bare/reference/bare/modules), and `--storage` overrides both—see [Storage and distribution](/pear/explanation/storage-and-distribution):

```js file=<rootDir>/examples/getting-started/hello-pear-bare/bin.mjs#L23-L37 title="bin.mjs"
const updates = cmd.flags.updates
const storage = cmd.flags.storage || (isDev ? null : path.join(persistent(), appName))
const dir = storage || path.join(os.tmpdir(), 'pear', appName)

console.log(`${appName} v${pkg.version}`)
console.log(`Updates: ${updates === false ? 'disabled' : 'enabled'}`)

const app = new App({
  dir,
  app: isDev ? null : os.execPath(),
  updates,
  version: pkg.version,
  upgrade: pkg.upgrade,
  name: isWindows ? appName + '.exe' : appName
})
```

`App` (in `app.js`) is a [`ready-resource`](https://github.com/holepunchto/ready-resource). When it opens, it spawns the Bare worker with [`PearRuntime.run`](/pear/reference/pear/runtime#running-workers)—passing the runtime config as arguments—and wraps the worker's IPC pipe in a [`FramedStream`](https://www.npmjs.com/package/framed-stream):

```js file=<rootDir>/examples/getting-started/hello-pear-bare/app.js#L20-L38 title="app.js"
  _open() {
    this.IPC = PearRuntime.run(require.resolve('./workers/main.js'), [
      String(this.updates),
      this.version,
      this.upgrade,
      this.name,
      this.dir,
      this.app || ''
    ])
    this.pipe = new FramedStream(this.IPC)

    this.pipe.on('data', (data) => this._onmessage(data))
    this.pipe.on('error', (err) => this.emit('error', err))
    this.IPC.on('error', (err) => this.emit('error', err))
    this.IPC.on('exit', (code) => {
      if (code === 0 || this.closing !== null || this.closed) return
      this.emit('error', new Error(`Updates worker exited with code ${code}`))
    })
  }
```

The worker owns the peer-to-peer code and the [`pear-runtime`](/pear/reference/pear/runtime) updater. It messages the host, and `App` turns those into `updating`, `updated`, and `update-applied` events—which `bin.mjs` logs—applying each downloaded release automatically. Any other message the worker writes, such as its startup greeting, arrives as a generic `message` event that `bin.mjs` prints. `SIGHUP`, `SIGINT`, `SIGQUIT`, and `SIGTERM` handlers call `app.exit()` to close cleanly. For the model behind these updates, see [Pear OTA](/pear/reference/pear/runtime#updates):

```js file=<rootDir>/examples/getting-started/hello-pear-bare/bin.mjs#L39-L51 title="bin.mjs"
app.on('message', (message) => console.log(message))
app.on('updating', () => console.log('[updater] getting new update'))
app.on('updating-delta', (delta) => console.log('[updater]', delta))
app.on('updated', () => console.log('[updater] update complete... applying'))
app.on('update-applied', () =>
  console.log('[updater] applied update, restart to run latest version')
)
app.on('error', (err) => console.error('[app:error]', err))

process.on('SIGHUP', () => app.exit(129))
process.on('SIGINT', () => app.exit(130))
process.on('SIGQUIT', () => app.exit(131))
process.on('SIGTERM', () => app.exit(143))
```

`bin.mjs` also logs `updating-delta` events, which carry download progress, but this template's worker never forwards that progress, so that handler never fires.

<Mermaid
  chart="flowchart LR
  cli[&#x22;bin.mjs (entry)&#x22;] --> app[&#x22;app.js (App)&#x22;]
  app -->|&#x22;PearRuntime.run&#x22;| worker[&#x22;workers/main.js (Bare worker)&#x22;]
  worker --> updater[&#x22;pear-runtime updater&#x22;]
  worker --> store[&#x22;Corestore&#x22;]
  worker --> swarm[&#x22;Hyperswarm&#x22;]
  swarm <-->|&#x22;replicate update drive&#x22;| peers((seeding peers))"
/>

## Short-lived CLIs: hello-pear-daemon [#short-lived-clis-hello-pear-daemon]

`hello-pear-bare` keeps a long-lived process: the CLI stays up while its worker runs. If your tool is a short-lived command (like `git`) that shouldn't block on an update check, start from the sibling boilerplate [`holepunchto/hello-pear-daemon`](https://github.com/holepunchto/hello-pear-daemon) instead. It uses the same `upgrade` field, per-platform build targets, and [`pear install`](/pear/reference/pear/cli#pear-install)/[`pear seed`](/pear/explanation/availability-and-blind-peering#seeding-with-pear-seed) release flow. What differs is how updates run: `pear-runtime` lives in a detached [`bare-daemon`](https://github.com/holepunchto/bare-daemon) process that the foreground command spawns and returns from immediately, so the command exits while the daemon updates in the background.

| Repository                                                                      | Shape                                                                                                   | Use it for                                                           |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| [`hello-pear-bare`](https://github.com/holepunchto/hello-pear-bare) (this page) | `pear-runtime` inside a Bare [worker](/pear/explanation/workers) thread, with the worker source inlined | Long-lived programs (services, REPLs, TUIs) and reusable backends.   |
| [`hello-pear-daemon`](https://github.com/holepunchto/hello-pear-daemon)         | `pear-runtime` inside a detached `bare-daemon` updater                                                  | Short-lived CLI invocations that shouldn't block on an update check. |

See [Start a short-lived CLI from the hello-pear-daemon template](/p2p/getting-started/from-a-template/start-from-hello-pear-daemon) for the walkthrough.

## Build a standalone binary [#build-a-standalone-binary]

`npm run make` compiles `bin.mjs` and its dependencies into a single standalone executable with no peer dependencies—users don't need Node.js, Bare, or the [Pear CLI](/pear/reference/pear/cli) installed. `scripts/make.js` detects your OS and architecture and writes the binary to `out/make`, named after `productName || name` from `package.json` (`hello-pear-bare`, plus `.exe` on Windows). The template supports macOS, Linux, and Windows, each for `arm64`/`x64`. To build for a different supported target, set `HOST`:

```bash
HOST=linux-arm64 npm run make
```

`npm run make` signs the binary when signing credentials are in the environment—the same ones the [`make-pear-app`](/pear/reference/ci-and-release/github-actions#make-pear-app) GitHub Action provides. With none of them set, the binary is unsigned:

| Variable                | Platform | What it does                                                                                                                                                                               |
| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `MAC_CODESIGN_IDENTITY` | macOS    | Signs the executable with this identity and the hardened runtime.                                                                                                                          |
| `KEYCHAIN_PROFILE`      | macOS    | Also submits the signed executable to Apple's notary service with `xcrun notarytool`, using this keychain profile, and waits for the result. Set it together with `MAC_CODESIGN_IDENTITY`. |
| `WINDOWS_CERT_SHA1`     | Windows  | Signs the executable with the certificate that has this SHA-1 thumbprint, using `signtool.exe` from the Windows 10 SDK.                                                                    |

The per-platform `make:<platform>-<arch>` scripts in `package.json` are still there for one-off builds. Each one builds an **unsigned** binary into `out/<platform>-<arch>`, under the fixed name `hello-pear-bare`.

See [Bundle a Bare app](/bare/how-to/run-on-native/bundle-a-bare-app#standalone-executable-with-bare-build) for what `npm run make` runs under the hood—[`bare-build`](https://github.com/holepunchto/bare-build), the same tool behind any standalone Bare executable, Pear app or not.

## Install over the air [#install-over-the-air]

Distribute the executable through the usual channels—a download on your website, `apt`, or Homebrew.
You can also install it peer-to-peer: once your `upgrade` link is [seeding](/pear/explanation/availability-and-blind-peering#seeding-with-pear-seed) a release, pull it directly with the [`pear install`](/pear/reference/pear/cli#pear-install) command:

```bash
pear install pear://<key>
```

After the first install, new releases reach users through the swarm—no reinstall needed. For how staging and seeding work, see [Deploy your application](/pear/how-to/operate-an-app/manual-deployment/deployment) and [Installing applications](/pear/explanation/storage-and-distribution#installing-applications).

## Example: add a flag [#example-add-a-flag]

The boilerplate is a minimal CLI plus updater; `bin.mjs` holds the CLI and startup logic (your peer-to-peer code lives in the [worker](/pear/explanation/workers)). Flags are parsed with `paparam`, so adding one is a two-line change. Add a `--name` flag to the command definition:

```diff
 const cmd = command(
   appName,
   summary(pkg.description),
   flag('--storage <dir>', 'custom storage directory'),
-  flag('--no-updates', 'disable OTA updates for this run')
+  flag('--no-updates', 'disable OTA updates for this run'),
+  flag('--name <name>', 'who to greet on startup')
 )
```

Then log the greeting after the other startup logs:

```diff
 console.log(`Updates: ${updates === false ? 'disabled' : 'enabled'}`)
+
+if (cmd.flags.name) console.log(`Hello, ${cmd.flags.name}!`)
```

Run `npm start -- --name YourName`, and the process greets you on boot. That is the CLI layer in `bin.mjs`; your real peer-to-peer features go in the [worker](/pear/explanation/workers) (`workers/main.js`):

* open a [Corestore](/p2p/reference/helpers/corestore) core,
* join a [Hyperswarm](/p2p/reference/building-blocks/hyperswarm) topic, and
* run your protocol.

Reuse the storage directory the host passes to the worker so your data lands alongside the updater's—see [Storage and distribution](/pear/explanation/storage-and-distribution).

## Customize for your brand [#customize-for-your-brand]

Before you ship, set your identity in `package.json`:

* `name`, `productName`, `description`, `author`, and `license`.
* the `upgrade` `pear://` link (see [Create a valid upgrade link](#2-create-a-valid-upgrade-link)).

`bin.mjs` reads `productName || name` for both the binary name and the persistent storage directory, so set these before your first release. Then build with `npm run make` and release with [Deploy your application](/pear/how-to/operate-an-app/manual-deployment/deployment).

## Where to go next [#where-to-go-next]

* [Start from a template](/p2p/getting-started/from-a-template)—all the boilerplates, side by side.
* [Start a short-lived CLI from the hello-pear-daemon template](/p2p/getting-started/from-a-template/start-from-hello-pear-daemon)—the short-lived CLI counterpart, with a detached updater instead of a worker.
* [Start from the hello-pear-electron template](/p2p/getting-started/from-a-template/start-from-hello-pear-electron)—the desktop counterpart, with a renderer, preload bridge, and Bare worker.
* [Inside Bare](/bare/explanation/bare-runtime)—what the runtime this template builds on actually is, and its lifecycle.
* [How the stack fits together](/p2p/explanation/how-the-stack-fits-together)—where Bare and Pear sit relative to each other.
* [Runtime and languages](/pear/explanation/runtime-and-languages)—where Bare fits among Pear's runtimes.
* [Bundle a Bare app](/bare/how-to/run-on-native/bundle-a-bare-app)—the `bare-pack` and `bare-build` paths behind `npm run make`.
* [Build a local AI chat TUI with QVAC](/bare/how-to/add-on-device-ai/build-a-local-ai-chat-tui-with-qvac)—extend this template with a second worker for on-device LLM inference and a terminal UI.
* [Bare modules](/bare/reference/bare/modules)—the Bare standard library this template builds on.
* [Pear OTA](/pear/reference/pear/runtime)—the `pear-runtime` API behind the updater.
* [Migrate from pear run to Pear OTA](/pear/how-to/operate-an-app/migration)—if you're moving an existing app onto `pear-runtime`.
* [Publish with GitHub Actions](/pear/how-to/operate-an-app/github-actions/publish-with-github-actions)—stage a stable `pear://` link on every push with the `pear-ci` action.
* [Deploy your application](/pear/how-to/operate-an-app/manual-deployment/deployment)—the full-control manual path for staging, provisioning, and release lines.
* [Seeding with `pear seed`](/pear/explanation/availability-and-blind-peering#seeding-with-pear-seed)—keep your `pear://` link online so peers can fetch releases.
