@seamapi/cli 0.24.0 → 0.25.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 (98) hide show
  1. package/README.md +8 -4
  2. package/bin/cli.js +8 -2
  3. package/bin/cli.js.map +1 -1
  4. package/lib/args/parse.js +1 -0
  5. package/lib/args/parse.js.map +1 -1
  6. package/lib/auth/validate-token.js +1 -1
  7. package/lib/auth/validate-token.js.map +1 -1
  8. package/lib/blueprint/source-remote.js +1 -1
  9. package/lib/blueprint/source-remote.js.map +1 -1
  10. package/lib/commands/local/wizard.js +1 -1
  11. package/lib/commands/local/wizard.js.map +1 -1
  12. package/lib/commands/spec.js +8 -0
  13. package/lib/commands/spec.js.map +1 -1
  14. package/lib/http/api.d.ts +1 -1
  15. package/lib/http/api.js +1 -1
  16. package/lib/http/api.js.map +1 -1
  17. package/lib/http/client.d.ts +1 -1
  18. package/lib/http/client.js +1 -1
  19. package/lib/http/client.js.map +1 -1
  20. package/lib/http/memory-seam-api.js +1 -1
  21. package/lib/http/memory-seam-api.js.map +1 -1
  22. package/lib/http/request.js +1 -1
  23. package/lib/http/request.js.map +1 -1
  24. package/lib/interactions/access-code.d.ts +1 -1
  25. package/lib/interactions/access-code.js +2 -1
  26. package/lib/interactions/access-code.js.map +1 -1
  27. package/lib/interactions/acs-entrance.d.ts +1 -1
  28. package/lib/interactions/acs-entrance.js +2 -1
  29. package/lib/interactions/acs-entrance.js.map +1 -1
  30. package/lib/interactions/acs-system.d.ts +4 -1
  31. package/lib/interactions/acs-system.js +2 -1
  32. package/lib/interactions/acs-system.js.map +1 -1
  33. package/lib/interactions/acs-user.d.ts +1 -1
  34. package/lib/interactions/acs-user.js +5 -2
  35. package/lib/interactions/acs-user.js.map +1 -1
  36. package/lib/interactions/action-attempt-poll.d.ts +1 -1
  37. package/lib/interactions/blueprint-object.js +41 -13
  38. package/lib/interactions/blueprint-object.js.map +1 -1
  39. package/lib/interactions/connected-account.d.ts +1 -1
  40. package/lib/interactions/connected-account.js +2 -1
  41. package/lib/interactions/connected-account.js.map +1 -1
  42. package/lib/interactions/custom-metadata.js +4 -0
  43. package/lib/interactions/custom-metadata.js.map +1 -1
  44. package/lib/interactions/device.d.ts +1 -1
  45. package/lib/interactions/device.js +2 -1
  46. package/lib/interactions/device.js.map +1 -1
  47. package/lib/interactions/login.js +1 -1
  48. package/lib/interactions/login.js.map +1 -1
  49. package/lib/interactions/resource.d.ts +3 -1
  50. package/lib/interactions/resource.js +2 -1
  51. package/lib/interactions/resource.js.map +1 -1
  52. package/lib/interactions/timestamp.d.ts +1 -1
  53. package/lib/interactions/timestamp.js +4 -1
  54. package/lib/interactions/timestamp.js.map +1 -1
  55. package/lib/interactions/user-identity.d.ts +1 -1
  56. package/lib/interactions/user-identity.js +2 -1
  57. package/lib/interactions/user-identity.js.map +1 -1
  58. package/lib/interactions/workspace-id.js +1 -1
  59. package/lib/interactions/workspace-id.js.map +1 -1
  60. package/lib/memory-prompt.d.ts +10 -7
  61. package/lib/memory-prompt.js +12 -6
  62. package/lib/memory-prompt.js.map +1 -1
  63. package/lib/prompt.d.ts +27 -3
  64. package/lib/prompt.js +26 -1
  65. package/lib/prompt.js.map +1 -1
  66. package/lib/render/help.js +1 -1
  67. package/lib/render/help.js.map +1 -1
  68. package/lib/version.d.ts +1 -1
  69. package/lib/version.js +1 -1
  70. package/package.json +9 -13
  71. package/src/bin/cli.ts +9 -2
  72. package/src/lib/args/parse.ts +1 -0
  73. package/src/lib/auth/validate-token.ts +1 -1
  74. package/src/lib/blueprint/source-remote.ts +1 -1
  75. package/src/lib/commands/local/wizard.ts +1 -1
  76. package/src/lib/commands/spec.ts +8 -0
  77. package/src/lib/http/api.ts +1 -1
  78. package/src/lib/http/client.ts +1 -1
  79. package/src/lib/http/memory-seam-api.ts +1 -4
  80. package/src/lib/http/request.ts +1 -4
  81. package/src/lib/interactions/access-code.ts +11 -7
  82. package/src/lib/interactions/acs-entrance.ts +2 -1
  83. package/src/lib/interactions/acs-system.ts +8 -1
  84. package/src/lib/interactions/acs-user.ts +5 -4
  85. package/src/lib/interactions/action-attempt-poll.ts +1 -1
  86. package/src/lib/interactions/blueprint-object.ts +62 -13
  87. package/src/lib/interactions/connected-account.ts +2 -1
  88. package/src/lib/interactions/custom-metadata.ts +6 -0
  89. package/src/lib/interactions/device.ts +2 -1
  90. package/src/lib/interactions/login.ts +1 -1
  91. package/src/lib/interactions/resource.ts +4 -0
  92. package/src/lib/interactions/timestamp.ts +4 -1
  93. package/src/lib/interactions/user-identity.ts +2 -1
  94. package/src/lib/interactions/workspace-id.ts +1 -1
  95. package/src/lib/memory-prompt.ts +31 -9
  96. package/src/lib/prompt.ts +65 -4
  97. package/src/lib/render/help.ts +1 -1
  98. package/src/lib/version.ts +1 -1
