@seamapi/cli 0.0.1 → 0.4.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 (136) hide show
  1. package/README.md +59 -3
  2. package/bin/cli.d.ts +2 -0
  3. package/bin/cli.js +233 -0
  4. package/bin/cli.js.map +1 -0
  5. package/index.d.ts +2 -0
  6. package/index.js +5 -0
  7. package/index.js.map +1 -0
  8. package/lib/get-api-blueprint.d.ts +3 -0
  9. package/lib/get-api-blueprint.js +11 -0
  10. package/lib/get-api-blueprint.js.map +1 -0
  11. package/lib/get-command-blueprint-def.d.ts +2 -0
  12. package/lib/get-command-blueprint-def.js +11 -0
  13. package/lib/get-command-blueprint-def.js.map +1 -0
  14. package/lib/get-config-store.d.ts +2 -0
  15. package/lib/get-config-store.js +5 -0
  16. package/lib/get-config-store.js.map +1 -0
  17. package/lib/get-current-workspace-id.d.ts +1 -0
  18. package/lib/get-current-workspace-id.js +10 -0
  19. package/lib/get-current-workspace-id.js.map +1 -0
  20. package/lib/get-seam.d.ts +3 -0
  21. package/lib/get-seam.js +26 -0
  22. package/lib/get-seam.js.map +1 -0
  23. package/lib/get-server.d.ts +1 -0
  24. package/lib/get-server.js +7 -0
  25. package/lib/get-server.js.map +1 -0
  26. package/lib/interact-for-access-code.d.ts +3 -0
  27. package/lib/interact-for-access-code.js +19 -0
  28. package/lib/interact-for-access-code.js.map +1 -0
  29. package/lib/interact-for-acs-entrance.d.ts +1 -0
  30. package/lib/interact-for-acs-entrance.js +15 -0
  31. package/lib/interact-for-acs-entrance.js.map +1 -0
  32. package/lib/interact-for-acs-system.d.ts +1 -0
  33. package/lib/interact-for-acs-system.js +16 -0
  34. package/lib/interact-for-acs-system.js.map +1 -0
  35. package/lib/interact-for-acs-user.d.ts +1 -0
  36. package/lib/interact-for-acs-user.js +17 -0
  37. package/lib/interact-for-acs-user.js.map +1 -0
  38. package/lib/interact-for-action-attempt-poll.d.ts +2 -0
  39. package/lib/interact-for-action-attempt-poll.js +22 -0
  40. package/lib/interact-for-action-attempt-poll.js.map +1 -0
  41. package/lib/interact-for-array.d.ts +1 -0
  42. package/lib/interact-for-array.js +53 -0
  43. package/lib/interact-for-array.js.map +1 -0
  44. package/lib/interact-for-blueprint-object.d.ts +9 -0
  45. package/lib/interact-for-blueprint-object.js +235 -0
  46. package/lib/interact-for-blueprint-object.js.map +1 -0
  47. package/lib/interact-for-command-params.d.ts +5 -0
  48. package/lib/interact-for-command-params.js +11 -0
  49. package/lib/interact-for-command-params.js.map +1 -0
  50. package/lib/interact-for-command-selection.d.ts +2 -0
  51. package/lib/interact-for-command-selection.js +76 -0
  52. package/lib/interact-for-command-selection.js.map +1 -0
  53. package/lib/interact-for-connected-account.d.ts +1 -0
  54. package/lib/interact-for-connected-account.js +20 -0
  55. package/lib/interact-for-connected-account.js.map +1 -0
  56. package/lib/interact-for-custom-metadata.d.ts +6 -0
  57. package/lib/interact-for-custom-metadata.js +71 -0
  58. package/lib/interact-for-custom-metadata.js.map +1 -0
  59. package/lib/interact-for-device.d.ts +1 -0
  60. package/lib/interact-for-device.js +15 -0
  61. package/lib/interact-for-device.js.map +1 -0
  62. package/lib/interact-for-login.d.ts +1 -0
  63. package/lib/interact-for-login.js +39 -0
  64. package/lib/interact-for-login.js.map +1 -0
  65. package/lib/interact-for-resource.d.ts +11 -0
  66. package/lib/interact-for-resource.js +13 -0
  67. package/lib/interact-for-resource.js.map +1 -0
  68. package/lib/interact-for-server-selection.d.ts +1 -0
  69. package/lib/interact-for-server-selection.js +41 -0
  70. package/lib/interact-for-server-selection.js.map +1 -0
  71. package/lib/interact-for-timestamp.d.ts +1 -0
  72. package/lib/interact-for-timestamp.js +10 -0
  73. package/lib/interact-for-timestamp.js.map +1 -0
  74. package/lib/interact-for-use-remote-api-defs.d.ts +1 -0
  75. package/lib/interact-for-use-remote-api-defs.js +25 -0
  76. package/lib/interact-for-use-remote-api-defs.js.map +1 -0
  77. package/lib/interact-for-user-identity.d.ts +1 -0
  78. package/lib/interact-for-user-identity.js +15 -0
  79. package/lib/interact-for-user-identity.js.map +1 -0
  80. package/lib/interact-for-workspace-id.d.ts +1 -0
  81. package/lib/interact-for-workspace-id.js +31 -0
  82. package/lib/interact-for-workspace-id.js.map +1 -0
  83. package/lib/types.d.ts +5 -0
  84. package/lib/types.js +2 -0
  85. package/lib/types.js.map +1 -0
  86. package/lib/util/ellipsis.d.ts +1 -0
  87. package/lib/util/ellipsis.js +6 -0
  88. package/lib/util/ellipsis.js.map +1 -0
  89. package/lib/util/request-seam-api.d.ts +4 -0
  90. package/lib/util/request-seam-api.js +28 -0
  91. package/lib/util/request-seam-api.js.map +1 -0
  92. package/lib/util/with-loading.d.ts +1 -0
  93. package/lib/util/with-loading.js +14 -0
  94. package/lib/util/with-loading.js.map +1 -0
  95. package/lib/validate-token.d.ts +1 -0
  96. package/lib/validate-token.js +19 -0
  97. package/lib/validate-token.js.map +1 -0
  98. package/lib/version.d.ts +2 -0
  99. package/lib/version.js +3 -0
  100. package/lib/version.js.map +1 -0
  101. package/package.json +44 -18
  102. package/src/bin/cli.ts +265 -0
  103. package/src/index.ts +4 -1
  104. package/src/lib/get-api-blueprint.ts +17 -0
  105. package/src/lib/get-command-blueprint-def.ts +15 -0
  106. package/src/lib/get-config-store.ts +5 -0
  107. package/src/lib/get-current-workspace-id.ts +11 -0
  108. package/src/lib/get-seam.ts +43 -0
  109. package/src/lib/get-server.ts +9 -0
  110. package/src/lib/interact-for-access-code.ts +25 -0
  111. package/src/lib/interact-for-acs-entrance.ts +16 -0
  112. package/src/lib/interact-for-acs-system.ts +17 -0
  113. package/src/lib/interact-for-acs-user.ts +21 -0
  114. package/src/lib/interact-for-action-attempt-poll.ts +35 -0
  115. package/src/lib/interact-for-array.ts +61 -0
  116. package/src/lib/interact-for-blueprint-object.ts +274 -0
  117. package/src/lib/interact-for-command-params.ts +22 -0
  118. package/src/lib/interact-for-command-selection.ts +107 -0
  119. package/src/lib/interact-for-connected-account.ts +24 -0
  120. package/src/lib/interact-for-custom-metadata.ts +86 -0
  121. package/src/lib/interact-for-device.ts +15 -0
  122. package/src/lib/interact-for-login.ts +53 -0
  123. package/src/lib/interact-for-resource.ts +34 -0
  124. package/src/lib/interact-for-server-selection.ts +46 -0
  125. package/src/lib/interact-for-timestamp.ts +10 -0
  126. package/src/lib/interact-for-use-remote-api-defs.ts +27 -0
  127. package/src/lib/interact-for-user-identity.ts +16 -0
  128. package/src/lib/interact-for-workspace-id.ts +37 -0
  129. package/src/lib/types.ts +6 -0
  130. package/src/lib/util/ellipsis.ts +4 -0
  131. package/src/lib/util/request-seam-api.ts +43 -0
  132. package/src/lib/util/with-loading.ts +16 -0
  133. package/src/lib/validate-token.ts +28 -0
  134. package/src/lib/version.ts +3 -0
  135. package/src/lib/index.ts +0 -1
  136. package/src/lib/todo.ts +0 -1
