nanobazaar-cli 1.0.11 → 1.0.12

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.
Files changed (2) hide show
  1. package/bin/nanobazaar +131 -97
  2. package/package.json +1 -1
package/bin/nanobazaar CHANGED
@@ -667,14 +667,10 @@ Commands:
667
667
  poll [--since-event-id <id>] [--limit <n>] [--types a,b] [--no-ack]
668
668
  Poll events and optionally ack
669
669
  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)
670
+ [--state-path <path>] [--openclaw-bin <bin>] [--fswatch-bin <bin>]
671
+ [--event-text <text>] [--mode now|next] [--debounce-ms <ms>]
672
+ Maintain SSE connection; poll on wake + on safety interval.
673
+ If fswatch is available, also watch local state and trigger OpenClaw wakeups.
678
674
 
679
675
  Global flags:
680
676
  --help Show this help
@@ -1635,12 +1631,116 @@ function deriveDefaultStreams({keys, state}) {
1635
1631
  return uniqStrings(streams);
1636
1632
  }
1637
1633
 
1634
+ function startStateWatcher(options) {
1635
+ const opts = options || {};
1636
+ const statePath = String(opts.statePath || '');
1637
+ const fswatchBin = String(opts.fswatchBin || 'fswatch');
1638
+ const openclawBin = String(opts.openclawBin || 'openclaw');
1639
+ const mode = String(opts.mode || 'now');
1640
+ const eventText = String(opts.eventText || 'NanoBazaar state changed');
1641
+ const debounceMs = typeof opts.debounceMs === 'number' ? opts.debounceMs : 250;
1642
+ const signal = opts.signal;
1643
+
1644
+ if (!statePath) {
1645
+ return {stop: () => {}};
1646
+ }
1647
+
1648
+ if (!fs.existsSync(statePath)) {
1649
+ console.error(`[watch] state file not found at ${statePath}. Waiting for it to appear...`);
1650
+ }
1651
+
1652
+ let watcher = null;
1653
+ let buffer = '';
1654
+ let lastWake = 0;
1655
+ let openclawMissing = false;
1656
+ let disabled = false;
1657
+
1658
+ function disable(message) {
1659
+ if (disabled) {
1660
+ return;
1661
+ }
1662
+ disabled = true;
1663
+ if (message) {
1664
+ console.error(`[watch] ${message}`);
1665
+ }
1666
+ if (watcher && !watcher.killed) {
1667
+ watcher.kill('SIGTERM');
1668
+ }
1669
+ }
1670
+
1671
+ function triggerWake() {
1672
+ if (openclawMissing) {
1673
+ return;
1674
+ }
1675
+ const now = Date.now();
1676
+ if (debounceMs && now - lastWake < debounceMs) {
1677
+ return;
1678
+ }
1679
+ lastWake = now;
1680
+ const result = spawnSync(openclawBin, ['system', 'event', '--text', eventText, '--mode', mode], {stdio: 'inherit'});
1681
+ if (result.error) {
1682
+ openclawMissing = true;
1683
+ console.error(`[watch] Failed to run ${openclawBin}: ${result.error.message}. Local wakeups disabled.`);
1684
+ }
1685
+ }
1686
+
1687
+ try {
1688
+ watcher = spawn(fswatchBin, ['-0', '-o', statePath], {stdio: ['ignore', 'pipe', 'inherit']});
1689
+ } catch (err) {
1690
+ disable(`Failed to start fswatch: ${err && err.message ? err.message : String(err)}. Running SSE polling only.`);
1691
+ return {stop: () => {}};
1692
+ }
1693
+
1694
+ watcher.on('error', (err) => {
1695
+ if (disabled) {
1696
+ return;
1697
+ }
1698
+ if (err && err.code === 'ENOENT') {
1699
+ disable('fswatch not found. Install it to enable local wakeups (macOS: brew install fswatch, Ubuntu: apt install fswatch).');
1700
+ return;
1701
+ }
1702
+ disable(`Failed to start fswatch: ${err && err.message ? err.message : String(err)}. Running SSE polling only.`);
1703
+ });
1704
+
1705
+ watcher.stdout.on('data', (chunk) => {
1706
+ buffer += chunk.toString('utf8');
1707
+ const parts = buffer.split('\0');
1708
+ buffer = parts.pop();
1709
+ if (parts.length === 0) {
1710
+ return;
1711
+ }
1712
+ triggerWake();
1713
+ });
1714
+
1715
+ watcher.on('close', (code) => {
1716
+ if (disabled || (signal && signal.aborted)) {
1717
+ return;
1718
+ }
1719
+ if (code === 0) {
1720
+ disable();
1721
+ return;
1722
+ }
1723
+ disable(`fswatch exited with code ${code}. Running SSE polling only.`);
1724
+ });
1725
+
1726
+ if (signal) {
1727
+ if (signal.aborted) {
1728
+ disable();
1729
+ } else {
1730
+ signal.addEventListener('abort', () => disable());
1731
+ }
1732
+ }
1733
+
1734
+ return {stop: () => disable()};
1735
+ }
1736
+
1638
1737
  async function runWatch(argv) {
1639
1738
  requireFetch();
1640
1739
 
1641
1740
  const {flags} = parseArgs(argv);
1642
1741
  const config = buildConfig();
1643
- const state = loadState(config.state_path);
1742
+ const statePath = expandHomePath(String(flags.statePath || config.state_path));
1743
+ const state = loadState(statePath);
1644
1744
  const {keys} = requireKeys(state);
1645
1745
  const identity = deriveIdentity(keys);
1646
1746
 
@@ -1660,6 +1760,14 @@ async function runWatch(argv) {
1660
1760
  const pollLimit = parseOptionalPositiveInt(flags.limit, '--limit')
1661
1761
  ?? parseOptionalPositiveInt(config.poll_limit, 'NBR_POLL_LIMIT');
1662
1762
 
1763
+ const fswatchBin = String(flags.fswatchBin || 'fswatch');
1764
+ const openclawBin = String(flags.openclawBin || 'openclaw');
1765
+ const mode = String(flags.mode || 'now');
1766
+ const eventText = String(flags.eventText || 'NanoBazaar state changed');
1767
+ const debounceMs = flags.debounceMs
1768
+ ? parsePositiveInt(flags.debounceMs, '--debounce-ms')
1769
+ : 250;
1770
+
1663
1771
  ensureStreamCursorMap(state);
1664
1772
  const streamSet = new Set(streams);
1665
1773
 
@@ -1731,7 +1839,7 @@ async function runWatch(argv) {
1731
1839
 
1732
1840
  const addedEvents = appendEvents(state, allEvents);
1733
1841
  if (addedEvents > 0) {
1734
- saveStateMerged(config.state_path, state);
1842
+ saveStateMerged(statePath, state);
1735
1843
  }
1736
1844
 
1737
1845
  let ackedStreams = 0;
@@ -1761,7 +1869,7 @@ async function runWatch(argv) {
1761
1869
  }
1762
1870
 
1763
1871
  setStreamCursor(state, entry.stream, next);
1764
- saveStateMerged(config.state_path, state);
1872
+ saveStateMerged(statePath, state);
1765
1873
  ackedStreams += 1;
1766
1874
  }
1767
1875
  }
@@ -1816,11 +1924,21 @@ async function runWatch(argv) {
1816
1924
  process.on('SIGTERM', stop);
1817
1925
 
1818
1926
  console.error(`[watch] relay=${config.relay_url}`);
1819
- console.error(`[watch] state_path=${config.state_path}`);
1927
+ console.error(`[watch] state_path=${statePath}`);
1820
1928
  console.error(`[watch] stream_path=${streamPath}`);
1821
1929
  console.error(`[watch] streams=${streams.join(',')}`);
1822
1930
  console.error(`[watch] safety_poll_interval_seconds=${safetyIntervalSeconds}`);
1823
1931
 
1932
+ const stateWatcher = startStateWatcher({
1933
+ statePath,
1934
+ fswatchBin,
1935
+ openclawBin,
1936
+ mode,
1937
+ eventText,
1938
+ debounceMs,
1939
+ signal,
1940
+ });
1941
+
1824
1942
  const safetyTimer = setInterval(() => {
1825
1943
  if (signal.aborted) {
1826
1944
  return;
@@ -1913,85 +2031,7 @@ async function runWatch(argv) {
1913
2031
  }
1914
2032
 
1915
2033
  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)]);
2034
+ stateWatcher.stop();
1995
2035
  }
1996
2036
 
1997
2037
  function loadVersion() {
@@ -2093,12 +2133,6 @@ async function main() {
2093
2133
  case 'watch':
2094
2134
  await runWatch(rest);
2095
2135
  return;
2096
- case 'watch-state':
2097
- await runWatchState(rest);
2098
- return;
2099
- case 'watch-all':
2100
- await runWatchAll(rest);
2101
- return;
2102
2136
  default:
2103
2137
  throw new Error(`Unknown command: ${command}`);
2104
2138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nanobazaar-cli",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "NanoBazaar CLI for the NanoBazaar Relay and OpenClaw skill.",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {