@seamapi/cli 0.11.0 → 0.13.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.
Files changed (39) hide show
  1. package/README.md +71 -0
  2. package/bin/cli.js +38 -98
  3. package/bin/cli.js.map +1 -1
  4. package/completions/seam.bash +16 -0
  5. package/completions/seam.fish +15 -0
  6. package/completions/seam.zsh +19 -0
  7. package/lib/command-spec.d.ts +53 -0
  8. package/lib/command-spec.js +290 -0
  9. package/lib/command-spec.js.map +1 -0
  10. package/lib/completion/describe.d.ts +10 -0
  11. package/lib/completion/describe.js +16 -0
  12. package/lib/completion/describe.js.map +1 -0
  13. package/lib/completion/index.d.ts +28 -0
  14. package/lib/completion/index.js +92 -0
  15. package/lib/completion/index.js.map +1 -0
  16. package/lib/completion/render-bash.d.ts +2 -0
  17. package/lib/completion/render-bash.js +98 -0
  18. package/lib/completion/render-bash.js.map +1 -0
  19. package/lib/completion/render-fish.d.ts +2 -0
  20. package/lib/completion/render-fish.js +58 -0
  21. package/lib/completion/render-fish.js.map +1 -0
  22. package/lib/completion/render-zsh.d.ts +2 -0
  23. package/lib/completion/render-zsh.js +121 -0
  24. package/lib/completion/render-zsh.js.map +1 -0
  25. package/lib/render-help.d.ts +8 -0
  26. package/lib/render-help.js +150 -0
  27. package/lib/render-help.js.map +1 -0
  28. package/lib/version.d.ts +1 -1
  29. package/lib/version.js +1 -1
  30. package/package.json +2 -1
  31. package/src/bin/cli.ts +54 -104
  32. package/src/lib/command-spec.ts +400 -0
  33. package/src/lib/completion/describe.ts +21 -0
  34. package/src/lib/completion/index.ts +110 -0
  35. package/src/lib/completion/render-bash.ts +125 -0
  36. package/src/lib/completion/render-fish.ts +80 -0
  37. package/src/lib/completion/render-zsh.ts +157 -0
  38. package/src/lib/render-help.ts +197 -0
  39. package/src/lib/version.ts +1 -1
