@seamapi/cli 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/bin/cli.js +78 -100
- package/bin/cli.js.map +1 -1
- package/completions/seam.bash +11 -0
- package/completions/seam.fish +11 -0
- package/completions/seam.zsh +13 -0
- package/lib/command-spec.d.ts +53 -0
- package/lib/command-spec.js +290 -0
- package/lib/command-spec.js.map +1 -0
- package/lib/completion/describe.d.ts +10 -0
- package/lib/completion/describe.js +16 -0
- package/lib/completion/describe.js.map +1 -0
- package/lib/completion/index.d.ts +20 -0
- package/lib/completion/index.js +65 -0
- package/lib/completion/index.js.map +1 -0
- package/lib/completion/render-bash.d.ts +2 -0
- package/lib/completion/render-bash.js +98 -0
- package/lib/completion/render-bash.js.map +1 -0
- package/lib/completion/render-fish.d.ts +2 -0
- package/lib/completion/render-fish.js +58 -0
- package/lib/completion/render-fish.js.map +1 -0
- package/lib/completion/render-zsh.d.ts +2 -0
- package/lib/completion/render-zsh.js +121 -0
- package/lib/completion/render-zsh.js.map +1 -0
- package/lib/get-response-key.d.ts +9 -0
- package/lib/get-response-key.js +21 -0
- package/lib/get-response-key.js.map +1 -0
- package/lib/interact-for-action-attempt-poll.js +4 -3
- package/lib/interact-for-action-attempt-poll.js.map +1 -1
- package/lib/interact-for-array.js +9 -7
- package/lib/interact-for-array.js.map +1 -1
- package/lib/interact-for-blueprint-object.js +9 -8
- package/lib/interact-for-blueprint-object.js.map +1 -1
- package/lib/interact-for-command-selection.js +2 -2
- package/lib/interact-for-command-selection.js.map +1 -1
- package/lib/interact-for-custom-metadata.js +10 -8
- package/lib/interact-for-custom-metadata.js.map +1 -1
- package/lib/interact-for-login.js +8 -6
- package/lib/interact-for-login.js.map +1 -1
- package/lib/interact-for-resource.js +2 -2
- package/lib/interact-for-resource.js.map +1 -1
- package/lib/interact-for-server-selection.js +7 -5
- package/lib/interact-for-server-selection.js.map +1 -1
- package/lib/interact-for-timestamp.js +2 -2
- package/lib/interact-for-timestamp.js.map +1 -1
- package/lib/interact-for-use-remote-api-defs.js +4 -3
- package/lib/interact-for-use-remote-api-defs.js.map +1 -1
- package/lib/interact-for-workspace-id.js +2 -2
- package/lib/interact-for-workspace-id.js.map +1 -1
- package/lib/output/create-memory-output.d.ts +13 -0
- package/lib/output/create-memory-output.js +22 -0
- package/lib/output/create-memory-output.js.map +1 -0
- package/lib/output/create-output.d.ts +50 -0
- package/lib/output/create-output.js +34 -0
- package/lib/output/create-output.js.map +1 -0
- package/lib/output/get-output.d.ts +12 -0
- package/lib/output/get-output.js +21 -0
- package/lib/output/get-output.js.map +1 -0
- package/lib/output/resolve-output-format.d.ts +16 -0
- package/lib/output/resolve-output-format.js +23 -0
- package/lib/output/resolve-output-format.js.map +1 -0
- package/lib/output/select-response-payload.d.ts +16 -0
- package/lib/output/select-response-payload.js +33 -0
- package/lib/output/select-response-payload.js.map +1 -0
- package/lib/render-help.d.ts +8 -0
- package/lib/render-help.js +150 -0
- package/lib/render-help.js.map +1 -0
- package/lib/util/cli-args.d.ts +16 -1
- package/lib/util/cli-args.js +18 -2
- package/lib/util/cli-args.js.map +1 -1
- package/lib/util/prompt.d.ts +17 -0
- package/lib/util/prompt.js +25 -0
- package/lib/util/prompt.js.map +1 -0
- package/lib/util/read-stdin-json.d.ts +22 -0
- package/lib/util/read-stdin-json.js +42 -0
- package/lib/util/read-stdin-json.js.map +1 -0
- package/lib/util/request-seam-api.d.ts +5 -2
- package/lib/util/request-seam-api.js +13 -15
- package/lib/util/request-seam-api.js.map +1 -1
- package/lib/util/with-loading.js +6 -1
- package/lib/util/with-loading.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -1
- package/src/bin/cli.ts +100 -106
- package/src/lib/command-spec.ts +400 -0
- package/src/lib/completion/describe.ts +21 -0
- package/src/lib/completion/index.ts +82 -0
- package/src/lib/completion/render-bash.ts +125 -0
- package/src/lib/completion/render-fish.ts +80 -0
- package/src/lib/completion/render-zsh.ts +157 -0
- package/src/lib/get-response-key.ts +25 -0
- package/src/lib/interact-for-action-attempt-poll.ts +4 -3
- package/src/lib/interact-for-array.ts +9 -7
- package/src/lib/interact-for-blueprint-object.ts +9 -8
- package/src/lib/interact-for-command-selection.ts +2 -3
- package/src/lib/interact-for-custom-metadata.ts +10 -8
- package/src/lib/interact-for-login.ts +8 -6
- package/src/lib/interact-for-resource.ts +2 -3
- package/src/lib/interact-for-server-selection.ts +7 -6
- package/src/lib/interact-for-timestamp.ts +2 -2
- package/src/lib/interact-for-use-remote-api-defs.ts +4 -4
- package/src/lib/interact-for-workspace-id.ts +2 -2
- package/src/lib/output/create-memory-output.ts +39 -0
- package/src/lib/output/create-output.ts +104 -0
- package/src/lib/output/get-output.ts +24 -0
- package/src/lib/output/resolve-output-format.ts +33 -0
- package/src/lib/output/select-response-payload.ts +56 -0
- package/src/lib/render-help.ts +197 -0
- package/src/lib/util/cli-args.ts +32 -2
- package/src/lib/util/prompt.ts +38 -0
- package/src/lib/util/read-stdin-json.ts +59 -0
- package/src/lib/util/request-seam-api.ts +24 -20
- package/src/lib/util/with-loading.ts +9 -1
- package/src/lib/version.ts +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
export interface StdinLike extends AsyncIterable<string | Buffer> {
|
|
3
|
+
isTTY?: boolean | undefined;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Read request params piped into the CLI, e.g.,
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* $ echo '{"device_id": "..."}' | seam locks unlock-door --json
|
|
10
|
+
* $ seam locks unlock-door --json < params.json
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Returns null when stdin is a terminal or is empty,
|
|
14
|
+
* so an interactive session is never blocked waiting for input.
|
|
15
|
+
*/
|
|
16
|
+
export declare const readStdinJson: (stdin?: StdinLike | Readable) => Promise<Record<string, unknown> | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Parse JSON request params, e.g., from stdin or `--json '{"limit": 2}'`.
|
|
19
|
+
*
|
|
20
|
+
* Returns null when there is nothing to parse.
|
|
21
|
+
*/
|
|
22
|
+
export declare const parseJsonParams: (raw: string, source: string) => Record<string, unknown> | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read request params piped into the CLI, e.g.,
|
|
3
|
+
*
|
|
4
|
+
* ```
|
|
5
|
+
* $ echo '{"device_id": "..."}' | seam locks unlock-door --json
|
|
6
|
+
* $ seam locks unlock-door --json < params.json
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* Returns null when stdin is a terminal or is empty,
|
|
10
|
+
* so an interactive session is never blocked waiting for input.
|
|
11
|
+
*/
|
|
12
|
+
export const readStdinJson = async (stdin = process.stdin) => {
|
|
13
|
+
if (stdin.isTTY ?? false)
|
|
14
|
+
return null;
|
|
15
|
+
let raw = '';
|
|
16
|
+
for await (const chunk of stdin) {
|
|
17
|
+
raw += typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
18
|
+
}
|
|
19
|
+
return parseJsonParams(raw, 'stdin');
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Parse JSON request params, e.g., from stdin or `--json '{"limit": 2}'`.
|
|
23
|
+
*
|
|
24
|
+
* Returns null when there is nothing to parse.
|
|
25
|
+
*/
|
|
26
|
+
export const parseJsonParams = (raw, source) => {
|
|
27
|
+
const trimmed = raw.trim();
|
|
28
|
+
if (trimmed === '')
|
|
29
|
+
return null;
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = JSON.parse(trimmed);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new Error(`Could not parse JSON from ${source}: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
38
|
+
throw new Error(`Expected a JSON object of request params from ${source}, got ${Array.isArray(parsed) ? 'an array' : typeof parsed}`);
|
|
39
|
+
}
|
|
40
|
+
return parsed;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=read-stdin-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-stdin-json.js","sourceRoot":"","sources":["../../src/lib/util/read-stdin-json.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAChC,QAA8B,OAAO,CAAC,KAAK,EACF,EAAE;IAC3C,IAAK,KAAmB,CAAC,KAAK,IAAI,KAAK;QAAE,OAAO,IAAI,CAAA;IAEpD,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAChC,GAAG,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IACnE,CAAC;IAED,OAAO,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;AACtC,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,GAAW,EACX,MAAc,EACkB,EAAE;IAClC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;IAC1B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAE/B,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,6BAA6B,MAAM,KAAM,KAAe,CAAC,OAAO,EAAE,CACnE,CAAA;IACH,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,KAAK,CACb,iDAAiD,MAAM,SAAS,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,MAAM,EAAE,CACrH,CAAA;IACH,CAAC;IAED,OAAO,MAAiC,CAAA;AAC1C,CAAC,CAAA"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface RequestSeamApiOptions {
|
|
2
2
|
path: string;
|
|
3
3
|
params: Record<string, any>;
|
|
4
|
-
|
|
4
|
+
/** Response key for the endpoint, used to trim the reported payload. */
|
|
5
|
+
responseKey?: string | null | undefined;
|
|
6
|
+
}
|
|
7
|
+
export declare const RequestSeamApi: ({ path, params, responseKey, }: RequestSeamApiOptions) => Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { getSeam } from '../get-seam.js';
|
|
3
|
+
import { getOutput } from '../output/get-output.js';
|
|
4
|
+
import { selectResponsePayload } from '../output/select-response-payload.js';
|
|
3
5
|
import { withLoading } from './with-loading.js';
|
|
4
|
-
export const RequestSeamApi = async ({ path, params, }) => {
|
|
6
|
+
export const RequestSeamApi = async ({ path, params, responseKey, }) => {
|
|
5
7
|
const seam = await getSeam();
|
|
6
|
-
|
|
8
|
+
const output = getOutput();
|
|
9
|
+
output.info(`\n${chalk.green(path)}`);
|
|
10
|
+
output.info(`Request Params:`);
|
|
11
|
+
output.info(formatParams(params));
|
|
7
12
|
const response = await withLoading('Making request...', () => seam.client.post(path, params, {
|
|
8
13
|
validateStatus: () => true,
|
|
9
14
|
}));
|
|
10
|
-
logResponse(response);
|
|
11
|
-
return response;
|
|
12
|
-
};
|
|
13
|
-
const logResponse = (response) => {
|
|
14
15
|
if (response.status >= 400) {
|
|
15
|
-
|
|
16
|
+
output.warn(chalk.red(`[${response.status}]`));
|
|
17
|
+
process.exitCode = 1;
|
|
16
18
|
}
|
|
17
19
|
else {
|
|
18
|
-
|
|
20
|
+
output.info(chalk.green(`[${response.status}]`));
|
|
19
21
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
const logRequest = (apiPath, params) => {
|
|
24
|
-
console.log(`\n\n${chalk.green(apiPath)}`);
|
|
25
|
-
console.log(`Request Params:`);
|
|
26
|
-
console.log(params);
|
|
22
|
+
output.data(selectResponsePayload(response.data, { responseKey }));
|
|
23
|
+
return response;
|
|
27
24
|
};
|
|
25
|
+
const formatParams = (params) => JSON.stringify(params, null, 2);
|
|
28
26
|
//# sourceMappingURL=request-seam-api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-seam-api.js","sourceRoot":"","sources":["../../src/lib/util/request-seam-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"request-seam-api.js","sourceRoot":"","sources":["../../src/lib/util/request-seam-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAA;AAE7E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAS/C,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,EACnC,IAAI,EACJ,MAAM,EACN,WAAW,GACW,EAAE,EAAE;IAC1B,MAAM,IAAI,GAAG,MAAM,OAAO,EAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAE1B,MAAM,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACrC,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;IAC9B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAA;IAEjC,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAC3D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE;QAC7B,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;KAC3B,CAAC,CACH,CAAA;IAED,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QAC9C,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;IACtB,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,CAAA;IAElE,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED,MAAM,YAAY,GAAG,CAAC,MAA2B,EAAU,EAAE,CAC3D,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA"}
|
package/lib/util/with-loading.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createSpinner } from 'nanospinner';
|
|
2
|
+
import { getOutput } from '../output/get-output.js';
|
|
2
3
|
export const withLoading = async (message, fn) => {
|
|
3
|
-
|
|
4
|
+
if (!shouldSpin())
|
|
5
|
+
return await fn();
|
|
6
|
+
// Progress is not a command result, so it is rendered to stderr.
|
|
7
|
+
const spinner = createSpinner(message, { stream: process.stderr }).start();
|
|
4
8
|
try {
|
|
5
9
|
const result = await fn();
|
|
6
10
|
spinner.success();
|
|
@@ -11,4 +15,5 @@ export const withLoading = async (message, fn) => {
|
|
|
11
15
|
throw error;
|
|
12
16
|
}
|
|
13
17
|
};
|
|
18
|
+
const shouldSpin = () => getOutput().format === 'text' && process.stderr.isTTY === true;
|
|
14
19
|
//# sourceMappingURL=with-loading.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-loading.js","sourceRoot":"","sources":["../../src/lib/util/with-loading.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAC9B,OAAe,EACf,EAAoB,EACR,EAAE;IACd,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"with-loading.js","sourceRoot":"","sources":["../../src/lib/util/with-loading.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAEpD,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAC9B,OAAe,EACf,EAAoB,EACR,EAAE;IACd,IAAI,CAAC,UAAU,EAAE;QAAE,OAAO,MAAM,EAAE,EAAE,CAAA;IAEpC,iEAAiE;IACjE,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,CAAA;IAC1E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,EAAE,CAAA;QACzB,OAAO,CAAC,OAAO,EAAE,CAAA;QACjB,OAAO,MAAM,CAAA;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,EAAE,CAAA;QACf,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC,CAAA;AAED,MAAM,UAAU,GAAG,GAAY,EAAE,CAC/B,SAAS,EAAE,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAA"}
|
package/lib/version.d.ts
CHANGED
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seamapi/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "A command line interface (CLI) for interacting with the Seam API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"index.js.map",
|
|
46
46
|
"index.d.ts",
|
|
47
47
|
"bin",
|
|
48
|
+
"completions",
|
|
48
49
|
"lib",
|
|
49
50
|
"src",
|
|
50
51
|
"!test",
|
package/src/bin/cli.ts
CHANGED
|
@@ -3,12 +3,17 @@ import { randomBytes } from 'node:crypto'
|
|
|
3
3
|
import { isDeepStrictEqual as isEqual } from 'node:util'
|
|
4
4
|
|
|
5
5
|
import chalk from 'chalk'
|
|
6
|
-
import commandLineUsage from 'command-line-usage'
|
|
7
6
|
import type { ParsedArgs } from 'minimist'
|
|
8
|
-
import prompts from 'prompts'
|
|
9
7
|
|
|
8
|
+
import { getCommandSpec } from 'lib/command-spec.js'
|
|
9
|
+
import {
|
|
10
|
+
completionShells,
|
|
11
|
+
isCompletionShell,
|
|
12
|
+
renderCompletion,
|
|
13
|
+
} from 'lib/completion/index.js'
|
|
10
14
|
import { getConfigStore } from 'lib/config/index.js'
|
|
11
15
|
import { getApiBlueprint } from 'lib/get-api-blueprint.js'
|
|
16
|
+
import { getResponseKey } from 'lib/get-response-key.js'
|
|
12
17
|
import { getServer } from 'lib/get-server.js'
|
|
13
18
|
import { interactForActionAttemptPoll } from 'lib/interact-for-action-attempt-poll.js'
|
|
14
19
|
import { interactForCommandParams } from 'lib/interact-for-command-params.js'
|
|
@@ -17,105 +22,77 @@ import { interactForLogin } from 'lib/interact-for-login.js'
|
|
|
17
22
|
import { interactForServerSelection } from 'lib/interact-for-server-selection.js'
|
|
18
23
|
import { interactForUseRemoteApiDefs } from 'lib/interact-for-use-remote-api-defs.js'
|
|
19
24
|
import { interactForWorkspaceId } from 'lib/interact-for-workspace-id.js'
|
|
25
|
+
import { createOutput } from 'lib/output/create-output.js'
|
|
26
|
+
import { getOutput, setOutput } from 'lib/output/get-output.js'
|
|
27
|
+
import { resolveOutputFormat } from 'lib/output/resolve-output-format.js'
|
|
28
|
+
import { renderHelp } from 'lib/render-help.js'
|
|
20
29
|
import type { ContextHelpers } from 'lib/types.js'
|
|
21
30
|
import {
|
|
31
|
+
cliFlags,
|
|
22
32
|
getInteractivity,
|
|
23
33
|
type Interactivity,
|
|
24
|
-
interactivityFlags,
|
|
25
34
|
NonInteractiveError,
|
|
26
35
|
parseCliArgs,
|
|
27
36
|
} from 'lib/util/cli-args.js'
|
|
37
|
+
import { canPrompt, prompt } from 'lib/util/prompt.js'
|
|
38
|
+
import { readStdinJson } from 'lib/util/read-stdin-json.js'
|
|
28
39
|
import { RequestSeamApi } from 'lib/util/request-seam-api.js'
|
|
29
40
|
import { validateToken } from 'lib/validate-token.js'
|
|
30
41
|
import seamapiCliVersion from 'lib/version.js'
|
|
31
42
|
|
|
32
|
-
const sections = [
|
|
33
|
-
{
|
|
34
|
-
header: 'Seam CLI',
|
|
35
|
-
content:
|
|
36
|
-
'Every seam command runs as soon as every required property is given, and otherwise prompts you for what is missing with helpful suggestions. Pass -i to always review properties first, or -y to never be prompted. ',
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
header: 'Options',
|
|
40
|
-
optionList: [
|
|
41
|
-
{
|
|
42
|
-
name: 'help',
|
|
43
|
-
description: 'Display this help guide.',
|
|
44
|
-
alias: 'h',
|
|
45
|
-
type: Boolean,
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
name: 'interactive',
|
|
49
|
-
description:
|
|
50
|
-
'Always prompt to review and edit properties, prefilled with the given arguments.',
|
|
51
|
-
alias: 'i',
|
|
52
|
-
type: Boolean,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
name: 'non-interactive',
|
|
56
|
-
description:
|
|
57
|
-
'Never prompt: exit with an error if the command or any required property is missing.',
|
|
58
|
-
alias: 'y',
|
|
59
|
-
type: Boolean,
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: 'update',
|
|
63
|
-
description: 'Force an update of the cached Seam API definitions.',
|
|
64
|
-
type: Boolean,
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
header: 'Command List Examples',
|
|
70
|
-
content: [
|
|
71
|
-
{ name: 'seam', summary: 'Interactively select commands to execute.' },
|
|
72
|
-
{ name: 'seam login', summary: 'Login to Seam.' },
|
|
73
|
-
{
|
|
74
|
-
name: 'seam wizard',
|
|
75
|
-
summary: 'Set up Seam in the current project.',
|
|
76
|
-
},
|
|
77
|
-
{ name: 'seam select workspace', summary: 'Select your workspace.' },
|
|
78
|
-
{
|
|
79
|
-
name: 'seam connect-webviews create',
|
|
80
|
-
summary: 'Create a connect webview to connect devices.',
|
|
81
|
-
},
|
|
82
|
-
{ name: 'seam devices list', summary: 'List devices in your workspace.' },
|
|
83
|
-
{
|
|
84
|
-
name: 'seam devices list {bold --interactive}',
|
|
85
|
-
summary: 'Review and edit filters before listing devices.',
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
name: 'seam devices list {bold --non-interactive}',
|
|
89
|
-
summary: 'List devices, failing instead of prompting.',
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: 'seam locks unlock-door {bold --device-id} $MY_DOOR',
|
|
93
|
-
summary: 'Unlock a lock.',
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
name: "seam access-codes create {bold --code} '1234' {bold --name} 'My Code'",
|
|
97
|
-
summary: 'Create an access code.',
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
name: 'seam access-codes list {bold --device-id} $MY_DOOR',
|
|
101
|
-
summary: 'List you access codes.',
|
|
102
|
-
},
|
|
103
|
-
],
|
|
104
|
-
},
|
|
105
|
-
]
|
|
106
|
-
|
|
107
43
|
async function cli(args: ParsedArgs) {
|
|
108
44
|
const config = getConfigStore()
|
|
45
|
+
const output = getOutput()
|
|
46
|
+
|
|
47
|
+
const update = args['update'] === true
|
|
48
|
+
|
|
49
|
+
const helpFlag = args['help'] ?? args['h']
|
|
50
|
+
if (helpFlag != null) {
|
|
51
|
+
// Help comes from the cached API definitions so that it works without
|
|
52
|
+
// logging in, and offline once the cache is warm.
|
|
53
|
+
const spec = getCommandSpec(await getApiBlueprint(false, { update }))
|
|
54
|
+
|
|
55
|
+
// minimist reads the word after --help as its value, so 'seam --help
|
|
56
|
+
// devices' asks about devices just as 'seam devices --help' does.
|
|
57
|
+
const commandPath = [
|
|
58
|
+
...args._,
|
|
59
|
+
...(typeof helpFlag === 'string' ? [helpFlag] : []),
|
|
60
|
+
].map(toCommandWord)
|
|
61
|
+
|
|
62
|
+
const help = renderHelp(commandPath, spec)
|
|
63
|
+
|
|
64
|
+
if (help == null) {
|
|
65
|
+
output.error(chalk.red(`Unknown command: seam ${commandPath.join(' ')}`))
|
|
66
|
+
output.error(`Run 'seam --help' to see the available commands.`)
|
|
67
|
+
process.exitCode = 1
|
|
68
|
+
return
|
|
69
|
+
}
|
|
109
70
|
|
|
110
|
-
|
|
111
|
-
const usage = commandLineUsage(sections)
|
|
112
|
-
console.log(usage)
|
|
71
|
+
output.text(help)
|
|
113
72
|
return
|
|
114
73
|
}
|
|
115
74
|
|
|
116
75
|
if (args['version']) {
|
|
117
|
-
|
|
118
|
-
|
|
76
|
+
output.text(seamapiCliVersion)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (args._[0] === 'completion') {
|
|
81
|
+
const shell = args._[1]
|
|
82
|
+
|
|
83
|
+
if (!isCompletionShell(shell)) {
|
|
84
|
+
output.error(`Usage: seam completion <${completionShells.join('|')}>`)
|
|
85
|
+
process.exitCode = 1
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Completions always come from the cached API definitions so that they
|
|
90
|
+
// can be generated without logging in. They may lag the definitions
|
|
91
|
+
// served by Seam when config use-remote-api-defs is enabled.
|
|
92
|
+
output.text(
|
|
93
|
+
renderCompletion(shell, await getApiBlueprint(false, { update })),
|
|
94
|
+
)
|
|
95
|
+
return
|
|
119
96
|
}
|
|
120
97
|
|
|
121
98
|
if (
|
|
@@ -127,10 +104,10 @@ async function cli(args: ParsedArgs) {
|
|
|
127
104
|
const fakeApiUrl = `https://${randomstring}.fakeseamconnect.seam.vc`
|
|
128
105
|
|
|
129
106
|
config.set('server', fakeApiUrl)
|
|
130
|
-
|
|
107
|
+
output.info(`Server URL set to ${fakeApiUrl}`)
|
|
131
108
|
|
|
132
109
|
config.set(`${getServer()}.pat`, `seam_apikey1_token`)
|
|
133
|
-
|
|
110
|
+
output.info(`PAT set to use fakeseamconnect with "seam_apikey1_token"`)
|
|
134
111
|
return
|
|
135
112
|
}
|
|
136
113
|
|
|
@@ -139,11 +116,12 @@ async function cli(args: ParsedArgs) {
|
|
|
139
116
|
args._[0] !== 'login' &&
|
|
140
117
|
!isEqual(args._, ['select', 'server'])
|
|
141
118
|
) {
|
|
142
|
-
|
|
143
|
-
process.
|
|
119
|
+
output.error(`Not logged in. Please run "seam login"`)
|
|
120
|
+
process.exitCode = 1
|
|
121
|
+
return
|
|
144
122
|
}
|
|
145
123
|
|
|
146
|
-
args._ = args._.map(
|
|
124
|
+
args._ = args._.map(toCommandWord)
|
|
147
125
|
for (const k in args) {
|
|
148
126
|
args[k.toLowerCase().replace(/-/g, '_')] = args[k]
|
|
149
127
|
}
|
|
@@ -151,18 +129,17 @@ async function cli(args: ParsedArgs) {
|
|
|
151
129
|
const use_remote_api_defs =
|
|
152
130
|
args['remote_api_defs'] ?? config.get('use_remote_api_defs')
|
|
153
131
|
|
|
154
|
-
const update = args['update'] === true
|
|
155
|
-
delete args['update']
|
|
156
|
-
|
|
157
132
|
const blueprint = await getApiBlueprint(use_remote_api_defs ?? false, {
|
|
158
133
|
update,
|
|
159
134
|
})
|
|
160
135
|
|
|
161
|
-
|
|
136
|
+
// Params piped or redirected in, e.g., `seam devices list < params.json`.
|
|
137
|
+
// Params given as arguments take precedence over these.
|
|
138
|
+
const commandParams: Record<string, any> = { ...(await readStdinJson()) }
|
|
162
139
|
|
|
163
140
|
const ctx: ContextHelpers = {
|
|
164
141
|
blueprint,
|
|
165
|
-
interactivity: getInteractivity(args),
|
|
142
|
+
interactivity: getInteractivity(args, { canPrompt: canPrompt() }),
|
|
166
143
|
}
|
|
167
144
|
|
|
168
145
|
const isNonInteractive = ctx.interactivity === 'non-interactive'
|
|
@@ -173,7 +150,7 @@ async function cli(args: ParsedArgs) {
|
|
|
173
150
|
delete args[k]
|
|
174
151
|
const key = k.replace(/-/g, '_')
|
|
175
152
|
args[key] = v
|
|
176
|
-
if (
|
|
153
|
+
if (cliFlags.includes(key)) continue
|
|
177
154
|
commandParams[key] = v
|
|
178
155
|
}
|
|
179
156
|
|
|
@@ -204,10 +181,10 @@ async function cli(args: ParsedArgs) {
|
|
|
204
181
|
return
|
|
205
182
|
} else if (isEqual(selectedCommand, ['logout'])) {
|
|
206
183
|
config.delete('pat')
|
|
207
|
-
|
|
184
|
+
output.info('Logged out!')
|
|
208
185
|
return
|
|
209
186
|
} else if (isEqual(selectedCommand, ['config', 'reveal-location'])) {
|
|
210
|
-
|
|
187
|
+
output.text(config.path)
|
|
211
188
|
return
|
|
212
189
|
} else if (isEqual(selectedCommand, ['config', 'use-remote-api-defs'])) {
|
|
213
190
|
if (isNonInteractive) {
|
|
@@ -291,6 +268,7 @@ async function cli(args: ParsedArgs) {
|
|
|
291
268
|
const response = await RequestSeamApi({
|
|
292
269
|
path: apiPath,
|
|
293
270
|
params,
|
|
271
|
+
responseKey: getResponseKey(selectedCommand, ctx),
|
|
294
272
|
})
|
|
295
273
|
|
|
296
274
|
if (response.data?.connect_webview) {
|
|
@@ -301,10 +279,13 @@ async function cli(args: ParsedArgs) {
|
|
|
301
279
|
}
|
|
302
280
|
|
|
303
281
|
if (response.data?.action_attempt && !isNonInteractive) {
|
|
304
|
-
interactForActionAttemptPoll(response.data.action_attempt)
|
|
282
|
+
await interactForActionAttemptPoll(response.data.action_attempt)
|
|
305
283
|
}
|
|
306
284
|
}
|
|
307
285
|
|
|
286
|
+
const toCommandWord = (arg: string): string =>
|
|
287
|
+
arg.toLowerCase().replace(/_/g, '-')
|
|
288
|
+
|
|
308
289
|
const handleConnectWebviewResponse = async (
|
|
309
290
|
connect_webview: any,
|
|
310
291
|
interactivity: Interactivity,
|
|
@@ -315,7 +296,7 @@ const handleConnectWebviewResponse = async (
|
|
|
315
296
|
interactivity !== 'non-interactive' &&
|
|
316
297
|
process.env['INSIDE_WEB_BROWSER'] !== '1'
|
|
317
298
|
) {
|
|
318
|
-
const { action } = await
|
|
299
|
+
const { action } = await prompt({
|
|
319
300
|
type: 'confirm',
|
|
320
301
|
name: 'action',
|
|
321
302
|
message: 'Would you like to open the webview in your browser?',
|
|
@@ -338,17 +319,30 @@ const run = async (argv: string[]) => {
|
|
|
338
319
|
return
|
|
339
320
|
}
|
|
340
321
|
|
|
341
|
-
|
|
322
|
+
const args = parseCliArgs(argv)
|
|
323
|
+
|
|
324
|
+
const isTty = process.stdout.isTTY === true
|
|
325
|
+
|
|
326
|
+
setOutput(
|
|
327
|
+
createOutput({
|
|
328
|
+
format: resolveOutputFormat(argv, { isTty }),
|
|
329
|
+
colors: isTty,
|
|
330
|
+
}),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
await cli(args)
|
|
342
334
|
}
|
|
343
335
|
|
|
344
|
-
run(process.argv.slice(2)).catch((e) => {
|
|
336
|
+
run(process.argv.slice(2)).catch((e: unknown) => {
|
|
337
|
+
const output = getOutput()
|
|
338
|
+
process.exitCode = 1
|
|
339
|
+
|
|
345
340
|
if (e instanceof NonInteractiveError) {
|
|
346
|
-
|
|
347
|
-
|
|
341
|
+
output.error(chalk.red(e.message))
|
|
342
|
+
return
|
|
348
343
|
}
|
|
349
344
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
345
|
+
const error = e instanceof Error ? e : new Error(String(e))
|
|
346
|
+
output.error(chalk.red(`CLI Error: ${error.message}`))
|
|
347
|
+
if (error.stack != null) output.error(chalk.gray(error.stack))
|
|
354
348
|
})
|