nanobazaar-cli 2.0.1 → 2.0.3
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/CHANGELOG.md +13 -0
- package/bin/nanobazaar +55 -26
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to `nanobazaar-cli` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project follows Semantic Versioning.
|
|
6
6
|
|
|
7
|
+
## [2.0.3] - 2026-02-09
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- `nanobazaar watch` now wakes only on relay wake events; removed the safety interval.
|
|
11
|
+
|
|
12
|
+
## [2.0.2] - 2026-02-08
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- `job mark-paid` now defaults to an idempotency key derived from the request payload (prevents `409 idempotency collision` when retrying with updated evidence).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `NBR_IDEMPOTENCY_KEY` env override for commands that accept `--idempotency-key` (`job charge|mark-paid|deliver|reissue-charge`).
|
|
19
|
+
|
|
7
20
|
## [2.0.1] - 2026-02-08
|
|
8
21
|
|
|
9
22
|
### Changed
|
package/bin/nanobazaar
CHANGED
|
@@ -74,6 +74,35 @@ function sha256Hex(buffer) {
|
|
|
74
74
|
return crypto.createHash('sha256').update(buffer).digest('hex');
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function stableJsonNormalize(value) {
|
|
78
|
+
if (value === undefined) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
if (value === null || typeof value !== 'object') {
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
if (typeof value.toJSON === 'function') {
|
|
85
|
+
return stableJsonNormalize(value.toJSON());
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
return value.map((entry) => stableJsonNormalize(entry));
|
|
89
|
+
}
|
|
90
|
+
const out = {};
|
|
91
|
+
for (const key of Object.keys(value).sort()) {
|
|
92
|
+
const entry = value[key];
|
|
93
|
+
// Match JSON.stringify: omit undefined values in objects.
|
|
94
|
+
if (entry === undefined) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
out[key] = stableJsonNormalize(entry);
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function stableJsonStringify(value) {
|
|
103
|
+
return JSON.stringify(stableJsonNormalize(value));
|
|
104
|
+
}
|
|
105
|
+
|
|
77
106
|
function loadState(filePath) {
|
|
78
107
|
try {
|
|
79
108
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
@@ -289,6 +318,17 @@ function getEnvValue(name) {
|
|
|
289
318
|
return value && value.trim() ? value.trim() : '';
|
|
290
319
|
}
|
|
291
320
|
|
|
321
|
+
function resolveIdempotencyKey(flags, fallback) {
|
|
322
|
+
if (flags && flags.idempotencyKey) {
|
|
323
|
+
return String(flags.idempotencyKey);
|
|
324
|
+
}
|
|
325
|
+
const envKey = getEnvValue('NBR_IDEMPOTENCY_KEY');
|
|
326
|
+
if (envKey) {
|
|
327
|
+
return envKey;
|
|
328
|
+
}
|
|
329
|
+
return fallback ? String(fallback) : '';
|
|
330
|
+
}
|
|
331
|
+
|
|
292
332
|
function expandHomePath(value) {
|
|
293
333
|
if (!value) {
|
|
294
334
|
return value;
|
|
@@ -1235,10 +1275,10 @@ Commands:
|
|
|
1235
1275
|
Poll events and optionally ack
|
|
1236
1276
|
poll ack --up-to-event-id <id>
|
|
1237
1277
|
Advance the server-side poll cursor (used for 410 resync)
|
|
1238
|
-
watch [--stream-path /v0/stream]
|
|
1278
|
+
watch [--stream-path /v0/stream]
|
|
1239
1279
|
[--state-path <path>] [--openclaw-bin <bin>] [--event-text <text>]
|
|
1240
1280
|
[--mode now|next] [--no-openclaw]
|
|
1241
|
-
Maintain SSE connection; wake OpenClaw on relay wake events
|
|
1281
|
+
Maintain SSE connection; wake OpenClaw on relay wake events.
|
|
1242
1282
|
Does not poll or ack (OpenClaw should run /nanobazaar poll).
|
|
1243
1283
|
|
|
1244
1284
|
Global flags:
|
|
@@ -1990,7 +2030,7 @@ async function runJobCharge(argv) {
|
|
|
1990
2030
|
method: 'POST',
|
|
1991
2031
|
path: `/v0/jobs/${jobId}/charge`,
|
|
1992
2032
|
body: payload,
|
|
1993
|
-
idempotencyKey:
|
|
2033
|
+
idempotencyKey: resolveIdempotencyKey(flags, chargeId),
|
|
1994
2034
|
relayUrl: config.relay_url,
|
|
1995
2035
|
keys,
|
|
1996
2036
|
identity,
|
|
@@ -2060,11 +2100,15 @@ async function runJobMarkPaid(argv) {
|
|
|
2060
2100
|
const {keys} = requireKeys(state);
|
|
2061
2101
|
const identity = deriveIdentity(keys);
|
|
2062
2102
|
|
|
2103
|
+
const requestBody = payload && Object.keys(payload).length > 0 ? payload : undefined;
|
|
2104
|
+
const bodyHash = sha256Hex(stableJsonStringify(requestBody)).slice(0, 16);
|
|
2105
|
+
const defaultIdempotencyKey = `mark_paid:${jobId}:${bodyHash}`;
|
|
2106
|
+
|
|
2063
2107
|
const result = await signedRequest({
|
|
2064
2108
|
method: 'POST',
|
|
2065
2109
|
path: `/v0/jobs/${jobId}/mark_paid`,
|
|
2066
|
-
body:
|
|
2067
|
-
idempotencyKey:
|
|
2110
|
+
body: requestBody,
|
|
2111
|
+
idempotencyKey: resolveIdempotencyKey(flags, defaultIdempotencyKey),
|
|
2068
2112
|
relayUrl: config.relay_url,
|
|
2069
2113
|
keys,
|
|
2070
2114
|
identity,
|
|
@@ -2144,7 +2188,7 @@ async function runJobDeliver(argv) {
|
|
|
2144
2188
|
method: 'POST',
|
|
2145
2189
|
path: `/v0/jobs/${jobId}/deliver`,
|
|
2146
2190
|
body: {payload: built.envelope},
|
|
2147
|
-
idempotencyKey:
|
|
2191
|
+
idempotencyKey: resolveIdempotencyKey(flags, payloadId),
|
|
2148
2192
|
relayUrl: config.relay_url,
|
|
2149
2193
|
keys,
|
|
2150
2194
|
identity,
|
|
@@ -2272,7 +2316,7 @@ async function runJobReissueCharge(argv) {
|
|
|
2272
2316
|
method: 'POST',
|
|
2273
2317
|
path: `/v0/jobs/${jobId}/charge/reissue`,
|
|
2274
2318
|
body: payload,
|
|
2275
|
-
idempotencyKey:
|
|
2319
|
+
idempotencyKey: resolveIdempotencyKey(flags, payload.charge_id),
|
|
2276
2320
|
relayUrl: config.relay_url,
|
|
2277
2321
|
keys,
|
|
2278
2322
|
identity,
|
|
@@ -2991,11 +3035,9 @@ async function runWatch(argv) {
|
|
|
2991
3035
|
throw new Error('watch no longer polls. Remove polling flags (--no-ack/--no-fetch-payloads/--limit/--types/--print-polls/--output) and retry.');
|
|
2992
3036
|
}
|
|
2993
3037
|
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
? parsePositiveInt(flags.safetyPollInterval, '--safety-poll-interval')
|
|
2998
|
-
: 180;
|
|
3038
|
+
if (flags.safetyWakeInterval !== undefined || flags.safetyPollInterval !== undefined) {
|
|
3039
|
+
throw new Error('Safety wake interval has been removed. Remove --safety-wake-interval/--safety-poll-interval and retry.');
|
|
3040
|
+
}
|
|
2999
3041
|
|
|
3000
3042
|
const openclawBin = String(flags.openclawBin || 'openclaw');
|
|
3001
3043
|
const mode = String(flags.mode || 'now');
|
|
@@ -3006,7 +3048,6 @@ async function runWatch(argv) {
|
|
|
3006
3048
|
relay_url: config.relay_url,
|
|
3007
3049
|
state_path: statePath,
|
|
3008
3050
|
stream_path: streamPath,
|
|
3009
|
-
safety_wake_interval_seconds: safetyIntervalSeconds,
|
|
3010
3051
|
openclaw_enabled: openclawEnabled,
|
|
3011
3052
|
});
|
|
3012
3053
|
|
|
@@ -3047,7 +3088,7 @@ async function runWatch(argv) {
|
|
|
3047
3088
|
const label = queuedReason || reason;
|
|
3048
3089
|
queuedReason = null;
|
|
3049
3090
|
|
|
3050
|
-
// Best-effort: waking OpenClaw can be retried on the next wake
|
|
3091
|
+
// Best-effort: waking OpenClaw can be retried on the next wake event.
|
|
3051
3092
|
const didWake = triggerOpenclawWake(label);
|
|
3052
3093
|
console.error(`[watch] wake ${didWake ? 'ok' : 'skipped'} (${label})`);
|
|
3053
3094
|
}
|
|
@@ -3070,17 +3111,6 @@ async function runWatch(argv) {
|
|
|
3070
3111
|
console.error(`[watch] relay=${config.relay_url}`);
|
|
3071
3112
|
console.error(`[watch] state_path=${statePath}`);
|
|
3072
3113
|
console.error(`[watch] stream_path=${streamPath}`);
|
|
3073
|
-
console.error(`[watch] safety_wake_interval_seconds=${safetyIntervalSeconds}`);
|
|
3074
|
-
|
|
3075
|
-
const safetyTimer = setInterval(() => {
|
|
3076
|
-
if (signal.aborted) {
|
|
3077
|
-
return;
|
|
3078
|
-
}
|
|
3079
|
-
void runWakeLoop('safety');
|
|
3080
|
-
}, safetyIntervalSeconds * 1000);
|
|
3081
|
-
|
|
3082
|
-
// Kick off an initial wake so the agent can poll even if wakeups are missed.
|
|
3083
|
-
void runWakeLoop('startup');
|
|
3084
3114
|
|
|
3085
3115
|
let attempt = 0;
|
|
3086
3116
|
function isWakeEvent(evt) {
|
|
@@ -3157,7 +3187,6 @@ async function runWatch(argv) {
|
|
|
3157
3187
|
}
|
|
3158
3188
|
}
|
|
3159
3189
|
|
|
3160
|
-
clearInterval(safetyTimer);
|
|
3161
3190
|
}
|
|
3162
3191
|
|
|
3163
3192
|
const DISPATCH_BOOL_FLAGS = new Set([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nanobazaar-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "NanoBazaar CLI for the NanoBazaar Relay and OpenClaw skill.",
|
|
5
5
|
"homepage": "https://github.com/nanobazaar/nanobazaar/tree/main/packages/nanobazaar-cli#readme",
|
|
6
6
|
"license": "UNLICENSED",
|