Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/brave-falcons-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@offckb/cli": patch
---

Add daemon mode and structured JSON output for agent-friendly usage, plus a `node stop` command to terminate the daemon.

- `offckb node --daemon` starts the CKB devnet as a detached background process and writes the PID and logs to the devnet data folder.
- `offckb --json <command>` emits structured JSON log output for programmatic consumption.
- `offckb node stop` reads the daemon PID file and gracefully shuts down the daemon, falling back to force-kill if necessary. It now verifies the target process identity, handles stale PID files, and cleans up on error paths.
- Hardened daemon lifecycle: duplicate daemon starts are rejected, CLI entry resolution supports packaged/npx environments via `OFFCKB_CLI_PATH`, and log/PID directory creation failures are handled gracefully.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Options:

Commands:
node [CKB-Version] Use the CKB to start devnet
node stop Stop the running CKB devnet daemon
create [options] [project-name] Create a new CKB Smart Contract project in JavaScript.
deploy [options] Deploy contracts to different networks, only supports devnet and testnet
debug [options] Quickly debug transaction with tx-hash
Expand Down Expand Up @@ -118,6 +119,40 @@ offckb node --binary-path /path/to/your/ckb/binary

When using `--binary-path`, it will ignore the specified version and network, and only work for devnet.

**Run in Daemon Mode**

Start the devnet in the background so your terminal stays free:

```sh
offckb node --daemon
```

The daemon writes its logs and PID to the devnet data folder, for example:

- Logs: `~/Library/Application Support/offckb-nodejs/devnet/data/logs/daemon.log`
- PID file: `~/Library/Application Support/offckb-nodejs/devnet/data/logs/daemon.pid`

Stop the daemon later with:

```sh
offckb node stop
```

**Agent-Friendly JSON Output**

For programmatic consumption or agent integration, add `--json` to any command to emit structured JSON logs:

```sh
offckb node --json
offckb node --daemon --json
```

Each log line is a single JSON object:

```json
{"level":"info","message":"Launching CKB devnet Node...","timestamp":"2026-07-07T07:10:00.000Z"}
```

**RPC & Proxy RPC**

When the Devnet starts:
Expand Down
22 changes: 18 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node
import { Command } from 'commander';
import { startNode } from './cmd/node';
import { startNode, stopNode } from './cmd/node';
import { accounts } from './cmd/accounts';
import { clean } from './cmd/clean';
import { setUTF8EncodingForWindows } from './util/encoding';
Expand Down Expand Up @@ -28,18 +28,32 @@ setUTF8EncodingForWindows();
const program = new Command();
program.name('offckb').description(description).version(version).enablePositionalOptions();

program
program.option('--json', 'Output logs in JSON format for agent/programmatic consumption');
program.hook('preAction', (thisCommand) => {
const opts = thisCommand.opts();
if (opts.json) {
logger.setJsonMode(true);
}
});

const nodeCommand = program
.command('node [CKB-Version]')
.description('Use the CKB to start devnet')
.option('--network <network>', 'Specify the network to deploy to', 'devnet')
.option(
'-b, --binary-path <binaryPath>',
'Specify the CKB binary path to use, only for devnet, when set, will ignore version and network',
)
.action(async (version: string, options: { network: Network; binaryPath?: string }) => {
return startNode({ version, network: options.network, binaryPath: options.binaryPath });
.option('--daemon', 'Run the node in the background as a daemon (devnet only)')
.action(async (version: string, options: { network: Network; binaryPath?: string; daemon?: boolean }) => {
return startNode({ version, network: options.network, binaryPath: options.binaryPath, daemon: options.daemon });
});

nodeCommand
.command('stop')
.description('Stop the running CKB devnet daemon')
.action(async () => stopNode());

program
.command('create [project-name]')
.description('Create a new CKB Smart Contract project in JavaScript.')
Expand Down
Loading
Loading