Fork of evrythng/evrythng-cli
.
Requires Node.js version 10 or greater
Command Line Interface (CLI) for managing credentials and working with the
EVRYTHNG API from a terminal or scripts with
ease. For example, all Thngs with the device
tag!
$ evrythng thngs list --filter tags=device
See what's possible:
# See all commands, options, and switches
$ evrythng
# See all operations for a given resource type, such as Thngs:
$ evrythng thngs
Manage multiple API keys:
# Add a new API key to saved config
$ evrythng keys add personal us <some key>
# See saved keys
$ evrythng keys list
# or use in scripts
$ export OPERATOR_API_KEY=$(evrythng keys personal read)
Install the npm
module globally as a command:
$ npm i -g @chris-lewis/evrythng-cli
Then add at least one API key using an Operator API Key available from the 'Account Settings' page of the EVRYTHNG Dashboard:
This is automatically requested when first run, by default for the US API.
$ evrythng keys add $name $region $apiKey
For example:
Key truncated for brevity.
$ evrythng keys add prod us AGiWrH5OteA4aHiM...
After installation, the global npm
module can be used directly. In general,
the argument structure is:
$ evrythng <command> <params>... [<payload>] [<switches>...]
For example, creating a new product:
$ evrythng products create '{"name":"Some Fancy New Product"}'
Run evrythng
to see all commands, switches, and options.
Some resources and operation types have short aliases. For example, to list Thngs:
$ evrythng t l
Authentication is provided in two ways:
-
Using the
keys
command to store API Keys (Operator or Access Token) associated with different accounts and regions in the user's~/.evrythng-cli-config
file. Request will be sent with the currently selected key.# Select a stored key evrythng keys personal-eu use # Use in a command evrythng places list --per-page 10
-
Using the
--api-key
switch to either override the currently selected operator's API key, or provide a required key (such as the Application API Key when creating Application Users). An existing operator's name chosen when usingkeys add
is also accepted.Key truncated for brevity.
# Use a raw key evrythng accounts list --api-key AGiWrH5OteA4aHiM... # Or another saved operator by name evrythng accounts list --api-key personal-eu
You must add at least one key before you can begin using the CLI. You'll be guided the first time if you forget.
All API keys refer to a 'region' - an API URL where they exist in the EVRYTHNG Platform. See all available regions:
$ evrythng region list
Or even add your own!
$ evrythng region add test https://test.example.com
Read all places with IDs and names:
$ evrythng places list --summary
Read a Thng with a unique identifier, for use in a script:
#!/bin/bash
THNG_ID=$(evrythng thngs gs1:21:test-serial-1 read --field id)
echo $THNG_ID
Export places to CSV file:
You may need to specify a non-
utf8
encoding option for some consumers, such as using--csv-encoding utf16le
for Microsoft Excel.
$ evrythng places list --to-csv places.csv --per-page 100 --to-page 10
Create products from a CSV file:
$ evrythng products create --from-csv products.csv
The EVRYTHNG CLI allows plugins to be created and installed in order to
add/extend custom functionality as the user requires. These plugins are provided
with an api
parameter that contains methods and data they can use to implement
additional functionality, such as adding new commands.
Some example plugins can be found on GitHub.
In order to be considered a plugin, its npm
module must meet the following:
- Be installed in the same directory as the CLI, as will be the case when
installed globally with
-g
or as a project dependency (i.e: innode_modules
). - Have a package name beginning the prefix
evrythng-cli-plugin-
. - Have a single source file identifiable when it is
require
d, such as settingmain
in itspackage.json
. - That file must export a single function, which is provided the
api
parameter (see below). Asynchronous functions are not currently supported.
An example of such a plugin is shown below. The basic directory structure is:
- evrythng-cli-plugin-greeter
- package.json (with main: index.js)
- index.js
index.js
exports a single function that will be run when it is loaded:
module.exports = (api) => {
// Require minimum evrythng-cli version 1.6.0
api.requireVersion('1.6.0');
// Define a new command: 'greet $name'
const newCommand = {
about: 'Greet someone',
firstArg: 'greet',
operations: {
greetSomeoneByName: {
execute: ([name]) => console.log(`\nHello there, ${name}!`),
pattern: '$name',
},
},
};
// Register the new command
api.registerCommand(newCommand);
};
In the example above, a new command greet
is added with one operation that
is provided the remaining arguments, in the same way as regular built-in
commands. This is validated against a schema before being loaded - it must match
the structure of the above example.
The example command added in the example is then available as usual when using the CLI:
$ evrythng greet Charles
Hello there, Charles!
The api
parameter provided to a plugin's exported function contains the
following usable methods and data:
registerCommand()
- Register a new command.getOptions()
- Retrieve an object describing the user'soptions
from the CLI configuration file, which defines the persistentoptions
preferences.getSwitches()
- Retrieve an object describing the currently active switches.getConfig()
- Get aget()
/set()
interface to the config file.runCommand()
- Run a CLI command using a list of string arguments, such as['thngs', 'list']
.version
- String of the current runningevrythng-cli
version.requireVersion()
- Require a semver compatible version ofevrythng-cli
to load the plugin.getScope()
- Obtain an evrythng.js Operator or Access Token scope for the currently selected key.
The structure of launch parameters is as follows:
$ evrythng <command> <params>... [<payload>] [<switches>...]
A command is implemented by adding to commands.js
, and must have the following
exported structure:
module.exports = {
about,
firstArg,
operations,
}
For example:
module.exports = {
about: 'View rate limit information',
firstArg: 'rate-limits',
operations: {
read: {
execute: async () => http.get('/rateLimits'),
pattern: 'read',
},
},
};
This is so that a command can safely implements its functionality using
parameters that were provided to it. A command is selected when all arguments
match the pattern
provided by any given operations
item, including keywords
such as $id
or $type
.
If no command is matched, the help text is displayed. If a command is not fully
matched, but the arguments do start with a module's firstArg
, the syntax
for the module's operations
is printed to help guide the user.
So for example, the thngs $id read
command:
$ evrythng thngs UnghCKffVg8a9KwRwE5C9qBs read
would receive all tokens after its own name as args
when the operation is
called (i.e: all arguments matched its pattern
):
['UnghCKffVg8a9KwRwE5C9qBs', 'read']
and is implemented thus:
module.exports = {
about: 'Work with Thng resources.',
firstArg: 'thngs',
operations: {
readThng: {
execute: async ([id]) => http.get(`/thngs/${id}`),
pattern: '$id read',
},
},
};
This architecture allows easy extension for subcommands of existing commands, or as entirely new commands that are all agnostic to each other.
HTTP requests are performed using the post
, get
, put
, and delete
methods
exported by src/modules/http.js
.
Switches that affect pre- and post-request behavior (such as printing extra logs, applying query params, or formatting the API response) are handled transparently in this module, so the commands do not have to handle them themselves.
Any launch parameter that begins with --
is treated as a switch, and is
extracted from the launch parameters by switches.js
in the apply()
method
before the remaining args
are provided to the matched command.
After apply()
is called, a switch's state in any given invocation can be
determined as shown below for an example command:
$ evrythng thngs list --with-scopes
const switches = require('../modules/switches');
if (switches.SCOPES) {
// --with-scopes was specified
}
If a switch is configured in SWITCH_LIST
to be given with a value
(hasValue
), it is made available as value
. This is specified at invocation
time as follows:
$ evrythng thngs list --filter tags=test
The value would be read in code as:
const filter = switches.FILTER;
if (filter) {
console.log(`Filter value was ${filter}`);
}
Filter value was tags=test
The EVRYTHNG CLI uses the
evrythng-swagger
npm
module
to allow interactive building of POST
request payloads using the definitions
provided by the EVRYTHNG swagger.json
API description. This is invoked with
the --build
switch:
$ evrythng thngs create --build
The CLI then asks for each field in the definition
(that is not marked as
readOnly
) specified in the command that implements payloadBuilder.build()
,
in this case ThngDocument
:
createThng: {
execute: async ([, json]) => {
const payload = await util.buildPayload('ThngDocument', json);
return http.post('/thngs', payload);
},
pattern: 'create $payload',
},
The user is then asked to input their values, including sub-objects such as
customFields
:
$ evrythng thngs create --build
Provide values for each field (or leave blank to skip):
1/7: name (string): My New Thng
2/7: tags (comma-separated list of string): cli,generated,objects
3/7: description (string): This Thng was created interactively
4/7: customFields (object, leave 'key' blank to finish)
key: origin
value: evrythng-cli
key:
5/7: identifiers (object, leave 'key' blank to finish)
key: serial
value: 432897
key:
6/7: product (string):
7/7: collections (comma-separated list of string):
{
"id": "U5AgdeSQBg8aQKawwHsxkbcs",
"createdAt": 1530278943537,
"customFields": {
"origin": "evrythng-cli"
},
"tags": [
"cli",
"generated",
"objects"
],
"updatedAt": 1530278943537,
"name": "My New Thng",
"description": "This Thng was created interactively",
"identifiers": {
"serial": "432897"
}
}
Run npm test
to run the Mocha tests in the tests
directory.
Afterwards, see reports/index.html
for code coverage results.