@storacha/cli
Version:
Command Line Interface to the Storacha Network
449 lines (398 loc) • 12.7 kB
JavaScript
import sade from 'sade'
import open from 'open'
import updateNotifier from 'update-notifier'
import { getPkg } from './lib.js'
import {
Account,
Space,
Coupon,
Bridge,
accessClaim,
addSpace,
listSpaces,
useSpace,
spaceInfo,
createDelegation,
listDelegations,
revokeDelegation,
addProof,
listProofs,
upload,
remove,
list,
whoami,
usageReport,
getPlan,
createKey,
reset,
} from './index.js'
import {
blobAdd,
blobList,
blobRemove,
indexAdd,
uploadAdd,
uploadList,
uploadRemove,
filecoinInfo,
} from './can.js'
const pkg = getPkg()
updateNotifier({ pkg }).notify({ isGlobal: true })
const cli = sade('storacha')
cli
.version(pkg.version)
.example('login user@example.com')
.example('up path/to/files')
cli
.command('login [email]')
.example('login user@example.com')
.describe(
'Authenticate this agent with your email address to gain access to all capabilities that have been delegated to it.'
)
.option(
'--github',
'Use GitHub to authenticate. GitHub developer accounts automatically gain access to a trial plan.',
false
)
.action(Account.login)
cli
.command('plan get [email]')
.example('plan get user@example.com')
.describe('Displays plan given account is on')
.action(getPlan)
cli
.command('account ls')
.alias('account list')
.describe('List accounts this agent has been authorized to act on behalf of.')
.action(Account.list)
cli
.command('up [file]')
.alias('upload', 'put')
.describe('Store a file(s) to the service and register an upload.')
.option('-H, --hidden', 'Include paths that start with ".".', false)
.option('-c, --car', 'File is a CAR file.', false)
.option(
'--dedupe',
'Deduplicate repeated blocks as they are uploaded. Pass --no-dedupe to disable.',
true
)
.option(
'--wrap',
'Wrap single input file in a directory. Has no effect on directory or CAR uploads. Pass --no-wrap to disable.',
true
)
.option('--json', 'Format as newline delimited JSON', false)
.option('--verbose', 'Output more details.', false)
.option(
'--shard-size',
'Shard uploads into CAR files of approximately this size in bytes.'
)
.action(upload)
cli
.command('open <cid>')
.describe('Open CID on https://storacha.link')
.action((cid) => open(`https://storacha.link/ipfs/${cid}`))
cli
.command('ls')
.alias('list')
.describe('List uploads in the current space')
.option('--json', 'Format as newline delimited JSON')
.option('--shards', 'Pretty print with shards in output')
.action(list)
cli
.command('rm <root-cid>')
.example('rm bafy...')
.describe(
'Remove an upload from the uploads listing. Pass --shards to delete the actual data if you are sure no other uploads need them'
)
.option(
'--shards',
'Remove all shards referenced by the upload from the store. Use with caution and ensure other uploads do not reference the same shards.'
)
.action(remove)
cli
.command('whoami')
.describe('Print information about the current agent.')
.action(whoami)
cli
.command('space create [name]')
.describe('Create a new storacha space')
.option('-nr, --no-recovery', 'Skips recovery key setup')
.option('-n, --no-caution', 'Prints out recovery key without confirmation')
.option('-nc, --no-customer', 'Skip billing setup')
.option('-c, --customer <email>', 'Billing account email')
.option('-na, --no-account', 'Skip account setup')
.option('-a, --account <email>', 'Managing account email')
.option(
'-ag, --authorize-gateway-services <json>',
'Authorize Gateways to serve the content uploaded to this space, e.g: \'[{"id":"did:key:z6Mki...","serviceEndpoint":"https://gateway.example.com"}]\''
)
.option('-nga, --no-gateway-authorization', 'Skip Gateway Authorization')
.option(
'-at, --access-type <type>',
'Access type for the space: public or private (default: public)'
)
.option(
'-ep, --encryption-provider <provider>',
'Encryption provider for private spaces: google-kms (default: google-kms)'
)
.option(
'-ea, --encryption-algorithm <algorithm>',
'Encryption algorithm for private spaces (default: RSA_DECRYPT_OAEP_3072_SHA256)'
)
.action((name, options) => {
let authorizeGatewayServices = []
if (options['authorize-gateway-services']) {
try {
authorizeGatewayServices = JSON.parse(
options['authorize-gateway-services']
)
} catch (err) {
console.error('Invalid JSON format for --authorize-gateway-services')
process.exit(1)
}
}
// Validate access type
if (
options['access-type'] &&
!['public', 'private'].includes(options['access-type'])
) {
console.error('Invalid access type. Must be either "public" or "private"')
process.exit(1)
}
// Validate encryption provider
if (
options['encryption-provider'] &&
!['google-kms'].includes(options['encryption-provider'])
) {
console.error('Invalid encryption provider. Must be "google-kms"')
process.exit(1)
}
// Create access type object
let access
const accessType = options['access-type'] || 'public'
if (accessType === 'public') {
access = { type: 'public' }
} else {
const provider = options['encryption-provider'] || 'google-kms'
// Ensure only Google KMS is supported
if (provider !== 'google-kms') {
console.error(
'Invalid encryption provider. Only "google-kms" is supported for private spaces.'
)
process.exit(1)
}
const algorithm =
options['encryption-algorithm'] || 'RSA_DECRYPT_OAEP_3072_SHA256'
access = {
type: 'private',
encryption: { provider, algorithm },
}
}
const parsedOptions = {
...options,
// if defined it means we want to skip gateway authorization, so the client will not validate the gateway services
skipGatewayAuthorization: options['gateway-authorization'] === false,
// default to empty array if not set, so the client will validate the gateway services
authorizeGatewayServices: authorizeGatewayServices || [],
// pass through the access object
access,
}
return Space.create(name, parsedOptions)
})
cli
.command('space provision [name]')
.describe('Associating space with a billing account')
.option('-c, --customer', 'The email address of the billing account')
.option('--coupon', 'Coupon URL to provision space with')
.option('-p, -password', 'Coupon password')
.option(
'-p, --provider',
'The storage provider to associate with this space.'
)
.action(Space.provision)
cli
.command('space add <proof>')
.describe(
'Import a space from a proof: a CAR encoded UCAN delegating capabilities to this agent. proof is a filesystem path, or a base64 encoded cid string.'
)
.action(addSpace)
cli
.command('space ls')
.describe('List spaces known to the agent')
.action(listSpaces)
cli
.command('space info')
.describe('Show information about a space. Defaults to the current space.')
.option('-s, --space', 'The space to print information about.')
.option('--json', 'Format as newline delimited JSON')
.action(spaceInfo)
cli
.command('space use <did>')
.describe('Set the current space in use by the agent')
.action(useSpace)
cli
.command('coupon create <did>')
.option('--password', 'Password for created coupon.')
.option('-c, --can', 'One or more abilities to delegate.')
.option(
'-e, --expiration',
'Unix timestamp when the delegation is no longer valid. Zero indicates no expiration.',
0
)
.option(
'-o, --output',
'Path of file to write the exported delegation data to.'
)
.action(Coupon.issue)
cli
.command('bridge generate-tokens <did>')
.option('-c, --can', 'One or more abilities to delegate.')
.option(
'-e, --expiration',
'Unix timestamp (in seconds) when the delegation is no longer valid. Zero indicates no expiration.',
0
)
.option(
'-j, --json',
'If set, output JSON suitable to spread into the `headers` field of a `fetch` request.'
)
.action(Bridge.generateTokens)
cli
.command('delegation create <audience-did>')
.describe(
'Output a CAR encoded UCAN that delegates capabilities to the audience for the current space.'
)
.option('-c, --can', 'One or more abilities to delegate.')
.option(
'-n, --name',
'Human readable name for the audience receiving the delegation.'
)
.option(
'-t, --type',
'Type of the audience receiving the delegation, one of: device, app, service.'
)
.option(
'-e, --expiration',
'Unix timestamp when the delegation is no longer valid. Zero indicates no expiration.',
0
)
.option(
'-o, --output',
'Path of file to write the exported delegation data to.'
)
.option(
'--base64',
'Format as base64 identity CID string. Useful when saving it as an environment variable.'
)
.action(createDelegation)
cli
.command('delegation ls')
.describe('List delegations created by this agent for others.')
.option('--json', 'Format as newline delimited JSON')
.action(listDelegations)
cli
.command('delegation revoke <delegation-cid>')
.describe('Revoke a delegation by CID.')
.option(
'-p, --proof',
'Name of a file containing the delegation and any additional proofs needed to prove authority to revoke'
)
.action(revokeDelegation)
cli
.command('proof add <proof>')
.describe('Add a proof delegated to this agent.')
.option('--json', 'Format as newline delimited JSON')
.option('--dry-run', 'Decode and view the proof but do not add it')
.action(addProof)
cli
.command('proof ls')
.describe('List proofs of capabilities delegated to this agent.')
.option('--json', 'Format as newline delimited JSON')
.action(listProofs)
cli
.command('usage report')
.describe('Display report of current space usage in bytes.')
.option('--human', 'Format human readable values.', false)
.option('--json', 'Format as newline delimited JSON', false)
.action(usageReport)
cli
.command('can access claim')
.describe('Claim delegated capabilities for the authorized account.')
.action(accessClaim)
cli
.command('can blob add [data-path]')
.describe('Store a blob with the service.')
.action(blobAdd)
cli
.command('can blob ls')
.describe('List blobs in the current space.')
.option('--json', 'Format as newline delimited JSON')
.option('--size', 'The desired number of results to return')
.option(
'--cursor',
'An opaque string included in a prior blob/list response that allows the service to provide the next "page" of results'
)
.action(blobList)
cli
.command('can blob rm <multihash>')
.describe('Remove a blob from the store by base58btc encoded multihash.')
.action(blobRemove)
cli
.command('can index add <cid>')
.describe('Register an "index" with the service.')
.action(indexAdd)
cli
.command('can upload add <root-cid> <shard-cid>')
.describe(
'Register an upload - a DAG with the given root data CID that is stored in the given CAR shard(s), identified by CAR CIDs.'
)
.action(uploadAdd)
cli
.command('can upload ls')
.describe('List uploads in the current space.')
.option('--json', 'Format as newline delimited JSON')
.option('--shards', 'Pretty print with shards in output')
.option('--size', 'The desired number of results to return')
.option(
'--cursor',
'An opaque string included in a prior upload/list response that allows the service to provide the next "page" of results'
)
.option('--pre', 'If true, return the page of results preceding the cursor')
.action(uploadList)
cli
.command('can upload rm <root-cid>')
.describe('Remove an upload from the uploads listing.')
.action(uploadRemove)
cli
.command('can filecoin info <piece-cid>')
.describe('Get filecoin information for given PieceCid.')
.action(filecoinInfo)
cli
.command('key create')
.describe(
'Generate and print a new ed25519 key pair. Does not change your current signing key.'
)
.option('--json', 'output as json')
.action(createKey)
cli
.command('reset')
.describe(
'Remove all proofs/delegations from the store but retain the agent DID.'
)
.action(reset)
// show help text if no command provided
cli.command('help [cmd]', 'Show help text', { default: true }).action((cmd) => {
try {
cli.help(cmd)
} catch (err) {
console.log(`
ERROR
Invalid command: ${cmd}
Run \`$ storacha --help\` for more info.
`)
process.exit(1)
}
})
cli.parse(process.argv)