secure-scuttlebot classic
at main 13 kB view raw
1var fs = require('fs') 2var path = require('path') 3 4var manualEntries = { 5 start: { 6 description: 'Start the ssb-server daemon and generate ~/.ssb/manifest.json', 7 example: 'ssb-server start', 8 type: 'cli' 9 }, 10 config: { 11 description: 'Print the effective configuration used by the server', 12 example: 'ssb-server config', 13 type: 'sync' 14 }, 15 blobs: { 16 description: 'Manage blob storage, listing, adding, and pushing binary data.', 17 example: 'ssb-server blobs.ls' 18 }, 19 auth: { 20 description: 'Create the RPC authentication handshake for connecting to a remote peer.', 21 example: 'ssb-server auth net:example.com:8008~shs:<key>' 22 }, 23 address: { 24 description: 'Show the multi-server addresses clients can use to reach this server.', 25 example: 'ssb-server address' 26 }, 27 manifest: { 28 description: 'Dump the RPC manifest so other programs know what to call.', 29 example: 'ssb-server manifest' 30 }, 31 'multiserver.parse': { 32 description: 'Parse a multi-server address string into host/port/key components.', 33 example: 'ssb-server multiserver.parse net:example.com:8008~shs:<key>' 34 }, 35 'multiserver.address': { 36 description: 'Build a canonical multi-server address from host, port, and key.', 37 example: 'ssb-server multiserver.address --host example.com --port 8008 --key @abc.ed25519' 38 }, 39 multiserver: { 40 description: 'Helpers around multi-server address parsing and building.', 41 example: 'ssb-server multiserver.parse net:example.com:8008~shs:<key>' 42 }, 43 multiserverNet: { 44 description: 'Inspect the legacy net transport configuration used by multi-server.', 45 example: 'ssb-server multiserverNet' 46 }, 47 createFeedStream: { 48 description: 'Stream a feed by timestamp, optionally filtering with gt/gte/lt/lte and live=true.', 49 example: 'ssb-server createFeedStream --live --gt 0' 50 }, 51 messagesByType: { 52 description: 'Stream messages filtered by the `type` field, ordered by receive time.', 53 example: 'ssb-server messagesByType --type about' 54 }, 55 createUserStream: { 56 description: 'Stream a feed by sequence numbers with support for range filters.', 57 example: 'ssb-server createUserStream --id @alice --live' 58 }, 59 createWriteStream: { 60 description: 'Send newline-delimited JSON messages on stdin straight into the database.', 61 example: 'cat message.json | ssb-server createWriteStream' 62 }, 63 createSequenceStream: { 64 description: 'Stream the global sequence counter to track when the database advances.', 65 example: 'ssb-server createSequenceStream --live' 66 }, 67 links: { 68 description: 'Query link edges (`source`, `dest`, `rel`) between messages, feeds, and blobs.', 69 example: 'ssb-server links --source @alice' 70 }, 71 getLatest: { 72 description: 'Fetch a feed’s latest message so you can inspect the head of the log.', 73 example: 'ssb-server getLatest @alice' 74 }, 75 latest: { 76 description: 'Stream the latest sequence seen for every feed that this server follows.', 77 example: 'ssb-server latest --limit 20' 78 }, 79 latestSequence: { 80 description: 'Return the highest sequence number a feed has reached locally.', 81 example: 'ssb-server latestSequence @alice' 82 }, 83 del: { 84 description: 'Drop a message from the local log (only works if the message is still cached).', 85 example: 'ssb-server del %messageid.sha256' 86 }, 87 getVectorClock: { 88 description: 'Print the vector clock used when deciding how much of each feed we have.', 89 example: 'ssb-server getVectorClock' 90 }, 91 help: { 92 description: 'Show the CLI help overview or focus on a single command.', 93 example: 'ssb-server help publish' 94 }, 95 'plugins.help': { 96 description: 'List the plugin subcommands (install, uninstall, enable, disable).', 97 example: 'ssb-server plugins.help' 98 }, 99 plugins: { 100 description: 'Manage ssb-server plugins (install, uninstall, enable, disable).', 101 example: 'ssb-server plugins.install plugin@version' 102 }, 103 gossip: { 104 description: 'Manage gossip peers, connections, and their metadata.', 105 example: 'ssb-server gossip.peers' 106 }, 107 'gossip.peers': { 108 description: 'List the gossip table peers and their current connection state.', 109 example: 'ssb-server gossip.peers' 110 }, 111 'gossip.get': { 112 description: 'Inspect the gossip metadata for a peer by id or address.', 113 example: 'ssb-server gossip.get @peer' 114 }, 115 'gossip.ping': { 116 description: 'Ping a peer to measure reachability, RTT, and grab a quick status.', 117 example: 'ssb-server gossip.ping @peer' 118 }, 119 'gossip.help': { 120 description: 'Show gossip subcommands and their purpose.', 121 example: 'ssb-server gossip.help' 122 }, 123 'blobs.meta': { 124 description: 'Read metadata (size, timestamps) for a blob without streaming the payload.', 125 example: 'ssb-server blobs.meta &blobid.sha256' 126 }, 127 'blobs.changes': { 128 description: 'Stream notifications when blobs change locally (use --live to keep the stream open).', 129 example: 'ssb-server blobs.changes --live' 130 }, 131 'blobs.createWants': { 132 description: 'Watch the current want list for peers so you can mirror their needs.', 133 example: 'ssb-server blobs.createWants --live' 134 }, 135 'blobs.help': { 136 description: 'List available blob commands and their arguments.', 137 example: 'ssb-server blobs.help' 138 }, 139 'invite.use': { 140 description: 'Call this on the server that owns an invite code; it validates one code and publishes a follow for the provided feed.', 141 example: 'ssb-server invite.use INVITE_CODE --feed @friend' 142 }, 143 invite: { 144 description: 'Create, accept, or use invites for pubs.', 145 example: 'ssb-server invite.create 1' 146 }, 147 'friends.help': { 148 description: 'List the available friends.* subcommands.', 149 example: 'ssb-server friends.help' 150 }, 151 friends: { 152 description: 'Inspect the follow/block graph maintained by the friends plugin.', 153 example: 'ssb-server friends.hops' 154 }, 155 'friends.onEdge': { 156 description: 'Inspect edges (follows/blocks) on the friends graph between feeds.', 157 example: 'ssb-server friends.onEdge --start @alice' 158 }, 159 query: { 160 description: 'Run map/filter/reduce queries or explain how indexes are used.', 161 example: 'ssb-server query.read --query "{\\"type\\":\\"post\\"}"' 162 }, 163 'query.help': { 164 description: 'Show the query.* commands for running or explaining map-filter-reduce queries.', 165 example: 'ssb-server query.help' 166 }, 167 'links2.read': { 168 description: 'Run the newer links2 indexes to get fast link traversals.', 169 example: 'ssb-server links2.read --query "{\\"dest\\":\\"@id\\"}"' 170 }, 171 'links2.help': { 172 description: 'Describe how to use the links2 query interface.', 173 example: 'ssb-server links2.help' 174 }, 175 links2: { 176 description: 'Advanced multi-index link queries built on top of the links2 flumeview.', 177 example: 'ssb-server links2.read --query "{\\"dest\\":\\"@id\\"}"' 178 }, 179 replicate: { 180 description: 'Legacy replication controls for requesting and blocking feeds.', 181 example: 'ssb-server replicate.request --id @alice' 182 }, 183 ebt: { 184 description: 'EBT helpers for controlling replication with peers.', 185 example: 'ssb-server ebt.replicate --peer <multi-server-address>' 186 }, 187 'ebt.replicate': { 188 description: 'Open a duplex replication stream that speaks the EBT protocol.', 189 example: 'ssb-server ebt.replicate --peer <multi-server-address>' 190 }, 191 'ebt.request': { 192 description: 'Request that a peer replicate a specific feed.', 193 example: 'ssb-server ebt.request @alice' 194 }, 195 'ebt.block': { 196 description: 'Block or unblock replication for another feed.', 197 example: 'ssb-server ebt.block --from @you --to @them --blocking true' 198 }, 199 'ebt.peerStatus': { 200 description: 'Read the last-known metadata for an EBT peer connection.', 201 example: 'ssb-server ebt.peerStatus @peer' 202 }, 203 ooo: { 204 description: 'Out-of-order (ooo) helpers for sharing messages without full replication.', 205 example: 'ssb-server ooo.stream @friend' 206 }, 207 'ooo.stream': { 208 description: 'Stream messages handled out-of-order to avoid waiting for full replication.', 209 example: 'ssb-server ooo.stream @friend' 210 }, 211 'ooo.help': { 212 description: 'Show the commands exposed by the ooo (out-of-order) plugin.', 213 example: 'ssb-server ooo.help' 214 }, 215 ws: { 216 description: 'Inspect the websocket transport that Patchbay Lite and browsers rely on.', 217 example: 'ssb-server ws' 218 }, 219 frontend: { 220 description: 'Serve the built-in Patchbay Lite UI over ssb-ws.', 221 example: 'ssb-server frontend' 222 }, 223 private1: { 224 description: 'Expose the private1 transport helper for unix sockets or internal tooling.', 225 example: 'ssb-server private1' 226 }, 227 'list-commands': { 228 description: 'Print every available RPC command in the manifest/catalog.', 229 example: 'ssb-server list-commands' 230 } 231} 232 233function cloneArgs (args) { 234 if (!args) return {} 235 var copy = {} 236 Object.keys(args).forEach(function (key) { 237 copy[key] = args[key] 238 }) 239 return copy 240} 241 242function buildExample (name, args) { 243 var parts = ['ssb-server', name] 244 var argNames = args ? Object.keys(args).sort() : [] 245 argNames.forEach(function (arg) { 246 var type = args[arg] && args[arg].type ? args[arg].type : 'value' 247 parts.push('--' + arg + ' <' + type + '>') 248 }) 249 return parts.join(' ') 250} 251 252function addEntry (catalog, name, info) { 253 var args = cloneArgs(info.args) 254 catalog[name] = { 255 description: info.description || '', 256 args: args, 257 type: info.type || 'async', 258 example: info.example || buildExample(name, args) 259 } 260} 261 262function loadPluginEntries () { 263 var pluginsDir = path.join(__dirname, '..', 'plugins') 264 var catalog = {} 265 if (!fs.existsSync(pluginsDir)) return catalog 266 267 fs.readdirSync(pluginsDir).forEach(function (pluginName) { 268 var pluginPath = path.join(pluginsDir, pluginName) 269 var stats 270 try { 271 stats = fs.statSync(pluginPath) 272 } catch (_) { 273 return 274 } 275 if (!stats.isDirectory()) return 276 277 var helpFile = path.join(pluginPath, 'help.js') 278 if (!fs.existsSync(helpFile)) return 279 280 var helpModule 281 try { 282 helpModule = require(helpFile) 283 } catch (_) { 284 return 285 } 286 var commands = helpModule.commands || {} 287 var pluginDescription = helpModule.description 288 Object.keys(commands).forEach(function (commandName) { 289 var commandHelp = commands[commandName] || {} 290 var description = commandHelp.description || pluginDescription || '' 291 addEntry(catalog, pluginName + '.' + commandName, { 292 description: description, 293 args: commandHelp.args, 294 type: commandHelp.type 295 }) 296 }) 297 }) 298 299 return catalog 300} 301 302var externalHelpModules = [ 303 { name: 'ssb-db', prefix: '' }, 304 { name: 'ssb-blobs', prefix: 'blobs' }, 305 { name: 'ssb-gossip', prefix: 'gossip' }, 306 { name: 'ssb-replicate', prefix: 'replicate' }, 307 { name: 'ssb-query', prefix: 'query' }, 308 { name: 'ssb-links', prefix: 'links' }, 309 { name: 'ssb-ooo', prefix: 'ooo' }, 310 { name: 'ssb-plugins', prefix: 'plugins' } 311] 312 313function loadExternalHelpEntries () { 314 var catalog = {} 315 externalHelpModules.forEach(function (moduleInfo) { 316 var helpModule 317 try { 318 helpModule = require(moduleInfo.name + '/help') 319 } catch (_) { 320 return 321 } 322 var moduleDescription = helpModule.description 323 var commands = helpModule.commands || {} 324 Object.keys(commands).forEach(function (commandName) { 325 var info = commands[commandName] || {} 326 var name = moduleInfo.prefix ? moduleInfo.prefix + '.' + commandName : commandName 327 addEntry(catalog, name, { 328 description: info.description || moduleDescription || '', 329 args: info.args, 330 type: info.type 331 }) 332 }) 333 }) 334 return catalog 335} 336 337function createPlaceholder (name, manifestType) { 338 var entry = { 339 description: 'No detailed help is currently available for this command.', 340 args: {}, 341 type: manifestType || 'async', 342 example: 'ssb-server ' + name 343 } 344 return entry 345} 346 347function createCatalog (manifest) { 348 manifest = manifest || {} 349 var catalog = {} 350 351 var pluginEntries = loadPluginEntries() 352 Object.keys(pluginEntries).forEach(function (name) { 353 catalog[name] = pluginEntries[name] 354 }) 355 356 var externalEntries = loadExternalHelpEntries() 357 Object.keys(externalEntries).forEach(function (name) { 358 catalog[name] = externalEntries[name] 359 }) 360 361 Object.keys(manualEntries).forEach(function (name) { 362 addEntry(catalog, name, manualEntries[name]) 363 }) 364 365 Object.keys(manifest).forEach(function (name) { 366 if (!catalog[name]) { 367 catalog[name] = createPlaceholder(name, manifest[name]) 368 } else if (!catalog[name].type && manifest[name]) { 369 catalog[name].type = manifest[name] 370 } 371 }) 372 373 var names = Object.keys(catalog).sort() 374 375 return { 376 entries: catalog, 377 names: names, 378 get: function (name) { 379 return catalog[name] 380 }, 381 all: function () { 382 return names.map(function (name) { 383 return { name: name, entry: catalog[name] } 384 }) 385 } 386 } 387} 388 389module.exports = { 390 createCatalog: createCatalog 391}