package/src/bin/cli.ts ADDED
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+ import { randomBytes } from 'node:crypto'
3
+ import { isDeepStrictEqual as isEqual } from 'node:util'
4
+
5
+ import chalk from 'chalk'
6
+ import commandLineUsage from 'command-line-usage'
7
+ import parseArgs, { type ParsedArgs } from 'minimist'
8
+ import prompts from 'prompts'
9
+
10
+ import { getApiBlueprint } from 'lib/get-api-blueprint.js'
11
+ import { getConfigStore } from 'lib/get-config-store.js'
12
+ import { getServer } from 'lib/get-server.js'
13
+ import { interactForActionAttemptPoll } from 'lib/interact-for-action-attempt-poll.js'
14
+ import { interactForCommandParams } from 'lib/interact-for-command-params.js'
15
+ import { interactForCommandSelection } from 'lib/interact-for-command-selection.js'
16
+ import { interactForLogin } from 'lib/interact-for-login.js'
17
+ import { interactForServerSelection } from 'lib/interact-for-server-selection.js'
18
+ import { interactForUseRemoteApiDefs } from 'lib/interact-for-use-remote-api-defs.js'
19
+ import { interactForWorkspaceId } from 'lib/interact-for-workspace-id.js'
20
+ import type { ContextHelpers } from 'lib/types.js'
21
+ import { RequestSeamApi } from 'lib/util/request-seam-api.js'
22
+ import { validateToken } from 'lib/validate-token.js'
23
+ import seamapiCliVersion from 'lib/version.js'
24
+
25
+ const sections = [
26
+ {
27
+ header: 'Seam CLI',
28
+ content:
29
+ 'Every seam command is interactive and will prompt you for any missing required properties with helpful suggestions. To avoid automatic behavior, pass -y ',
30
+ },
31
+ {
32
+ header: 'Options',
33
+ optionList: [
34
+ {
35
+ name: 'help',
36
+ description: 'Display this help guide.',
37
+ alias: 'h',
38
+ type: Boolean,
39
+ },
40
+ ],
41
+ },
42
+ {
43
+ header: 'Command List Examples',
44
+ content: [
45
+ { name: 'seam', summary: 'Interactively select commands to execute.' },
46
+ { name: 'seam login', summary: 'Login to Seam.' },
47
+ { name: 'seam select workspace', summary: 'Select your workspace.' },
48
+ {
49
+ name: 'seam connect-webviews create',
50
+ summary: 'Create a connect webview to connect devices.',
51
+ },
52
+ { name: 'seam devices list', summary: 'List devices in your workspace.' },
53
+ {
54
+ name: 'seam locks unlock-door {bold --device-id} $MY_DOOR',
55
+ summary: 'Unlock a lock.',
56
+ },
57
+ {
58
+ name: "seam access-codes create {bold --code} '1234' {bold --name} 'My Code'",
59
+ summary: 'Create an access code.',
60
+ },
61
+ {
62
+ name: 'seam access-codes list {bold --device-id} $MY_DOOR',
63
+ summary: 'List you access codes.',
64
+ },
65
+ ],
66
+ },
67
+ ]
68
+
69
+ async function cli(args: ParsedArgs) {
70
+ const config = getConfigStore()
71
+
72
+ if (args['help'] || args['h']) {
73
+ const usage = commandLineUsage(sections)
74
+ console.log(usage)
75
+ return
76
+ }
77
+
78
+ if (args['version']) {
79
+ console.log(seamapiCliVersion)
80
+ process.exit(0)
81
+ }
82
+
83
+ if (
84
+ args._[0] === 'config' &&
85
+ args._[1] === 'set' &&
86
+ args._[2] === 'fake-server'
87
+ ) {
88
+ const randomstring = randomBytes(5).toString('hex')
89
+ const fakeApiUrl = `https://${randomstring}.fakeseamconnect.seam.vc`
90
+
91
+ config.set('server', fakeApiUrl)
92
+ console.log(`Server URL set to ${fakeApiUrl}`)
93
+
94
+ config.set(`${getServer()}.pat`, `seam_apikey1_token`)
95
+ console.log(`PAT set to use fakeseamconnect with "seam_apikey1_token"`)
96
+ return
97
+ }
98
+
99
+ if (
100
+ !config.get(`${getServer()}.pat`) &&
101
+ args._[0] !== 'login' &&
102
+ !isEqual(args._, ['select', 'server'])
103
+ ) {
104
+ console.log(`Not logged in. Please run "seam login"`)
105
+ process.exit(1)
106
+ }
107
+
108
+ args._ = args._.map((arg) => arg.toLowerCase().replace(/_/g, '-'))
109
+ for (const k in args) {
110
+ args[k.toLowerCase().replace(/-/g, '_')] = args[k]
111
+ }
112
+
113
+ const use_remote_api_defs =
114
+ args['remote_api_defs'] ?? config.get('use_remote_api_defs')
115
+
116
+ const blueprint = await getApiBlueprint(use_remote_api_defs ?? false)
117
+
118
+ const commandParams: Record<string, any> = {}
119
+
120
+ /**
121
+ * Whether or not to auto-select first option
122
+ */
123
+ const is_interactive = args['y'] !== true
124
+
125
+ const ctx: ContextHelpers = {
126
+ blueprint,
127
+ is_interactive,
128
+ }
129
+
130
+ for (const k in args) {
131
+ if (k === '_') continue
132
+ const v = args[k]
133
+ delete args[k]
134
+ args[k.replace(/-/g, '_')] = v
135
+ commandParams[k.replace(/-/g, '_')] = v
136
+ }
137
+
138
+ const selectedCommand = await interactForCommandSelection(args._, ctx)
139
+ if (isEqual(selectedCommand, ['login'])) {
140
+ if (args['server']) {
141
+ config.set('server', args['server'])
142
+ config.delete('current_workspace_id')
143
+ }
144
+ if (args['token']) {
145
+ const token = String(args['token']).trim()
146
+ await validateToken(token, args['workspace_id'])
147
+ config.set(`${getServer()}.pat`, token)
148
+ config.delete('current_workspace_id')
149
+ }
150
+ if (args['workspace_id']) {
151
+ config.set(`current_workspace_id`, args['workspace_id'])
152
+ }
153
+ if (args['token'] || args['workspace_id'] || args['server']) {
154
+ return
155
+ }
156
+ await interactForLogin()
157
+ return
158
+ } else if (isEqual(selectedCommand, ['logout'])) {
159
+ config.delete('pat')
160
+ console.log('Logged out!')
161
+ return
162
+ } else if (isEqual(selectedCommand, ['config', 'reveal-location'])) {
163
+ console.log(config.path)
164
+ return
165
+ } else if (isEqual(selectedCommand, ['config', 'use-remote-api-defs'])) {
166
+ await interactForUseRemoteApiDefs()
167
+ return
168
+ } else if (isEqual(selectedCommand, ['select', 'workspace'])) {
169
+ await interactForWorkspaceId()
170
+ return
171
+ } else if (isEqual(selectedCommand, ['events', 'list'])) {
172
+ if (!commandParams['since']) {
173
+ const date = new Date()
174
+ date.setMonth(date.getMonth() - 1)
175
+ commandParams['since'] = date.toISOString()
176
+ }
177
+ } else if (isEqual(selectedCommand, ['select', 'server'])) {
178
+ if (args['server']) {
179
+ config.set('server', args['server'])
180
+ config.delete('current_workspace_id')
181
+ return
182
+ }
183
+ await interactForServerSelection()
184
+ return
185
+ } else if (isEqual(selectedCommand, ['health', 'get-health'])) {
186
+ await RequestSeamApi({
187
+ path: '/health/get_health',
188
+ params: {},
189
+ })
190
+
191
+ return
192
+ }
193
+ // TODO - do this using the OpenAPI spec for the command rather than
194
+ // explicitly encoding the property names
195
+ if (commandParams['accepted_providers']) {
196
+ commandParams['accepted_providers'] =
197
+ commandParams['accepted_providers'].split(',')
198
+ }
199
+
200
+ // Hit 'back' on a top-level command path, so we start again
201
+ const lastCommandPath = selectedCommand.slice(-1)[0]
202
+ if (lastCommandPath === '[Back]') {
203
+ return await cli({
204
+ ...args,
205
+ _: [],
206
+ })
207
+ }
208
+
209
+ const params = await interactForCommandParams(
210
+ { command: selectedCommand, params: commandParams },
211
+ ctx,
212
+ )
213
+
214
+ if (params === '[Back]') {
215
+ const previousCommands = [...selectedCommand]
216
+ previousCommands.pop()
217
+ return await cli({
218
+ ...args,
219
+ _: previousCommands,
220
+ })
221
+ }
222
+
223
+ const apiPath = `/${selectedCommand.join('/').replace(/-/g, '_')}`
224
+
225
+ if (apiPath.includes('/events/list') && params.between) {
226
+ delete params.since
227
+ }
228
+
229
+ const response = await RequestSeamApi({
230
+ path: apiPath,
231
+ params,
232
+ })
233
+
234
+ if (response.data?.connect_webview) {
235
+ await handleConnectWebviewResponse(response.data.connect_webview)
236
+ }
237
+
238
+ if (response.data?.action_attempt) {
239
+ interactForActionAttemptPoll(response.data.action_attempt)
240
+ }
241
+ }
242
+
243
+ const handleConnectWebviewResponse = async (connect_webview: any) => {
244
+ const url = connect_webview.url
245
+
246
+ if (process.env['INSIDE_WEB_BROWSER'] !== '1') {
247
+ const { action } = await prompts({
248
+ type: 'confirm',
249
+ name: 'action',
250
+ message: 'Would you like to open the webview in your browser?',
251
+ })
252
+
253
+ if (action) {
254
+ const { default: open } = await import('open')
255
+ await open(url)
256
+ }
257
+ }
258
+ }
259
+
260
+ cli(parseArgs(process.argv.slice(2), { string: ['code'] })).catch((e) => {
261
+ console.log(chalk.red(`CLI Error: ${e.toString()}\n${e.stack}`))
262
+ if (e.toString().includes('object Object')) {
263
+ console.log(e)
264
+ }
265
+ })
package/src/index.ts CHANGED
@@ -1 +1,4 @@
1
- export * from 'lib/index.js'
1
+ // This package is a CLI, not a library: nothing here is part of a public API.
2
+ // The entrypoint exists so that importing the package resolves to a module
3
+ // instead of failing, for consumers that depend on it to get the bin.
4
+ export default null
@@ -0,0 +1,17 @@
1
+ import { type Blueprint, createBlueprint } from '@seamapi/blueprint'
2
+ import { getOpenapiSchema } from '@seamapi/http/connect'
3
+ import * as seamTypes from '@seamapi/types/connect'
4
+
5
+ import { getServer } from './get-server.js'
6
+
7
+ export type ApiBlueprint = Blueprint
8
+
9
+ export const getApiBlueprint = async (
10
+ useRemoteDefinitions: boolean,
11
+ ): Promise<ApiBlueprint> => {
12
+ const typesModule = useRemoteDefinitions
13
+ ? { ...seamTypes, openapi: await getOpenapiSchema(getServer()) }
14
+ : seamTypes
15
+
16
+ return createBlueprint(typesModule, { omitUndocumented: true })
17
+ }
@@ -0,0 +1,15 @@
1
+ import type { ContextHelpers } from './types.js'
2
+ export const getCommandBlueprintDef = (
3
+ cmd: string[],
4
+ helpers: ContextHelpers,
5
+ ) => {
6
+ const path = `/${cmd.join('/').replace(/-/g, '_')}`
7
+ const def = helpers.blueprint.routes
8
+ .flatMap((route) => route.endpoints)
9
+ .find((endpoint) => endpoint.path === path)
10
+ if (!def) {
11
+ throw new Error(`No definition for path ${path}`)
12
+ }
13
+
14
+ return def
15
+ }
@@ -0,0 +1,5 @@
1
+ import Configstore from 'configstore'
2
+
3
+ export const getConfigStore = () => {
4
+ return new Configstore('seam-cli', {})
5
+ }
@@ -0,0 +1,11 @@
1
+ import { getConfigStore } from './get-config-store.js'
2
+ import { interactForWorkspaceId } from './interact-for-workspace-id.js'
3
+
4
+ export const getCurrentWorkspaceId = async (): Promise<string> => {
5
+ const configStore = getConfigStore()
6
+
7
+ const currentWorkspaceId = configStore.get('current_workspace_id')
8
+ if (typeof currentWorkspaceId === 'string') return currentWorkspaceId
9
+
10
+ return await interactForWorkspaceId()
11
+ }
@@ -0,0 +1,43 @@
1
+ import {
2
+ isConsoleSessionToken,
3
+ isPersonalAccessToken,
4
+ SeamHttp,
5
+ SeamHttpWithoutWorkspace,
6
+ } from '@seamapi/http/connect'
7
+
8
+ import { getConfigStore } from './get-config-store.js'
9
+ import { getServer } from './get-server.js'
10
+
11
+ export const getSeam = async (): Promise<SeamHttp> => {
12
+ const config = getConfigStore()
13
+
14
+ const token = config.get(`${getServer()}.pat`) as string
15
+
16
+ const workspaceId = config.get('current_workspace_id') as string
17
+
18
+ const options = { endpoint: getServer() }
19
+
20
+ if (isPersonalAccessToken(token)) {
21
+ return SeamHttp.fromPersonalAccessToken(token, workspaceId, options)
22
+ }
23
+
24
+ if (isConsoleSessionToken(token)) {
25
+ return SeamHttp.fromConsoleSessionToken(token, workspaceId, options)
26
+ }
27
+
28
+ return SeamHttp.fromApiKey(token, options)
29
+ }
30
+
31
+ export const getSeamMultiWorkspace = async (): Promise<
32
+ SeamHttpWithoutWorkspace | SeamHttp
33
+ > => {
34
+ const config = getConfigStore()
35
+ const token = config.get(`${getServer()}.pat`) as string
36
+ const options = { endpoint: getServer() }
37
+
38
+ if (isPersonalAccessToken(token)) {
39
+ return SeamHttpWithoutWorkspace.fromPersonalAccessToken(token, options)
40
+ }
41
+
42
+ return getSeam()
43
+ }
@@ -0,0 +1,9 @@
1
+ import { getConfigStore } from './get-config-store.js'
2
+
3
+ export const getServer = (): string => {
4
+ const config = getConfigStore()
5
+
6
+ const server = config.get('server')
7
+
8
+ return typeof server === 'string' ? server : 'https://connect.getseam.com'
9
+ }
@@ -0,0 +1,25 @@
1
+ import { getSeam } from './get-seam.js'
2
+ import { interactForDevice } from './interact-for-device.js'
3
+ import { interactForResource } from './interact-for-resource.js'
4
+
5
+ export const interactForAccessCode = async ({
6
+ device_id,
7
+ }: {
8
+ device_id?: string
9
+ }) => {
10
+ const seam = await getSeam()
11
+
12
+ if (!device_id) {
13
+ device_id = await interactForDevice()
14
+ }
15
+
16
+ return interactForResource({
17
+ resourceName: 'access_code',
18
+ fetchResources: () => seam.accessCodes.list({ device_id }),
19
+ toChoice: (accessCode) => ({
20
+ title: accessCode.name ?? '<No Name>',
21
+ value: accessCode.access_code_id,
22
+ description: `${accessCode.type} ${accessCode.access_code_id}`,
23
+ }),
24
+ })
25
+ }
@@ -0,0 +1,16 @@
1
+ import { getSeam } from './get-seam.js'
2
+ import { interactForResource } from './interact-for-resource.js'
3
+
4
+ export const interactForAcsEntrance = async () => {
5
+ const seam = await getSeam()
6
+
7
+ return interactForResource({
8
+ resourceName: 'ACS entrance',
9
+ fetchResources: () => seam.acs.entrances.list(),
10
+ toChoice: (entrance) => ({
11
+ title: entrance.display_name ?? '<No Name>',
12
+ value: entrance.acs_entrance_id,
13
+ description: entrance.acs_entrance_id,
14
+ }),
15
+ })
16
+ }
@@ -0,0 +1,17 @@
1
+ import { getSeam } from './get-seam.js'
2
+ import { interactForResource } from './interact-for-resource.js'
3
+
4
+ export const interactForAcsSystem = async (message?: string) => {
5
+ const seam = await getSeam()
6
+
7
+ return interactForResource({
8
+ resourceName: 'ACS system',
9
+ fetchResources: () => seam.acs.systems.list(),
10
+ message,
11
+ toChoice: (system) => ({
12
+ title: `${system.name} ${system.external_type_display_name}`,
13
+ value: system.acs_system_id,
14
+ description: system.acs_system_id,
15
+ }),
16
+ })
17
+ }
@@ -0,0 +1,21 @@
1
+ import { getSeam } from './get-seam.js'
2
+ import { interactForAcsSystem } from './interact-for-acs-system.js'
3
+ import { interactForResource } from './interact-for-resource.js'
4
+
5
+ export const interactForAcsUser = async () => {
6
+ const seam = await getSeam()
7
+
8
+ const acs_system_id = await interactForAcsSystem(
9
+ 'What acs_system does the acs_user belong to?',
10
+ )
11
+
12
+ return interactForResource({
13
+ resourceName: 'ACS user',
14
+ fetchResources: () => seam.acs.users.list({ acs_system_id }),
15
+ toChoice: (user) => ({
16
+ title: `${user.display_name} ${user.email_address}`,
17
+ value: user.acs_user_id,
18
+ description: user.acs_user_id,
19
+ }),
20
+ })
21
+ }
@@ -0,0 +1,35 @@
1
+ import type { ActionAttemptsGetResponse } from '@seamapi/http/connect'
2
+ import prompts from 'prompts'
3
+
4
+ import { getSeam } from './get-seam.js'
5
+ import { withLoading } from './util/with-loading.js'
6
+
7
+ export const interactForActionAttemptPoll = async (
8
+ action_attempt: ActionAttemptsGetResponse['action_attempt'],
9
+ ) => {
10
+ if (action_attempt.status === 'pending') {
11
+ const { poll_for_action_attempt } = await prompts({
12
+ name: 'poll_for_action_attempt',
13
+ message: "Would you like to poll the action attempt until it's ready?",
14
+ type: 'toggle',
15
+ initial: true,
16
+ active: 'yes',
17
+ inactive: 'no',
18
+ })
19
+
20
+ if (poll_for_action_attempt) {
21
+ const seam = await getSeam()
22
+ const { action_attempt_id } = action_attempt
23
+
24
+ const updated_action_attempt = await withLoading(
25
+ 'Polling action attempt...',
26
+ () =>
27
+ seam.actionAttempts.get(
28
+ { action_attempt_id },
29
+ { waitForActionAttempt: { pollingInterval: 240, timeout: 10_000 } },
30
+ ),
31
+ )
32
+ console.dir(updated_action_attempt, { depth: null })
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,61 @@
1
+ import prompts from 'prompts'
2
+
3
+ export const interactForArray = async (
4
+ array: string[],
5
+ message: string,
6
+ ): Promise<string[]> => {
7
+ const updatedArray = [...array]
8
+
9
+ const displayList = () => {
10
+ console.log(`${message} Current list:`)
11
+ if (updatedArray.length > 0) {
12
+ updatedArray.forEach((item, index) => {
13
+ console.log(`${index + 1}: ${item}`)
14
+ })
15
+ } else {
16
+ console.log('The list is currently empty.')
17
+ }
18
+ }
19
+
20
+ let action: string
21
+ do {
22
+ displayList()
23
+
24
+ const response = await prompts({
25
+ type: 'select',
26
+ name: 'action',
27
+ message: 'Choose an action:',
28
+ choices: [
29
+ { title: 'Add an item', value: 'add' },
30
+ { title: 'Remove an item', value: 'remove' },
31
+ { title: 'Finish editing', value: 'done' },
32
+ ],
33
+ })
34
+
35
+ action = response.action
36
+
37
+ if (action === 'add') {
38
+ const { newItem } = await prompts({
39
+ type: 'text',
40
+ name: 'newItem',
41
+ message: 'Enter the new item:',
42
+ })
43
+ if (newItem) {
44
+ updatedArray.push(newItem)
45
+ }
46
+ } else if (action === 'remove') {
47
+ const { index } = await prompts({
48
+ type: 'number',
49
+ name: 'index',
50
+ message: 'Enter the index of the item to remove:',
51
+ validate: (value) =>
52
+ value > 0 && value <= updatedArray.length ? true : 'Invalid index',
53
+ })
54
+ if (index) {
55
+ updatedArray.splice(index - 1, 1)
56
+ }
57
+ }
58
+ } while (action !== 'done')
59
+
60
+ return updatedArray
61
+ }