@@ -0,0 +1,121 @@
1
+ import { flagTokens, } from '../command-spec.js';
2
+ import { describeForShell } from './describe.js';
3
+ export const renderZshCompletion = (spec) => {
4
+ const valuelessTokens = spec.globalFlags
5
+ .filter(({ takesValue }) => !takesValue)
6
+ .flatMap(flagTokens)
7
+ .sort();
8
+ return `${[
9
+ header,
10
+ renderCase('_seam_subcommands', subcommandBranches(spec)),
11
+ renderCase('_seam_flags', flagBranches(spec)),
12
+ renderCase('_seam_flag_values', flagValueBranches(spec)),
13
+ `_seam_global_flags() {\n _seam_reply+=(${describeFlags(spec.globalFlags)})\n}`,
14
+ completionFunction(valuelessTokens),
15
+ dispatch,
16
+ ].join('\n\n')}\n`;
17
+ };
18
+ const header = `#compdef seam
19
+
20
+ # zsh completion for the seam command.
21
+ #
22
+ # Generated by @seamapi/cli from the Seam API definitions.
23
+ # Do not edit: regenerate with 'seam completion zsh'.
24
+ #
25
+ # Load it for the current shell with
26
+ #
27
+ # source <(seam completion zsh)
28
+ #
29
+ # or install it for every shell with
30
+ #
31
+ # seam completion zsh > "\${fpath[1]}/_seam"`;
32
+ const completionFunction = (valuelessTokens) => `_seam() {
33
+ local -a _seam_reply
34
+ local -a valueless
35
+ local command previous word
36
+ local -i index
37
+
38
+ valueless=(${valuelessTokens.join(' ')})
39
+
40
+ # The command path is the run of words before the first flag.
41
+ command=''
42
+ for (( index = 2; index < CURRENT; index++ )); do
43
+ word="\${words[index]}"
44
+ if [[ "$word" == -* ]]; then
45
+ break
46
+ fi
47
+ command="\${command:+$command }$word"
48
+ done
49
+
50
+ previous=''
51
+ if (( CURRENT > 1 )); then
52
+ previous="\${words[CURRENT - 1]}"
53
+ fi
54
+
55
+ # Completing the value of a flag that takes one.
56
+ if [[ "$previous" == -* ]] && (( \${valueless[(Ie)$previous]} == 0 )); then
57
+ _seam_flag_values "$command $previous"
58
+ if (( \${#_seam_reply} )); then
59
+ _describe -t values 'value' _seam_reply
60
+ fi
61
+ return
62
+ fi
63
+
64
+ if [[ "\${words[CURRENT]}" == -* ]]; then
65
+ _seam_flags "$command"
66
+ _seam_global_flags
67
+ _describe -t options 'option' _seam_reply
68
+ return
69
+ fi
70
+
71
+ _seam_subcommands "$command"
72
+ if (( \${#_seam_reply} )); then
73
+ _describe -t commands 'command' _seam_reply
74
+ return
75
+ fi
76
+
77
+ _seam_flags "$command"
78
+ _seam_global_flags
79
+ _describe -t options 'option' _seam_reply
80
+ }`;
81
+ // The script runs in three ways. Autoloaded from fpath as _seam, it must
82
+ // complete the in-flight request: funcstack holds _seam. Evaluated by the
83
+ // loader stub inside the autoloaded _seam, the same applies, but eval pushes
84
+ // '(eval)' onto funcstack, so search the whole stack rather than the top.
85
+ // Sourced into a shell, funcstack holds no _seam: register with compdef.
86
+ const dispatch = `if (( \${funcstack[(I)_seam]} )); then
87
+ _seam "$@"
88
+ else
89
+ compdef _seam seam
90
+ fi`;
91
+ const subcommandBranches = (spec) => spec.groups.map((group) => ({
92
+ pattern: group.path.join(' '),
93
+ entries: group.subcommands.map(({ name, description }) => describe(name, description)),
94
+ }));
95
+ const flagBranches = (spec) => spec.commands
96
+ .filter(({ flags }) => flags.length > 0)
97
+ .map((command) => ({
98
+ pattern: command.path.join(' '),
99
+ entries: [describeFlags(command.flags)],
100
+ }));
101
+ const flagValueBranches = (spec) => spec.commands.flatMap((command) => command.flags
102
+ .filter(({ values }) => values.length > 0)
103
+ .flatMap((flag) => flagTokens(flag).map((token) => ({
104
+ pattern: `${command.path.join(' ')} ${token}`,
105
+ entries: flag.values.map((value) => `'${value}'`),
106
+ }))));
107
+ const describeFlags = (flags) => flags
108
+ .flatMap((flag) => flagTokens(flag).map((token) => describe(token, flag.description)))
109
+ .join(' ');
110
+ const describe = (value, description) => {
111
+ const summary = describeForShell(description);
112
+ return summary === '' ? `'${value}'` : `'${value}:${summary}'`;
113
+ };
114
+ const renderCase = (name, branches) => [
115
+ `${name}() {`,
116
+ ` case "$1" in`,
117
+ ...branches.map(({ pattern, entries }) => ` ('${pattern}') _seam_reply+=(${entries.join(' ')}) ;;`),
118
+ ` esac`,
119
+ `}`,
120
+ ].join('\n');
121
+ //# sourceMappingURL=render-zsh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-zsh.js","sourceRoot":"","sources":["../../src/lib/completion/render-zsh.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type CommandSpec } from './command-spec.js';
2
+ /**
3
+ * Render the help guide for a command path, or `null` when no command or
4
+ * group goes by that path.
5
+ *
6
+ * An empty path is the guide for `seam` itself.
7
+ */
8
+ export declare const renderHelp: (path: string[], spec: CommandSpec) => string | null;
@@ -0,0 +1,150 @@
1
+ import commandLineUsage, {} from 'command-line-usage';
2
+ import { findCommand, findGroup, } from './command-spec.js';
3
+ /**
4
+ * Render the help guide for a command path, or `null` when no command or
5
+ * group goes by that path.
6
+ *
7
+ * An empty path is the guide for `seam` itself.
8
+ */
9
+ export const renderHelp = (path, spec) => {
10
+ const group = findGroup(spec, path);
11
+ if (group != null)
12
+ return commandLineUsage(groupSections(group, spec));
13
+ const command = findCommand(spec, path);
14
+ if (command != null)
15
+ return commandLineUsage(commandSections(command, spec));
16
+ return null;
17
+ };
18
+ const overview = '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.';
19
+ const outputSection = {
20
+ header: 'Output',
21
+ content: [
22
+ 'Only the response is written to stdout, so it is safe to pipe. Prompts, progress, and other information are written to stderr.',
23
+ 'The response is trimmed to the response key and pagination.',
24
+ 'Request params may be piped or redirected in as a JSON object. Params given as arguments win over params read from stdin.',
25
+ ],
26
+ };
27
+ const examples = [
28
+ { name: 'seam', summary: 'Interactively select commands to execute.' },
29
+ { name: 'seam login', summary: 'Login to Seam.' },
30
+ { name: 'seam wizard', summary: 'Set up Seam in the current project.' },
31
+ { name: 'seam select workspace', summary: 'Select your workspace.' },
32
+ {
33
+ name: 'seam connect-webviews create',
34
+ summary: 'Create a connect webview to connect devices.',
35
+ },
36
+ { name: 'seam devices list', summary: 'List devices in your workspace.' },
37
+ {
38
+ name: 'seam devices list {bold --interactive}',
39
+ summary: 'Review and edit filters before listing devices.',
40
+ },
41
+ {
42
+ name: 'seam devices list {bold --non-interactive}',
43
+ summary: 'List devices, failing instead of prompting.',
44
+ },
45
+ {
46
+ name: 'seam locks unlock-door {bold --device-id} $MY_DOOR',
47
+ summary: 'Unlock a lock.',
48
+ },
49
+ {
50
+ name: "seam access-codes create {bold --code} '1234' {bold --name} 'My Code'",
51
+ summary: 'Create an access code.',
52
+ },
53
+ {
54
+ name: 'seam devices list > devices.json',
55
+ summary: 'Write the response to a file as JSON.',
56
+ },
57
+ {
58
+ name: 'cat params.json | seam locks unlock-door',
59
+ summary: 'Pipe request params in as JSON.',
60
+ },
61
+ {
62
+ name: 'seam completion bash',
63
+ summary: 'Print a shell completion script for bash, fish, or zsh.',
64
+ },
65
+ ];
66
+ const groupSections = (group, spec) => {
67
+ const isRoot = group.path.length === 0;
68
+ const name = ['seam', ...group.path].join(' ');
69
+ return [
70
+ isRoot
71
+ ? { header: 'Seam CLI', content: overview }
72
+ : { header: name, content: `Commands under ${name}.` },
73
+ { header: 'Usage', content: `${name} <command> [options]` },
74
+ ...commandSectionsForGroup(group, isRoot),
75
+ optionSection(spec.globalFlags),
76
+ ...(isRoot
77
+ ? [outputSection, { header: 'Command List Examples', content: examples }]
78
+ : []),
79
+ { content: `Run '${name} <command> --help' to see a command in detail.` },
80
+ ];
81
+ };
82
+ const commandSectionsForGroup = (group, isRoot) => {
83
+ const content = (subcommands) => subcommands.map(({ name, description }) => ({ name, summary: description }));
84
+ // The root guide separates the commands of the CLI itself from the commands
85
+ // that call the Seam API. Anywhere deeper the split adds nothing: a group
86
+ // holds commands of one kind.
87
+ if (!isRoot) {
88
+ return [{ header: 'Commands', content: content(group.subcommands) }];
89
+ }
90
+ const cli = group.subcommands.filter(({ kind }) => kind === 'cli');
91
+ const api = group.subcommands.filter(({ kind }) => kind === 'api');
92
+ return [
93
+ { header: 'Commands', content: content(cli) },
94
+ { header: 'API Commands', content: content(api) },
95
+ ].filter((section) => section.content.length > 0);
96
+ };
97
+ const commandSections = (command, spec) => {
98
+ const name = ['seam', ...command.path].join(' ');
99
+ const hasFlags = command.flags.length > 0;
100
+ return [
101
+ {
102
+ header: name,
103
+ content: [command.title, command.description].filter((line) => line !== ''),
104
+ },
105
+ { header: 'Usage', content: `${name} [options]` },
106
+ // The command's own parameters are what the request is made of, so keep
107
+ // them apart from the options every seam command takes.
108
+ ...(hasFlags ? [optionSection(command.flags, 'Parameters')] : []),
109
+ optionSection(spec.globalFlags),
110
+ ...(hasFlags
111
+ ? [
112
+ {
113
+ content: 'Any required parameter left out is prompted for interactively.',
114
+ },
115
+ ]
116
+ : []),
117
+ ];
118
+ };
119
+ const optionSection = (flags, header = 'Options') => ({
120
+ header,
121
+ optionList: flags.map(toOptionDefinition),
122
+ });
123
+ const maxDocumentedValues = 8;
124
+ const toOptionDefinition = (flag) => {
125
+ const description = [
126
+ flag.isRequired ? '{bold [required]}' : '',
127
+ flag.description,
128
+ describeValues(flag),
129
+ ]
130
+ .filter((part) => part !== '')
131
+ .join(' ');
132
+ return {
133
+ // command-line-usage renders a nameless option as the short form alone.
134
+ name: flag.long ?? '',
135
+ ...(flag.short == null ? {} : { alias: flag.short }),
136
+ // A flag with no value must be typed as a boolean, or the guide labels it
137
+ // as taking a string.
138
+ type: flag.takesValue ? String : Boolean,
139
+ ...(flag.takesValue ? { typeLabel: '{underline value}' } : {}),
140
+ description,
141
+ };
142
+ };
143
+ const describeValues = (flag) => {
144
+ if (flag.values.length === 0)
145
+ return '';
146
+ const shown = flag.values.slice(0, maxDocumentedValues).join(', ');
147
+ const rest = flag.values.length - maxDocumentedValues;
148
+ return rest > 0 ? `One of: ${shown}, and ${rest} more.` : `One of: ${shown}.`;
149
+ };
150
+ //# sourceMappingURL=render-help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-help.js","sourceRoot":"","sources":["../src/lib/render-help.ts"],"names":[],"mappings":"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"}
package/lib/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- declare const seamapiCliVersion = "0.11.0";
1
+ declare const seamapiCliVersion = "0.13.0";
2
2
  declare const seamapiBlueprintVersion = "1.2.0";
