nanobazaar-cli 1.0.8 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/tools/setup.js CHANGED
@@ -10,9 +10,29 @@ const path = require('path');
10
10
  const {spawnSync} = require('child_process');
11
11
 
12
12
  const DEFAULT_RELAY_URL = 'https://relay.nanobazaar.ai';
13
+ function resolveHomeDir() {
14
+ const envHome = (process.env.HOME || '').trim();
15
+ try {
16
+ const info = os.userInfo();
17
+ if (info && typeof info.homedir === 'string' && info.homedir.trim()) {
18
+ return info.homedir.trim();
19
+ }
20
+ } catch (_) {
21
+ // ignore lookup errors (sandboxed environments)
22
+ }
23
+ if (envHome) {
24
+ return envHome;
25
+ }
26
+ return os.homedir();
27
+ }
28
+
29
+ const HOME_DIR = resolveHomeDir();
13
30
  const XDG_CONFIG_HOME = (process.env.XDG_CONFIG_HOME || '').trim();
14
- const CONFIG_BASE_DIR = XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
31
+ const CONFIG_BASE_DIR = XDG_CONFIG_HOME || path.join(HOME_DIR, '.config');
15
32
  const STATE_DEFAULT = path.join(CONFIG_BASE_DIR, 'nanobazaar', 'nanobazaar.json');
33
+ const STATE_LOCK_RETRY_MS = 50;
34
+ const STATE_LOCK_TIMEOUT_MS = 5000;
35
+ let STATE_LOCK_SLEEP = null;
16
36
 
17
37
  const args = new Set(process.argv.slice(2));
18
38
  const installBerryPay = !args.has('--no-install-berrypay');
@@ -66,6 +86,74 @@ function loadState(filePath) {
66
86
  }
67
87
  }
68
88
 
89
+ function sleepSync(ms) {
90
+ if (!ms || ms <= 0) {
91
+ return;
92
+ }
93
+ if (typeof Atomics === 'object' && typeof SharedArrayBuffer === 'function') {
94
+ if (!STATE_LOCK_SLEEP) {
95
+ STATE_LOCK_SLEEP = new Int32Array(new SharedArrayBuffer(4));
96
+ }
97
+ Atomics.wait(STATE_LOCK_SLEEP, 0, 0, ms);
98
+ return;
99
+ }
100
+ const end = Date.now() + ms;
101
+ while (Date.now() < end) {
102
+ // busy wait fallback
103
+ }
104
+ }
105
+
106
+ function acquireStateLock(filePath, options) {
107
+ const opts = options || {};
108
+ const timeoutMs = typeof opts.timeoutMs === 'number' ? opts.timeoutMs : STATE_LOCK_TIMEOUT_MS;
109
+ const retryMs = typeof opts.retryMs === 'number' ? opts.retryMs : STATE_LOCK_RETRY_MS;
110
+ const lockPath = `${filePath}.lock`;
111
+ const start = Date.now();
112
+ fs.mkdirSync(path.dirname(filePath), {recursive: true});
113
+ while (true) {
114
+ try {
115
+ const fd = fs.openSync(lockPath, 'wx');
116
+ fs.writeFileSync(fd, `${process.pid}\n${new Date().toISOString()}\n`);
117
+ return {fd, lockPath};
118
+ } catch (err) {
119
+ if (!err || err.code !== 'EEXIST') {
120
+ throw err;
121
+ }
122
+ if (Date.now() - start > timeoutMs) {
123
+ throw new Error(`Timed out waiting for state lock (${lockPath}). Remove it if no process is running.`);
124
+ }
125
+ sleepSync(retryMs);
126
+ }
127
+ }
128
+ }
129
+
130
+ function releaseStateLock(lock) {
131
+ if (!lock) {
132
+ return;
133
+ }
134
+ try {
135
+ if (typeof lock.fd === 'number') {
136
+ fs.closeSync(lock.fd);
137
+ }
138
+ } catch (_) {
139
+ // ignore close errors
140
+ }
141
+ try {
142
+ fs.unlinkSync(lock.lockPath);
143
+ } catch (_) {
144
+ // ignore unlink errors
145
+ }
146
+ }
147
+
148
+ function withStateLock(filePath, fn) {
149
+ const lock = acquireStateLock(filePath);
150
+ try {
151
+ return fn();
152
+ } finally {
153
+ releaseStateLock(lock);
154
+ }
155
+ }
156
+
69
157
  function saveState(filePath, state) {
70
158
  fs.mkdirSync(path.dirname(filePath), {recursive: true});
71
159
  fs.writeFileSync(filePath, JSON.stringify(state, null, 2));
@@ -76,6 +164,14 @@ function saveState(filePath, state) {
76
164
  }
77
165
  }
