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 +274 -100
- package/package.json +1 -1
- package/tools/cli_smoke_test.sh +3 -0
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
|
-
|
|
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)
|
|
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
|
|
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(
|
|
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(
|
|
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=${
|
|
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
|
|
2034
|
-
|
|
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
package/tools/cli_smoke_test.sh
CHANGED
|
@@ -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."
|