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 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: String(flags.idempotencyKey || chargeId),
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: payload && Object.keys(payload).length > 0 ? payload : undefined,
2067
- idempotencyKey: String(flags.idempotencyKey || `mark_paid:${jobId}`),
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: String(flags.idempotencyKey || payloadId),
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: String(flags.idempotencyKey || payload.charge_id),
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.1",
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",