OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-overlay / examples / cli.js

102 lines · javascript ref:
#!/usr/bin/env node
/**
 * Mini CLI example for hyper-overlay
 *
 * Usage:
 *   node examples/cli.js put <path> <file>     Store a file
 *   node examples/cli.js get <path> [outfile]   Retrieve a file
 *   node examples/cli.js ls [dir]               List files
 *   node examples/cli.js stats                  Show overlay stats
 *   node examples/cli.js del <path>             Delete a file
 *
 * NOTE: This CLI uses its OWN storage path by default. The web example
 * (examples/web.js) uses a different default. They don't share state —
 * RocksDB has a single-writer lock, so two processes can't open the same
 * Corestore at once. For shared state across CLI/web/native interfaces in
 * production, embed hyper-overlay in a single daemon and have all clients
 * talk to the daemon via hyper-svc (planned).
 */

import fs from 'fs'
import Corestore from 'corestore'
import { HyperOverlay } from '../index.js'

const args = process.argv.slice(2)
const cmd = args[0]

function fmt (b) {
  if (b < 1024) return b + ' B'
  if (b < 1048576) return (b / 1024).toFixed(1) + ' KB'
  return (b / 1048576).toFixed(1) + ' MB'
}

async function main () {
  const storageDir = process.env.OVERLAY_STORAGE || './.overlay-cli'
  const store = new Corestore(storageDir)
  const overlay = new HyperOverlay(store, { namespace: 'example' })
  await overlay.ready()

  try {
    if (cmd === 'put') {
      const overlayPath = args[1]
      const filePath = args[2]
      if (!overlayPath || !filePath) { console.error('Usage: cli.js put <path> <file>'); process.exit(1) }
      const data = fs.readFileSync(filePath)
      const result = await overlay.put(overlayPath, data)
      console.log('Stored: %s', overlayPath)
      console.log('  oid:    %s', result.oid.slice(0, 16) + '...')
      console.log('  chunks: %d', result.chunkCount)
      console.log('  size:   %s', fmt(data.length))
    } else if (cmd === 'get') {
      const overlayPath = args[1]
      const outFile = args[2]
      if (!overlayPath) { console.error('Usage: cli.js get <path> [outfile]'); process.exit(1) }
      const data = await overlay.get(overlayPath)
      if (!data) { console.error('Not found: %s', overlayPath); process.exit(1) }
      if (outFile) {
        fs.writeFileSync(outFile, data)
        console.log('Written: %s (%s)', outFile, fmt(data.length))
      } else {
        process.stdout.write(data)
      }
    } else if (cmd === 'ls') {
      const dir = args[1] || ''
      const files = await overlay.list(dir)
      if (files.length === 0) { console.log('(empty)') } else {
        for (const f of files) {
          console.log('  %s  %s  %d chunks', f.path.padEnd(40), fmt(f.size).padStart(8), f.chunkCount)
        }
        console.log('\n%d files', files.length)
      }
    } else if (cmd === 'stats') {
      const s = await overlay.stats()
      console.log('Chunks:    %d', s.chunkCount || 0)
      console.log('Manifests: %d', s.manifestCount || 0)
      const files = await overlay.list()
      console.log('Files:     %d', files.length)
      console.log('Total:     %s', fmt(files.reduce((sum, f) => sum + f.size, 0)))
    } else if (cmd === 'del') {
      const overlayPath = args[1]
      if (!overlayPath) { console.error('Usage: cli.js del <path>'); process.exit(1) }
      await overlay.del(overlayPath)
      console.log('Deleted: %s', overlayPath)
    } else {
      console.log('hyper-overlay example CLI')
      console.log('  put <path> <file>      Store a local file in overlay')
      console.log('  get <path> [outfile]   Retrieve a file (stdout if no outfile)')
      console.log('  ls [dir]               List files')
      console.log('  stats                  Show chunk/manifest stats')
      console.log('  del <path>             Delete a file')
      console.log('')
      console.log('Storage: OVERLAY_STORAGE env var (default: ./.overlay-cli)')
      console.log('Note: this CLI does NOT share state with examples/web.js — they use')
      console.log('different storage paths because RocksDB only allows one writer.')
    }
  } finally {
    await overlay.close()
    await store.close()
  }
}

main().catch((err) => { console.error(err.message); process.exit(1) })