3
3
  export { seamapiBlueprintVersion };
4
4
  export default seamapiCliVersion;
package/lib/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Versions are replaced with generated values when the package is packed.
2
- const seamapiCliVersion = '0.11.0';
2
+ const seamapiCliVersion = '0.13.0';
3
3
  const seamapiBlueprintVersion = '1.2.0';
4
4
  export { seamapiBlueprintVersion };
5
5
  export default seamapiCliVersion;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamapi/cli",
3
- "version": "0.11.0",
3
+ "version": "0.13.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,9 +3,14 @@ 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
7
 
8
+ import { getCommandSpec } from 'lib/command-spec.js'
9
+ import {
10
+ completionShells,
11
+ isCompletionShell,
12
+ renderCompletion,
13
+ } from 'lib/completion/index.js'
9
14
  import { getConfigStore } from 'lib/config/index.js'
10
15
  import { getApiBlueprint } from 'lib/get-api-blueprint.js'
11
16
  import { getResponseKey } from 'lib/get-response-key.js'
@@ -20,6 +25,7 @@ import { interactForWorkspaceId } from 'lib/interact-for-workspace-id.js'
20
25
  import { createOutput } from 'lib/output/create-output.js'
21
26
  import { getOutput, setOutput } from 'lib/output/get-output.js'