package/package.json CHANGED
@@ -1,22 +1,18 @@
1
1
  {
2
2
  "name": "@seamapi/cli",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "A command-line interface (CLI) for interacting with the Seam API.",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": {
11
- "types": "./index.d.ts",
12
- "default": "./index.js"
13
- }
10
+ "types": "./index.d.ts",
11
+ "default": "./index.js"
14
12
  },
15
13
  "./cli": {
16
- "import": {
17
- "types": "./bin/cli.d.ts",
18
- "default": "./bin/cli.js"
19
- }
14
+ "types": "./bin/cli.d.ts",
15
+ "default": "./bin/cli.js"
20
16
  }
21
17
  },
22
18
  "module": "index.js",
@@ -76,13 +72,13 @@
76
72
  "report": "vitest run --coverage"
77
73
  },
78
74
  "engines": {
79
- "node": ">=22.11.0",
75
+ "node": ">=22.12.0",
80
76
  "npm": ">=10.0.0"
81
77
  },
82
78
  "devEngines": {
83
79
  "runtime": {
84
80
  "name": "node",
85
- "version": "^24.11.0 || ^22.11.0",
81
+ "version": "^24.11.0 || ^22.12.0",
86
82
  "onFail": "warn"
87
83
  },
88
84
  "packageManager": {
@@ -94,8 +90,8 @@
94
90
  "dependencies": {
95
91
  "@clack/prompts": "^1.7.0",
96
92
  "@seamapi/blueprint": "1.5.0",
97
- "@seamapi/http": "2.2.0",
98
- "@seamapi/wizard": "0.7.0",
93
+ "@seamapi/http": "2.3.0",
94
+ "@seamapi/wizard": "0.8.0",
99
95
  "chalk": "^6.0.0",
100
96
  "command-line-usage": "^7.0.4",
101
97
  "configstore": "^8.0.0",
package/src/bin/cli.ts CHANGED
@@ -29,7 +29,7 @@ import { createSeamApi, type SeamApi } from 'lib/http/api.js'
29
29
  import { interactForCommandSelection } from 'lib/interactions/index.js'
30
30
  import { getOutput, setOutput } from 'lib/output/get-output.js'
31
31
  import { createOutput } from 'lib/output/output.js'
32
- import { readStdinJson } from 'lib/output/read-stdin-json.js'
32
+ import { parseJsonParams, readStdinJson } from 'lib/output/read-stdin-json.js'
33
33
  import { resolveOutputFormat } from 'lib/output/resolve-output-format.js'
34
34
  import { setAuthOverrides } from 'lib/overrides.js'
35
35
  import { canPrompt } from 'lib/prompt.js'
@@ -168,7 +168,14 @@ async function cli(args: ParsedArgs, argv: string[]) {
168
168
 
169
169
  // Params piped or redirected in, e.g., `seam devices list < params.json`.
170
170
  const pipedParams = await readStdinJson()
171
- const stdinParams: Record<string, any> = { ...pipedParams }
171
+ const rawParams =
172
+ args['raw'] == null ? null : parseJsonParams(String(args['raw']), '--raw')
173
+ // Inline raw params take precedence over piped params, while ordinary
174
+ // command arguments still take precedence over both.
175
+ const stdinParams: Record<string, any> = {
176
+ ...pipedParams,
177
+ ...rawParams,
178
+ }
172
179
 
173
180
  const auth = resolveAuth(config)
174
181
  let seamApi: Promise<SeamApi> | null = null
@@ -35,6 +35,7 @@ export const cliFlags: string[] = [
35
35
  'h',
36
36
  'help',
37
37
  'json',
38
+ 'raw',
38
39
  'remote_schema',
39
40
  'update',
40
41
  'version',
@@ -3,7 +3,7 @@ import {
3
3
  isPersonalAccessToken,
4
4
  SeamHttp,
5
5
  SeamHttpWithoutWorkspace,
6
- } from '@seamapi/http/connect'
6
+ } from '@seamapi/http'
7
7
 
8
8
  import { resolveAuth } from 'lib/context.js'
9
9
 
@@ -9,7 +9,7 @@ import { resolveAuth } from 'lib/context.js'
9
9
  export const createRemoteBlueprint = async (): Promise<Blueprint> => {
10
10
  const [{ createBlueprint }, { getOpenapiSchema }] = await Promise.all([
11
11
  import('@seamapi/blueprint'),
12
- import('@seamapi/http/connect'),
12
+ import('@seamapi/http'),
13
13
  ])
14
14
  const openapi = await getOpenapiSchema(resolveAuth().endpoint)
15
15
 
@@ -1,6 +1,6 @@
1
1
  import { join } from 'node:path'
2
2
 
3
- import { isApiKey } from '@seamapi/http/connect'
3
+ import { isApiKey } from '@seamapi/http'
4
4
  import type { StorageAdapter, WizardAdapter, WizardAuth } from '@seamapi/wizard'
5
5
  import Configstore from 'configstore'
6
6
 
@@ -104,6 +104,14 @@ export const globalFlags: CommandFlag[] = [
104
104
  takesValue: false,
105
105
  isRequired: false,
106
106
  },
107
+ {
108
+ long: 'raw',
109
+ short: null,
110
+ description: 'Pass request parameters as an inline JSON object.',
111
+ values: [],
112
+ takesValue: true,
113
+ isRequired: false,
114
+ },
107
115
  {
108
116
  long: 'non-interactive',
109
117
  short: 'y',
@@ -1,4 +1,4 @@
1
- import { type SeamHttp, SeamHttpRequest } from '@seamapi/http/connect'
1
+ import { type SeamHttp, SeamHttpRequest } from '@seamapi/http'
2
2
 
3
3
  import type { AuthContext } from 'lib/context.js'
4
4
 
@@ -3,7 +3,7 @@ import {
3
3
  isPersonalAccessToken,
4
4
  SeamHttp,
5
5
  SeamHttpWithoutWorkspace,
6
- } from '@seamapi/http/connect'
6
+ } from '@seamapi/http'
7
7
 
8
8
  import { type AuthContext, resolveAuth } from 'lib/context.js'
9
9
  import { tokenEnvVar, workspaceIdEnvVar } from 'lib/env.js'
@@ -1,7 +1,4 @@
1
- import {
2
- SeamHttpApiError,
3
- SeamHttpInvalidInputError,
4
- } from '@seamapi/http/connect'
1
+ import { SeamHttpApiError, SeamHttpInvalidInputError } from '@seamapi/http'
5
2
 
6
3
  import type { ApiRequestOptions, SeamApi, SeamApiRequest } from './api.js'
7
4
 
@@ -1,7 +1,4 @@
1
- import {
2
- isSeamHttpApiError,
3
- type SeamHttpApiError,
4
- } from '@seamapi/http/connect'
1
+ import { isSeamHttpApiError, type SeamHttpApiError } from '@seamapi/http'
5
2
  import chalk from 'chalk'
6
3
 
7
4
  import type { Output } from 'lib/output/output.js'
@@ -3,13 +3,16 @@ import { getSeam } from 'lib/http/client.js'
3
3
  import { interactForDevice } from './device.js'
4
4
  import { interactForResource } from './resource.js'
5
5
 
6
- export const interactForAccessCode = async ({
7
- // The key is a Seam API parameter name: callers pass the blueprint params
8
- // bag through as-is, so only the binding may be camelCase.
9
- device_id: deviceId,
10
- }: {
11
- device_id?: string
12
- }) => {
6
+ export const interactForAccessCode = async (
7
+ {
8
+ // The key is a Seam API parameter name: callers pass the blueprint params
9
+ // bag through as-is, so only the binding may be camelCase.
10
+ device_id: deviceId,
11
+ }: {
12
+ device_id?: string
13
+ },
14
+ initialValue?: string,
15
+ ) => {
13
16
  const seam = await getSeam()
14
17
 
15
18
  if (!deviceId) {
@@ -19,6 +22,7 @@ export const interactForAccessCode = async ({
19
22
  return interactForResource({
20
23
  resourceName: 'access_code',
21
24
  fetchResources: () => seam.accessCodes.list({ device_id: deviceId }),
25
+ initialValue,
22
26
  toChoice: (accessCode) => ({
23
27
  title: accessCode.name ?? '<No Name>',
24
28
  value: accessCode.access_code_id,
@@ -2,12 +2,13 @@ import { getSeam } from 'lib/http/client.js'
2
2
 
3
3
  import { interactForResource } from './resource.js'
4
4
 
5
- export const interactForAcsEntrance = async () => {
5
+ export const interactForAcsEntrance = async (initialValue?: string) => {
6
6
  const seam = await getSeam()
7
7
 
8
8
  return interactForResource({
9
9
  resourceName: 'ACS entrance',
10
10
  fetchResources: () => seam.acs.entrances.list(),
11
+ initialValue,
11
12
  toChoice: (entrance) => ({
12
13
  title: entrance.display_name ?? '<No Name>',
13
14
  value: entrance.acs_entrance_id,
@@ -2,13 +2,20 @@ import { getSeam } from 'lib/http/client.js'
2
2
 
3
3
  import { interactForResource } from './resource.js'
4
4
 
5
- export const interactForAcsSystem = async (message?: string) => {
5
+ export const interactForAcsSystem = async ({
6
+ message,
7
+ initialValue,
8
+ }: {
9
+ message?: string | undefined
10
+ initialValue?: string | undefined
11
+ } = {}) => {
6
12
  const seam = await getSeam()
7
13
 
8
14
  return interactForResource({
9
15
  resourceName: 'ACS system',
10
16
  fetchResources: () => seam.acs.systems.list(),
11
17
  message,
18
+ initialValue,
12
19
  toChoice: (system) => ({
13
20
  title: `${system.name} ${system.external_type_display_name}`,
14
21
  value: system.acs_system_id,
@@ -3,16 +3,17 @@ import { getSeam } from 'lib/http/client.js'
3
3
  import { interactForAcsSystem } from './acs-system.js'
4
4
  import { interactForResource } from './resource.js'
5
5
 
6
- export const interactForAcsUser = async () => {
6
+ export const interactForAcsUser = async (initialValue?: string) => {
7
7
  const seam = await getSeam()
8
8
 
9
- const acsSystemId = await interactForAcsSystem(
10
- 'What acs_system does the acs_user belong to?',
11
- )
9
+ const acsSystemId = await interactForAcsSystem({
10
+ message: 'What acs_system does the acs_user belong to?',
11
+ })
12
12
 
13
13
  return interactForResource({
14
14
  resourceName: 'ACS user',
15
15
  fetchResources: () => seam.acs.users.list({ acs_system_id: acsSystemId }),
16
+ initialValue,
16
17
  toChoice: (user) => ({
17
18
  title: `${user.display_name} ${user.email_address}`,
18
19
  value: user.acs_user_id,
@@ -1,4 +1,4 @@
1
- import type { ActionAttemptsGetResponse } from '@seamapi/http/connect'
1
+ import type { ActionAttemptsGetResponse } from '@seamapi/http'
2
2
 
3
3
  import { getSeam } from 'lib/http/client.js'
4
4
  import { getOutput } from 'lib/output/get-output.js'
@@ -207,32 +207,47 @@ export const interactForBlueprintObject = async (
207
207
  }
208
208
  }
209
209
 
210
+ const current = args.params[paramToEdit]
211
+
210
212
  if (paramToEdit === 'device_id') {
211
- args.params[paramToEdit] = await interactForDevice()
213
+ args.params[paramToEdit] = await interactForDevice(toText(current))
212
214
  return interactForBlueprintObject(args, ctx)
213
215
  } else if (paramToEdit === 'access_code_id') {
214
- args.params[paramToEdit] = await interactForAccessCode(args.params as any)
216
+ args.params[paramToEdit] = await interactForAccessCode(
217
+ args.params as any,
218
+ toText(current),
219
+ )
215
220
  return interactForBlueprintObject(args, ctx)
216
221
  } else if (paramToEdit === 'connected_account_id') {
217
- const connectedAccountId = await interactForConnectedAccount()
222
+ const connectedAccountId = await interactForConnectedAccount(
223
+ toText(current),
224
+ )
218
225
  args.params[paramToEdit] = connectedAccountId
219
226
  return interactForBlueprintObject(args, ctx)
220
227
  } else if (
221
228
  paramToEdit === 'user_identity_id' ||
222
229
  paramToEdit === 'user_identity_ids'
223
230
  ) {
224
- const userIdentityId = await interactForUserIdentity()
231
+ const userIdentityId = await interactForUserIdentity(
232
+ paramToEdit === 'user_identity_ids'
233
+ ? toTextList(current)[0]
234
+ : toText(current),
235
+ )
225
236
  args.params[paramToEdit] =
226
237
  paramToEdit === 'user_identity_ids' ? [userIdentityId] : userIdentityId
227
238
  return interactForBlueprintObject(args, ctx)
228
239
  } else if (paramToEdit.endsWith('acs_system_id')) {
229
- args.params[paramToEdit] = await interactForAcsSystem()
240
+ args.params[paramToEdit] = await interactForAcsSystem({
241
+ initialValue: toText(current),
242
+ })
230
243
  return interactForBlueprintObject(args, ctx)
231
244
  } else if (paramToEdit.endsWith('acs_user_id')) {
232
- args.params[paramToEdit] = await interactForAcsUser()
245
+ args.params[paramToEdit] = await interactForAcsUser(toText(current))
233
246
  return interactForBlueprintObject(args, ctx)
234
247
  } else if (paramToEdit.endsWith('acs_entrance_id')) {
235
- args.params['acs_entrance_id'] = await interactForAcsEntrance()
248
+ args.params['acs_entrance_id'] = await interactForAcsEntrance(
249
+ toText(args.params['acs_entrance_id']),
250
+ )
236
251
  return interactForBlueprintObject(args, ctx)
237
252
  } else if (
238
253
  paramToEdit.endsWith('_at') ||
@@ -240,14 +255,14 @@ export const interactForBlueprintObject = async (
240
255
  paramToEdit.endsWith('_before') ||
241
256
  paramToEdit.endsWith('_after')
242
257
  ) {
243
- args.params[paramToEdit] = await interactForTimestamp()
258
+ args.params[paramToEdit] = await interactForTimestamp(toText(current))
244
259
  return interactForBlueprintObject(args, ctx)
245
260
  } else if (
246
261
  paramToEdit === 'custom_metadata' ||
247
262
  paramToEdit === 'custom_metadata_has'
248
263
  ) {
249
264
  args.params[paramToEdit] = await interactForCustomMetadata(
250
- args.params[paramToEdit] || {},
265
+ toRecord(current),
251
266
  )
252
267
  return interactForBlueprintObject(args, ctx)
253
268
  }
@@ -256,10 +271,11 @@ export const interactForBlueprintObject = async (
256
271
  if (['string', 'id', 'datetime'].includes(prop.format)) {
257
272
  let value
258
273
  if (prop.format === 'datetime') {
259
- value = await interactForTimestamp()
274
+ value = await interactForTimestamp(toText(current))
260
275
  } else {
261
276
  value = await promptText({
262
277
  message: withBackHint(`${paramToEdit}:`),
278
+ initialValue: toText(current),
263
279
  })
264
280
  }
265
281
  args.params[paramToEdit] = value
@@ -271,13 +287,14 @@ export const interactForBlueprintObject = async (
271
287
  label: v.name,
272
288
  value: v.name,
273
289
  })),
290
+ initialValue: toText(current),
274
291
  })
275
292
  args.params[paramToEdit] = value
276
293
  return interactForBlueprintObject(args, ctx)
277
294
  } else if (prop.format === 'boolean') {
278
295
  const value = await promptConfirm({
279
296
  message: withBackHint(`${paramToEdit}:`),
280
- initialValue: true,
297
+ initialValue: toBoolean(current) ?? true,
281
298
  active: 'true',
282
299
  inactive: 'false',
283
300
  })
@@ -292,12 +309,13 @@ export const interactForBlueprintObject = async (
292
309
  label: v.name,
293
310
  value: v.name,
294
311
  })),
312
+ initialValues: toTextList(current),
295
313
  })
296
314
  args.params[paramToEdit] = value
297
315
  return interactForBlueprintObject(args, ctx)
298
316
  } else if (prop.format === 'list') {
299
317
  args.params[paramToEdit] = await interactForArray(
300
- args.params[paramToEdit] || [],
318
+ toTextList(current),
301
319
  `Edit the list for ${paramToEdit}`,
302
320
  )
303
321
  return interactForBlueprintObject(args, ctx)
@@ -305,7 +323,7 @@ export const interactForBlueprintObject = async (
305
323
  args.params[paramToEdit] = await interactForBlueprintObject(
306
324
  {
307
325
  command: args.command,
308
- params: {},
326
+ params: toRecord(current),
309
327
  parameters: prop.parameters,
310
328
  isSubProperty: true,
311
329
  subPropertyPath: paramToEdit,
@@ -316,6 +334,7 @@ export const interactForBlueprintObject = async (
316
334
  } else if (prop.format === 'number') {
317
335
  const value = await promptNumber({
318
336
  message: withBackHint(`${paramToEdit}:`),
337
+ initialValue: toNumber(current),
319
338
  })
320
339
 
321
340
  args.params[paramToEdit] = value
@@ -332,3 +351,33 @@ export const interactForBlueprintObject = async (
332
351
  `Didn't know how to handle Blueprint parameter for property: "${paramToEdit}"`,
333
352
  )
334
353
  }
354
+
355
+ /*
356
+ * A parameter's current value in the shape its editor starts from, so that
357
+ * editing a parameter that already has a value continues from that value
358
+ * instead of from nothing — whether it came from an argument, from the JSON
359
+ * piped in, or from an earlier trip through this menu.
360
+ *
361
+ * Params are passed through as given, so a value need not match the format
362
+ * its parameter documents: the JSON piped in is arbitrary, and a schema can
363
+ * change under a script. A value the editor cannot start from is left out of
364
+ * it, which opens the editor as an unset parameter does, rather than
365
+ * rendering a value of the wrong type as text to edit.
366
+ */
367
+
368
+ const toText = (value: unknown): string | undefined =>
369
+ typeof value === 'string' ? value : undefined
370
+
371
+ const toNumber = (value: unknown): number | undefined =>
372
+ typeof value === 'number' ? value : undefined
373
+
374
+ const toBoolean = (value: unknown): boolean | undefined =>
375
+ typeof value === 'boolean' ? value : undefined
376
+
377
+ const toTextList = (value: unknown): string[] =>
378
+ Array.isArray(value) ? value.filter((item) => typeof item === 'string') : []
379
+
380
+ const toRecord = (value: unknown): Record<string, any> =>
381
+ typeof value === 'object' && value !== null && !Array.isArray(value)
382
+ ? (value as Record<string, any>)
383
+ : {}
@@ -1,12 +1,13 @@
1
1
  import { getSeam } from 'lib/http/client.js'
2
2
 
3
3
  import { interactForResource } from './resource.js'
4
- export const interactForConnectedAccount = async () => {
4
+ export const interactForConnectedAccount = async (initialValue?: string) => {
5
5
  const seam = await getSeam()
6
6
 
7
7
  return interactForResource({
8
8
  resourceName: 'connected_account',
9
9
  fetchResources: () => seam.connectedAccounts.list(),
10
+ initialValue,
10
11
  toChoice: (connectedAccount) => {
11
12
  const identifiers = Object.values(
12
13
  connectedAccount.user_identifier ?? {},
@@ -53,10 +53,16 @@ export const interactForCustomMetadata = async (
53
53
  message: withBackHint('Enter a key to add or edit:'),
54
54
  })
55
55
 
56
+ // Editing an existing key continues from the value it has, so a value
57
+ // is corrected rather than retyped.
58
+ const currentValue = updatedCustomMetadata[newKey]
59
+
56
60
  let newValue: string | boolean = await promptText({
57
61
  message: withBackHint(
58
62
  'Enter the new value to add or edit (or null to delete):',
59
63
  ),
64
+ initialValue:
65
+ currentValue == null ? undefined : currentValue.toString(),
60
66
  })
61
67
  if (newKey) {
62
68
  if (newValue === 'false' || newValue === 'true') {
@@ -1,12 +1,13 @@
1
1
  import { getSeam } from 'lib/http/client.js'
2
2
 
3
3
  import { interactForResource } from './resource.js'
4
- export const interactForDevice = async () => {
4
+ export const interactForDevice = async (initialValue?: string) => {
5
5
  const seam = await getSeam()
6
6
 
7
7
  return interactForResource({
8
8
  resourceName: 'device',
9
9
  fetchResources: () => seam.devices.list(),
10
+ initialValue,
10
11
  toChoice: (device) => ({
11
12
  title: device.properties.name ?? '<No Name>',
12
13
  value: device.device_id,
@@ -1,4 +1,4 @@
1
- import { isApiKey, isPersonalAccessToken } from '@seamapi/http/connect'
1
+ import { isApiKey, isPersonalAccessToken } from '@seamapi/http'
2
2
  import chalk from 'chalk'
3
3
 
4
4
  import { assertMutable, storeToken } from 'lib/auth/operations.js'
@@ -12,11 +12,14 @@ export const interactForResource = async <Resource>({
12
12
  fetchResources,
13
13
  toChoice,
14
14
  message = `Select a ${resourceName}:`,
15
+ initialValue,
15
16
  }: {
16
17
  resourceName: string
17
18
  fetchResources: () => Promise<Resource[]>
18
19
  toChoice: (resource: Resource) => ResourceChoice
19
20
  message?: string | undefined
21
+ /** The resource already chosen, for the list to open on. */
22
+ initialValue?: string | undefined
20
23
  }) => {
21
24
  const resources = await withLoading(
22
25
  `Fetching ${resourceName.replace(/_/g, ' ')}s...`,
@@ -30,5 +33,6 @@ export const interactForResource = async <Resource>({
30
33
  const { title, value, description } = toChoice(resource)
31
34
  return { label: title, value, hint: description }
32
35
  }),
36
+ initialValue,
33
37
  })
34
38
  }
@@ -1,11 +1,14 @@
1
1
  import { promptText, withBackHint } from 'lib/prompt.js'
2
2
 
3
- export const interactForTimestamp = async () => {
3
+ export const interactForTimestamp = async (currentValue?: string) => {
4
4
  const now = new Date().toISOString()
5
5
  const timestamp = await promptText({
6
6
  message: withBackHint('Enter a timestamp:'),
7
7
  placeholder: now,
8
8
  defaultValue: now,
9
+ // A timestamp already given is offered for editing, rather than making
10
+ // the user type it out again to shift it by an hour.
11
+ initialValue: currentValue,
9
12
  validate: (value) => {
10
13
  if (value == null || value === '') return undefined
11
14
  if (Number.isNaN(new Date(value).getTime())) {
@@ -2,12 +2,13 @@ import { getSeam } from 'lib/http/client.js'
2
2
 
3
3
  import { interactForResource } from './resource.js'
4
4
 
5
- export const interactForUserIdentity = async () => {
5
+ export const interactForUserIdentity = async (initialValue?: string) => {
6
6
  const seam = await getSeam()
7
7
 
8
8
  return interactForResource({
9
9
  resourceName: 'user_identity',
10
10
  fetchResources: () => seam.userIdentities.list(),
11
+ initialValue,
11
12
  toChoice: (userIdentity) => ({
12
13
  title: `${userIdentity.email_address} "${userIdentity.full_name}: ${userIdentity.user_identity_key}`,
13
14
  value: userIdentity.user_identity_id,
@@ -1,4 +1,4 @@
1
- import { SeamHttpWithoutWorkspace } from '@seamapi/http/connect'
1
+ import { SeamHttpWithoutWorkspace } from '@seamapi/http'
2
2
 
3
3
  import { assertMutable, selectWorkspace } from 'lib/auth/operations.js'
4
4
  import { getConfig } from 'lib/config/index.js'
@@ -3,6 +3,7 @@ import type {
3
3
  PromptChoice,
4
4
  PromptClient,
5
5
  PromptConfirmOptions,
6
+ PromptMultiselectOptions,
6
7
  PromptNumberOptions,
7
8
  PromptSelectOptions,
8
9
  PromptTextOptions,
@@ -19,6 +20,9 @@ export interface PromptQuestion {
19
20
  | 'autocompleteMultiselect'
20
21
  message: string
21
22
  choices?: Array<PromptChoice<unknown>>
23
+ /** What the question was seeded with — what the user sees to edit. */
24
+ initialValue?: unknown
25
+ initialValues?: unknown[] | undefined
22
26
  }
23
27
 
24
28
  /** Scripted in place of an answer to dismiss that prompt. */
@@ -44,35 +48,53 @@ export class MemoryPromptClient implements PromptClient {
44
48
 
45
49
  canPrompt = (): boolean => true
46
50
 
47
- text = async ({ message }: PromptTextOptions): Promise<string> =>
48
- this.answer({ kind: 'text', message }) as string
51
+ text = async ({
52
+ message,
53
+ initialValue,
54
+ }: PromptTextOptions): Promise<string> =>
55
+ this.answer({ kind: 'text', message, initialValue }) as string
49
56
 
50
- number = async ({ message }: PromptNumberOptions): Promise<number> =>
51
- this.answer({ kind: 'number', message }) as number
57
+ number = async ({
58
+ message,
59
+ initialValue,
60
+ }: PromptNumberOptions): Promise<number> =>
61
+ this.answer({ kind: 'number', message, initialValue }) as number
52
62
 
53
- confirm = async ({ message }: PromptConfirmOptions): Promise<boolean> =>
54
- this.answer({ kind: 'confirm', message }) as boolean
63
+ confirm = async ({
64
+ message,
65
+ initialValue,
66
+ }: PromptConfirmOptions): Promise<boolean> =>
67
+ this.answer({ kind: 'confirm', message, initialValue }) as boolean
55
68
 
56
69
  select = async <Value>({
57
70
  message,
58
71
  choices,
72
+ initialValue,
59
73
  }: PromptSelectOptions<Value>): Promise<Value> =>
60
- this.answer({ kind: 'select', message, choices }) as Value
74
+ this.answer({ kind: 'select', message, choices, initialValue }) as Value
61
75
 
62
76
  autocomplete = async <Value>({
63
77
  message,
64
78
  choices,
79
+ initialValue,
65
80
  }: PromptSelectOptions<Value>): Promise<Value> =>
66
- this.answer({ kind: 'autocomplete', message, choices }) as Value
81
+ this.answer({
82
+ kind: 'autocomplete',
83
+ message,
84
+ choices,
85
+ initialValue,
86
+ }) as Value
67
87
 
68
88
  autocompleteMultiselect = async <Value>({
69
89
  message,
70
90
  choices,
71
- }: PromptSelectOptions<Value>): Promise<Value[]> =>
91
+ initialValues,
92
+ }: PromptMultiselectOptions<Value>): Promise<Value[]> =>
72
93
  this.answer({
73
94
  kind: 'autocompleteMultiselect',
74
95
  message,
75
96
  choices,
97
+ initialValues,
76
98
  }) as Value[]
77
99
 
78
100
  private answer(question: PromptQuestion): unknown {