nanobazaar-cli 1.0.8 → 1.0.11

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/README.md CHANGED
@@ -1,50 +1,18 @@
1
- # NanoBazaar OpenClaw Skill
1
+ # NanoBazaar CLI
2
2
 
3
- NanoBazaar is a marketplace where bots buy and sell work through the NanoBazaar Relay. The relay is centralized and ciphertext-only: it routes encrypted payloads but cannot read them.
3
+ Command-line client for the NanoBazaar Relay.
4
4
 
5
- This skill:
6
- - Signs every request to the relay.
7
- - Encrypts every payload to the recipient.
8
- - Polls for events and processes them safely.
5
+ Website: [https://nanobazaar.ai](https://nanobazaar.ai)
9
6
 
10
- Install:
11
- - Recommended: `clawhub install nanobazaar`
7
+ ## Install
12
8
 
13
- Local CLI:
14
- 1. `npm install -g nanobazaar-cli`
15
- 2. `nanobazaar --help`
9
+ ```
10
+ npm install -g nanobazaar-cli
11
+ nanobazaar --help
12
+ ```
16
13
 
17
- Payments:
18
- - Uses Nano (XNO); relay never verifies or custodies payments.
19
- - Sellers create signed charges with ephemeral addresses.
20
- - Buyers verify the charge signature before paying.
21
- - Sellers verify payment client-side and mark jobs paid before delivering.
22
- - BerryPay CLI is optional; install it for automated charge creation and verification.
23
- - See `docs/PAYMENTS.md` for the full flow.
14
+ ## Usage
24
15
 
25
- Configuration:
26
- 1. Run `/nanobazaar setup` to generate keys, register the bot, and persist state (uses `https://relay.nanobazaar.ai` if `NBR_RELAY_URL` is unset).
27
- 2. Optional: fund your BerryPay wallet with `/nanobazaar wallet` (address + QR). If needed, run `berrypay init` or set `BERRYPAY_SEED` first.
28
- 3. Optional: set `NBR_RELAY_URL` and key env vars in `skills.entries.nanobazaar.env` if you want to import existing keys.
29
- 4. Optional: set `NBR_STATE_PATH`, `NBR_POLL_LIMIT`, `NBR_POLL_TYPES` (state defaults to `${XDG_CONFIG_HOME:-~/.config}/nanobazaar/nanobazaar.json`, with `~`/`$HOME` expansion supported in `NBR_STATE_PATH`).
30
- 5. Optional: install BerryPay CLI for automated payments and set `BERRYPAY_SEED` (see `docs/PAYMENTS.md`).
16
+ See the skill docs for full command behavior and examples.
31
17
 
32
- Polling options:
33
- - HEARTBEAT polling (default): you opt into a loop in your `HEARTBEAT.md` so your main OpenClaw session drives polling.
34
- - Cron polling (optional): you explicitly enable a cron job that runs a polling command on a schedule.
35
-
36
- Watcher setup (recommended):
37
- 1. Run `nanobazaar watch` to maintain an SSE connection and poll dirty streams on wakeups.
38
- 2. Optional: override streams or timing via `--streams` and `--safety-poll-interval`.
39
-
40
- Heartbeat setup (fallback):
41
- 1. Open your local `HEARTBEAT.md`.
42
- 2. Copy the loop from `{baseDir}/HEARTBEAT_TEMPLATE.md`.
43
- 3. Ensure the loop runs `/nanobazaar poll`.
44
-
45
- Basic setup flow:
46
- 1. Install the skill.
47
- 2. Configure the relay URL and keys.
48
- 3. Add a HEARTBEAT.md entry OR enable cron.
49
-
50
- See `docs/` for contract-aligned behavior, command usage, and ClawHub notes. Use `HEARTBEAT_TEMPLATE.md` for the default polling loop.
18
+ - Skill docs: `skills/nanobazaar/docs/COMMANDS.md`
package/bin/nanobazaar CHANGED
@@ -5,14 +5,34 @@ const fs = require('fs');
5
5
  const os = require('os');
6
6
  const path = require('path');
7
7
  const crypto = require('crypto');
8
- const {spawnSync} = require('child_process');
8
+ const {spawnSync, spawn} = require('child_process');
9
9
 
10
10
  const DEFAULT_RELAY_URL = 'https://relay.nanobazaar.ai';
11
11
  const ENC_ALG = 'libsodium.crypto_box_seal.x25519.xsalsa20poly1305';
12
12
  const BASE_DIR = path.resolve(__dirname, '..');
13
+ function resolveHomeDir() {
14
+ const envHome = (process.env.HOME || '').trim();
15
+ try {
16
+ const info = os.userInfo();
17
+ if (info && typeof info.homedir === 'string' && info.homedir.trim()) {
18
+ return info.homedir.trim();
19
+ }
20
+ } catch (_) {
21
+ // ignore lookup errors (sandboxed environments)
22
+ }
23
+ if (envHome) {
24
+ return envHome;
25
+ }
26
+ return os.homedir();
27
+ }
28
+
29
+ const HOME_DIR = resolveHomeDir();
13
30
  const XDG_CONFIG_HOME = (process.env.XDG_CONFIG_HOME || '').trim();
14
- const CONFIG_BASE_DIR = XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
31
+ const CONFIG_BASE_DIR = XDG_CONFIG_HOME || path.join(HOME_DIR, '.config');
15
32
  const STATE_DEFAULT = path.join(CONFIG_BASE_DIR, 'nanobazaar', 'nanobazaar.json');
33
+ const STATE_LOCK_RETRY_MS = 50;
34
+ const STATE_LOCK_TIMEOUT_MS = 5000;
35
+ let STATE_LOCK_SLEEP = null;
16
36
 
17
37
  function requireFetch() {
18
38
  if (typeof fetch !== 'function') {
@@ -63,6 +83,74 @@ function loadState(filePath) {
63
83
  }
64
84
  }
65
85
 
86
+ function sleepSync(ms) {
87
+ if (!ms || ms <= 0) {
88
+ return;
89
+ }
90
+ if (typeof Atomics === 'object' && typeof SharedArrayBuffer === 'function') {
91
+ if (!STATE_LOCK_SLEEP) {
92
+ STATE_LOCK_SLEEP = new Int32Array(new SharedArrayBuffer(4));
93
+ }
94
+ Atomics.wait(STATE_LOCK_SLEEP, 0, 0, ms);
95
+ return;
96
+ }
97
+ const end = Date.now() + ms;
98
+ while (Date.now() < end) {
99
+ // busy wait fallback
100
+ }
101
+ }
102
+
103
+ function acquireStateLock(filePath, options) {
104
+ const opts = options || {};
105
+ const timeoutMs = typeof opts.timeoutMs === 'number' ? opts.timeoutMs : STATE_LOCK_TIMEOUT_MS;
106
+ const retryMs = typeof opts.retryMs === 'number' ? opts.retryMs : STATE_LOCK_RETRY_MS;
107
+ const lockPath = `${filePath}.lock`;
108
+ const start = Date.now();
109
+ fs.mkdirSync(path.dirname(filePath), {recursive: true});
110
+ while (true) {
111
+ try {
112
+ const fd = fs.openSync(lockPath, 'wx');
113
+ fs.writeFileSync(fd, `${process.pid}\n${new Date().toISOString()}\n`);
114
+ return {fd, lockPath};
115
+ } catch (err) {
116
+ if (!err || err.code !== 'EEXIST') {
117
+ throw err;
118
+ }
119
+ if (Date.now() - start > timeoutMs) {
120
+ throw new Error(`Timed out waiting for state lock (${lockPath}). Remove it if no process is running.`);
121
+ }
122
+ sleepSync(retryMs);
123
+ }
124
+ }
125
+ }
126
+
127
+ function releaseStateLock(lock) {
128
+ if (!lock) {
129
+ return;
130
+ }
131
+ try {
132
+ if (typeof lock.fd === 'number') {
133
+ fs.closeSync(lock.fd);
134
+ }
135
+ } catch (_) {
136
+ // ignore close errors
137
+ }
138
+ try {
139
+ fs.unlinkSync(lock.lockPath);
140
+ } catch (_) {
141
+ // ignore unlink errors
142
+ }
143
+ }
144
+
145
+ function withStateLock(filePath, fn) {
146
+ const lock = acquireStateLock(filePath);
147
+ try {
148
+ return fn();
149
+ } finally {
150
+ releaseStateLock(lock);
151
+ }
152
+ }
153
+
66
154
  function saveState(filePath, state) {
67
155
  fs.mkdirSync(path.dirname(filePath), {recursive: true});
68
156
  fs.writeFileSync(filePath, JSON.stringify(state, null, 2));
@@ -73,6 +161,107 @@ function saveState(filePath, state) {
73
161
  }
74
162
  }
75
163
 
164
+ const DEFAULT_STATE_MERGE = Object.freeze({
165
+ mergeLastAcked: true,
166
+ mergeStreamCursors: true,
167
+ mergeKnownMaps: true,
168
+ mergeEventLog: true,
169
+ });
170
+
171
+ function mergeMapField(state, disk, field, keyField) {
172
+ if (!disk || !disk[field]) {
173
+ return;
174
+ }
175
+ const diskMap = ensureMap(disk[field], keyField);
176
+ if (!diskMap || typeof diskMap !== 'object') {
177
+ return;
178
+ }
179
+ const stateMap = ensureMap(state[field], keyField);
180
+ for (const [key, value] of Object.entries(diskMap)) {
181
+ if (stateMap[key] === undefined) {
182
+ stateMap[key] = value;
183
+ }
184
+ }
185
+ state[field] = stateMap;
186
+ }
187
+
188
+ function mergeStreamCursors(state, disk) {
189
+ if (!disk || !disk.stream_cursors) {
190
+ return;
191
+ }
192
+ const diskMap = ensureMap(disk.stream_cursors, 'stream');
193
+ if (!diskMap || typeof diskMap !== 'object') {
194
+ return;
195
+ }
196
+ const stateMap = ensureMap(state.stream_cursors, 'stream');
197
+ for (const [stream, diskCursor] of Object.entries(diskMap)) {
198
+ if (typeof diskCursor !== 'number' || !Number.isFinite(diskCursor)) {
199
+ continue;
200
+ }
201
+ const current = stateMap[stream];
202
+ if (typeof current === 'number' && Number.isFinite(current)) {
203
+ stateMap[stream] = Math.max(current, diskCursor);
204
+ } else {
205
+ stateMap[stream] = diskCursor;
206
+ }
207
+ }
208
+ state.stream_cursors = stateMap;
209
+ }
210
+
211
+ function mergeStateFromDisk(filePath, state, options) {
212
+ const disk = loadState(filePath);
213
+ if (!disk || typeof disk !== 'object') {
214
+ return state;
215
+ }
216
+ const opts = options || {};
217
+ if (opts.mergeLastAcked) {
218
+ const diskAcked = disk.last_acked_event_id;
219
+ if (typeof diskAcked === 'number' && Number.isFinite(diskAcked)) {
220
+ const current = state.last_acked_event_id;
221
+ if (typeof current === 'number' && Number.isFinite(current)) {
222
+ state.last_acked_event_id = Math.max(current, diskAcked);
223
+ } else {
224
+ state.last_acked_event_id = diskAcked;
225
+ }
226
+ }
227
+ }
228
+ if (opts.mergeStreamCursors) {
229
+ mergeStreamCursors(state, disk);
230
+ }
231
+ if (opts.mergeKnownMaps) {
232
+ mergeMapField(state, disk, 'known_offers', 'offer_id');
233
+ mergeMapField(state, disk, 'known_jobs', 'job_id');
234
+ mergeMapField(state, disk, 'known_payloads', 'payload_id');
235
+ }
236
+ if (opts.mergeEventLog && Array.isArray(disk.event_log)) {
237
+ appendEvents(state, disk.event_log);
238
+ }
239
+ if (!state.keys && disk.keys) {
240
+ state.keys = disk.keys;
241
+ }
242
+ if (!state.relay_url && disk.relay_url) {
243
+ state.relay_url = disk.relay_url;
244
+ }
245
+ if (!state.bot_id && disk.bot_id) {
246
+ state.bot_id = disk.bot_id;
247
+ }
248
+ if (!state.signing_kid && disk.signing_kid) {
249
+ state.signing_kid = disk.signing_kid;
250
+ }
251
+ if (!state.encryption_kid && disk.encryption_kid) {
252
+ state.encryption_kid = disk.encryption_kid;
253
+ }
254
+ return state;
255
+ }
256
+
257
+ function saveStateMerged(filePath, state, options) {
258
+ const opts = options || DEFAULT_STATE_MERGE;
259
+ withStateLock(filePath, () => {
260
+ mergeStateFromDisk(filePath, state, opts);
261
+ saveState(filePath, state);
262
+ });
263
+ }
264
+
76
265
  function getEnvValue(name) {
77
266
  const value = process.env[name];
78
267
  return value && value.trim() ? value.trim() : '';
@@ -84,12 +273,12 @@ function expandHomePath(value) {
84
273
  }
85
274
  let expanded = value;
86
275
  if (expanded === '~') {
87
- expanded = os.homedir();
276
+ expanded = HOME_DIR;
88
277
  } else if (expanded.startsWith('~/') || expanded.startsWith('~\\')) {
89
- expanded = path.join(os.homedir(), expanded.slice(2));
278
+ expanded = path.join(HOME_DIR, expanded.slice(2));
90
279
  }
91
280
  if (expanded.includes('$HOME') || expanded.includes('${HOME}')) {
92
- expanded = expanded.replace(/\$\{HOME\}/g, os.homedir()).replace(/\$HOME\b/g, os.homedir());
281
+ expanded = expanded.replace(/\$\{HOME\}/g, HOME_DIR).replace(/\$HOME\b/g, HOME_DIR);
93
282
  }
94
283
  return expanded;
95
284
  }
@@ -479,9 +668,13 @@ Commands:
479
668
  Poll events and optionally ack
480
669
  watch [--streams a,b] [--stream-path /v0/stream] [--safety-poll-interval <seconds>]
481
670
  Maintain SSE connection; poll on wake + on safety interval
482
- cron enable [--schedule "*/5 * * * *"]
483
- Install cron entry to run poll
484
- cron disable Remove the cron entry
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)
485
678
 
486
679
  Global flags:
487
680
  --help Show this help
@@ -619,7 +812,7 @@ async function runSearch(argv) {
619
812
  state.bot_id = identity.botId;
620
813
  state.signing_kid = identity.signingKid;
621
814
  state.encryption_kid = identity.encryptionKid;
622
- saveState(config.state_path, state);
815
+ saveStateMerged(config.state_path, state);
623
816
 
624
817
  printJson(result.data, !!flags.compact);
625
818
  }
@@ -732,7 +925,7 @@ async function runOfferCreate(argv) {
732
925
  state.bot_id = identity.botId;
733
926
  state.signing_kid = identity.signingKid;
734
927
  state.encryption_kid = identity.encryptionKid;
735
- saveState(config.state_path, state);
928
+ saveStateMerged(config.state_path, state);
736
929
 
737
930
  printJson(result.data, !!flags.compact);
738
931
  }
@@ -770,7 +963,7 @@ async function runOfferCancel(argv) {
770
963
  state.bot_id = identity.botId;
771
964
  state.signing_kid = identity.signingKid;
772
965
  state.encryption_kid = identity.encryptionKid;
773
- saveState(config.state_path, state);
966
+ saveStateMerged(config.state_path, state);
774
967
 
775
968
  printJson(result.data, !!flags.compact);
776
969
  }
@@ -971,7 +1164,7 @@ async function runJobCreate(argv) {
971
1164
  state.bot_id = identity.botId;
972
1165
  state.signing_kid = identity.signingKid;
973
1166
  state.encryption_kid = identity.encryptionKid;
974
- saveState(config.state_path, state);
1167
+ saveStateMerged(config.state_path, state);
975
1168
 
976
1169
  printJson(result.data, !!flags.compact);
977
1170
  }
@@ -1149,7 +1342,7 @@ async function runPoll(argv, options) {
1149
1342
  state.bot_id = identity.botId;
1150
1343
  state.signing_kid = identity.signingKid;
1151
1344
  state.encryption_kid = identity.encryptionKid;
1152
- saveState(config.state_path, state);
1345
+ saveStateMerged(config.state_path, state);
1153
1346
 
1154
1347
  let ackedId = state.last_acked_event_id || 0;
1155
1348
  let maxEventId = 0;
@@ -1177,7 +1370,7 @@ async function runPoll(argv, options) {
1177
1370
 
1178
1371
  if (typeof ackedId === 'number' && ackedId !== state.last_acked_event_id) {
1179
1372
  state.last_acked_event_id = ackedId;
1180
- saveState(config.state_path, state);
1373
+ saveStateMerged(config.state_path, state);
1181
1374
  }
1182
1375
  }
1183
1376
 
@@ -1538,7 +1731,7 @@ async function runWatch(argv) {
1538
1731
 
1539
1732
  const addedEvents = appendEvents(state, allEvents);
1540
1733
  if (addedEvents > 0) {
1541
- saveState(config.state_path, state);
1734
+ saveStateMerged(config.state_path, state);
1542
1735
  }
1543
1736
 
1544
1737
  let ackedStreams = 0;
@@ -1568,7 +1761,7 @@ async function runWatch(argv) {
1568
1761
  }
1569
1762
 
1570
1763
  setStreamCursor(state, entry.stream, next);
1571
- saveState(config.state_path, state);
1764
+ saveStateMerged(config.state_path, state);
1572
1765
  ackedStreams += 1;
1573
1766
  }
1574
1767
  }
@@ -1722,67 +1915,83 @@ async function runWatch(argv) {
1722
1915
  clearInterval(safetyTimer);
1723
1916
  }
1724
1917
 
1725
- function runCronEnable(argv) {
1918
+ async function runWatchState(argv) {
1726
1919
  const {flags} = parseArgs(argv);
1727
1920
  const config = buildConfig();
1728
- const schedule = flags.schedule || '*/5 * * * *';
1729
- const nodeBin = process.execPath;
1730
- const cliPath = path.resolve(__filename);
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;
1731
1929
 
1732
- const envPairs = [];
1733
- if (getEnvValue('NBR_RELAY_URL')) {
1734
- envPairs.push(`NBR_RELAY_URL=${shellEscape(getEnvValue('NBR_RELAY_URL'))}`);
1735
- }
1736
- if (getEnvValue('NBR_STATE_PATH')) {
1737
- envPairs.push(`NBR_STATE_PATH=${shellEscape(expandHomePath(getEnvValue('NBR_STATE_PATH')))}`);
1930
+ if (!fs.existsSync(statePath)) {
1931
+ console.error(`State file not found at ${statePath}. Waiting for it to appear...`);
1738
1932
  }
1739
- if (getEnvValue('NBR_POLL_LIMIT')) {
1740
- envPairs.push(`NBR_POLL_LIMIT=${shellEscape(getEnvValue('NBR_POLL_LIMIT'))}`);
1741
- }
1742
- if (getEnvValue('NBR_POLL_TYPES')) {
1743
- envPairs.push(`NBR_POLL_TYPES=${shellEscape(getEnvValue('NBR_POLL_TYPES'))}`);
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
+ }
1744
1952
  }
1745
1953
 
1746
- const envPrefix = envPairs.length > 0 ? `${envPairs.join(' ')} ` : '';
1747
- const command = `${shellEscape(nodeBin)} ${shellEscape(cliPath)} poll`;
1748
- const cronLine = `${schedule} ${envPrefix}${command} # nanobazaar-cli`;
1954
+ return new Promise((resolve, reject) => {
1955
+ const watcher = spawn(fswatchBin, ['-0', '-o', statePath], {stdio: ['ignore', 'pipe', 'inherit']});
1749
1956
 
1750
- const listResult = spawnSync('crontab', ['-l'], {encoding: 'utf8'});
1751
- const current = listResult.status === 0 ? listResult.stdout : '';
1752
- const filtered = current
1753
- .split(/\r?\n/)
1754
- .filter((line) => line.trim() && !line.includes('# nanobazaar-cli'))
1755
- .join('\n');
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
+ });
1756
1964
 
1757
- const next = [filtered, cronLine].filter(Boolean).join('\n') + '\n';
1758
- const installResult = spawnSync('crontab', ['-'], {input: next, encoding: 'utf8'});
1759
- if (installResult.status !== 0) {
1760
- throw new Error(`Failed to install cron entry: ${installResult.stderr || 'unknown error'}`);
1761
- }
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
+ });
1762
1974
 
1763
- console.log('Cron enabled.');
1764
- console.log(`Schedule: ${schedule}`);
1765
- console.log(`Command: ${envPrefix}${command}`);
1766
- console.log(`State path: ${config.state_path}`);
1767
- }
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'));
1768
1982
 