22
27
  import { resolveOutputFormat } from 'lib/output/resolve-output-format.js'
28
+ import { renderHelp } from 'lib/render-help.js'
23
29
  import type { ContextHelpers } from 'lib/types.js'
24
30
  import {
25
31
  cliFlags,
@@ -34,109 +40,35 @@ import { RequestSeamApi } from 'lib/util/request-seam-api.js'
34
40
  import { validateToken } from 'lib/validate-token.js'
35
41
  import seamapiCliVersion from 'lib/version.js'
36
42
 
37
- const sections = [
38
- {
39
- header: 'Seam CLI',
40
- content:
41
- '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. ',
42
- },
43
- {
44
- header: 'Options',
45
- optionList: [
46
- {
47
- name: 'help',
48
- description: 'Display this help guide.',
49
- alias: 'h',
50
- type: Boolean,
51
- },
52
- {
53
- name: 'interactive',
54
- description:
55
- 'Always prompt to review and edit properties, prefilled with the given arguments.',
56
- alias: 'i',
57
- type: Boolean,
58
- },
59
- {
60
- name: 'non-interactive',
61
- description:
62
- 'Never prompt: exit with an error if the command or any required property is missing.',
63
- alias: 'y',
64
- type: Boolean,
65
- },
66
- {
67
- name: 'json',
68
- description:
69
- 'Write the response to stdout as JSON. Enabled automatically when stdout is not a terminal, disable with {bold --no-json}.',
70
- type: Boolean,
71
- },
72
- {
73
- name: 'update',
74
- description: 'Force an update of the cached Seam API definitions.',
75
- type: Boolean,
76
- },
77
- ],
78
- },
79
- {
80
- header: 'Output',
81
- content: [
82
- 'Only the response is written to stdout, so it is safe to pipe. Prompts, progress, and other information are written to stderr.',
83
- 'The response is trimmed to the response key and pagination.',
84
- 'Request params may be piped or redirected in as a JSON object. Params given as arguments win over params read from stdin.',
85
- ],
86
- },
87
- {
88
- header: 'Command List Examples',
89
- content: [
90
- { name: 'seam', summary: 'Interactively select commands to execute.' },
91
- { name: 'seam login', summary: 'Login to Seam.' },
92
- {
93
- name: 'seam wizard',
94
- summary: 'Set up Seam in the current project.',
95
- },
96
- { name: 'seam select workspace', summary: 'Select your workspace.' },
97
- {
98
- name: 'seam connect-webviews create',
99
- summary: 'Create a connect webview to connect devices.',
100
- },
101
- { name: 'seam devices list', summary: 'List devices in your workspace.' },
102
- {
103
- name: 'seam devices list {bold --interactive}',
104
- summary: 'Review and edit filters before listing devices.',
105
- },
106
- {
107
- name: 'seam devices list {bold --non-interactive}',
108
- summary: 'List devices, failing instead of prompting.',
109
- },
110
- {
111
- name: 'seam locks unlock-door {bold --device-id} $MY_DOOR',
112
- summary: 'Unlock a lock.',
113
- },
114
- {
115
- name: "seam access-codes create {bold --code} '1234' {bold --name} 'My Code'",
116
- summary: 'Create an access code.',
117
- },
118
- {
119
- name: 'seam access-codes list {bold --device-id} $MY_DOOR',
120
- summary: 'List you access codes.',
121
- },
122
- {
123
- name: 'seam devices list > devices.json',
124
- summary: 'Write the response to a file as JSON.',
125
- },
126
- {
127
- name: 'cat params.json | seam locks unlock-door',
128
- summary: 'Pipe request params in as JSON.',
129
- },
130
- ],
131
- },
132
- ]
133
-
134
43
  async function cli(args: ParsedArgs) {
135
44
  const config = getConfigStore()
136
45
  const output = getOutput()
137
46
 
138
- if (args['help'] || args['h']) {
139
- output.text(commandLineUsage(sections))
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
+ }
70
+
71
+ output.text(help)
140
72
  return
141
73
  }
142
74
 
@@ -145,6 +77,24 @@ async function cli(args: ParsedArgs) {
145
77
  return
146
78
  }
147
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
96
+ }
97
+
148
98
  if (
149
99
  args._[0] === 'config' &&
150
100
  args._[1] === 'set' &&
@@ -171,7 +121,7 @@ async function cli(args: ParsedArgs) {
171
121
  return
172
122
  }
173
123
 
174
- args._ = args._.map((arg) => arg.toLowerCase().replace(/_/g, '-'))
124
+ args._ = args._.map(toCommandWord)
175
125
  for (const k in args) {
176
126
  args[k.toLowerCase().replace(/-/g, '_')] = args[k]
177
127
  }
@@ -179,9 +129,6 @@ async function cli(args: ParsedArgs) {
179
129
  const use_remote_api_defs =
180
130
  args['remote_api_defs'] ?? config.get('use_remote_api_defs')
181
131
 
182
- const update = args['update'] === true
183
- delete args['update']
184
-
185
132
  const blueprint = await getApiBlueprint(use_remote_api_defs ?? false, {
186
133
  update,
187
134
  })
@@ -336,6 +283,9 @@ async function cli(args: ParsedArgs) {
336
283
  }
337
284
  }
338
285
 
286
+ const toCommandWord = (arg: string): string =>
287
+ arg.toLowerCase().replace(/_/g, '-')
288
+
339
289
  const handleConnectWebviewResponse = async (
340
290
  connect_webview: any,
341
291
  interactivity: Interactivity,