2220e12c31 fix(cli): wait for old daemon to finish ongoing requests before restarting (wallet-lock race) (#98)
* fix(cli): wait for the old daemon to finish ongoing requests during restart

The daemon shuts down gracefully: it stops listening right after /stop,
but keeps serving ongoing requests before disposing of the wallet and
releasing wallet.pid. The restart flows only waited for the health check
to go down, then spawned a replacement daemon that failed to claim the
routstrd wallet lock and exited with code 1, surfacing a confusing
'Cannot claim the routstrd wallet lock ... PID X is still running' error.

Add waitForDaemonToExit() and use it in restart, mode, the post-update
restart, and stop:

- Phase 1: wait (10s) for the health check to stop responding.
- Phase 2: while the old process still holds the wallet lock, show
  'Finishing all ongoing requests...' (heartbeat every 10s) and wait up
  to 10 minutes for it to exit. A stale lock (dead PID) is not waited on;
  after the timeout the error names the holding PID and how to force it.

stop now also waits for full exit instead of returning as soon as /stop
is acknowledged, and no longer auto-starts a daemon when none is running.

* fix(cli): offer 'kill -9 <PID>' to force stop a draining daemon

The drain progress messages and the drain-timeout error now suggest
'kill -9 <PID>' so the user can force the old daemon out instead of
waiting for stuck requests. A plain SIGTERM would not work: the daemon's
signal handler re-runs the same graceful shutdown (server.close()), which
keeps waiting for ongoing requests, so only SIGKILL can interrupt a stuck
drain. The wait loop already treats the resulting dead-PID lock as
released (same liveness semantics as claimPidFile), so the restart
proceeds cleanly.

The heartbeat interval is now injectable (drainHeartbeatMs, default 10s)
like the other timing knobs, which also lets the tests cover the
heartbeat message.

---------

Co-authored-by: redshift <213178690+1ftredsh@users.noreply.github.com>
2026-09-08 15:09:37 +00:00
2026-04-06 18:53:44 +01:00
2026-03-30 21:06:30 +01:00
2026-03-19 16:25:57 +00:00
2026-03-21 23:19:38 +00:00
2026-03-21 23:19:38 +00:00
2026-03-21 23:19:38 +00:00
2026-02-24 05:55:21 +00:00
bug
2026-03-28 22:36:13 +00:00

routstrd

Routstr daemon - A CLI tool for managing routstr processes, similar to cocod (a Cashu wallet daemon).

Overview

routstrd is a Bun-based CLI tool that provides a background daemon for the Routstr protocol. It integrates with cocod for wallet management and uses the Routstr SDK to handle provider routing and model discovery.

Routstr for Teams

For team-based routing, see routstrd-auth.

Features

  • Daemon Mode: Run routstrd as a background HTTP server
  • Wallet Integration: Works with cocod for Cashu token management
  • Provider Routing: Automatically discovers and routes requests to available providers
  • Config Management: Stores configuration in ~/.routstrd/

Requirements

curl -fsSL https://bun.com/install | bash

Installation

Step 1: Install

Global with bun:

bun i -g routstrd

OR - From source:

git clone https://github.com/routstr/routstrd.git
cd routstrd
bun install
bun link

Step 2: Setup & Fund

routstrd onboard
routstrd receive <cashu>       # receive a Cashu token
routstrd receive 2100         # to top up 2100 sats with lightning

Step 3: Integrate with Claude Code

routstrd clients add --claude-code  # or --pi-agent / --opencode

Use Routstrd Skill

Tip: You can also install the routstrd skill so the agent can manage routstrd for you.

More Commands

Start Daemon

Start the background daemon:

routstrd start

With custom port:

routstrd start --port 9000

The daemon binds to 127.0.0.1 by default. To expose it on another interface:

routstrd start --host 0.0.0.0

Only expose the daemon behind appropriate network controls.

With specific provider:

routstrd start --provider https://your-provider.com

CLI Commands

Check daemon status:

routstrd status

Get wallet balance:

routstrd balance

Test connection:

routstrd ping

Refresh models and client integrations on demand:

routstrd clients --manual-refresh   # same as `routstrd refresh`

Turn the daemon's scheduled refresh on or off (no restart needed):

routstrd clients --disable-automatic-refresh
routstrd clients --enable-automatic-refresh

Stop the daemon:

routstrd stop

NPC (Lightning Address)

The in-process wallet registers the NPC (npubx.cash) plugin, which gives the daemon a persistent Lightning address backed by the wallet's Cashu mints. Payments to the address are imported into the wallet automatically (websocket push, plus manual sync on demand).

# Show your NPC Lightning address (username@npubx.cash, or npub fallback)
routstrd wallet npc address

# Claim a username (quote first, then confirm to pay the claim fee from the wallet)
routstrd wallet npc username myname
routstrd wallet npc username myname --confirm

# Manually sync paid NPC quotes into the wallet
routstrd wallet npc sync

Equivalent daemon endpoints: GET /wallet/npc/address, POST /wallet/npc/username, POST /wallet/npc/sync.

Daemon API

The daemon exposes an HTTP server (default port 8008) with the following endpoints:

Health Check

GET /health

Automatic Refresh Settings

POST /settings/auto-refresh

Request body:

{ "enabled": false }

Enables or disables the scheduled refresh job. Persisted to the daemon's config.json as autoRefresh.enabled and picked up on the next tick, so no daemon restart is required.

Route Request

POST /

Request body:

{
  "model": "model-id",
  "messages": [...],
  "stream": false
}

Response:

{
  "choices": [...],
  "usage": {...}
}

Wallet storage

The in-process Cashu wallet stores its mnemonic and proof database in ~/.routstrd/wallet/. On first startup, an existing wallet in ~/.cocod/ is migrated automatically after routstrd verifies that the legacy cocod daemon is not running. Back up your mnemonic before upgrading.

Set ROUTSTRD_WALLET_DIR to override the canonical wallet directory. The COCOD_DIR, COCOD_SOCKET, and COCOD_PID variables are retained only for locating and excluding a legacy external cocod process.

If both ~/.routstrd/wallet and ~/.cocod contain different wallets, startup refuses to migrate rather than picking a mnemonic for you. Run routstrd wallet doctor to compare the two wallets (mnemonic fingerprints, timestamps, and balances) and see which one to keep.

Configuration

Configuration is stored in ~/.routstrd/config.json:

{
  "port": 8008,
  "host": "127.0.0.1",
  "provider": null,
  "cocodPath": null,
  "autoRefresh": { "enabled": true }
}

autoRefresh.enabled (default true) controls the daemon's scheduled refresh job, which re-fetches Nostr events, routstr21 models, and client integrations every 21 minutes. Set it to false (or run routstrd clients --disable-automatic-refresh) to turn the schedule off and refresh manually with routstrd clients --manual-refresh. autoRefresh.intervalMs overrides the 21-minute interval.

Environment Variables

  • ROUTSTRD_DIR - Config directory (default: ~/.routstrd)
  • ROUTSTRD_SOCKET - Socket path (default: ~/.routstrd/routstrd.sock)
  • ROUTSTRD_PID - PID file path (default: ~/.routstrd/routstrd.pid)

Development

Install dependencies:

bun install

Run CLI:

bun run start

Run daemon:

bun run start

Typecheck:

bun run lint

Manual chat-completions smoke test

With a funded daemon running, create or reuse a client API key and pass one or more current model IDs to the smoke script:

routstrd clients add --name smoke-test
ROUTSTRD_API_KEY=<api-key> scripts/smoke/chat-completions.sh <model> [model ...]

Set ROUTSTRD_BASE_URL to test a daemon at a different address. The script makes live provider requests that may spend wallet funds, so it is intentionally not part of bun test.

Project Structure

routstrd/
├── src/
│   ├── index.ts       # Entry point with shebang
│   ├── cli.ts         # Commander CLI commands
│   ├── cli-shared.ts  # IPC utilities
│   ├── daemon.ts      # HTTP server daemon
│   └── utils/
│       └── config.ts  # Path configuration
├── package.json
└── tsconfig.json

License

MIT

S
Description
A Routstr daemon that runs locally to route you to the best Routstr provider from all available Routstr nodes.
Readme
5.3 MiB
Languages
TypeScript 98.8%
Shell 1.2%