@seamapi/cli 0.0.1 → 0.5.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 +54 -3
- package/bin/cli.d.ts +2 -0
- package/bin/cli.js +233 -0
- package/bin/cli.js.map +1 -0
- package/index.d.ts +2 -0
- package/index.js +5 -0
- package/index.js.map +1 -0
- package/lib/blueprint.d.ts +6 -0
- package/lib/blueprint.js +37 -0
- package/lib/blueprint.js.map +1 -0
- package/lib/get-api-blueprint.d.ts +3 -0
- package/lib/get-api-blueprint.js +19 -0
- package/lib/get-api-blueprint.js.map +1 -0
- package/lib/get-command-blueprint-def.d.ts +2 -0
- package/lib/get-command-blueprint-def.js +11 -0
- package/lib/get-command-blueprint-def.js.map +1 -0
- package/lib/get-config-store.d.ts +2 -0
- package/lib/get-config-store.js +5 -0
- package/lib/get-config-store.js.map +1 -0
- package/lib/get-current-workspace-id.d.ts +1 -0
- package/lib/get-current-workspace-id.js +10 -0
- package/lib/get-current-workspace-id.js.map +1 -0
- package/lib/get-seam.d.ts +3 -0
- package/lib/get-seam.js +26 -0
- package/lib/get-seam.js.map +1 -0
- package/lib/get-server.d.ts +1 -0
- package/lib/get-server.js +7 -0
- package/lib/get-server.js.map +1 -0
- package/lib/interact-for-access-code.d.ts +3 -0
- package/lib/interact-for-access-code.js +19 -0
- package/lib/interact-for-access-code.js.map +1 -0
- package/lib/interact-for-acs-entrance.d.ts +1 -0
- package/lib/interact-for-acs-entrance.js +15 -0
- package/lib/interact-for-acs-entrance.js.map +1 -0
- package/lib/interact-for-acs-system.d.ts +1 -0
- package/lib/interact-for-acs-system.js +16 -0
- package/lib/interact-for-acs-system.js.map +1 -0
- package/lib/interact-for-acs-user.d.ts +1 -0
- package/lib/interact-for-acs-user.js +17 -0
- package/lib/interact-for-acs-user.js.map +1 -0
- package/lib/interact-for-action-attempt-poll.d.ts +2 -0
- package/lib/interact-for-action-attempt-poll.js +22 -0
- package/lib/interact-for-action-attempt-poll.js.map +1 -0
- package/lib/interact-for-array.d.ts +1 -0
- package/lib/interact-for-array.js +53 -0
- package/lib/interact-for-array.js.map +1 -0
- package/lib/interact-for-blueprint-object.d.ts +9 -0
- package/lib/interact-for-blueprint-object.js +235 -0
- package/lib/interact-for-blueprint-object.js.map +1 -0
- package/lib/interact-for-command-params.d.ts +5 -0
- package/lib/interact-for-command-params.js +11 -0
- package/lib/interact-for-command-params.js.map +1 -0
- package/lib/interact-for-command-selection.d.ts +2 -0
- package/lib/interact-for-command-selection.js +76 -0
- package/lib/interact-for-command-selection.js.map +1 -0
- package/lib/interact-for-connected-account.d.ts +1 -0
- package/lib/interact-for-connected-account.js +20 -0
- package/lib/interact-for-connected-account.js.map +1 -0
- package/lib/interact-for-custom-metadata.d.ts +6 -0
- package/lib/interact-for-custom-metadata.js +71 -0
- package/lib/interact-for-custom-metadata.js.map +1 -0
- package/lib/interact-for-device.d.ts +1 -0
- package/lib/interact-for-device.js +15 -0
- package/lib/interact-for-device.js.map +1 -0
- package/lib/interact-for-login.d.ts +1 -0
- package/lib/interact-for-login.js +39 -0
- package/lib/interact-for-login.js.map +1 -0
- package/lib/interact-for-resource.d.ts +11 -0
- package/lib/interact-for-resource.js +13 -0
- package/lib/interact-for-resource.js.map +1 -0
- package/lib/interact-for-server-selection.d.ts +1 -0
- package/lib/interact-for-server-selection.js +41 -0
- package/lib/interact-for-server-selection.js.map +1 -0
- package/lib/interact-for-timestamp.d.ts +1 -0
- package/lib/interact-for-timestamp.js +10 -0
- package/lib/interact-for-timestamp.js.map +1 -0
- package/lib/interact-for-use-remote-api-defs.d.ts +1 -0
- package/lib/interact-for-use-remote-api-defs.js +25 -0
- package/lib/interact-for-use-remote-api-defs.js.map +1 -0
- package/lib/interact-for-user-identity.d.ts +1 -0
- package/lib/interact-for-user-identity.js +15 -0
- package/lib/interact-for-user-identity.js.map +1 -0
- package/lib/interact-for-workspace-id.d.ts +1 -0
- package/lib/interact-for-workspace-id.js +31 -0
- package/lib/interact-for-workspace-id.js.map +1 -0
- package/lib/types.d.ts +5 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/lib/util/ellipsis.d.ts +1 -0
- package/lib/util/ellipsis.js +6 -0
- package/lib/util/ellipsis.js.map +1 -0
- package/lib/util/request-seam-api.d.ts +4 -0
- package/lib/util/request-seam-api.js +28 -0
- package/lib/util/request-seam-api.js.map +1 -0
- package/lib/util/with-loading.d.ts +1 -0
- package/lib/util/with-loading.js +14 -0
- package/lib/util/with-loading.js.map +1 -0
- package/lib/validate-token.d.ts +1 -0
- package/lib/validate-token.js +19 -0
- package/lib/validate-token.js.map +1 -0
- package/lib/version.d.ts +2 -0
- package/lib/version.js +3 -0
- package/lib/version.js.map +1 -0
- package/package.json +44 -18
- package/src/bin/cli.ts +265 -0
- package/src/index.ts +4 -1
- package/src/lib/blueprint.ts +53 -0
- package/src/lib/get-api-blueprint.ts +27 -0
- package/src/lib/get-command-blueprint-def.ts +15 -0
- package/src/lib/get-config-store.ts +5 -0
- package/src/lib/get-current-workspace-id.ts +11 -0
- package/src/lib/get-seam.ts +43 -0
- package/src/lib/get-server.ts +9 -0
- package/src/lib/interact-for-access-code.ts +25 -0
- package/src/lib/interact-for-acs-entrance.ts +16 -0
- package/src/lib/interact-for-acs-system.ts +17 -0
- package/src/lib/interact-for-acs-user.ts +21 -0
- package/src/lib/interact-for-action-attempt-poll.ts +35 -0
- package/src/lib/interact-for-array.ts +61 -0
- package/src/lib/interact-for-blueprint-object.ts +274 -0
- package/src/lib/interact-for-command-params.ts +22 -0
- package/src/lib/interact-for-command-selection.ts +107 -0
- package/src/lib/interact-for-connected-account.ts +24 -0
- package/src/lib/interact-for-custom-metadata.ts +89 -0
- package/src/lib/interact-for-device.ts +15 -0
- package/src/lib/interact-for-login.ts +53 -0
- package/src/lib/interact-for-resource.ts +34 -0
- package/src/lib/interact-for-server-selection.ts +46 -0
- package/src/lib/interact-for-timestamp.ts +10 -0
- package/src/lib/interact-for-use-remote-api-defs.ts +27 -0
- package/src/lib/interact-for-user-identity.ts +16 -0
- package/src/lib/interact-for-workspace-id.ts +37 -0
- package/src/lib/types.ts +6 -0
- package/src/lib/util/ellipsis.ts +4 -0
- package/src/lib/util/request-seam-api.ts +43 -0
- package/src/lib/util/with-loading.ts +16 -0
- package/src/lib/validate-token.ts +28 -0
- package/src/lib/version.ts +3 -0
- package/src/lib/index.ts +0 -1
- package/src/lib/todo.ts +0 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { isDeepStrictEqual as isEqual } from 'node:util'
|
|
2
|
+
|
|
3
|
+
import prompts from 'prompts'
|
|
4
|
+
|
|
5
|
+
import type { ContextHelpers } from './types.js'
|
|
6
|
+
|
|
7
|
+
const uniqBy = <T>(items: T[], keyOf: (item: T) => unknown): T[] => {
|
|
8
|
+
const seen = new Set<unknown>()
|
|
9
|
+
return items.filter((item) => {
|
|
10
|
+
const key = keyOf(item)
|
|
11
|
+
if (seen.has(key)) return false
|
|
12
|
+
seen.add(key)
|
|
13
|
+
return true
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ergonomicOrder = ['create', 'list', 'get', 'update', 'unlock_door']
|
|
18
|
+
|
|
19
|
+
function ergonomicSort(aStr: string, bStr: string) {
|
|
20
|
+
let a = ergonomicOrder.indexOf(aStr)
|
|
21
|
+
if (a === -1) a = ergonomicOrder.length
|
|
22
|
+
let b = ergonomicOrder.indexOf(bStr)
|
|
23
|
+
if (b === -1) b = ergonomicOrder.length
|
|
24
|
+
|
|
25
|
+
return a > b ? 1 : a < b ? -1 : 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function interactForCommandSelection(
|
|
29
|
+
commandPath: string[],
|
|
30
|
+
helpers: ContextHelpers,
|
|
31
|
+
) {
|
|
32
|
+
const commands = helpers.blueprint.routes
|
|
33
|
+
.flatMap((route) => route.endpoints)
|
|
34
|
+
.map((endpoint) =>
|
|
35
|
+
endpoint.path.replace(/_/g, '-').replace(/^\//, '').split('/'),
|
|
36
|
+
)
|
|
37
|
+
.concat([
|
|
38
|
+
['login'],
|
|
39
|
+
['logout'],
|
|
40
|
+
['config', 'reveal-location'],
|
|
41
|
+
['config', 'use-remote-api-defs'],
|
|
42
|
+
['select', 'workspace'],
|
|
43
|
+
['select', 'server'],
|
|
44
|
+
['health', 'get-health'],
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
const possibleCommands = uniqBy(
|
|
48
|
+
commandPath.length === 0
|
|
49
|
+
? commands
|
|
50
|
+
: commands.filter((cmd) =>
|
|
51
|
+
isEqual(cmd.slice(0, commandPath.length), commandPath),
|
|
52
|
+
),
|
|
53
|
+
(v) => v[commandPath.length],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if (possibleCommands.length === 0) {
|
|
57
|
+
throw new Error('No possible commands')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
possibleCommands.length === 1 &&
|
|
62
|
+
possibleCommands[0]?.length === commandPath.length
|
|
63
|
+
) {
|
|
64
|
+
return commandPath
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Add dynamic 'back' command for sub-commands to allow returning
|
|
68
|
+
// to previous level.
|
|
69
|
+
if (commandPath.length > 0) {
|
|
70
|
+
possibleCommands.push([...commandPath, '[Back]'])
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const commandPathStr = commandPath.join('/').replace(/-/g, '_')
|
|
74
|
+
|
|
75
|
+
const res = await prompts({
|
|
76
|
+
name: 'Command',
|
|
77
|
+
type: 'autocomplete',
|
|
78
|
+
choices: [
|
|
79
|
+
...possibleCommands.map((cmd) => ({
|
|
80
|
+
title:
|
|
81
|
+
cmd?.[commandPath.length] ?? `[Call /${commandPathStr} Directly]`,
|
|
82
|
+
value: cmd?.[commandPath.length] ?? '<none>',
|
|
83
|
+
})),
|
|
84
|
+
].sort((a, b) => ergonomicSort(a.value, b.value)),
|
|
85
|
+
message: `Select a command: /${commandPathStr}`,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
if (res?.Command === undefined) {
|
|
89
|
+
throw new Error('Bailed')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (res?.Command === '<none>') {
|
|
93
|
+
return commandPath
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const newCommandPath = [...commandPath, res.Command]
|
|
97
|
+
|
|
98
|
+
const fullCommand = possibleCommands.find((cmd) =>
|
|
99
|
+
isEqual(newCommandPath, cmd),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if (!fullCommand) {
|
|
103
|
+
return interactForCommandSelection(newCommandPath, helpers)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return fullCommand
|
|
107
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getSeam } from './get-seam.js'
|
|
2
|
+
import { interactForResource } from './interact-for-resource.js'
|
|
3
|
+
export const interactForConnectedAccount = async () => {
|
|
4
|
+
const seam = await getSeam()
|
|
5
|
+
|
|
6
|
+
return interactForResource({
|
|
7
|
+
resourceName: 'connected_account',
|
|
8
|
+
fetchResources: () => seam.connectedAccounts.list(),
|
|
9
|
+
toChoice: (connectedAccount) => {
|
|
10
|
+
const identifiers = Object.values(
|
|
11
|
+
connectedAccount.user_identifier ?? {},
|
|
12
|
+
).filter((value): value is string => typeof value === 'string')
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
title:
|
|
16
|
+
connectedAccount.user_identifier?.email ??
|
|
17
|
+
identifiers[0] ??
|
|
18
|
+
'<No Name>',
|
|
19
|
+
value: connectedAccount.connected_account_id,
|
|
20
|
+
description: `${connectedAccount.account_type} ${connectedAccount.connected_account_id}`,
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import prompts from 'prompts'
|
|
2
|
+
|
|
3
|
+
// Structurally the CustomMetadata of @seamapi/types, spelled out here so the
|
|
4
|
+
// published declarations do not depend on a development-only package.
|
|
5
|
+
type CustomMetadata = Record<string, string | boolean>
|
|
6
|
+
|
|
7
|
+
type UpdatedCustomMetadata = {
|
|
8
|
+
[x: string]: string | boolean | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const interactForCustomMetadata = async (
|
|
12
|
+
custom_metadata: CustomMetadata,
|
|
13
|
+
) => {
|
|
14
|
+
const updated_custom_metadata: UpdatedCustomMetadata = { ...custom_metadata }
|
|
15
|
+
|
|
16
|
+
const displayCurrentCustomMetadata = () => {
|
|
17
|
+
console.log('custom_metadata:')
|
|
18
|
+
if (Object.keys(updated_custom_metadata).length > 0) {
|
|
19
|
+
Object.keys(updated_custom_metadata).forEach((key, index) => {
|
|
20
|
+
console.log(`${index + 1}: ${key}: ${updated_custom_metadata[key]}`)
|
|
21
|
+
})
|
|
22
|
+
} else {
|
|
23
|
+
console.log('The custom metadata param is empty.')
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let action: string
|
|
28
|
+
|
|
29
|
+
do {
|
|
30
|
+
displayCurrentCustomMetadata()
|
|
31
|
+
|
|
32
|
+
const response = await prompts({
|
|
33
|
+
type: 'select',
|
|
34
|
+
name: 'action',
|
|
35
|
+
message: 'Choose an action:',
|
|
36
|
+
choices: [
|
|
37
|
+
{ title: 'Add an item to params', value: 'add' },
|
|
38
|
+
{ title: 'Remove an item from params', value: 'remove' },
|
|
39
|
+
{ title: 'Finish editing params', value: 'done' },
|
|
40
|
+
],
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
action = response.action
|
|
44
|
+
|
|
45
|
+
if (action === 'add') {
|
|
46
|
+
const { newKey } = await prompts({
|
|
47
|
+
type: 'text',
|
|
48
|
+
name: 'newKey',
|
|
49
|
+
message: 'Enter a key to add or edit:',
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
let { newValue } = await prompts({
|
|
53
|
+
type: 'text',
|
|
54
|
+
name: 'newValue',
|
|
55
|
+
message: 'Enter the new value to add or edit (or null to delete):',
|
|
56
|
+
})
|
|
57
|
+
if (newKey) {
|
|
58
|
+
if (newValue === 'false' || newValue === 'true') {
|
|
59
|
+
newValue = Boolean(newValue)
|
|
60
|
+
}
|
|
61
|
+
if (newValue === 'null') {
|
|
62
|
+
updated_custom_metadata[newKey] = null
|
|
63
|
+
} else {
|
|
64
|
+
updated_custom_metadata[newKey] = newValue
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} else if (action === 'remove') {
|
|
68
|
+
const { custom_key_to_remove } = await prompts({
|
|
69
|
+
type: 'select',
|
|
70
|
+
name: 'custom_key_to_remove',
|
|
71
|
+
message: 'Choose a key-value pair to remove from params:',
|
|
72
|
+
choices: Object.keys(updated_custom_metadata).map(
|
|
73
|
+
(custom_metadata_key) => {
|
|
74
|
+
return {
|
|
75
|
+
title: `${custom_metadata_key}: ${updated_custom_metadata[custom_metadata_key]}`,
|
|
76
|
+
value: custom_metadata_key,
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
),
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
if (custom_key_to_remove) {
|
|
83
|
+
delete custom_metadata[custom_key_to_remove]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} while (action !== 'done')
|
|
87
|
+
|
|
88
|
+
return updated_custom_metadata
|
|
89
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getSeam } from './get-seam.js'
|
|
2
|
+
import { interactForResource } from './interact-for-resource.js'
|
|
3
|
+
export const interactForDevice = async () => {
|
|
4
|
+
const seam = await getSeam()
|
|
5
|
+
|
|
6
|
+
return interactForResource({
|
|
7
|
+
resourceName: 'device',
|
|
8
|
+
fetchResources: () => seam.devices.list(),
|
|
9
|
+
toChoice: (device) => ({
|
|
10
|
+
title: device.properties.name ?? '<No Name>',
|
|
11
|
+
value: device.device_id,
|
|
12
|
+
description: `${device.device_type} ${device.device_id}`,
|
|
13
|
+
}),
|
|
14
|
+
})
|
|
15
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isApiKey, isPersonalAccessToken } from '@seamapi/http/connect'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import prompts from 'prompts'
|
|
4
|
+
|
|
5
|
+
import { getConfigStore } from './get-config-store.js'
|
|
6
|
+
import { getServer } from './get-server.js'
|
|
7
|
+
import { interactForWorkspaceId } from './interact-for-workspace-id.js'
|
|
8
|
+
import { withLoading } from './util/with-loading.js'
|
|
9
|
+
import { validateToken } from './validate-token.js'
|
|
10
|
+
|
|
11
|
+
export const interactForLogin = async () => {
|
|
12
|
+
const config = await getConfigStore()
|
|
13
|
+
|
|
14
|
+
if (getServer().includes('localhost')) {
|
|
15
|
+
console.log(
|
|
16
|
+
`You're using a local Seam Connect instance, you can enter the API Key to your local user, you can create a new user from:\n\n${getServer()}/admin/create_user_with_api_key`,
|
|
17
|
+
)
|
|
18
|
+
} else {
|
|
19
|
+
console.log(
|
|
20
|
+
`To login, navigate to the URL below and create a new Personal Access Token (PAT) and paste the PAT in the provided box:\n\nhttps://console.seam.co/settings/access-tokens\n\n`,
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(
|
|
25
|
+
chalk.gray(
|
|
26
|
+
'> Note: You can enter an API Key here for single-workspace access',
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const { pat } = await prompts({
|
|
31
|
+
name: 'pat',
|
|
32
|
+
type: 'text',
|
|
33
|
+
message: 'Personal Access Token:',
|
|
34
|
+
})
|
|
35
|
+
const token = pat?.trim()
|
|
36
|
+
|
|
37
|
+
if (!token) {
|
|
38
|
+
throw new Error('No token provided')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (isPersonalAccessToken(token)) {
|
|
42
|
+
await interactForWorkspaceId(token)
|
|
43
|
+
} else if (isApiKey(token)) {
|
|
44
|
+
await withLoading('Validating API key...', () => validateToken(token))
|
|
45
|
+
} else {
|
|
46
|
+
throw new Error(
|
|
47
|
+
'Invalid token: expected a Personal Access Token or API Key',
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
config.set(`${getServer()}.pat`, token)
|
|
52
|
+
console.log(`Token saved! You may begin using the CLI!`)
|
|
53
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import prompts from 'prompts'
|
|
2
|
+
|
|
3
|
+
import { withLoading } from './util/with-loading.js'
|
|
4
|
+
|
|
5
|
+
export interface ResourceChoice {
|
|
6
|
+
title: string
|
|
7
|
+
value: string
|
|
8
|
+
description?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const interactForResource = async <Resource>({
|
|
12
|
+
resourceName,
|
|
13
|
+
fetchResources,
|
|
14
|
+
toChoice,
|
|
15
|
+
message = `Select a ${resourceName}:`,
|
|
16
|
+
}: {
|
|
17
|
+
resourceName: string
|
|
18
|
+
fetchResources: () => Promise<Resource[]>
|
|
19
|
+
toChoice: (resource: Resource) => ResourceChoice
|
|
20
|
+
message?: string | undefined
|
|
21
|
+
}) => {
|
|
22
|
+
const resources = await withLoading(
|
|
23
|
+
`Fetching ${resourceName.replace(/_/g, ' ')}s...`,
|
|
24
|
+
fetchResources,
|
|
25
|
+
)
|
|
26
|
+
const { resourceId } = await prompts({
|
|
27
|
+
name: 'resourceId',
|
|
28
|
+
type: 'autocomplete',
|
|
29
|
+
message,
|
|
30
|
+
choices: resources.map(toChoice),
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return resourceId as string
|
|
34
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
import prompts from 'prompts'
|
|
4
|
+
|
|
5
|
+
import { getConfigStore } from './get-config-store.js'
|
|
6
|
+
import { getServer } from './get-server.js'
|
|
7
|
+
|
|
8
|
+
export async function interactForServerSelection() {
|
|
9
|
+
const servers = [
|
|
10
|
+
'http://localhost:3020',
|
|
11
|
+
'https://connect.getseam.com',
|
|
12
|
+
'https://fakeseamconnect.seam.vc',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const { server } = await prompts([
|
|
16
|
+
{
|
|
17
|
+
type: 'select',
|
|
18
|
+
name: 'server',
|
|
19
|
+
message: 'Select a server:',
|
|
20
|
+
choices: servers.map((server) => ({ title: server, value: server })),
|
|
21
|
+
},
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
const config = getConfigStore()
|
|
25
|
+
if (server === servers[2]) {
|
|
26
|
+
let { userUrlSeed } = await prompts([
|
|
27
|
+
{
|
|
28
|
+
type: 'text',
|
|
29
|
+
name: 'userUrlSeed',
|
|
30
|
+
message:
|
|
31
|
+
'You can input a custom server URL or leave this field empty to use a new fakeserver.',
|
|
32
|
+
},
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
if (userUrlSeed.trim().length === 0) {
|
|
36
|
+
userUrlSeed = randomBytes(5).toString('hex')
|
|
37
|
+
}
|
|
38
|
+
config.set('server', `https://${userUrlSeed}.fakeseamconnect.seam.vc`)
|
|
39
|
+
config.set(`${getServer()}.pat`, `seam_apikey1_token`)
|
|
40
|
+
console.log(`PAT set to use fakeseamconnect with "seam_apikey1_token"`)
|
|
41
|
+
} else {
|
|
42
|
+
config.set('server', server)
|
|
43
|
+
}
|
|
44
|
+
config.delete('current_workspace_id')
|
|
45
|
+
console.log(`Server set to ${server}`)
|
|
46
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import prompts from 'prompts'
|
|
2
|
+
|
|
3
|
+
import { getConfigStore } from './get-config-store.js'
|
|
4
|
+
|
|
5
|
+
export async function interactForUseRemoteApiDefs() {
|
|
6
|
+
const { use_remote_api_defs } = await prompts([
|
|
7
|
+
{
|
|
8
|
+
type: 'select',
|
|
9
|
+
name: 'use_remote_api_defs',
|
|
10
|
+
message: 'Always use remote API Definitions?',
|
|
11
|
+
choices: [
|
|
12
|
+
{
|
|
13
|
+
title: 'Yes',
|
|
14
|
+
value: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
title: 'No',
|
|
18
|
+
value: false,
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
const config = getConfigStore()
|
|
25
|
+
config.set('use_remote_api_defs', use_remote_api_defs)
|
|
26
|
+
console.log(`Use remote API Definitions: ${use_remote_api_defs}`)
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getSeam } from './get-seam.js'
|
|
2
|
+
import { interactForResource } from './interact-for-resource.js'
|
|
3
|
+
|
|
4
|
+
export const interactForUserIdentity = async () => {
|
|
5
|
+
const seam = await getSeam()
|
|
6
|
+
|
|
7
|
+
return interactForResource({
|
|
8
|
+
resourceName: 'user_identity',
|
|
9
|
+
fetchResources: () => seam.userIdentities.list(),
|
|
10
|
+
toChoice: (userIdentity) => ({
|
|
11
|
+
title: `${userIdentity.email_address} "${userIdentity.full_name}: ${userIdentity.user_identity_key}`,
|
|
12
|
+
value: userIdentity.user_identity_id,
|
|
13
|
+
description: userIdentity.user_identity_id,
|
|
14
|
+
}),
|
|
15
|
+
})
|
|
16
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SeamHttpWithoutWorkspace } from '@seamapi/http/connect'
|
|
2
|
+
import prompts from 'prompts'
|
|
3
|
+
|
|
4
|
+
import { getConfigStore } from './get-config-store.js'
|
|
5
|
+
import { getSeamMultiWorkspace } from './get-seam.js'
|
|
6
|
+
import { getServer } from './get-server.js'
|
|
7
|
+
import { withLoading } from './util/with-loading.js'
|
|
8
|
+
|
|
9
|
+
export const interactForWorkspaceId = async (personalAccessToken?: string) => {
|
|
10
|
+
const config = getConfigStore()
|
|
11
|
+
const seam = personalAccessToken
|
|
12
|
+
? SeamHttpWithoutWorkspace.fromPersonalAccessToken(personalAccessToken, {
|
|
13
|
+
endpoint: getServer(),
|
|
14
|
+
})
|
|
15
|
+
: await getSeamMultiWorkspace()
|
|
16
|
+
|
|
17
|
+
const workspaces = await withLoading('Fetching workspaces...', () =>
|
|
18
|
+
seam.workspaces.list(),
|
|
19
|
+
)
|
|
20
|
+
const { workspaceId } = await prompts({
|
|
21
|
+
name: 'workspaceId',
|
|
22
|
+
type: 'select',
|
|
23
|
+
message: 'Select a workspace:',
|
|
24
|
+
choices: workspaces.map((workspace: any) => ({
|
|
25
|
+
title: workspace.name,
|
|
26
|
+
value: workspace.workspace_id,
|
|
27
|
+
description: workspace.workspace_id,
|
|
28
|
+
})),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
if (workspaceId) {
|
|
32
|
+
config.set('current_workspace_id', workspaceId)
|
|
33
|
+
return workspaceId
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw new Error('Bailed')
|
|
37
|
+
}
|
package/src/lib/types.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
import { getSeam } from 'lib/get-seam.js'
|
|
4
|
+
|
|
5
|
+
import { withLoading } from './with-loading.js'
|
|
6
|
+
|
|
7
|
+
export const RequestSeamApi = async ({
|
|
8
|
+
path,
|
|
9
|
+
params,
|
|
10
|
+
}: {
|
|
11
|
+
path: string
|
|
12
|
+
params: Record<string, any>
|
|
13
|
+
}) => {
|
|
14
|
+
const seam = await getSeam()
|
|
15
|
+
|
|
16
|
+
logRequest(path, params)
|
|
17
|
+
|
|
18
|
+
const response = await withLoading('Making request...', () =>
|
|
19
|
+
seam.client.post(path, params, {
|
|
20
|
+
validateStatus: () => true,
|
|
21
|
+
}),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logResponse(response)
|
|
25
|
+
|
|
26
|
+
return response
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const logResponse = (response: { status: number; data: unknown }) => {
|
|
30
|
+
if (response.status >= 400) {
|
|
31
|
+
console.log(chalk.red(`\n\n[${response.status}]\n`))
|
|
32
|
+
} else {
|
|
33
|
+
console.log(chalk.green(`\n\n[${response.status}]`))
|
|
34
|
+
}
|
|
35
|
+
console.dir(response.data, { depth: null })
|
|
36
|
+
console.log('\n')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const logRequest = (apiPath: string, params: Record<string, any>) => {
|
|
40
|
+
console.log(`\n\n${chalk.green(apiPath)}`)
|
|
41
|
+
console.log(`Request Params:`)
|
|
42
|
+
console.log(params)
|
|
43
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createSpinner } from 'nanospinner'
|
|
2
|
+
|
|
3
|
+
export const withLoading = async <T>(
|
|
4
|
+
message: string,
|
|
5
|
+
fn: () => Promise<T>,
|
|
6
|
+
): Promise<T> => {
|
|
7
|
+
const spinner = createSpinner(message).start()
|
|
8
|
+
try {
|
|
9
|
+
const result = await fn()
|
|
10
|
+
spinner.success()
|
|
11
|
+
return result
|
|
12
|
+
} catch (error) {
|
|
13
|
+
spinner.error()
|
|
14
|
+
throw error
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isApiKey,
|
|
3
|
+
isPersonalAccessToken,
|
|
4
|
+
SeamHttp,
|
|
5
|
+
SeamHttpWithoutWorkspace,
|
|
6
|
+
} from '@seamapi/http/connect'
|
|
7
|
+
|
|
8
|
+
import { getServer } from './get-server.js'
|
|
9
|
+
|
|
10
|
+
export const validateToken = async (token: string, workspaceId?: string) => {
|
|
11
|
+
const options = { endpoint: getServer() }
|
|
12
|
+
|
|
13
|
+
if (isPersonalAccessToken(token)) {
|
|
14
|
+
const seam = workspaceId
|
|
15
|
+
? SeamHttp.fromPersonalAccessToken(token, workspaceId, options)
|
|
16
|
+
: SeamHttpWithoutWorkspace.fromPersonalAccessToken(token, options)
|
|
17
|
+
await seam.workspaces.list()
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (isApiKey(token)) {
|
|
22
|
+
const seam = SeamHttp.fromApiKey(token, options)
|
|
23
|
+
await seam.workspaces.list()
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error('Invalid token: expected a Personal Access Token or API Key')
|
|
28
|
+
}
|
package/src/lib/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { todo } from './todo.js'
|
package/src/lib/todo.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const todo = (x: string): string => x
|