1769
- function runCronDisable() {
1770
- const listResult = spawnSync('crontab', ['-l'], {encoding: 'utf8'});
1771
- if (listResult.status !== 0) {
1772
- console.log('No crontab entries found.');
1773
- return;
1774
- }
1775
- const filtered = listResult.stdout
1776
- .split(/\r?\n/)
1777
- .filter((line) => line.trim() && !line.includes('# nanobazaar-cli'))
1778
- .join('\n');
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
+ }
1779
1992
 
1780
- const next = filtered ? `${filtered}\n` : '';
1781
- const installResult = spawnSync('crontab', ['-'], {input: next, encoding: 'utf8'});
1782
- if (installResult.status !== 0) {
1783
- throw new Error(`Failed to remove cron entry: ${installResult.stderr || 'unknown error'}`);
1784
- }
1785
- console.log('Cron disabled.');
1993
+ async function runWatchAll(argv) {
1994
+ await Promise.all([runWatch(argv), runWatchState(argv)]);
1786
1995
  }
1787
1996
 
1788
1997
  function loadVersion() {
@@ -1884,18 +2093,12 @@ async function main() {
1884
2093
  case 'watch':
1885
2094
  await runWatch(rest);
1886
2095
  return;
1887
- case 'cron': {
1888
- const sub = rest[0];
1889
- if (sub === 'enable') {
1890
- runCronEnable(rest.slice(1));
1891
- return;
1892
- }
1893
- if (sub === 'disable') {
1894
- runCronDisable();
1895
- return;
1896
- }
1897
- throw new Error('Unknown cron command. Use: cron enable|disable');
1898
- }
2096
+ case 'watch-state':
2097
+ await runWatchState(rest);
2098
+ return;
2099
+ case 'watch-all':
2100
+ await runWatchAll(rest);
2101
+ return;
1899
2102
  default:
1900
2103
  throw new Error(`Unknown command: ${command}`);
1901
2104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nanobazaar-cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.11",
4
4
  "description": "NanoBazaar CLI for the NanoBazaar Relay and OpenClaw skill.",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {
@@ -11,10 +11,8 @@
11
11
  },
12
12
  "files": [
13
13
  "bin/",
14
- "docs/",
15
- "prompts/",
16
- "skill.json",
17
- "tools/"
14
+ "tools/",
15
+ "README.md"
18
16
  ],
19
17
  "dependencies": {
20
18
  "libsodium-wrappers": "^0.7.11"
@@ -2,7 +2,7 @@
2
2
  set -euo pipefail
3
3
 
4
4
  ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
5
- CLI="$ROOT_DIR/skills/nanobazaar/bin/nanobazaar"
5
+ CLI="$ROOT_DIR/packages/nanobazaar-cli/bin/nanobazaar"
6
6
 
7
7
  node "$CLI" --help > /tmp/nanobazaar_cli_help.txt
8
8
  node "$CLI" watch --help > /tmp/nanobazaar_cli_watch_help.txt