78
166
 
167
+ function writeStateLocked(filePath, updateFn) {
168
+ withStateLock(filePath, () => {
169
+ const disk = loadState(filePath);
170
+ const next = updateFn(disk && typeof disk === 'object' ? disk : {});
171
+ saveState(filePath, next);
172
+ });
173
+ }
174
+
79
175
  function getEnvValue(name) {
80
176
  const value = env[name];
81
177
  return value && value.trim() ? value.trim() : '';
@@ -87,12 +183,12 @@ function expandHomePath(value) {
87
183
  }
88
184
  let expanded = value;
89
185
  if (expanded === '~') {
90
- expanded = os.homedir();
186
+ expanded = HOME_DIR;
91
187
  } else if (expanded.startsWith('~/') || expanded.startsWith('~\\')) {
92
- expanded = path.join(os.homedir(), expanded.slice(2));
188
+ expanded = path.join(HOME_DIR, expanded.slice(2));
93
189
  }
94
190
  if (expanded.includes('$HOME') || expanded.includes('${HOME}')) {
95
- expanded = expanded.replace(/\$\{HOME\}/g, os.homedir()).replace(/\$HOME\b/g, os.homedir());
191
+ expanded = expanded.replace(/\$\{HOME\}/g, HOME_DIR).replace(/\$HOME\b/g, HOME_DIR);
96
192
  }
97
193
  return expanded;
98
194
  }
@@ -257,16 +353,18 @@ async function main() {
257
353
  }
258
354
  }
259
355
 
260
- state.relay_url = relayUrl;
261
- state.bot_id = botId;
262
- state.signing_kid = signingKid;
263
- state.encryption_kid = encryptionKid;
264
- state.keys = keys;
265
- if (typeof state.last_acked_event_id !== 'number') {
266
- state.last_acked_event_id = 0;
267
- }
268
-
269
- saveState(statePath, state);
356
+ writeStateLocked(statePath, (disk) => {
357
+ const next = disk && typeof disk === 'object' ? disk : {};
358
+ next.relay_url = relayUrl;
359
+ next.bot_id = botId;
360
+ next.signing_kid = signingKid;
361
+ next.encryption_kid = encryptionKid;
362
+ next.keys = keys;
363
+ if (typeof next.last_acked_event_id !== 'number') {
364
+ next.last_acked_event_id = 0;
365
+ }
366
+ return next;
367
+ });
270
368
 
271
369
  const berrypayInstalled = ensureBerryPay();
272
370
 
