Skip to content

Commit

Permalink
Interactive query and execute command (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
Spissable committed Feb 27, 2021
1 parent 583802a commit 304f7cd
Show file tree
Hide file tree
Showing 20 changed files with 336 additions and 101 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ $ npm install -g gassi-cli
$ gassi COMMAND
running command...
$ gassi (-v|--version|version)
gassi-cli/0.1.4 linux-x64 node-v14.12.0
gassi-cli/0.2.0 linux-x64 node-v14.4.0
$ gassi --help [COMMAND]
USAGE
$ gassi COMMAND
Expand All @@ -34,7 +34,7 @@ USAGE

<!-- commands -->
* [`gassi disconnect`](#gassi-disconnect)
* [`gassi execute PARAMNAME PARAMVALUE`](#gassi-execute-paramname-paramvalue)
* [`gassi execute [PARAMNAME] [PARAMVALUE]`](#gassi-execute-paramname-paramvalue)
* [`gassi help [COMMAND]`](#gassi-help-command)
* [`gassi query`](#gassi-query)
* [`gassi sync`](#gassi-sync)
Expand All @@ -53,25 +53,25 @@ OPTIONS
-u, --uri=uri (required) uri of the service
```

_See code: [src/commands/disconnect.ts](https://github.com/Spissable/gassi-cli/blob/v0.1.4/src/commands/disconnect.ts)_
_See code: [src/commands/disconnect.ts](https://github.com/Spissable/gassi-cli/blob/v0.2.0/src/commands/disconnect.ts)_

## `gassi execute PARAMNAME PARAMVALUE`
## `gassi execute [PARAMNAME] [PARAMVALUE]`

Sends an EXECUTE request intent

```
USAGE
$ gassi execute PARAMNAME PARAMVALUE
$ gassi execute [PARAMNAME] [PARAMVALUE]
OPTIONS
-c, --command=command (required) command to execute
-c, --command=command command to execute
-h, --help show CLI help
-i, --id=id (required) id to query
-i, --id=id id to query
-t, --token=token (required) oauth access token
-u, --uri=uri (required) uri of the service
```

_See code: [src/commands/execute.ts](https://github.com/Spissable/gassi-cli/blob/v0.1.4/src/commands/execute.ts)_
_See code: [src/commands/execute.ts](https://github.com/Spissable/gassi-cli/blob/v0.2.0/src/commands/execute.ts)_

## `gassi help [COMMAND]`

Expand All @@ -88,7 +88,7 @@ OPTIONS
--all see all commands in CLI
```

_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.0/src/commands/help.ts)_
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.2/src/commands/help.ts)_

## `gassi query`

Expand All @@ -100,12 +100,12 @@ USAGE
OPTIONS
-h, --help show CLI help
-i, --id=id (required) id to query
-i, --id=id id to query
-t, --token=token (required) oauth access token
-u, --uri=uri (required) uri of the service
```

_See code: [src/commands/query.ts](https://github.com/Spissable/gassi-cli/blob/v0.1.4/src/commands/query.ts)_
_See code: [src/commands/query.ts](https://github.com/Spissable/gassi-cli/blob/v0.2.0/src/commands/query.ts)_

## `gassi sync`

Expand All @@ -121,5 +121,5 @@ OPTIONS
-u, --uri=uri (required) uri of the service
```

_See code: [src/commands/sync.ts](https://github.com/Spissable/gassi-cli/blob/v0.1.4/src/commands/sync.ts)_
_See code: [src/commands/sync.ts](https://github.com/Spissable/gassi-cli/blob/v0.2.0/src/commands/sync.ts)_
<!-- commandsstop -->
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "gassi-cli",
"description": "Run SYNC, QUERY, EXECUTE and DISCONNECT requests on Google SmartHome easily",
"version": "0.1.4",
"version": "0.2.0",
"author": "Spiss, Lukas @Spissable",
"bin": {
"gassi": "./bin/run"
Expand All @@ -12,12 +12,16 @@
"@oclif/config": "^1.17.0",
"@oclif/plugin-help": "^3.2.2",
"axios": "^0.21.1",
"fs-extra": "^9.1.0",
"inquirer": "^7.3.3",
"tslib": "^2.1.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@oclif/dev-cli": "^1.26.0",
"@types/axios": "^0.14.0",
"@types/fs-extra": "^9.0.7",
"@types/inquirer": "^7.3.1",
"@types/jest": "^26.0.20",
"@types/node": "^14.14.25",
"@types/uuid": "^8.3.0",
Expand Down
10 changes: 2 additions & 8 deletions src/commands/disconnect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command, flags } from "@oclif/command";
import axios from "axios";
import { v4 as uuid } from "uuid";
import { DisconnectRequest } from "../entities/DisconnectRequest";

export default class Disconnect extends Command {
static description = "Sends a DISCONNECT request intent";
Expand All @@ -24,7 +25,7 @@ export default class Disconnect extends Command {
async run() {
const { flags } = this.parse(Disconnect);
const requestId = uuid();
const disconnectBody: DisconnectBody = {
const disconnectBody: DisconnectRequest = {
requestId,
inputs: [
{
Expand Down Expand Up @@ -52,10 +53,3 @@ export default class Disconnect extends Command {
);
}
}

interface DisconnectBody {
requestId: string;
inputs: {
intent: "action.devices.DISCONNECT";
}[];
}
37 changes: 37 additions & 0 deletions src/commands/execute.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import * as nock from "nock";
import { stdout } from "stdout-stderr";
import * as inquirer from "inquirer";

const configStub = jest.fn();
jest.mock("../util/configUtil.ts", () => ({
readConfig: configStub.mockResolvedValue({ ids: [] }),
}));
import Execute from "./execute";

describe("EXECUTE intent", () => {
Expand Down Expand Up @@ -217,4 +223,35 @@ describe("EXECUTE intent", () => {
`Request some.uuid failed with:\n${JSON.stringify(errorReply, null, 2)}\n`
);
});

test("Prompts work", async () => {
const mock = nock(testHost, {
reqheaders: { Authorization: "Bearer some.token" },
})
.post("/", onOffRequest)
.reply(200, executeReply);

jest
.spyOn(inquirer, "prompt")
.mockResolvedValueOnce({ id: "some.id" })
.mockResolvedValueOnce({ command: "OnOff" })
.mockResolvedValueOnce({ param: "on" })
.mockResolvedValueOnce({ value: "true" });

stdout.start();
await Execute.run(["-t", "some.token", "-u", testHost]);
stdout.stop();

expect(mock.isDone()).toBeTruthy();
expect(stdout.output).toEqual(JSON.stringify(executeReply, null, 2) + "\n");
});

test("Config error is caught", async () => {
configStub.mockRejectedValueOnce(new Error());

await expect(
Execute.run(["-t", "some.token", "-u", testHost])
).rejects.toThrowError("Please run sync first or provide arguments.");
stdout.stop();
});
});
99 changes: 74 additions & 25 deletions src/commands/execute.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Command, flags } from "@oclif/command";
import axios from "axios";
import { v4 as uuid } from "uuid";
import { parseInput } from "../util/util";
import { parseInput } from "../util/parseUtil";
import { readConfig } from "../util/configUtil";
import { ExecuteRequest } from "../entities/ExecuteRequest";
import * as inquirer from "inquirer";
import { executeCommands } from "../constants/executeCommands";

export default class Execute extends Command {
static description = "Sends an EXECUTE request intent";
Expand All @@ -23,32 +27,41 @@ export default class Execute extends Command {
char: "i",
description: "id to query",
env: "id",
required: true,
required: false,
}),
command: flags.string({
char: "c",
description: "command to execute",
env: "command",
required: true,
required: false,
}),

help: flags.help({ char: "h" }),
};

static args = [
{ name: "paramName", required: true },
{ name: "paramName", required: false },
{
name: "paramValue",
required: true,
required: false,
},
];

async run() {
const { args, flags } = this.parse(Execute);
const paramValue = parseInput(args.paramValue);
const command = flags.command;
const id =
flags.id ||
(await this.promptId().catch(() => {
this.error("Please run sync first or provide arguments.");
}));
const command = flags.command || (await this.promptCommand());
const paramName = args.paramName || (await this.promptParamName(command));
const paramValueStr =
args.paramValue || (await this.promptParamValue(command, paramName));
const paramValue = parseInput(paramValueStr);
const requestId = uuid();
let commandParams: any = { [args.paramName]: paramValue };

let commandParams: any = { [paramName]: paramValue };
if (command === "SetModes") {
commandParams = {
updateModeSettings: {
Expand All @@ -62,15 +75,15 @@ export default class Execute extends Command {
},
};
}
const executeBody: ExecuteBody = {
const executeBody: ExecuteRequest = {
requestId,
inputs: [
{
intent: "action.devices.EXECUTE",
payload: {
commands: [
{
devices: [{ id: flags.id }],
devices: [{ id }],
execution: [
{
command: `action.devices.commands.${command}`,
Expand Down Expand Up @@ -102,20 +115,56 @@ export default class Execute extends Command {
}
);
}
}

interface ExecuteBody {
requestId: string;
inputs: {
intent: "action.devices.EXECUTE";
payload: {
commands: {
devices: { id: string }[];
execution: {
command: string;
params: any;
}[];
}[];
};
}[];
async promptId(): Promise<string> {
const syncedDevices = await readConfig(this.config.configDir);
const responses = await inquirer.prompt([
{
name: "id",
message: "select a device id",
type: "list",
choices: syncedDevices.ids,
},
]);
return responses.id;
}

async promptCommand(): Promise<string> {
const commands = Object.keys(executeCommands);
const responses = await inquirer.prompt([
{
name: "command",
message: "select a command",
type: "list",
choices: commands,
},
]);
return responses.command;
}

async promptParamName(command: string): Promise<string> {
const params = Object.keys(executeCommands[command]);
const responses = await inquirer.prompt([
{
name: "param",
message: "select a param",
type: "list",
choices: params,
},
]);
return responses.param;
}

async promptParamValue(command: string, param: string): Promise<string> {
const values = executeCommands[command][param];
const responses = await inquirer.prompt([
{
name: "value",
message: "select a value",
type: "list",
choices: values,
},
]);
return responses.value;
}
}
Loading

0 comments on commit 304f7cd

Please sign in to comment.