@seamapi/cli 0.17.1 → 0.18.1

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.
@@ -17,10 +17,12 @@ import { ellipsis } from './util/ellipsis.js'
17
17
  import {
18
18
  promptAutocomplete,
19
19
  promptAutocompleteMultiselect,
20
+ PromptCancelledError,
20
21
  promptConfirm,
21
22
  promptNumber,
22
23
  promptSelect,
23
24
  promptText,
25
+ withBackHint,
24
26
  } from './util/prompt.js'
25
27
 
26
28
  const ergonomicPropOrder = [
@@ -88,58 +90,67 @@ export const interactForBlueprintObject = async (
88
90
  return ergonomicPropOrder.indexOf(prop)
89
91
  }
90
92
 
91
- const parameterSelectionMessage = args.isSubProperty
92
- ? `Editing "${args.subPropertyPath}"`
93
- : `[${cmdPath}] Parameters`
93
+ const parameterSelectionMessage = withBackHint(
94
+ args.isSubProperty
95
+ ? `Editing "${args.subPropertyPath}"`
96
+ : `[${cmdPath}] Parameters`,
97
+ )
94
98
 
95
99
  getOutput().info()
96
- const paramToEdit = await promptAutocomplete({
97
- message: parameterSelectionMessage,
98
- choices: [
99
- ...(haveAllRequiredParams && !args.isSubProperty
100
- ? [
101
- {
102
- value: 'done',
103
- label: `[Make API Call] ${cmdPath}`,
104
- },
105
- ]
106
- : []),
107
- ...(haveAllRequiredParams && args.isSubProperty
108
- ? [
109
- {
110
- label: `[Save]`,
111
- value: 'done',
112
- },
113
- ]
114
- : []),
115
- ...Object.keys(properties)
116
- .map((k) => {
117
- return {
118
- label: k + (required.includes(k) ? '*' : ''),
119
- value: k,
120
- hint:
121
- args.params[k] !== undefined
122
- ? typeof args.params[k] === 'object'
123
- ? ellipsis(JSON.stringify(args.params[k]), 60)
124
- : `[${args.params[k]}]`
125
- : undefined,
126
- }
127
- })
128
- .sort((a, b) => propSortScore(b.value) - propSortScore(a.value)),
129
- ...(args.isSubProperty
130
- ? [
131
- {
132
- label: `[Leave Empty]`,
133
- value: 'empty',
134
- },
135
- ]
136
- : []),
137
- {
138
- label: `[Back]`,
139
- value: 'back',
140
- },
141
- ],
142
- })
100
+ let paramToEdit: string
101
+ try {
102
+ paramToEdit = await promptAutocomplete({
103
+ message: parameterSelectionMessage,
104
+ choices: [
105
+ ...(haveAllRequiredParams && !args.isSubProperty
106
+ ? [
107
+ {
108
+ value: 'done',
109
+ label: `[Make API Call] ${cmdPath}`,
110
+ },
111
+ ]
112
+ : []),
113
+ ...(haveAllRequiredParams && args.isSubProperty
114
+ ? [
115
+ {
116
+ label: `[Save]`,
117
+ value: 'done',
118
+ },
119
+ ]
120
+ : []),
121
+ ...Object.keys(properties)
122
+ .map((k) => {
123
+ return {
124
+ label: k + (required.includes(k) ? '*' : ''),
125
+ value: k,
126
+ hint:
127
+ args.params[k] !== undefined
128
+ ? typeof args.params[k] === 'object'
129
+ ? ellipsis(JSON.stringify(args.params[k]), 60)
130
+ : `[${args.params[k]}]`
131
+ : undefined,
132
+ }
133
+ })
134
+ .sort((a, b) => propSortScore(b.value) - propSortScore(a.value)),
135
+ ...(args.isSubProperty
136
+ ? [
137
+ {
138
+ label: `[Leave Empty]`,
139
+ value: 'empty',
140
+ },
141
+ ]
142
+ : []),
143
+ {
144
+ label: `[Back]`,
145
+ value: 'back',
146
+ },
147
+ ],
148
+ })
149
+ } catch (error) {
150
+ // Dismissing the menu means the same as choosing to go back.
151
+ if (!(error instanceof PromptCancelledError)) throw error
152
+ paramToEdit = 'back'
153
+ }
143
154
 
144
155
  if (paramToEdit === 'empty') {
145
156
  return undefined
@@ -160,121 +171,128 @@ export const interactForBlueprintObject = async (
160
171
 
161
172
  const prop = properties[paramToEdit]
162
173
 
163
- if (paramToEdit === 'device_id') {
164
- args.params[paramToEdit] = await interactForDevice()
165
- return interactForBlueprintObject(args, ctx)
166
- } else if (paramToEdit === 'access_code_id') {
167
- args.params[paramToEdit] = await interactForAccessCode(args.params as any)
168
- return interactForBlueprintObject(args, ctx)
169
- } else if (paramToEdit === 'connected_account_id') {
170
- const connectedAccountId = await interactForConnectedAccount()
171
- args.params[paramToEdit] = connectedAccountId
172
- return interactForBlueprintObject(args, ctx)
173
- } else if (
174
- paramToEdit === 'user_identity_id' ||
175
- paramToEdit === 'user_identity_ids'
176
- ) {
177
- const userIdentityId = await interactForUserIdentity()
178
- args.params[paramToEdit] =
179
- paramToEdit === 'user_identity_ids' ? [userIdentityId] : userIdentityId
180
- return interactForBlueprintObject(args, ctx)
181
- } else if (paramToEdit.endsWith('acs_system_id')) {
182
- args.params[paramToEdit] = await interactForAcsSystem()
183
- return interactForBlueprintObject(args, ctx)
184
- } else if (paramToEdit.endsWith('acs_user_id')) {
185
- args.params[paramToEdit] = await interactForAcsUser()
186
- return interactForBlueprintObject(args, ctx)
187
- } else if (paramToEdit.endsWith('acs_entrance_id')) {
188
- args.params['acs_entrance_id'] = await interactForAcsEntrance()
189
- return interactForBlueprintObject(args, ctx)
190
- } else if (
191
- paramToEdit.endsWith('_at') ||
192
- paramToEdit === 'since' ||
193
- paramToEdit.endsWith('_before') ||
194
- paramToEdit.endsWith('_after')
195
- ) {
196
- args.params[paramToEdit] = await interactForTimestamp()
197
- return interactForBlueprintObject(args, ctx)
198
- } else if (
199
- paramToEdit === 'custom_metadata' ||
200
- paramToEdit === 'custom_metadata_has'
201
- ) {
202
- args.params[paramToEdit] = await interactForCustomMetadata(
203
- args.params[paramToEdit] || {},
204
- )
205
- return interactForBlueprintObject(args, ctx)
206
- }
207
-
208
- if (prop) {
209
- if (['string', 'id', 'datetime'].includes(prop.format)) {
210
- let value
211
- if (prop.format === 'datetime') {
212
- value = await interactForTimestamp()
213
- } else {
214
- value = await promptText({
215
- message: `${paramToEdit}:`,
216
- })
217
- }
218
- args.params[paramToEdit] = value
174
+ // Dismissing any prompt below returns to the parameter menu with the
175
+ // parameter left as it was, rather than ending the whole command.
176
+ try {
177
+ if (paramToEdit === 'device_id') {
178
+ args.params[paramToEdit] = await interactForDevice()
219
179
  return interactForBlueprintObject(args, ctx)
220
- } else if (prop.format === 'enum') {
221
- const value = await promptSelect({
222
- message: `${paramToEdit}:`,
223
- choices: prop.values.map((v) => ({
224
- label: v.name,
225
- value: v.name,
226
- })),
227
- })
228
- args.params[paramToEdit] = value
180
+ } else if (paramToEdit === 'access_code_id') {
181
+ args.params[paramToEdit] = await interactForAccessCode(args.params as any)
229
182
  return interactForBlueprintObject(args, ctx)
230
- } else if (prop.format === 'boolean') {
231
- const value = await promptConfirm({
232
- message: `${paramToEdit}:`,
233
- initialValue: true,
234
- active: 'true',
235
- inactive: 'false',
236
- })
237
-
238
- args.params[paramToEdit] = value
239
-
183
+ } else if (paramToEdit === 'connected_account_id') {
184
+ const connectedAccountId = await interactForConnectedAccount()
185
+ args.params[paramToEdit] = connectedAccountId
240
186
  return interactForBlueprintObject(args, ctx)
241
- } else if (prop.format === 'list' && prop.itemFormat === 'enum') {
242
- const value = await promptAutocompleteMultiselect({
243
- message: `${paramToEdit}:`,
244
- choices: prop.itemEnumValues.map((v) => ({
245
- label: v.name,
246
- value: v.name,
247
- })),
248
- })
249
- args.params[paramToEdit] = value
187
+ } else if (
188
+ paramToEdit === 'user_identity_id' ||
189
+ paramToEdit === 'user_identity_ids'
190
+ ) {
191
+ const userIdentityId = await interactForUserIdentity()
192
+ args.params[paramToEdit] =
193
+ paramToEdit === 'user_identity_ids' ? [userIdentityId] : userIdentityId
250
194
  return interactForBlueprintObject(args, ctx)
251
- } else if (prop.format === 'list') {
252
- args.params[paramToEdit] = await interactForArray(
253
- args.params[paramToEdit] || [],
254
- `Edit the list for ${paramToEdit}`,
255
- )
195
+ } else if (paramToEdit.endsWith('acs_system_id')) {
196
+ args.params[paramToEdit] = await interactForAcsSystem()
256
197
  return interactForBlueprintObject(args, ctx)
257
- } else if (prop.format === 'object') {
258
- args.params[paramToEdit] = await interactForBlueprintObject(
259
- {
260
- command: args.command,
261
- params: {},
262
- parameters: prop.parameters,
263
- isSubProperty: true,
264
- subPropertyPath: paramToEdit,
265
- },
266
- ctx,
198
+ } else if (paramToEdit.endsWith('acs_user_id')) {
199
+ args.params[paramToEdit] = await interactForAcsUser()
200
+ return interactForBlueprintObject(args, ctx)
201
+ } else if (paramToEdit.endsWith('acs_entrance_id')) {
202
+ args.params['acs_entrance_id'] = await interactForAcsEntrance()
203
+ return interactForBlueprintObject(args, ctx)
204
+ } else if (
205
+ paramToEdit.endsWith('_at') ||
206
+ paramToEdit === 'since' ||
207
+ paramToEdit.endsWith('_before') ||
208
+ paramToEdit.endsWith('_after')
209
+ ) {
210
+ args.params[paramToEdit] = await interactForTimestamp()
211
+ return interactForBlueprintObject(args, ctx)
212
+ } else if (
213
+ paramToEdit === 'custom_metadata' ||
214
+ paramToEdit === 'custom_metadata_has'
215
+ ) {
216
+ args.params[paramToEdit] = await interactForCustomMetadata(
217
+ args.params[paramToEdit] || {},
267
218
  )
268
219
  return interactForBlueprintObject(args, ctx)
269
- } else if (prop.format === 'number') {
270
- const value = await promptNumber({
271
- message: `${paramToEdit}:`,
272
- })
220
+ }
273
221
 
274
- args.params[paramToEdit] = value
222
+ if (prop) {
223
+ if (['string', 'id', 'datetime'].includes(prop.format)) {
224
+ let value
225
+ if (prop.format === 'datetime') {
226
+ value = await interactForTimestamp()
227
+ } else {
228
+ value = await promptText({
229
+ message: withBackHint(`${paramToEdit}:`),
230
+ })
231
+ }
232
+ args.params[paramToEdit] = value
233
+ return interactForBlueprintObject(args, ctx)
234
+ } else if (prop.format === 'enum') {
235
+ const value = await promptSelect({
236
+ message: withBackHint(`${paramToEdit}:`),
237
+ choices: prop.values.map((v) => ({
238
+ label: v.name,
239
+ value: v.name,
240
+ })),
241
+ })
242
+ args.params[paramToEdit] = value
243
+ return interactForBlueprintObject(args, ctx)
244
+ } else if (prop.format === 'boolean') {
245
+ const value = await promptConfirm({
246
+ message: withBackHint(`${paramToEdit}:`),
247
+ initialValue: true,
248
+ active: 'true',
249
+ inactive: 'false',
250
+ })
275
251
 
276
- return interactForBlueprintObject(args, ctx)
252
+ args.params[paramToEdit] = value
253
+
254
+ return interactForBlueprintObject(args, ctx)
255
+ } else if (prop.format === 'list' && prop.itemFormat === 'enum') {
256
+ const value = await promptAutocompleteMultiselect({
257
+ message: withBackHint(`${paramToEdit}:`),
258
+ choices: prop.itemEnumValues.map((v) => ({
259
+ label: v.name,
260
+ value: v.name,
261
+ })),
262
+ })
263
+ args.params[paramToEdit] = value
264
+ return interactForBlueprintObject(args, ctx)
265
+ } else if (prop.format === 'list') {
266
+ args.params[paramToEdit] = await interactForArray(
267
+ args.params[paramToEdit] || [],
268
+ `Edit the list for ${paramToEdit}`,
269
+ )
270
+ return interactForBlueprintObject(args, ctx)
271
+ } else if (prop.format === 'object') {
272
+ args.params[paramToEdit] = await interactForBlueprintObject(
273
+ {
274
+ command: args.command,
275
+ params: {},
276
+ parameters: prop.parameters,
277
+ isSubProperty: true,
278
+ subPropertyPath: paramToEdit,
279
+ },
280
+ ctx,
281
+ )
282
+ return interactForBlueprintObject(args, ctx)
283
+ } else if (prop.format === 'number') {
284
+ const value = await promptNumber({
285
+ message: withBackHint(`${paramToEdit}:`),
286
+ })
287
+
288
+ args.params[paramToEdit] = value
289
+
290
+ return interactForBlueprintObject(args, ctx)
291
+ }
277
292
  }
293
+ } catch (error) {
294
+ if (!(error instanceof PromptCancelledError)) throw error
295
+ return interactForBlueprintObject(args, ctx)
278
296
  }
279
297
 
280
298
  throw new Error(
@@ -2,7 +2,11 @@ import { isDeepStrictEqual as isEqual } from 'node:util'
2
2
 
3
3
  import type { ContextHelpers } from './types.js'
4
4
  import { NonInteractiveError } from './util/cli-args.js'
5
- import { promptAutocomplete } from './util/prompt.js'
5
+ import {
6
+ promptAutocomplete,
7
+ PromptCancelledError,
8
+ withBackHint,
9
+ } from './util/prompt.js'
6
10
 
7
11
  const uniqBy = <T>(items: T[], keyOf: (item: T) => unknown): T[] => {
8
12
  const seen = new Set<unknown>()
@@ -92,16 +96,30 @@ export async function interactForCommandSelection(
92
96
 
93
97
  const commandPathStr = commandPath.join('/').replace(/-/g, '_')
94
98
 
95
- const selectedCommand = await promptAutocomplete({
96
- message: `Select a command: /${commandPathStr}`,
97
- choices: [
98
- ...possibleCommands.map((cmd) => ({
99
- label:
100
- cmd?.[commandPath.length] ?? `[Call /${commandPathStr} Directly]`,
101
- value: cmd?.[commandPath.length] ?? '<none>',
102
- })),
103
- ].sort((a, b) => ergonomicSort(a.value, b.value)),
104
- })
99
+ // Only a sub-command menu has a level to go back to, so only it says so.
100
+ const selectMessage = `Select a command: /${commandPathStr}`
101
+
102
+ let selectedCommand: string
103
+ try {
104
+ selectedCommand = await promptAutocomplete({
105
+ message:
106
+ commandPath.length > 0 ? withBackHint(selectMessage) : selectMessage,
107
+ choices: [
108
+ ...possibleCommands.map((cmd) => ({
109
+ label:
110
+ cmd?.[commandPath.length] ?? `[Call /${commandPathStr} Directly]`,
111
+ value: cmd?.[commandPath.length] ?? '<none>',
112
+ })),
113
+ ].sort((a, b) => ergonomicSort(a.value, b.value)),
114
+ })
115
+ } catch (error) {
116
+ if (!(error instanceof PromptCancelledError)) throw error
117
+ // Dismissing the menu means the same as its [Back] entry, which is only
118
+ // offered when there is a level to go back to. At the top there is none,
119
+ // so dismissing it stops the CLI as it always has.
120
+ if (commandPath.length === 0) throw error
121
+ selectedCommand = '[Back]'
122
+ }
105
123
 
106
124
  if (selectedCommand === '<none>') {
107
125
  return commandPath
@@ -1,5 +1,10 @@
1
1
  import { getOutput } from './output/get-output.js'
2
- import { promptSelect, promptText } from './util/prompt.js'
2
+ import {
3
+ PromptCancelledError,
4
+ promptSelect,
5
+ promptText,
6
+ withBackHint,
7
+ } from './util/prompt.js'
3
8
 
4
9
  // Structurally the CustomMetadata of @seamapi/types, spelled out here so the
5
10
  // published declarations do not depend on a development-only package.
@@ -31,45 +36,62 @@ export const interactForCustomMetadata = async (
31
36
  do {
32
37
  displayCurrentCustomMetadata()
33
38
 
34
- action = await promptSelect({
35
- message: 'Choose an action:',
36
- choices: [
37
- { label: 'Add an item to params', value: 'add' },
38
- { label: 'Remove an item from params', value: 'remove' },
39
- { label: 'Finish editing params', value: 'done' },
40
- ],
41
- })
42
-
43
- if (action === 'add') {
44
- const newKey = await promptText({
45
- message: 'Enter a key to add or edit:',
39
+ try {
40
+ action = await promptSelect({
41
+ message: withBackHint('Choose an action:'),
42
+ choices: [
43
+ { label: 'Add an item to params', value: 'add' },
44
+ { label: 'Remove an item from params', value: 'remove' },
45
+ { label: 'Finish editing params', value: 'done' },
46
+ ],
46
47
  })
48
+ } catch (error) {
49
+ if (!(error instanceof PromptCancelledError)) throw error
50
+ // Dismissing the action menu finishes editing, keeping the changes.
51
+ break
52
+ }
47
53
 
48
- let newValue: string | boolean = await promptText({
49
- message: 'Enter the new value to add or edit (or null to delete):',
50
- })
51
- if (newKey) {
52
- if (newValue === 'false' || newValue === 'true') {
53
- newValue = newValue === 'true'
54
- }
55
- if (newValue === 'null') {
56
- updatedCustomMetadata[newKey] = null
57
- } else {
58
- updatedCustomMetadata[newKey] = newValue
59
- }
60
- }
61
- } else if (action === 'remove') {
62
- const customKeyToRemove = await promptSelect({
63
- message: 'Choose a key-value pair to remove from params:',
64
- choices: Object.keys(updatedCustomMetadata).map((customMetadataKey) => {
65
- return {
66
- label: `${customMetadataKey}: ${updatedCustomMetadata[customMetadataKey]}`,
67
- value: customMetadataKey,
54
+ try {
55
+ if (action === 'add') {
56
+ const newKey = await promptText({
57
+ message: withBackHint('Enter a key to add or edit:'),
58
+ })
59
+
60
+ let newValue: string | boolean = await promptText({
61
+ message: withBackHint(
62
+ 'Enter the new value to add or edit (or null to delete):',
63
+ ),
64
+ })
65
+ if (newKey) {
66
+ if (newValue === 'false' || newValue === 'true') {
67
+ newValue = newValue === 'true'
68
68
  }
69
- }),
70
- })
69
+ if (newValue === 'null') {
70
+ updatedCustomMetadata[newKey] = null
71
+ } else {
72
+ updatedCustomMetadata[newKey] = newValue
73
+ }
74
+ }
75
+ } else if (action === 'remove') {
76
+ const customKeyToRemove = await promptSelect({
77
+ message: withBackHint(
78
+ 'Choose a key-value pair to remove from params:',
79
+ ),
80
+ choices: Object.keys(updatedCustomMetadata).map(
81
+ (customMetadataKey) => {
82
+ return {
83
+ label: `${customMetadataKey}: ${updatedCustomMetadata[customMetadataKey]}`,
84
+ value: customMetadataKey,
85
+ }
86
+ },
87
+ ),
88
+ })
71
89
 
72
- delete updatedCustomMetadata[customKeyToRemove]
90
+ delete updatedCustomMetadata[customKeyToRemove]
91
+ }
92
+ } catch (error) {
93
+ if (!(error instanceof PromptCancelledError)) throw error
94
+ // Dismissing an inner prompt returns to the action menu.
73
95
  }
74
96
  } while (action !== 'done')
75
97
 
@@ -1,4 +1,4 @@
1
- import { promptAutocomplete } from './util/prompt.js'
1
+ import { promptAutocomplete, withBackHint } from './util/prompt.js'
2
2
  import { withLoading } from './util/with-loading.js'
3
3
 
4
4
  export interface ResourceChoice {
@@ -23,7 +23,9 @@ export const interactForResource = async <Resource>({
23
23
  fetchResources,
24
24
  )
25
25
  return await promptAutocomplete({
26
- message,
26
+ // Resource pickers are only reached from the parameter flow, which
27
+ // returns to its menu when one is dismissed.
28
+ message: withBackHint(message),
27
29
  choices: resources.map((resource) => {
28
30
  const { title, value, description } = toChoice(resource)
29
31
  return { label: title, value, hint: description }
@@ -1,9 +1,9 @@
1
- import { promptText } from './util/prompt.js'
1
+ import { promptText, withBackHint } from './util/prompt.js'
2
2
 
3
3
  export const interactForTimestamp = async () => {
4
4
  const now = new Date().toISOString()
5
5
  const timestamp = await promptText({
6
- message: 'Enter a timestamp:',
6
+ message: withBackHint('Enter a timestamp:'),
7
7
  placeholder: now,
8
8
  defaultValue: now,
9
9
  validate: (value) => {
@@ -1,3 +1,6 @@
1
+ import type { EventEmitter } from 'node:events'
2
+ import type { Key } from 'node:readline'
3
+
1
4
  import {
2
5
  autocomplete,
3
6
  autocompleteMultiselect,
@@ -7,6 +10,7 @@ import {
7
10
  select,
8
11
  text,
9
12
  } from '@clack/prompts'
13
+ import chalk from 'chalk'
10
14
 
11
15
  import { NonInteractiveError } from './cli-args.js'
12
16
 
@@ -34,12 +38,63 @@ export interface PromptChoice<Value> {
34
38
  hint?: string | undefined
35
39
  }
36
40
 
41
+ /**
42
+ * Note on a prompt message that dismissing it returns to the previous step.
43
+ *
44
+ * Only for prompts whose caller catches the dismissal: elsewhere it still
45
+ * stops the CLI, and saying otherwise would mislead. The note goes in the
46
+ * message because clack renders its own keyboard hints from a hardcoded list
47
+ * that a caller cannot add to.
48
+ */
49
+ export const withBackHint = (message: string): string =>
50
+ `${message} ${chalk.dim('· Esc: go back')}`
51
+
37
52
  const ensureInteractive = (): void => {
38
53
  if (!canPrompt()) {
39
54
  throw new NonInteractiveError(
40
55
  'Cannot prompt without a terminal: pass the missing arguments, or pipe them in as JSON',
41
56
  )
42
57
  }
58
+ installArrowKeyAliases()
59
+ }
60
+
61
+ /**
62
+ * The arrow keypress an Emacs-style control keypress stands for, or
63
+ * undefined for any other key: ctrl-p is up and ctrl-n is down.
64
+ */
65
+ export const arrowKeyFor = (key: Key | undefined): Key | undefined => {
66
+ if (key?.ctrl !== true || key.meta === true || key.shift === true) {
67
+ return undefined
68
+ }
69
+ const base = { ctrl: false, meta: false, shift: false }
70
+ if (key.name === 'p') return { ...base, name: 'up', sequence: '\x1B[A' }
71
+ if (key.name === 'n') return { ...base, name: 'down', sequence: '\x1B[B' }
72
+ return undefined
73
+ }
74
+
75
+ /**
76
+ * Re-emit Emacs-style control keypresses as the arrow keys they stand for.
77
+ *
78
+ * Clack navigates on the readline key name, so a synthetic arrow keypress
79
+ * moves the cursor in every prompt kind. Its own alias table cannot express
80
+ * this: aliases match bare key names, unaware of ctrl, and are ignored by
81
+ * prompts that track typed input, such as autocomplete.
82
+ */
83
+ export const emitArrowKeyAliases = (input: EventEmitter): void => {
84
+ input.on('keypress', (_char, key: Key | undefined) => {
85
+ const arrowKey = arrowKeyFor(key)
86
+ if (arrowKey !== undefined) input.emit('keypress', undefined, arrowKey)
87
+ })
88
+ }
89
+
90
+ let arrowKeyAliasesInstalled = false
91
+
92
+ // Keypress events only flow while a prompt has stdin in raw mode, so the
93
+ // listener is inert the rest of the time and never holds the process open.
94
+ const installArrowKeyAliases = (): void => {
95
+ if (arrowKeyAliasesInstalled) return
96
+ arrowKeyAliasesInstalled = true
97
+ emitArrowKeyAliases(process.stdin)
43
98
  }
44
99
 
45
100
  const unwrap = <Value>(value: Value | symbol): Value => {
@@ -1,5 +1,5 @@
1
1
  // Versions are replaced with generated values when the package is packed.
2
- const seamapiCliVersion = '0.17.1'
2
+ const seamapiCliVersion = '0.18.1'
3
3
  const seamapiBlueprintVersion = '1.2.0'
4
4
 
5
5
  export { seamapiBlueprintVersion }