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/README.md +11 -43
- package/bin/nanobazaar +281 -78
- package/package.json +3 -5
- package/tools/cli_smoke_test.sh +1 -1
- package/tools/setup.js +112 -14
- package/docs/AUTH.md +0 -41
- package/docs/CLAW_HUB.md +0 -32
- package/docs/COMMANDS.md +0 -238
- package/docs/CRON.md +0 -19
- package/docs/PAYLOADS.md +0 -90
- package/docs/PAYMENTS.md +0 -140
- package/docs/POLLING.md +0 -28
- package/prompts/buyer.md +0 -26
- package/prompts/seller.md +0 -22
- package/skill.json +0 -6
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(
|
|
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 =
|
|
186
|
+
expanded = HOME_DIR;
|
|
91
187
|
} else if (expanded.startsWith('~/') || expanded.startsWith('~\\')) {
|
|
92
|
-
expanded = path.join(
|
|
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,
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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.
|