#!/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) })