@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.
- package/README.md +8 -4
- package/bin/cli.js +8 -2
- package/bin/cli.js.map +1 -1
- package/lib/args/parse.js +1 -0
- package/lib/args/parse.js.map +1 -1
- package/lib/auth/validate-token.js +1 -1
- package/lib/auth/validate-token.js.map +1 -1
- package/lib/blueprint/source-remote.js +1 -1
- package/lib/blueprint/source-remote.js.map +1 -1
- package/lib/commands/local/wizard.js +1 -1
- package/lib/commands/local/wizard.js.map +1 -1
- package/lib/commands/spec.js +8 -0
- package/lib/commands/spec.js.map +1 -1
- package/lib/http/api.d.ts +1 -1
- package/lib/http/api.js +1 -1
- package/lib/http/api.js.map +1 -1
- package/lib/http/client.d.ts +1 -1
- package/lib/http/client.js +1 -1
- package/lib/http/client.js.map +1 -1
- package/lib/http/memory-seam-api.js +1 -1
- package/lib/http/memory-seam-api.js.map +1 -1
- package/lib/http/request.js +1 -1
- package/lib/http/request.js.map +1 -1
- package/lib/interactions/access-code.d.ts +1 -1
- package/lib/interactions/access-code.js +2 -1
- package/lib/interactions/access-code.js.map +1 -1
- package/lib/interactions/acs-entrance.d.ts +1 -1
- package/lib/interactions/acs-entrance.js +2 -1
- package/lib/interactions/acs-entrance.js.map +1 -1
- package/lib/interactions/acs-system.d.ts +4 -1
- package/lib/interactions/acs-system.js +2 -1
- package/lib/interactions/acs-system.js.map +1 -1
- package/lib/interactions/acs-user.d.ts +1 -1
- package/lib/interactions/acs-user.js +5 -2
- package/lib/interactions/acs-user.js.map +1 -1
- package/lib/interactions/action-attempt-poll.d.ts +1 -1
- package/lib/interactions/blueprint-object.js +41 -13
- package/lib/interactions/blueprint-object.js.map +1 -1
- package/lib/interactions/connected-account.d.ts +1 -1
- package/lib/interactions/connected-account.js +2 -1
- package/lib/interactions/connected-account.js.map +1 -1
- package/lib/interactions/custom-metadata.js +4 -0
- package/lib/interactions/custom-metadata.js.map +1 -1
- package/lib/interactions/device.d.ts +1 -1
- package/lib/interactions/device.js +2 -1
- package/lib/interactions/device.js.map +1 -1
- package/lib/interactions/login.js +1 -1
- package/lib/interactions/login.js.map +1 -1
- package/lib/interactions/resource.d.ts +3 -1
- package/lib/interactions/resource.js +2 -1
- package/lib/interactions/resource.js.map +1 -1
- package/lib/interactions/timestamp.d.ts +1 -1
- package/lib/interactions/timestamp.js +4 -1
- package/lib/interactions/timestamp.js.map +1 -1
- package/lib/interactions/user-identity.d.ts +1 -1
- package/lib/interactions/user-identity.js +2 -1
- package/lib/interactions/user-identity.js.map +1 -1
- package/lib/interactions/workspace-id.js +1 -1
- package/lib/interactions/workspace-id.js.map +1 -1
- package/lib/memory-prompt.d.ts +10 -7
- package/lib/memory-prompt.js +12 -6
- package/lib/memory-prompt.js.map +1 -1
- package/lib/prompt.d.ts +27 -3
- package/lib/prompt.js +26 -1
- package/lib/prompt.js.map +1 -1
- package/lib/render/help.js +1 -1
- package/lib/render/help.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +9 -13
- package/src/bin/cli.ts +9 -2
- package/src/lib/args/parse.ts +1 -0
- package/src/lib/auth/validate-token.ts +1 -1
- package/src/lib/blueprint/source-remote.ts +1 -1
- package/src/lib/commands/local/wizard.ts +1 -1
- package/src/lib/commands/spec.ts +8 -0
- package/src/lib/http/api.ts +1 -1
- package/src/lib/http/client.ts +1 -1
- package/src/lib/http/memory-seam-api.ts +1 -4
- package/src/lib/http/request.ts +1 -4
- package/src/lib/interactions/access-code.ts +11 -7
- package/src/lib/interactions/acs-entrance.ts +2 -1
- package/src/lib/interactions/acs-system.ts +8 -1
- package/src/lib/interactions/acs-user.ts +5 -4
- package/src/lib/interactions/action-attempt-poll.ts +1 -1
- package/src/lib/interactions/blueprint-object.ts +62 -13
- package/src/lib/interactions/connected-account.ts +2 -1
- package/src/lib/interactions/custom-metadata.ts +6 -0
- package/src/lib/interactions/device.ts +2 -1
- package/src/lib/interactions/login.ts +1 -1
- package/src/lib/interactions/resource.ts +4 -0
- package/src/lib/interactions/timestamp.ts +4 -1
- package/src/lib/interactions/user-identity.ts +2 -1
- package/src/lib/interactions/workspace-id.ts +1 -1
- package/src/lib/memory-prompt.ts +31 -9
- package/src/lib/prompt.ts +65 -4
- package/src/lib/render/help.ts +1 -1
- 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.
|
|
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
|
-
"
|
|
11
|
-
|
|
12
|
-
"default": "./index.js"
|
|
13
|
-
}
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"default": "./index.js"
|
|
14
12
|
},
|
|
15
13
|
"./cli": {
|
|
16
|
-
"
|
|
17
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
98
|
-
"@seamapi/wizard": "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
|
|
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
|
package/src/lib/args/parse.ts
CHANGED
|
@@ -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
|
|
12
|
+
import('@seamapi/http'),
|
|
13
13
|
])
|
|
14
14
|
const openapi = await getOpenapiSchema(resolveAuth().endpoint)
|
|
15
15
|
|
package/src/lib/commands/spec.ts
CHANGED
|
@@ -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',
|
package/src/lib/http/api.ts
CHANGED
package/src/lib/http/client.ts
CHANGED
package/src/lib/http/request.ts
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 (
|
|
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,
|
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -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,
|
package/src/lib/memory-prompt.ts
CHANGED
|
@@ -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 ({
|
|
48
|
-
|
|
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 ({
|
|
51
|
-
|
|
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 ({
|
|
54
|
-
|
|
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({
|
|
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
|
-
|
|
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 {
|