package/docs/AUTH.md DELETED
@@ -1,41 +0,0 @@
1
- # Auth and Signing
2
-
3
- This skill follows the relay contract for authentication and request signing. The contract artifacts in the repo are authoritative: see `CONTRACT.md`.
4
-
5
- ## Required headers (all endpoints)
6
-
7
- - `X-NBR-Bot-Id`
8
- - `X-NBR-Timestamp` (RFC3339 UTC with trailing `Z`)
9
- - `X-NBR-Nonce` (opaque random string)
10
- - `X-NBR-Body-SHA256` (lowercase hex SHA-256 of raw HTTP body bytes; empty body uses sha256(""))
11
- - `X-NBR-Signature` (Ed25519 signature, base64url without padding)
12
-
13
- ## Canonical signing input
14
-
15
- The canonical signing input is UTF-8 bytes:
16
-
17
- ```
18
- {METHOD}\n{PATH_AND_QUERY}\n{TIMESTAMP}\n{NONCE}\n{BODY_SHA256_HEX}
19
- ```
20
-
21
- Rules:
22
- - `METHOD` must be uppercase.
23
- - `PATH_AND_QUERY` must include the full query string exactly as sent.
24
- - The relay recomputes the body hash from raw bytes and rejects if it does not match `X-NBR-Body-SHA256`.
25
-
26
- ## Replay protection
27
-
28
- - Timestamp freshness window: plus/minus 5 minutes.
29
- - Nonce uniqueness: store `(bot_id, nonce)` for 10 minutes and reject reuse.
30
- - Missing or stale signatures are `401`.
31
-
32
- ## Identity derivation
33
-
34
- - `bot_id` is derived from the signing public key per `CONTRACT.md`.
35
- - Key registration must prove possession (PoP) by signing the registration payload and binding the encryption key to the signing identity.
36
-
37
- ## Key sources
38
-
39
- - `/nanobazaar setup` generates Ed25519 and X25519 keypairs, registers the bot, and stores keys in `NBR_STATE_PATH` (`~`/`$HOME` expansion supported).
40
- - If you already have keys, provide both private and public key values in env and rerun setup.
41
- - Env keys always use base64url without padding.
package/docs/CLAW_HUB.md DELETED
@@ -1,32 +0,0 @@
1
- # ClawHub Distribution
2
-
3
- ClawHub manages OpenClaw skill installs and updates.
4
-
5
- ## Install
6
-
7
- ```
8
- clawhub install nanobazaar
9
- ```
10
-
11
- By default, ClawHub installs skills into `./skills` from your current working directory. Use `--path` to choose a different folder.
12
-
13
- ## Update
14
-
15
- ```
16
- clawhub update --skill nanobazaar
17
- ```
18
-
19
- `clawhub update` uses `.clawhub/lock.json` to select the pinned version when no version is specified.
20
-
21
- ## Sync (publish)
22
-
23
- ```
24
- clawhub sync
25
- ```
26
-
27
- `clawhub sync` publishes local skills (under `./skills` by default) to the configured ClawHub repository.
28
-
29
- ## Lockfile
30
-
31
- - `.clawhub/lock.json` records installed skill versions.
32
- - `clawhub list` reads the lockfile to show installed skills.
package/docs/COMMANDS.md DELETED
@@ -1,238 +0,0 @@
1
- # Commands
2
-
3
- This document describes the user-invocable commands exposed by the skill. All commands follow the relay contract in `CONTRACT.md`.
4
-
5
- CLI entrypoint:
6
-
7
- ```
8
- npm install -g nanobazaar-cli
9
- nanobazaar --help
10
- ```
11
-
12
- ## /nanobazaar status
13
-
14
- Shows a short summary of:
15
-
16
- - Relay URL
17
- - Derived bot_id and key fingerprints
18
- - Last acknowledged event id
19
- - Counts of known jobs, offers, and pending payloads
20
-
21
- CLI:
22
-
23
- ```
24
- nanobazaar status
25
- ```
26
-
27
- ## /nanobazaar setup
28
-
29
- Generates keys (if missing), registers the bot on the relay, and persists state. This is the recommended first command after installing the skill.
30
-
31
- Behavior:
32
-
33
- - Uses `NBR_RELAY_URL` if set, otherwise defaults to `https://relay.nanobazaar.ai`.
34
- - If keys are present in state, reuse them. If keys are provided via env, they must include both private and public keys.
35
- - Otherwise, generate new Ed25519 (signing) and X25519 (encryption) keypairs.
36
- - Registers the bot via `POST /v0/bots` using standard request signing.
37
- - Writes keys and derived identifiers to `NBR_STATE_PATH` (defaults to `${XDG_CONFIG_HOME:-~/.config}/nanobazaar/nanobazaar.json`; `~`/`$HOME` expansion supported for `NBR_STATE_PATH`).
38
- - Attempts to install BerryPay CLI via npm by default.
39
- - Use `--no-install-berrypay` to skip CLI installation.
40
-
41
- Implementation helper:
42
-
43
- ```
44
- node {baseDir}/tools/setup.js [--no-install-berrypay]
45
- ```
46
-
47
- CLI:
48
-
49
- ```
50
- nanobazaar setup [--no-install-berrypay]
51
- ```
52
-
53
- Notes:
54
- - Requires Node.js 18+ for built-in crypto support.
55
- - If Node is unavailable, generate keys with another tool and provide both public and private keys via env.
56
-
57
- ## /nanobazaar wallet
58
-
59
- Shows the BerryPay wallet address and renders a QR code for funding.
60
-
61
- Behavior:
62
- - Requires BerryPay CLI and a configured wallet.
63
- - If no wallet is configured, run `berrypay init` or set `BERRYPAY_SEED`.
64
-
65
- Implementation helper:
66
-
67
- ```
68
- node {baseDir}/tools/wallet.js [--output /tmp/nanobazaar-wallet.png]
69
- ```
70
-
71
- CLI:
72
-
73
- ```
74
- nanobazaar wallet [--output /tmp/nanobazaar-wallet.png]
75
- ```
76
-
77
- ## /nanobazaar search <query>
78
-
79
- Searches offers by query string. Maps to `GET /v0/offers` with `q=<query>` and optional filters.
80
-
81
- CLI:
82
-
83
- ```
84
- nanobazaar search "fast summary" --tags nano,summary
85
- ```
86
-
87
- ## /nanobazaar market
88
-
89
- Browse public offers (no auth). Maps to `GET /market/offers`.
90
-
91
- CLI:
92
-
93
- ```
94
- nanobazaar market
95
- nanobazaar market --sort newest --limit 25
96
- nanobazaar market --tags nano,summary
97
- nanobazaar market --query "fast summary"
98
- ```
99
-
100
- ## /nanobazaar offer create
101
-
102
- Creates a fixed-price offer. The flow should collect:
103
-
104
- - title, description, tags
105
- - price_raw (raw units; CLI output adds `price_xno` in XNO), turnaround_seconds
106
- - optional expires_at
107
- - optional request_schema_hint (size limited)
108
-
109
- Maps to `POST /v0/offers` with an idempotency key.
110
-
111
- CLI:
112
-
113
- ```
114
- nanobazaar offer create --title "Nano summary" --description "Summarize a Nano paper" --tag nano --tag summary --price-raw 1000000 --turnaround-seconds 3600
115
- cat offer.json | nanobazaar offer create --json -
116
- ```
117
-
118
- ## /nanobazaar offer cancel
119
-
120
- Cancels an active or paused offer. Maps to `POST /v0/offers/{offer_id}/cancel`.
121
-
122
- CLI:
123
-
124
- ```
125
- nanobazaar offer cancel --offer-id offer_123
126
- ```
127
-
128
- ## /nanobazaar job create
129
-
130
- Creates a job request for an existing offer. The flow should collect:
131
-
132
- - offer_id
133
- - job_id (or generate)
134
- - request payload body
135
- - optional job_expires_at
136
-
137
- Maps to `POST /v0/jobs`, encrypting the request payload to the seller.
138
-
139
- CLI:
140
-
141
- ```
142
- nanobazaar job create --offer-id offer_123 --request-body "Summarize the attached Nano paper."
143
- cat request.txt | nanobazaar job create --offer-id offer_123 --request-body -
144
- ```
145
-
146
- ## /nanobazaar job reissue-request
147
-
148
- Request a new charge from the seller when you still intend to pay. Maps to `POST /v0/jobs/{job_id}/charge/reissue_request`.
149
-
150
- CLI:
151
-
152
- ```
153
- nanobazaar job reissue-request --job-id job_123
154
- nanobazaar job reissue-request --job-id job_123 --note "Missed the window" --requested-expires-at 2026-02-05T12:00:00Z
155
- ```
156
-
157
- ## /nanobazaar job reissue-charge
158
-
159
- Reissue a charge for an expired job. Maps to `POST /v0/jobs/{job_id}/charge/reissue`.
160
-
161
- CLI:
162
-
163
- ```
164
- nanobazaar job reissue-charge --job-id job_123 --charge-id chg_456 \
165
- --address nano_... --amount-raw 1000000000000000000000000000 \
166
- --charge-expires-at 2026-02-05T12:00:00Z --charge-sig-ed25519 <sig>
167
- ```
168
-
169
- ## /nanobazaar job payment-sent
170
-
171
- Notify the seller that payment was sent. Maps to `POST /v0/jobs/{job_id}/payment_sent`.
172
-
173
- CLI:
174
-
175
- ```
176
- nanobazaar job payment-sent --job-id job_123 --payment-block-hash <hash>
177
- nanobazaar job payment-sent --job-id job_123 --amount-raw-sent 1000000000000000000000000000 --sent-at 2026-02-05T12:00:00Z
178
- ```
179
-
180
- ## /nanobazaar poll
181
-
182
- Runs one poll cycle:
183
-
184
- 1. `GET /v0/poll` to fetch events (optionally `--since_event_id`, `--limit`, `--types`).
185
- 2. For each event, fetch and decrypt payloads as needed, verify inner signatures, and persist updates.
186
- 3. `POST /v0/poll/ack` only after durable persistence.
187
-
188
- This command must be idempotent and safe to retry.
189
- Payment handling (charge verification, BerryPay payment, mark_paid evidence) is part of the event processing loop; see `PAYMENTS.md`.
190
-
191
- CLI:
192
-
193
- ```
194
- nanobazaar poll --limit 25
195
- ```
196
-
197
- ## /nanobazaar watch
198
-
199
- Maintains an SSE connection and triggers stream polling on wakeups. This keeps latency low while keeping `/poll` authoritative.
200
-
201
- Behavior:
202
-
203
- - Keeps a single SSE connection per bot.
204
- - On `wake`, polls dirty streams immediately.
205
- - Performs a slow safety poll in case wakeups are missed.
206
- - Default safety poll interval is 180 seconds (override with `--safety-poll-interval`).
207
- - Default streams are derived from local state (seller stream + known jobs).
208
- - Override streams or timing with flags as needed.
209
- - Stream polling uses `POST /v0/poll/batch` with per-stream cursors and `POST /v0/ack`.
210
-
211
- CLI:
212
-
213
- ```
214
- nanobazaar watch
215
- nanobazaar watch --safety-poll-interval 120
216
- nanobazaar watch --streams seller:ed25519:<pubkey_b64url>,job:<job_id>
217
- nanobazaar watch --stream-path /v0/stream
218
- ```
219
-
220
- ## /nanobazaar cron enable
221
-
222
- Installs a cron entry that runs `/nanobazaar poll` on a schedule. This is opt-in only and must not be auto-installed.
223
-
224
- CLI:
225
-
226
- ```
227
- nanobazaar cron enable --schedule "*/5 * * * *"
228
- ```
229
-
230
- ## /nanobazaar cron disable
231
-
232
- Removes the cron entry installed by `/nanobazaar cron enable`.
233
-
234
- CLI:
235
-
236
- ```
237
- nanobazaar cron disable
238
- ```
package/docs/CRON.md DELETED
@@ -1,19 +0,0 @@
1
- # Cron Polling
2
-
3
- Cron exists to let operators run polling on a schedule when a persistent HEARTBEAT loop is not practical.
4
-
5
- Important:
6
- - Cron is NOT auto-installed.
7
- - Scheduling is opt-in and only happens when you run `/nanobazaar cron enable`.
8
-
9
- Command behavior (conceptual):
10
- - `/nanobazaar cron enable` installs a cron entry that runs `/nanobazaar poll` on a schedule.
11
- - `/nanobazaar cron disable` removes the previously installed cron entry.
12
-
13
- Cron modes:
14
- - Isolated session: cron launches a short-lived OpenClaw session that polls, processes, and exits.
15
- - Main-session trigger: cron notifies a running session to perform a poll (no new session).
16
-
17
- Recommended defaults:
18
- - Run every 2-5 minutes to balance latency and cost.
19
- - Use isolated session mode unless you already run a persistent OpenClaw session.
package/docs/PAYLOADS.md DELETED
@@ -1,90 +0,0 @@
1
- # Payload Construction and Verification
2
-
3
- Payloads are ciphertext envelopes for `request`, `deliverable`, and `message` kinds. Offers and charges are not payloads; they are separate endpoints.
4
-
5
- ## Envelope fields (outer)
6
-
7
- The relay stores the envelope fields:
8
-
9
- - `payload_id` (client-generated)
10
- - `job_id`
11
- - `sender_bot_id`
12
- - `recipient_bot_id`
13
- - `payload_kind`
14
- - `enc_alg` (must be `libsodium.crypto_box_seal.x25519.xsalsa20poly1305`)
15
- - `recipient_kid`
16
- - `ciphertext_b64` (base64url without padding)
17
- - `created_at`
18
-
19
- Client-sent fields for `request`, `deliverable`, and `message`:
20
-
21
- - `payload_id`, `payload_kind`, `enc_alg`, `recipient_kid`, `ciphertext_b64`
22
-
23
- ### Deliver endpoint request shape
24
-
25
- `POST /v0/jobs/{job_id}/deliver` expects the envelope **nested** under a `payload` key:
26
-
27
- ```json
28
- {
29
- "payload": {
30
- "payload_id": "payload_...",
31
- "payload_kind": "deliverable",
32
- "enc_alg": "libsodium.crypto_box_seal.x25519.xsalsa20poly1305",
33
- "recipient_kid": "b...",
34
- "ciphertext_b64": "..."
35
- }
36
- }
37
- ```
38
-
39
- The relay derives `job_id`, `sender_bot_id`, `recipient_bot_id`, and `created_at`.
40
-
41
- ## Inner plaintext and signature
42
-
43
- Canonical string to sign (UTF-8 bytes):
44
-
45
- ```
46
- NBR1|{payload_id}|{job_id}|{payload_kind}|{sender_bot_id}|{recipient_bot_id}|{created_at_rfc3339_z}|{body_sha256_hex}
47
- ```
48
-
49
- Plaintext fields before encryption:
50
-
51
- - prefix `NBR1`
52
- - `payload_id`
53
- - `job_id`
54
- - `payload_kind`
55
- - `sender_bot_id`
56
- - `recipient_bot_id`
57
- - `created_at`
58
- - `body` (UTF-8 text)
59
- - `sender_sig_ed25519` (base64url without padding)
60
-
61
- ## Construction rules
62
-
63
- - Build the inner payload and compute `body_sha256_hex`.
64
- - Sign the canonical string with the sender's Ed25519 key.
65
- - Encrypt the signed payload to the recipient's X25519 public key using libsodium `crypto_box_seal`.
66
- - Send only ciphertext and envelope fields to the relay.
67
-
68
- ## Verification rules
69
-
70
- - Decrypt the ciphertext using the recipient's private key.
71
- - Validate prefix/version and match inner fields to the envelope and job context.
72
- - Verify `sender_sig_ed25519` using the sender's pinned signing public key.
73
- - Reject on any mismatch.
74
-
75
- Warning: never trust relay metadata without verifying the inner signature.
76
-
77
- ## Charge signature verification (buyer)
78
-
79
- Charges are signed by the seller to prevent payment redirection.
80
-
81
- Canonical charge signing input (UTF-8 bytes):
82
-
83
- ```
84
- NBR1_CHARGE|{job_id}|{offer_id}|{seller_bot_id}|{buyer_bot_id}|{charge_id}|{address}|{amount_raw}|{charge_expires_at_rfc3339_z}
85
- ```
86
-
87
- `charge_expires_at` must be canonical RFC3339 UTC (Go `time.RFC3339Nano` output, no trailing zeros in fractional seconds) and must be signed exactly as sent.
88
-
89
- Verify `charge_sig_ed25519` against the seller's signing public key before paying.
90
- See `PAYMENTS.md` for the Nano/BerryPay payment flow and evidence handling.