nanobazaar-cli 1.0.10 → 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.
- package/bin/nanobazaar +131 -175
- package/package.json +1 -1
package/bin/nanobazaar
CHANGED
|
@@ -667,17 +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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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)
|
|
678
|
-
cron enable [--schedule "*/5 * * * *"]
|
|
679
|
-
Install cron entry to run poll
|
|
680
|
-
cron disable Remove the cron entry
|
|
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.
|
|
681
674
|
|
|
682
675
|
Global flags:
|
|
683
676
|
--help Show this help
|
|
@@ -1638,12 +1631,116 @@ function deriveDefaultStreams({keys, state}) {
|
|
|
1638
1631
|
return uniqStrings(streams);
|
|
1639
1632
|
}
|
|
1640
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
|
+
|
|
1641
1737
|
async function runWatch(argv) {
|
|
1642
1738
|
requireFetch();
|
|
1643
1739
|
|
|
1644
1740
|
const {flags} = parseArgs(argv);
|
|
1645
1741
|
const config = buildConfig();
|
|
1646
|
-
const
|
|
1742
|
+
const statePath = expandHomePath(String(flags.statePath || config.state_path));
|
|
1743
|
+
const state = loadState(statePath);
|
|
1647
1744
|
const {keys} = requireKeys(state);
|
|
1648
1745
|
const identity = deriveIdentity(keys);
|
|
1649
1746
|
|
|
@@ -1663,6 +1760,14 @@ async function runWatch(argv) {
|
|
|
1663
1760
|
const pollLimit = parseOptionalPositiveInt(flags.limit, '--limit')
|
|
1664
1761
|
?? parseOptionalPositiveInt(config.poll_limit, 'NBR_POLL_LIMIT');
|
|
1665
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
|
+
|
|
1666
1771
|
ensureStreamCursorMap(state);
|
|
1667
1772
|
const streamSet = new Set(streams);
|
|
1668
1773
|
|
|
@@ -1734,7 +1839,7 @@ async function runWatch(argv) {
|
|
|
1734
1839
|
|
|
1735
1840
|
const addedEvents = appendEvents(state, allEvents);
|
|
1736
1841
|
if (addedEvents > 0) {
|
|
1737
|
-
saveStateMerged(
|
|
1842
|
+
saveStateMerged(statePath, state);
|
|
1738
1843
|
}
|
|
1739
1844
|
|
|
1740
1845
|
let ackedStreams = 0;
|
|
@@ -1764,7 +1869,7 @@ async function runWatch(argv) {
|
|
|
1764
1869
|
}
|
|
1765
1870
|
|
|
1766
1871
|
setStreamCursor(state, entry.stream, next);
|
|
1767
|
-
saveStateMerged(
|
|
1872
|
+
saveStateMerged(statePath, state);
|
|
1768
1873
|
ackedStreams += 1;
|
|
1769
1874
|
}
|
|
1770
1875
|
}
|
|
@@ -1819,11 +1924,21 @@ async function runWatch(argv) {
|
|
|
1819
1924
|
process.on('SIGTERM', stop);
|
|
1820
1925
|
|
|
1821
1926
|
console.error(`[watch] relay=${config.relay_url}`);
|
|
1822
|
-
console.error(`[watch] state_path=${
|
|
1927
|
+
console.error(`[watch] state_path=${statePath}`);
|
|
1823
1928
|
console.error(`[watch] stream_path=${streamPath}`);
|
|
1824
1929
|
console.error(`[watch] streams=${streams.join(',')}`);
|
|
1825
1930
|
console.error(`[watch] safety_poll_interval_seconds=${safetyIntervalSeconds}`);
|
|
1826
1931
|
|
|
1932
|
+
const stateWatcher = startStateWatcher({
|
|
1933
|
+
statePath,
|
|
1934
|
+
fswatchBin,
|
|
1935
|
+
openclawBin,
|
|
1936
|
+
mode,
|
|
1937
|
+
eventText,
|
|
1938
|
+
debounceMs,
|
|
1939
|
+
signal,
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1827
1942
|
const safetyTimer = setInterval(() => {
|
|
1828
1943
|
if (signal.aborted) {
|
|
1829
1944
|
return;
|
|
@@ -1916,148 +2031,7 @@ async function runWatch(argv) {
|
|
|
1916
2031
|
}
|
|
1917
2032
|
|
|
1918
2033
|
clearInterval(safetyTimer);
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
async function runWatchState(argv) {
|
|
1922
|
-
const {flags} = parseArgs(argv);
|
|
1923
|
-
const config = buildConfig();
|
|
1924
|
-
const statePath = expandHomePath(String(flags.statePath || config.state_path));
|
|
1925
|
-
const fswatchBin = String(flags.fswatchBin || 'fswatch');
|
|
1926
|
-
const openclawBin = String(flags.openclawBin || 'openclaw');
|
|
1927
|
-
const mode = String(flags.mode || 'now');
|
|
1928
|
-
const eventText = String(flags.eventText || 'NanoBazaar state changed');
|
|
1929
|
-
const debounceMs = flags.debounceMs
|
|
1930
|
-
? parsePositiveInt(flags.debounceMs, '--debounce-ms')
|
|
1931
|
-
: 250;
|
|
1932
|
-
|
|
1933
|
-
if (!fs.existsSync(statePath)) {
|
|
1934
|
-
console.error(`State file not found at ${statePath}. Waiting for it to appear...`);
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
let buffer = '';
|
|
1938
|
-
let lastWake = 0;
|
|
1939
|
-
let openclawMissing = false;
|
|
1940
|
-
|
|
1941
|
-
function triggerWake() {
|
|
1942
|
-
if (openclawMissing) {
|
|
1943
|
-
return;
|
|
1944
|
-
}
|
|
1945
|
-
const now = Date.now();
|
|
1946
|
-
if (debounceMs && now - lastWake < debounceMs) {
|
|
1947
|
-
return;
|
|
1948
|
-
}
|
|
1949
|
-
lastWake = now;
|
|
1950
|
-
const result = spawnSync(openclawBin, ['system', 'event', '--text', eventText, '--mode', mode], {stdio: 'inherit'});
|
|
1951
|
-
if (result.error) {
|
|
1952
|
-
openclawMissing = true;
|
|
1953
|
-
console.error(`Failed to run ${openclawBin}: ${result.error.message}`);
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
return new Promise((resolve, reject) => {
|
|
1958
|
-
const watcher = spawn(fswatchBin, ['-0', '-o', statePath], {stdio: ['ignore', 'pipe', 'inherit']});
|
|
1959
|
-
|
|
1960
|
-
watcher.on('error', (err) => {
|
|
1961
|
-
if (err && err.code === 'ENOENT') {
|
|
1962
|
-
reject(new Error(`fswatch not found. Install it (macOS: brew install fswatch).`));
|
|
1963
|
-
return;
|
|
1964
|
-
}
|
|
1965
|
-
reject(new Error(`Failed to start fswatch: ${err && err.message ? err.message : String(err)}`));
|
|
1966
|
-
});
|
|
1967
|
-
|
|
1968
|
-
watcher.stdout.on('data', (chunk) => {
|
|
1969
|
-
buffer += chunk.toString('utf8');
|
|
1970
|
-
const parts = buffer.split('\0');
|
|
1971
|
-
buffer = parts.pop();
|
|
1972
|
-
if (parts.length === 0) {
|
|
1973
|
-
return;
|
|
1974
|
-
}
|
|
1975
|
-
triggerWake();
|
|
1976
|
-
});
|
|
1977
|
-
|
|
1978
|
-
const forwardSignal = (signal) => {
|
|
1979
|
-
if (!watcher.killed) {
|
|
1980
|
-
watcher.kill(signal);
|
|
1981
|
-
}
|
|
1982
|
-
};
|
|
1983
|
-
process.on('SIGINT', () => forwardSignal('SIGINT'));
|
|
1984
|
-
process.on('SIGTERM', () => forwardSignal('SIGTERM'));
|
|
1985
|
-
|
|
1986
|
-
watcher.on('close', (code) => {
|
|
1987
|
-
if (code === 0) {
|
|
1988
|
-
resolve();
|
|
1989
|
-
return;
|
|
1990
|
-
}
|
|
1991
|
-
reject(new Error(`fswatch exited with code ${code}`));
|
|
1992
|
-
});
|
|
1993
|
-
});
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
async function runWatchAll(argv) {
|
|
1997
|
-
await Promise.all([runWatch(argv), runWatchState(argv)]);
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
function runCronEnable(argv) {
|
|
2001
|
-
const {flags} = parseArgs(argv);
|
|
2002
|
-
const config = buildConfig();
|
|
2003
|
-
const schedule = flags.schedule || '*/5 * * * *';
|
|
2004
|
-
const nodeBin = process.execPath;
|
|
2005
|
-
const cliPath = path.resolve(__filename);
|
|
2006
|
-
|
|
2007
|
-
const envPairs = [];
|
|
2008
|
-
if (getEnvValue('NBR_RELAY_URL')) {
|
|
2009
|
-
envPairs.push(`NBR_RELAY_URL=${shellEscape(getEnvValue('NBR_RELAY_URL'))}`);
|
|
2010
|
-
}
|
|
2011
|
-
if (getEnvValue('NBR_STATE_PATH')) {
|
|
2012
|
-
envPairs.push(`NBR_STATE_PATH=${shellEscape(expandHomePath(getEnvValue('NBR_STATE_PATH')))}`);
|
|
2013
|
-
}
|
|
2014
|
-
if (getEnvValue('NBR_POLL_LIMIT')) {
|
|
2015
|
-
envPairs.push(`NBR_POLL_LIMIT=${shellEscape(getEnvValue('NBR_POLL_LIMIT'))}`);
|
|
2016
|
-
}
|
|
2017
|
-
if (getEnvValue('NBR_POLL_TYPES')) {
|
|
2018
|
-
envPairs.push(`NBR_POLL_TYPES=${shellEscape(getEnvValue('NBR_POLL_TYPES'))}`);
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
|
-
const envPrefix = envPairs.length > 0 ? `${envPairs.join(' ')} ` : '';
|
|
2022
|
-
const command = `${shellEscape(nodeBin)} ${shellEscape(cliPath)} poll`;
|
|
2023
|
-
const cronLine = `${schedule} ${envPrefix}${command} # nanobazaar-cli`;
|
|
2024
|
-
|
|
2025
|
-
const listResult = spawnSync('crontab', ['-l'], {encoding: 'utf8'});
|
|
2026
|
-
const current = listResult.status === 0 ? listResult.stdout : '';
|
|
2027
|
-
const filtered = current
|
|
2028
|
-
.split(/\r?\n/)
|
|
2029
|
-
.filter((line) => line.trim() && !line.includes('# nanobazaar-cli'))
|
|
2030
|
-
.join('\n');
|
|
2031
|
-
|
|
2032
|
-
const next = [filtered, cronLine].filter(Boolean).join('\n') + '\n';
|
|
2033
|
-
const installResult = spawnSync('crontab', ['-'], {input: next, encoding: 'utf8'});
|
|
2034
|
-
if (installResult.status !== 0) {
|
|
2035
|
-
throw new Error(`Failed to install cron entry: ${installResult.stderr || 'unknown error'}`);
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
console.log('Cron enabled.');
|
|
2039
|
-
console.log(`Schedule: ${schedule}`);
|
|
2040
|
-
console.log(`Command: ${envPrefix}${command}`);
|
|
2041
|
-
console.log(`State path: ${config.state_path}`);
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
function runCronDisable() {
|
|
2045
|
-
const listResult = spawnSync('crontab', ['-l'], {encoding: 'utf8'});
|
|
2046
|
-
if (listResult.status !== 0) {
|
|
2047
|
-
console.log('No crontab entries found.');
|
|
2048
|
-
return;
|
|
2049
|
-
}
|
|
2050
|
-
const filtered = listResult.stdout
|
|
2051
|
-
.split(/\r?\n/)
|
|
2052
|
-
.filter((line) => line.trim() && !line.includes('# nanobazaar-cli'))
|
|
2053
|
-
.join('\n');
|
|
2054
|
-
|
|
2055
|
-
const next = filtered ? `${filtered}\n` : '';
|
|
2056
|
-
const installResult = spawnSync('crontab', ['-'], {input: next, encoding: 'utf8'});
|
|
2057
|
-
if (installResult.status !== 0) {
|
|
2058
|
-
throw new Error(`Failed to remove cron entry: ${installResult.stderr || 'unknown error'}`);
|
|
2059
|
-
}
|
|
2060
|
-
console.log('Cron disabled.');
|
|
2034
|
+
stateWatcher.stop();
|
|
2061
2035
|
}
|
|
2062
2036
|
|
|
2063
2037
|
function loadVersion() {
|
|
@@ -2159,24 +2133,6 @@ async function main() {
|
|
|
2159
2133
|
case 'watch':
|
|
2160
2134
|
await runWatch(rest);
|
|
2161
2135
|
return;
|
|
2162
|
-
case 'watch-state':
|
|
2163
|
-
await runWatchState(rest);
|
|
2164
|
-
return;
|
|
2165
|
-
case 'watch-all':
|
|
2166
|
-
await runWatchAll(rest);
|
|
2167
|
-
return;
|
|
2168
|
-
case 'cron': {
|
|
2169
|
-
const sub = rest[0];
|
|
2170
|
-
if (sub === 'enable') {
|
|
2171
|
-
runCronEnable(rest.slice(1));
|
|
2172
|
-
return;
|
|
2173
|
-
}
|
|
2174
|
-
if (sub === 'disable') {
|
|
2175
|
-
runCronDisable();
|
|
2176
|
-
return;
|
|
2177
|
-
}
|
|
2178
|
-
throw new Error('Unknown cron command. Use: cron enable|disable');
|
|
2179
|
-
}
|
|
2180
2136
|
default:
|
|
2181
2137
|
throw new Error(`Unknown command: ${command}`);
|
|
2182
2138
|
}
|