nanobazaar-cli 1.0.11 → 1.0.13

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/bin/nanobazaar CHANGED
@@ -412,6 +412,74 @@ function parseArgs(argv) {
412
412
  return {flags, positionals};
413
413
  }
414
414
 
415
+ function parseBoolish(value) {
416
+ if (value === undefined || value === null) {
417
+ return undefined;
418
+ }
419
+ if (value === true || value === false) {
420
+ return value;
421
+ }
422
+ const raw = String(value).trim().toLowerCase();
423
+ if (!raw) {
424
+ return undefined;
425
+ }
426
+ if (raw === '0' || raw === 'false' || raw === 'no' || raw === 'off') {
427
+ return false;
428
+ }
429
+ if (raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on') {
430
+ return true;
431
+ }
432
+ return true;
433
+ }
434
+
435
+ function isDebugEnabled(flags) {
436
+ const flagValue = parseBoolish(flags ? flags.debug : undefined);
437
+ if (flagValue !== undefined) {
438
+ return flagValue;
439
+ }
440
+ return parseBoolish(getEnvValue('NBR_DEBUG')) === true;
441
+ }
442
+
443
+ function previewString(value, maxLen) {
444
+ if (value === undefined || value === null) {
445
+ return '';
446
+ }
447
+ const raw = String(value).replace(/\r/g, '\\r').replace(/\n/g, '\\n');
448
+ const limit = typeof maxLen === 'number' && Number.isFinite(maxLen) ? Math.max(0, Math.floor(maxLen)) : 0;
449
+ if (!limit || raw.length <= limit) {
450
+ return raw;
451
+ }
452
+ if (limit <= 3) {
453
+ return raw.slice(0, limit);
454
+ }
455
+ return raw.slice(0, limit - 3) + '...';
456
+ }
457
+
458
+ function previewJson(value, maxLen) {
459
+ try {
460
+ return previewString(JSON.stringify(value), maxLen);
461
+ } catch (_) {
462
+ return previewString(String(value), maxLen);
463
+ }
464
+ }
465
+
466
+ function createDebugLogger(enabled, scope) {
467
+ const on = !!enabled;
468
+ const tag = scope ? String(scope) : 'debug';
469
+ return (message, payload) => {
470
+ if (!on) {
471
+ return;
472
+ }
473
+ const ts = new Date().toISOString();
474
+ const prefix = `[${tag}][debug] ${ts}`;
475
+ if (payload === undefined) {
476
+ console.error(`${prefix} ${message}`);
477
+ return;
478
+ }
479
+ console.error(`${prefix} ${message} ${typeof payload === 'string' ? previewString(payload, 2000) : previewJson(payload, 4000)}`);
480
+ };
481
+ }
482
+
415
483
  function readTextInput(value, label) {
416
484
  if (value === undefined || value === null) {
417
485
  return '';
@@ -667,18 +735,15 @@ Commands:
667
735
  poll [--since-event-id <id>] [--limit <n>] [--types a,b] [--no-ack]
668
736
  Poll events and optionally ack
669
737
  watch [--streams a,b] [--stream-path /v0/stream] [--safety-poll-interval <seconds>]
670
- Maintain SSE connection; poll on wake + on safety interval
671
- watch-state [--state-path <path>] [--openclaw-bin <bin>] [--fswatch-bin <bin>]
672
- [--event-text <text>] [--mode now|next] [--debounce-ms <ms>]
673
- Watch state file and trigger OpenClaw wakeups
674
- watch-all [--streams a,b] [--stream-path /v0/stream] [--safety-poll-interval <seconds>]
675
- [--state-path <path>] [--openclaw-bin <bin>] [--fswatch-bin <bin>]
676
- [--event-text <text>] [--mode now|next] [--debounce-ms <ms>]
677
- Run relay watch + state watcher together (recommended)
738
+ [--state-path <path>] [--openclaw-bin <bin>] [--fswatch-bin <bin>]
739
+ [--event-text <text>] [--mode now|next] [--debounce-ms <ms>]
740
+ Maintain SSE connection; poll on wake + on safety interval.
741
+ If fswatch is available, also watch local state and trigger OpenClaw wakeups.
678
742
 
679
743
  Global flags:
680
744
  --help Show this help
681
745
  --version Print skill version
746
+ --debug Print verbose debug logs to stderr (or set NBR_DEBUG=1)
682
747
 
683
748
  Notes:
684
749
  - Defaults to relay: ${DEFAULT_RELAY_URL}
@@ -1302,6 +1367,7 @@ async function runJobPaymentSent(argv) {
1302
1367
  async function runPoll(argv, options) {
1303
1368
  const quiet = options && options.quiet;
1304
1369
  const {flags} = parseArgs(argv);
1370
+ const debugLog = createDebugLogger(isDebugEnabled(flags), 'poll');
1305
1371
  const config = buildConfig();
1306
1372
  const state = loadState(config.state_path);
1307
1373
  const {keys} = requireKeys(state);
@@ -1311,6 +1377,17 @@ async function runPoll(argv, options) {
1311
1377
  const limit = flags.limit || config.poll_limit;
1312
1378
  const types = flags.types || config.poll_types;
1313
1379
 
1380
+ debugLog('request', {
1381
+ method: 'GET',
1382
+ path: '/v0/poll',
1383
+ since_event_id: since ?? null,
1384
+ limit: limit ?? null,
1385
+ types: types ?? null,
1386
+ ack: flags.ack !== false,
1387
+ relay_url: config.relay_url,
1388
+ state_path: config.state_path,
1389
+ });
1390
+
1314
1391
  const result = await signedRequest({
1315
1392
  method: 'GET',
1316
1393
  path: '/v0/poll',
@@ -1333,7 +1410,8 @@ async function runPoll(argv, options) {
1333
1410
  }
1334
1411
 
1335
1412
  const events = result.data && result.data.events ? result.data.events : [];
1336
- appendEvents(state, events);
1413
+ const addedEvents = appendEvents(state, events);
1414
+ debugLog('response', {status: result.response.status, events: events.length, added: addedEvents});
1337
1415
 
1338
1416
  if (typeof state.last_acked_event_id !== 'number') {
1339
1417
  state.last_acked_event_id = 0;
@@ -1350,6 +1428,7 @@ async function runPoll(argv, options) {
1350
1428
  maxEventId = events.reduce((max, event) => Math.max(max, event.event_id), 0);
1351
1429
 
1352
1430
  // Ack only after events are durably persisted to local state.
1431
+ debugLog('ack request', {up_to_event_id: maxEventId});
1353
1432
  const ackResult = await signedRequest({
1354
1433
  method: 'POST',
1355
1434
  path: '/v0/poll/ack',
@@ -1367,6 +1446,7 @@ async function runPoll(argv, options) {
1367
1446
  } else {
1368
1447
  ackedId = maxEventId;
1369
1448
  }
1449
+ debugLog('ack ok', {last_acked_event_id: ackedId});
1370
1450
 
1371
1451
  if (typeof ackedId === 'number' && ackedId !== state.last_acked_event_id) {
1372
1452
  state.last_acked_event_id = ackedId;
@@ -1635,12 +1715,131 @@ function deriveDefaultStreams({keys, state}) {
1635
1715
  return uniqStrings(streams);
1636
1716
  }
1637
1717
 
1718
+ function startStateWatcher(options) {
1719
+ const opts = options || {};
1720
+ const statePath = String(opts.statePath || '');
1721
+ const fswatchBin = String(opts.fswatchBin || 'fswatch');
1722
+ const openclawBin = String(opts.openclawBin || 'openclaw');
1723
+ const mode = String(opts.mode || 'now');
1724
+ const eventText = String(opts.eventText || 'NanoBazaar state changed');
1725
+ const debounceMs = typeof opts.debounceMs === 'number' ? opts.debounceMs : 250;
1726
+ const debugLog = typeof opts.debugLog === 'function' ? opts.debugLog : null;
1727
+ const signal = opts.signal;
1728
+
1729
+ if (!statePath) {
1730
+ return {stop: () => {}};
1731
+ }
1732
+
1733
+ if (!fs.existsSync(statePath)) {
1734
+ console.error(`[watch] state file not found at ${statePath}. Waiting for it to appear...`);
1735
+ }
1736
+
1737
+ let watcher = null;
1738
+ let buffer = '';
1739
+ let lastWake = 0;
1740
+ let openclawMissing = false;
1741
+ let disabled = false;
1742
+
1743
+ function disable(message) {
1744
+ if (disabled) {
1745
+ return;
1746
+ }
1747
+ disabled = true;
1748
+ if (message) {
1749
+ console.error(`[watch] ${message}`);
1750
+ }
1751
+ if (watcher && !watcher.killed) {
1752
+ watcher.kill('SIGTERM');
1753
+ }
1754
+ }
1755
+
1756
+ function triggerWake() {
1757
+ if (openclawMissing) {
1758
+ return;
1759
+ }
1760
+ const now = Date.now();
1761
+ if (debounceMs && now - lastWake < debounceMs) {
1762
+ return;
1763
+ }
1764
+ lastWake = now;
1765
+ if (debugLog) {
1766
+ debugLog('local wake: invoking openclaw', {openclaw_bin: openclawBin, mode, event_text: eventText});
1767
+ }
1768
+ const result = spawnSync(openclawBin, ['system', 'event', '--text', eventText, '--mode', mode], {stdio: 'inherit'});
1769
+ if (result.error) {
1770
+ openclawMissing = true;
1771
+ console.error(`[watch] Failed to run ${openclawBin}: ${result.error.message}. Local wakeups disabled.`);
1772
+ if (debugLog) {
1773
+ debugLog('local wake: openclaw failed', result.error && result.error.message ? result.error.message : String(result.error));
1774
+ }
1775
+ return;
1776
+ }
1777
+ if (debugLog) {
1778
+ debugLog('local wake: openclaw ok');
1779
+ }
1780
+ }
1781
+
1782
+ try {
1783
+ watcher = spawn(fswatchBin, ['-0', '-o', statePath], {stdio: ['ignore', 'pipe', 'inherit']});
1784
+ } catch (err) {
1785
+ disable(`Failed to start fswatch: ${err && err.message ? err.message : String(err)}. Running SSE polling only.`);
1786
+ return {stop: () => {}};
1787
+ }
1788
+
1789
+ watcher.on('error', (err) => {
1790
+ if (disabled) {
1791
+ return;
1792
+ }
1793
+ if (err && err.code === 'ENOENT') {
1794
+ disable('fswatch not found. Install it to enable local wakeups (macOS: brew install fswatch, Ubuntu: apt install fswatch).');
1795
+ return;
1796
+ }
1797
+ disable(`Failed to start fswatch: ${err && err.message ? err.message : String(err)}. Running SSE polling only.`);
1798
+ });
1799
+
1800
+ watcher.stdout.on('data', (chunk) => {
1801
+ buffer += chunk.toString('utf8');
1802
+ const parts = buffer.split('\0');
1803
+ buffer = parts.pop();
1804
+ if (parts.length === 0) {
1805
+ return;
1806
+ }
1807
+ if (debugLog) {
1808
+ debugLog('local wake: fswatch event', {count: parts.length});
1809
+ }
1810
+ triggerWake();
1811
+ });
1812
+
1813
+ watcher.on('close', (code) => {
1814
+ if (disabled || (signal && signal.aborted)) {
1815
+ return;
1816
+ }
1817
+ if (code === 0) {
1818
+ disable();
1819
+ return;
1820
+ }
1821
+ disable(`fswatch exited with code ${code}. Running SSE polling only.`);
1822
+ });
1823
+
1824
+ if (signal) {
1825
+ if (signal.aborted) {
1826
+ disable();
1827
+ } else {
1828
+ signal.addEventListener('abort', () => disable());
1829
+ }
1830
+ }
1831
+
1832
+ return {stop: () => disable()};
1833
+ }
1834
+
1638
1835
  async function runWatch(argv) {
1639
1836
  requireFetch();
1640
1837
 
1641
1838
  const {flags} = parseArgs(argv);
1839
+ const debugLog = createDebugLogger(isDebugEnabled(flags), 'watch');
1642
1840
  const config = buildConfig();
1643
- const state = loadState(config.state_path);
1841
+ const statePath = expandHomePath(String(flags.statePath || config.state_path));
1842
+ const state = loadState(statePath);
1644
1843
  const {keys} = requireKeys(state);
1645
1844
  const identity = deriveIdentity(keys);
1646
1845
 
@@ -1660,9 +1859,28 @@ async function runWatch(argv) {
1660
1859
  const pollLimit = parseOptionalPositiveInt(flags.limit, '--limit')
1661
1860
  ?? parseOptionalPositiveInt(config.poll_limit, 'NBR_POLL_LIMIT');
1662
1861
 
1862
+ const fswatchBin = String(flags.fswatchBin || 'fswatch');
1863
+ const openclawBin = String(flags.openclawBin || 'openclaw');
1864
+ const mode = String(flags.mode || 'now');
1865
+ const eventText = String(flags.eventText || 'NanoBazaar state changed');
1866
+ const debounceMs = flags.debounceMs
1867
+ ? parsePositiveInt(flags.debounceMs, '--debounce-ms')
1868
+ : 250;
1869
+
1663
1870
  ensureStreamCursorMap(state);
1664
1871
  const streamSet = new Set(streams);
1665
1872
 
1873
+ debugLog('init', {
1874
+ relay_url: config.relay_url,
1875
+ state_path: statePath,
1876
+ stream_path: streamPath,
1877
+ streams,
1878
+ safety_poll_interval_seconds: safetyIntervalSeconds,
1879
+ ack: flags.ack !== false,
1880
+ poll_limit: pollLimit ?? null,
1881
+ print_polls: printPolls,
1882
+ });
1883
+
1666
1884
  let pollInFlight = false;
1667
1885
  let pollQueued = false;
1668
1886
  let queuedReason = null;
@@ -1692,6 +1910,7 @@ async function runWatch(argv) {
1692
1910
  body.limit = pollLimit;
1693
1911
  }
1694
1912
 
1913
+ debugLog('poll/batch request', {reason, body});
1695
1914
  const result = await signedRequest({
1696
1915
  method: 'POST',
1697
1916
  path: '/v0/poll/batch',
@@ -1707,6 +1926,11 @@ async function runWatch(argv) {
1707
1926
  }
1708
1927
 
1709
1928
  const results = result.data && Array.isArray(result.data.results) ? result.data.results : [];
1929
+ debugLog('poll/batch ok', results.map((entry) => ({
1930
+ stream: entry && entry.stream ? entry.stream : null,
1931
+ events: entry && Array.isArray(entry.events) ? entry.events.length : 0,
1932
+ next: entry && typeof entry.next === 'number' ? entry.next : null,
1933
+ })));
1710
1934
  const allEvents = [];
1711
1935
  for (const entry of results) {
1712
1936
  if (!entry || !Array.isArray(entry.events)) {
@@ -1723,6 +1947,16 @@ async function runWatch(argv) {
1723
1947
  }
1724
1948
  }
1725
1949
  }
1950
+ if (allEvents.length > 0) {
1951
+ for (const event of allEvents) {
1952
+ debugLog('event', {
1953
+ stream: event && event.stream ? event.stream : null,
1954
+ event_id: event && event.event_id !== undefined ? event.event_id : null,
1955
+ event_type: event && event.event_type ? event.event_type : null,
1956
+ data_preview: event && event.data !== undefined ? previewJson(event.data, 500) : null,
1957
+ });
1958
+ }
1959
+ }
1726
1960
 
1727
1961
  state.relay_url = config.relay_url;
1728
1962
  state.bot_id = identity.botId;
@@ -1731,7 +1965,7 @@ async function runWatch(argv) {
1731
1965
 
1732
1966
  const addedEvents = appendEvents(state, allEvents);
1733
1967
  if (addedEvents > 0) {
1734
- saveStateMerged(config.state_path, state);
1968
+ saveStateMerged(statePath, state);
1735
1969
  }
1736
1970
 
1737
1971
  let ackedStreams = 0;
@@ -1746,6 +1980,7 @@ async function runWatch(argv) {
1746
1980
  continue;
1747
1981
  }
1748
1982
 
1983
+ debugLog('ack request', {stream: entry.stream, from: current, to: next});
1749
1984
  const ackResult = await signedRequest({
1750
1985
  method: 'POST',
1751
1986
  path: '/v0/ack',
@@ -1761,8 +1996,9 @@ async function runWatch(argv) {
1761
1996
  }
1762
1997
 
1763
1998
  setStreamCursor(state, entry.stream, next);
1764
- saveStateMerged(config.state_path, state);
1999
+ saveStateMerged(statePath, state);
1765
2000
  ackedStreams += 1;
2001
+ debugLog('ack ok', {stream: entry.stream, ack: next});
1766
2002
  }
1767
2003
  }
1768
2004
 
@@ -1816,11 +2052,22 @@ async function runWatch(argv) {
1816
2052
  process.on('SIGTERM', stop);
1817
2053
 
1818
2054
  console.error(`[watch] relay=${config.relay_url}`);
1819
- console.error(`[watch] state_path=${config.state_path}`);
2055
+ console.error(`[watch] state_path=${statePath}`);
1820
2056
  console.error(`[watch] stream_path=${streamPath}`);
1821
2057
  console.error(`[watch] streams=${streams.join(',')}`);
1822
2058
  console.error(`[watch] safety_poll_interval_seconds=${safetyIntervalSeconds}`);
1823
2059
 
2060
+ const stateWatcher = startStateWatcher({
2061
+ statePath,
2062
+ fswatchBin,
2063
+ openclawBin,
2064
+ mode,
2065
+ eventText,
2066
+ debounceMs,
2067
+ debugLog,
2068
+ signal,
2069
+ });
2070
+
1824
2071
  const safetyTimer = setInterval(() => {
1825
2072
  if (signal.aborted) {
1826
2073
  return;
@@ -1887,8 +2134,14 @@ async function runWatch(argv) {
1887
2134
  await consumeSseStream(response.body, {
1888
2135
  signal,
1889
2136
  onEvent: (evt) => {
2137
+ debugLog('sse event', {
2138
+ event: evt && evt.event ? evt.event : null,
2139
+ id: evt && evt.id ? evt.id : null,
2140
+ data_preview: evt && evt.data ? previewString(evt.data, 500) : null,
2141
+ });
1890
2142
  const wakeStreams = extractWakeStreams(evt);
1891
2143
  if (wakeStreams !== null) {
2144
+ debugLog('wake detected', {streams: wakeStreams});
1892
2145
  void runPollLoop('wake', wakeStreams);
1893
2146
  }
1894
2147
  },
@@ -1913,85 +2166,7 @@ async function runWatch(argv) {
1913
2166
  }
1914
2167
 
1915
2168
  clearInterval(safetyTimer);
1916
- }
1917
-
1918
- async function runWatchState(argv) {
1919
- const {flags} = parseArgs(argv);
1920
- const config = buildConfig();
1921
- const statePath = expandHomePath(String(flags.statePath || config.state_path));
1922
- const fswatchBin = String(flags.fswatchBin || 'fswatch');
1923
- const openclawBin = String(flags.openclawBin || 'openclaw');
1924
- const mode = String(flags.mode || 'now');
1925
- const eventText = String(flags.eventText || 'NanoBazaar state changed');
1926
- const debounceMs = flags.debounceMs
1927
- ? parsePositiveInt(flags.debounceMs, '--debounce-ms')
1928
- : 250;
1929
-
1930
- if (!fs.existsSync(statePath)) {
1931
- console.error(`State file not found at ${statePath}. Waiting for it to appear...`);
1932
- }
1933
-
1934
- let buffer = '';
1935
- let lastWake = 0;
1936
- let openclawMissing = false;
1937
-
1938
- function triggerWake() {
1939
- if (openclawMissing) {
1940
- return;
1941
- }
1942
- const now = Date.now();
1943
- if (debounceMs && now - lastWake < debounceMs) {
1944
- return;
1945
- }
1946
- lastWake = now;
1947
- const result = spawnSync(openclawBin, ['system', 'event', '--text', eventText, '--mode', mode], {stdio: 'inherit'});
1948
- if (result.error) {
1949
- openclawMissing = true;
1950
- console.error(`Failed to run ${openclawBin}: ${result.error.message}`);
1951
- }
1952
- }
1953
-
1954
- return new Promise((resolve, reject) => {
1955
- const watcher = spawn(fswatchBin, ['-0', '-o', statePath], {stdio: ['ignore', 'pipe', 'inherit']});
1956
-
1957
- watcher.on('error', (err) => {
1958
- if (err && err.code === 'ENOENT') {
1959
- reject(new Error(`fswatch not found. Install it (macOS: brew install fswatch).`));
1960
- return;
1961
- }
1962
- reject(new Error(`Failed to start fswatch: ${err && err.message ? err.message : String(err)}`));
1963
- });
1964
-
1965
- watcher.stdout.on('data', (chunk) => {
1966
- buffer += chunk.toString('utf8');
1967
- const parts = buffer.split('\0');
1968
- buffer = parts.pop();
1969
- if (parts.length === 0) {
1970
- return;
1971
- }
1972
- triggerWake();
1973
- });
1974
-
1975
- const forwardSignal = (signal) => {
1976
- if (!watcher.killed) {
1977
- watcher.kill(signal);
1978
- }
1979
- };
1980
- process.on('SIGINT', () => forwardSignal('SIGINT'));
1981
- process.on('SIGTERM', () => forwardSignal('SIGTERM'));
1982
-
1983
- watcher.on('close', (code) => {
1984
- if (code === 0) {
1985
- resolve();
1986
- return;
1987
- }
1988
- reject(new Error(`fswatch exited with code ${code}`));
1989
- });
1990
- });
1991
- }
1992
-
1993
- async function runWatchAll(argv) {
1994
- await Promise.all([runWatch(argv), runWatchState(argv)]);
2169
+ stateWatcher.stop();
1995
2170
  }
1996
2171
 
1997
2172
  function loadVersion() {
@@ -2030,8 +2205,13 @@ async function main() {
2030
2205
  return;
2031
2206
  }
2032
2207
 
2033
- const command = argv[0];
2034
- const rest = argv.slice(1);
2208
+ const commandIndex = argv.findIndex((arg) => arg && arg !== '-' && !arg.startsWith('-'));
2209
+ if (commandIndex === -1) {
2210
+ printHelp();
2211
+ return;
2212
+ }
2213
+ const command = argv[commandIndex];
2214
+ const rest = argv.slice(0, commandIndex).concat(argv.slice(commandIndex + 1));
2035
2215
 
2036
2216
  switch (command) {
2037
2217
  case 'status':
@@ -2093,12 +2273,6 @@ async function main() {
2093
2273
  case 'watch':
2094
2274
  await runWatch(rest);
2095
2275
  return;
2096
- case 'watch-state':
2097
- await runWatchState(rest);
2098
- return;
2099
- case 'watch-all':
2100
- await runWatchAll(rest);
2101
- return;
2102
2276
  default:
2103
2277
  throw new Error(`Unknown command: ${command}`);
2104
2278
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nanobazaar-cli",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "NanoBazaar CLI for the NanoBazaar Relay and OpenClaw skill.",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {
@@ -5,7 +5,10 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
5
5
  CLI="$ROOT_DIR/packages/nanobazaar-cli/bin/nanobazaar"
6
6
 
7
7
  node "$CLI" --help > /tmp/nanobazaar_cli_help.txt
8
+ node "$CLI" --debug --help > /tmp/nanobazaar_cli_help_debug.txt
8
9
  node "$CLI" watch --help > /tmp/nanobazaar_cli_watch_help.txt
10
+ node "$CLI" --debug watch --help > /tmp/nanobazaar_cli_watch_help_pre_debug.txt
11
+ node "$CLI" watch --debug --help > /tmp/nanobazaar_cli_watch_help_post_debug.txt
9
12
  node "$CLI" config --json > /tmp/nanobazaar_cli_config.json
10
13
 
11
14
  echo "CLI smoke test passed."