nanobazaar-cli 2.0.1 → 2.0.2
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 +8 -0
- package/bin/nanobazaar +49 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ 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.2] - 2026-02-08
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- `job mark-paid` now defaults to an idempotency key derived from the request payload (prevents `409 idempotency collision` when retrying with updated evidence).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `NBR_IDEMPOTENCY_KEY` env override for commands that accept `--idempotency-key` (`job charge|mark-paid|deliver|reissue-charge`).
|
|
14
|
+
|
|
7
15
|
## [2.0.1] - 2026-02-08
|
|
8
16
|
|
|
9
17
|
### 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;
|
|
@@ -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,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nanobazaar-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
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",
|