yoto-nodejs-client 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +736 -0
  3. package/bin/auth.d.ts +3 -0
  4. package/bin/auth.d.ts.map +1 -0
  5. package/bin/auth.js +130 -0
  6. package/bin/content.d.ts +3 -0
  7. package/bin/content.d.ts.map +1 -0
  8. package/bin/content.js +117 -0
  9. package/bin/devices.d.ts +3 -0
  10. package/bin/devices.d.ts.map +1 -0
  11. package/bin/devices.js +239 -0
  12. package/bin/groups.d.ts +3 -0
  13. package/bin/groups.d.ts.map +1 -0
  14. package/bin/groups.js +80 -0
  15. package/bin/icons.d.ts +3 -0
  16. package/bin/icons.d.ts.map +1 -0
  17. package/bin/icons.js +100 -0
  18. package/bin/lib/cli-helpers.d.ts +21 -0
  19. package/bin/lib/cli-helpers.d.ts.map +1 -0
  20. package/bin/lib/cli-helpers.js +140 -0
  21. package/bin/lib/token-helpers.d.ts +14 -0
  22. package/bin/lib/token-helpers.d.ts.map +1 -0
  23. package/bin/lib/token-helpers.js +151 -0
  24. package/bin/refresh-token.d.ts +3 -0
  25. package/bin/refresh-token.d.ts.map +1 -0
  26. package/bin/refresh-token.js +168 -0
  27. package/bin/token-info.d.ts +3 -0
  28. package/bin/token-info.d.ts.map +1 -0
  29. package/bin/token-info.js +351 -0
  30. package/index.d.ts +218 -0
  31. package/index.d.ts.map +1 -0
  32. package/index.js +689 -0
  33. package/lib/api-endpoints/auth.d.ts +56 -0
  34. package/lib/api-endpoints/auth.d.ts.map +1 -0
  35. package/lib/api-endpoints/auth.js +209 -0
  36. package/lib/api-endpoints/auth.test.js +27 -0
  37. package/lib/api-endpoints/constants.d.ts +6 -0
  38. package/lib/api-endpoints/constants.d.ts.map +1 -0
  39. package/lib/api-endpoints/constants.js +31 -0
  40. package/lib/api-endpoints/content.d.ts +275 -0
  41. package/lib/api-endpoints/content.d.ts.map +1 -0
  42. package/lib/api-endpoints/content.js +518 -0
  43. package/lib/api-endpoints/content.test.js +250 -0
  44. package/lib/api-endpoints/devices.d.ts +202 -0
  45. package/lib/api-endpoints/devices.d.ts.map +1 -0
  46. package/lib/api-endpoints/devices.js +404 -0
  47. package/lib/api-endpoints/devices.test.js +483 -0
  48. package/lib/api-endpoints/family-library-groups.d.ts +75 -0
  49. package/lib/api-endpoints/family-library-groups.d.ts.map +1 -0
  50. package/lib/api-endpoints/family-library-groups.js +247 -0
  51. package/lib/api-endpoints/family-library-groups.test.js +272 -0
  52. package/lib/api-endpoints/family.d.ts +39 -0
  53. package/lib/api-endpoints/family.d.ts.map +1 -0
  54. package/lib/api-endpoints/family.js +166 -0
  55. package/lib/api-endpoints/family.test.js +184 -0
  56. package/lib/api-endpoints/helpers.d.ts +29 -0
  57. package/lib/api-endpoints/helpers.d.ts.map +1 -0
  58. package/lib/api-endpoints/helpers.js +104 -0
  59. package/lib/api-endpoints/icons.d.ts +62 -0
  60. package/lib/api-endpoints/icons.d.ts.map +1 -0
  61. package/lib/api-endpoints/icons.js +201 -0
  62. package/lib/api-endpoints/icons.test.js +118 -0
  63. package/lib/api-endpoints/media.d.ts +37 -0
  64. package/lib/api-endpoints/media.d.ts.map +1 -0
  65. package/lib/api-endpoints/media.js +155 -0
  66. package/lib/api-endpoints/test-helpers.d.ts +7 -0
  67. package/lib/api-endpoints/test-helpers.d.ts.map +1 -0
  68. package/lib/api-endpoints/test-helpers.js +64 -0
  69. package/lib/mqtt/client.d.ts +124 -0
  70. package/lib/mqtt/client.d.ts.map +1 -0
  71. package/lib/mqtt/client.js +558 -0
  72. package/lib/mqtt/commands.d.ts +69 -0
  73. package/lib/mqtt/commands.d.ts.map +1 -0
  74. package/lib/mqtt/commands.js +238 -0
  75. package/lib/mqtt/factory.d.ts +12 -0
  76. package/lib/mqtt/factory.d.ts.map +1 -0
  77. package/lib/mqtt/factory.js +107 -0
  78. package/lib/mqtt/index.d.ts +5 -0
  79. package/lib/mqtt/index.d.ts.map +1 -0
  80. package/lib/mqtt/index.js +81 -0
  81. package/lib/mqtt/mqtt.test.js +168 -0
  82. package/lib/mqtt/topics.d.ts +34 -0
  83. package/lib/mqtt/topics.d.ts.map +1 -0
  84. package/lib/mqtt/topics.js +295 -0
  85. package/lib/pkg.cjs +3 -0
  86. package/lib/pkg.d.cts +70 -0
  87. package/lib/pkg.d.cts.map +1 -0
  88. package/lib/token.d.ts +29 -0
  89. package/lib/token.d.ts.map +1 -0
  90. package/lib/token.js +240 -0
  91. package/package.json +91 -0
  92. package/yoto.png +0 -0
package/bin/auth.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["auth.js"],"names":[],"mappings":""}
package/bin/auth.js ADDED
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @import {ArgscloptsParseArgsOptionsConfig} from 'argsclopts'
5
+ */
6
+
7
+ import { printHelpText } from 'argsclopts'
8
+ import { parseArgs } from 'node:util'
9
+ import { YotoClient } from '../index.js'
10
+ import { pkg } from '../lib/pkg.cjs'
11
+ import { DEFAULT_CLIENT_ID } from '../lib/api-endpoints/constants.js'
12
+ import { sleep, saveTokensToEnv } from './lib/token-helpers.js'
13
+ import {
14
+ getCommonOptions,
15
+ handleCliError,
16
+ printHeader
17
+ } from './lib/cli-helpers.js'
18
+
19
+ /** @type {ArgscloptsParseArgsOptionsConfig} */
20
+ const options = {
21
+ ...getCommonOptions(),
22
+ output: {
23
+ type: 'string',
24
+ short: 'o',
25
+ default: '.env',
26
+ help: 'Output file for tokens (default: .env)'
27
+ }
28
+ }
29
+
30
+ const args = parseArgs({ options, strict: false })
31
+
32
+ if (args.values['help']) {
33
+ await printHelpText({
34
+ options,
35
+ name: 'yoto-auth',
36
+ version: pkg.version,
37
+ exampleFn: ({ name }) => ` Yoto device flow authentication helper\n\n Example: ${name} --client-id your-client-id\n`
38
+ })
39
+ process.exit(0)
40
+ }
41
+
42
+ const clientId = String(args.values['client-id'] || process.env['YOTO_CLIENT_ID'] || DEFAULT_CLIENT_ID)
43
+
44
+ const outputFile = String(args.values['output'] || '.env')
45
+
46
+ async function main () {
47
+ printHeader('Yoto Device Flow Authentication')
48
+ console.log()
49
+
50
+ // Step 1: Request device code
51
+ console.log('Requesting device code...\n')
52
+
53
+ const deviceAuth = await YotoClient.requestDeviceCode({ clientId })
54
+
55
+ // Step 2: Display instructions to user
56
+ console.log('┌────────────────────────────────────────────────────┐')
57
+ console.log('│ Please complete authentication in your browser: │')
58
+ console.log('└────────────────────────────────────────────────────┘\n')
59
+
60
+ if (deviceAuth.verification_uri_complete) {
61
+ console.log(' Visit this URL (code included):')
62
+ console.log(` ${deviceAuth.verification_uri_complete}\n`)
63
+ console.log(' Or manually enter:')
64
+ console.log(` URL: ${deviceAuth.verification_uri}`)
65
+ console.log(` Code: ${deviceAuth.user_code}\n`)
66
+ } else {
67
+ console.log(` URL: ${deviceAuth.verification_uri}`)
68
+ console.log(` Code: ${deviceAuth.user_code}\n`)
69
+ }
70
+
71
+ const expiresInMinutes = Math.round(deviceAuth.expires_in / 60)
72
+ console.log(` Code expires in: ${expiresInMinutes} minute(s)`)
73
+ console.log(` Polling every: ${deviceAuth.interval} second(s)\n`)
74
+
75
+ // Step 3: Poll for authorization
76
+ console.log('Waiting for authorization...')
77
+
78
+ let pollInterval = deviceAuth.interval * 1000
79
+ const startTime = Date.now()
80
+ const expiresAt = startTime + (deviceAuth.expires_in * 1000)
81
+
82
+ while (true) {
83
+ // Check if we've exceeded the expiration time
84
+ if (Date.now() >= expiresAt) {
85
+ console.error('\n❌ Device code has expired. Please run the command again.')
86
+ process.exit(1)
87
+ }
88
+
89
+ try {
90
+ const tokens = await YotoClient.exchangeToken({
91
+ grantType: 'urn:ietf:params:oauth:grant-type:device_code',
92
+ deviceCode: deviceAuth.device_code,
93
+ clientId
94
+ })
95
+
96
+ // Success! Save tokens to .env file
97
+ console.log('\n✅ Authorization successful!\n')
98
+
99
+ await saveTokensToEnv(outputFile, tokens, clientId)
100
+
101
+ console.log(`✨ Tokens saved to ${outputFile}`)
102
+ console.log('\nYou can now use these environment variables:')
103
+ console.log(' - YOTO_ACCESS_TOKEN')
104
+ console.log(' - YOTO_REFRESH_TOKEN')
105
+ console.log(' - YOTO_CLIENT_ID')
106
+
107
+ process.exit(0)
108
+ } catch (err) {
109
+ const error = /** @type {any} */ (err)
110
+ if (error.body?.error === 'authorization_pending') {
111
+ process.stdout.write('.')
112
+ await sleep(pollInterval)
113
+ continue
114
+ } else if (error.body?.error === 'slow_down') {
115
+ pollInterval += 5000
116
+ console.log(`\n⚠️ Slowing down polling to ${pollInterval / 1000}s...`)
117
+ await sleep(pollInterval)
118
+ continue
119
+ } else if (error.body?.error === 'expired_token') {
120
+ console.error('\n❌ Device code has expired. Please run the command again.')
121
+ process.exit(1)
122
+ } else {
123
+ handleCliError(error)
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ // Run the main function
130
+ await main().catch(handleCliError)
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["content.js"],"names":[],"mappings":""}
package/bin/content.js ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @import { ArgscloptsParseArgsOptionsConfig } from 'argsclopts'
5
+ */
6
+
7
+ import { printHelpText } from 'argsclopts'
8
+ import { parseArgs } from 'node:util'
9
+ import { pkg } from '../lib/pkg.cjs'
10
+ import {
11
+ getCommonOptions,
12
+ loadTokensFromEnv,
13
+ createYotoClient,
14
+ handleCliError,
15
+ printHeader
16
+ } from './lib/cli-helpers.js'
17
+
18
+ /** @type {ArgscloptsParseArgsOptionsConfig} */
19
+ const options = {
20
+ ...getCommonOptions(),
21
+ 'card-id': {
22
+ type: 'string',
23
+ short: 'd',
24
+ help: 'Specific card/content ID to fetch'
25
+ },
26
+ 'show-deleted': {
27
+ type: 'boolean',
28
+ help: 'Include deleted content (for MYO content list)'
29
+ },
30
+ timezone: {
31
+ type: 'string',
32
+ short: 't',
33
+ help: 'Timezone identifier (e.g., "Pacific/Auckland")'
34
+ },
35
+ 'signing-type': {
36
+ type: 'string',
37
+ short: 's',
38
+ help: 'Type of URL signing: "full" or "pre"'
39
+ },
40
+ playable: {
41
+ type: 'boolean',
42
+ short: 'p',
43
+ help: 'Return playable signed URLs'
44
+ }
45
+ }
46
+
47
+ const args = parseArgs({ options, strict: false })
48
+
49
+ if (args.values['help']) {
50
+ await printHelpText({
51
+ options,
52
+ name: 'yoto-content',
53
+ version: pkg.version,
54
+ exampleFn: ({ name }) => ` Yoto content information helper\n\n Examples:\n ${name} # List all MYO content\n ${name} --show-deleted # List all MYO content including deleted\n ${name} --card-id abc123 # Get specific content\n ${name} --card-id abc123 --playable # Get content with playable URLs\n ${name} --card-id abc123 --timezone "Pacific/Auckland" --signing-type full --playable\n`
55
+ })
56
+ process.exit(0)
57
+ }
58
+
59
+ // Load tokens from environment
60
+ const { clientId, refreshToken, accessToken, envFile } = loadTokensFromEnv(args)
61
+
62
+ const cardId = args.values['card-id'] ? String(args.values['card-id']) : null
63
+ const showDeleted = Boolean(args.values['show-deleted'])
64
+ const timezone = args.values['timezone'] ? String(args.values['timezone']) : undefined
65
+ const signingType = args.values['signing-type'] ? /** @type {'full' | 'pre'} */ (String(args.values['signing-type'])) : undefined
66
+ const playable = Boolean(args.values['playable'])
67
+
68
+ // Validate signing-type if provided
69
+ if (signingType && signingType !== 'full' && signingType !== 'pre') {
70
+ console.error('❌ Invalid --signing-type value')
71
+ console.error('Valid values: "full" or "pre"')
72
+ process.exit(1)
73
+ }
74
+
75
+ async function main () {
76
+ printHeader('Yoto Content')
77
+
78
+ try {
79
+ // Create client
80
+ const client = createYotoClient({
81
+ clientId,
82
+ refreshToken,
83
+ accessToken,
84
+ outputFile: envFile
85
+ })
86
+
87
+ if (cardId) {
88
+ // Get specific content
89
+ console.log(`\nFetching content: ${cardId}\n`)
90
+ const content = await client.getContent({
91
+ cardId,
92
+ ...(timezone && { timezone }),
93
+ ...(signingType && { signingType }),
94
+ ...(playable && { playable })
95
+ })
96
+ console.dir(content, { depth: null, colors: true })
97
+ } else {
98
+ // Get all MYO content
99
+ console.log(`\nFetching all MYO content${showDeleted ? ' (including deleted)' : ''}...\n`)
100
+ const myoContent = await client.getUserMyoContent({
101
+ ...(showDeleted && { showDeleted })
102
+ })
103
+ console.dir(myoContent, { depth: null, colors: true })
104
+ }
105
+
106
+ process.exit(0)
107
+ } catch (error) {
108
+ handleCliError(error)
109
+ }
110
+ }
111
+
112
+ // Run the main function
113
+ await main().catch(err => {
114
+ console.error('\n❌ Unexpected error:')
115
+ console.error(err)
116
+ process.exit(1)
117
+ })
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=devices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devices.d.ts","sourceRoot":"","sources":["devices.js"],"names":[],"mappings":""}
package/bin/devices.js ADDED
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @import { ArgscloptsParseArgsOptionsConfig } from 'argsclopts'
5
+ */
6
+
7
+ import { printHelpText } from 'argsclopts'
8
+ import { parseArgs } from 'node:util'
9
+ import { pkg } from '../lib/pkg.cjs'
10
+ import {
11
+ getCommonOptions,
12
+ loadTokensFromEnv,
13
+ createYotoClient,
14
+ handleCliError,
15
+ printHeader
16
+ } from './lib/cli-helpers.js'
17
+ import { createYotoMqttClient } from '../lib/mqtt/index.js'
18
+
19
+ /** @type {ArgscloptsParseArgsOptionsConfig} */
20
+ const options = {
21
+ ...getCommonOptions(),
22
+ 'device-id': {
23
+ type: 'string',
24
+ short: 'd',
25
+ help: 'Specific device ID to fetch'
26
+ },
27
+ status: {
28
+ type: 'boolean',
29
+ short: 's',
30
+ help: 'Get device status (requires --device-id)'
31
+ },
32
+ mqtt: {
33
+ type: 'boolean',
34
+ short: 'm',
35
+ help: 'Connect to MQTT and listen for device messages (requires --device-id)'
36
+ },
37
+ 'mqtt-stop-after-seconds': {
38
+ type: 'string',
39
+ help: 'Auto-disconnect MQTT after N seconds (requires --mqtt)'
40
+ }
41
+ }
42
+
43
+ const args = parseArgs({ options, strict: false })
44
+
45
+ if (args.values['help']) {
46
+ await printHelpText({
47
+ options,
48
+ name: 'yoto-devices',
49
+ version: pkg.version,
50
+ exampleFn: ({ name }) => ` Yoto devices information helper\n\n Examples:\n ${name} # List all devices\n ${name} --device-id abc123 # Get specific device\n ${name} --device-id abc123 --status # Get device status\n ${name} --device-id abc123 --mqtt # Listen to MQTT messages\n ${name} --device-id abc123 --mqtt --mqtt-stop-after-seconds 10 # Sample MQTT for 10s\n`
51
+ })
52
+ process.exit(0)
53
+ }
54
+
55
+ // Load tokens from environment
56
+ const { clientId, refreshToken, accessToken, envFile } = loadTokensFromEnv(args)
57
+
58
+ const deviceId = args.values['device-id'] ? String(args.values['device-id']) : null
59
+ const getStatus = Boolean(args.values['status'])
60
+ const useMqtt = Boolean(args.values['mqtt'])
61
+ const mqttStopAfter = args.values['mqtt-stop-after-seconds'] ? Number(args.values['mqtt-stop-after-seconds']) : null
62
+
63
+ if (getStatus && !deviceId) {
64
+ console.error('❌ --status flag requires --device-id')
65
+ console.error('Usage: yoto-devices --device-id abc123 --status')
66
+ process.exit(1)
67
+ }
68
+
69
+ if (useMqtt && !deviceId) {
70
+ console.error('❌ --mqtt flag requires --device-id')
71
+ console.error('Usage: yoto-devices --device-id abc123 --mqtt')
72
+ process.exit(1)
73
+ }
74
+
75
+ if (mqttStopAfter && !useMqtt) {
76
+ console.error('❌ --mqtt-stop-after-seconds requires --mqtt')
77
+ console.error('Usage: yoto-devices --device-id abc123 --mqtt --mqtt-stop-after-seconds 10')
78
+ process.exit(1)
79
+ }
80
+
81
+ async function main () {
82
+ printHeader('Yoto Devices')
83
+
84
+ try {
85
+ // Create client
86
+ const client = createYotoClient({
87
+ clientId,
88
+ refreshToken,
89
+ accessToken,
90
+ outputFile: envFile
91
+ })
92
+
93
+ if (deviceId) {
94
+ if (getStatus) {
95
+ // Get device status
96
+ console.log(`\nFetching status for device: ${deviceId}\n`)
97
+ const status = await client.getDeviceStatus({ deviceId })
98
+ console.dir(status, { depth: null, colors: true })
99
+ } else {
100
+ // Get specific device from list
101
+ console.log(`\nFetching device: ${deviceId}\n`)
102
+ const devicesResponse = await client.getDevices()
103
+ const device = devicesResponse.devices.find(d => d.deviceId === deviceId)
104
+
105
+ if (device) {
106
+ console.log('Device:')
107
+ console.dir(device, { depth: null, colors: true })
108
+
109
+ // Get device config
110
+ console.log('\nDevice Config:')
111
+ const config = await client.getDeviceConfig({ deviceId })
112
+ console.dir(config, { depth: null, colors: true })
113
+
114
+ // Get device shortcuts
115
+ console.log('\nDevice Shortcuts:')
116
+ const shortcuts = await client.getDeviceConfig({ deviceId })
117
+ if (shortcuts.device?.shortcuts) {
118
+ console.dir(shortcuts.device.shortcuts, { depth: null, colors: true })
119
+ } else {
120
+ console.log('No shortcuts available')
121
+ }
122
+ } else {
123
+ console.error(`❌ Device with ID '${deviceId}' not found`)
124
+ process.exit(1)
125
+ }
126
+ }
127
+ } else {
128
+ // Get all devices
129
+ console.log('\nFetching all devices...\n')
130
+ const devicesResponse = await client.getDevices()
131
+ console.dir(devicesResponse, { depth: null, colors: true })
132
+ }
133
+
134
+ // MQTT listening mode
135
+ if (useMqtt && deviceId) {
136
+ console.log('\n' + '='.repeat(60))
137
+ console.log('🔌 Connecting to MQTT...')
138
+ console.log('='.repeat(60))
139
+
140
+ const mqttClient = createYotoMqttClient({
141
+ deviceId,
142
+ accessToken
143
+ })
144
+
145
+ // Setup message handlers
146
+ mqttClient.on('connected', () => {
147
+ console.log('✅ Connected to MQTT broker')
148
+ console.log(`📡 Subscribed to device/${deviceId}/*`)
149
+ console.log('\n👂 Listening for messages... (Ctrl+C to stop)\n')
150
+
151
+ if (mqttStopAfter) {
152
+ console.log(`⏱️ Will auto-disconnect after ${mqttStopAfter} seconds\n`)
153
+ }
154
+
155
+ // Request initial status and events after a delay to ensure subscriptions are ready
156
+ setTimeout(() => {
157
+ mqttClient.requestStatus().catch(err => {
158
+ console.error('⚠️ Failed to request status:', err.message)
159
+ })
160
+ mqttClient.requestEvents().catch(err => {
161
+ console.error('⚠️ Failed to request events:', err.message)
162
+ })
163
+ }, 1000)
164
+ })
165
+
166
+ mqttClient.on('events', (message) => {
167
+ const timestamp = new Date().toISOString()
168
+ console.log(`\n📨 EVENTS MESSAGE [${timestamp}]:`)
169
+ console.log('─'.repeat(60))
170
+ console.dir(message, { depth: null, colors: true })
171
+ })
172
+
173
+ mqttClient.on('status', (message) => {
174
+ const timestamp = new Date().toISOString()
175
+ console.log(`\n📊 STATUS MESSAGE [${timestamp}]:`)
176
+ console.log('─'.repeat(60))
177
+ console.dir(message, { depth: null, colors: true })
178
+ })
179
+
180
+ mqttClient.on('response', (message) => {
181
+ const timestamp = new Date().toISOString()
182
+ console.log(`\n✉️ RESPONSE MESSAGE [${timestamp}]:`)
183
+ console.log('─'.repeat(60))
184
+ console.dir(message, { depth: null, colors: true })
185
+ })
186
+
187
+ mqttClient.on('disconnected', () => {
188
+ console.log('\n❌ Disconnected from MQTT broker')
189
+ })
190
+
191
+ mqttClient.on('reconnecting', () => {
192
+ console.log('\n🔄 Reconnecting to MQTT broker...')
193
+ })
194
+
195
+ mqttClient.on('error', (error) => {
196
+ console.error('\n⚠️ MQTT Error:', error.message)
197
+ })
198
+
199
+ // Handle graceful shutdown
200
+ const cleanup = async () => {
201
+ console.log('\n\n🛑 Shutting down...')
202
+ try {
203
+ await mqttClient.disconnect()
204
+ console.log('✅ Disconnected cleanly')
205
+ process.exit(0)
206
+ } catch (err) {
207
+ console.error('❌ Error during disconnect:', err)
208
+ process.exit(1)
209
+ }
210
+ }
211
+
212
+ process.on('SIGINT', cleanup)
213
+ process.on('SIGTERM', cleanup)
214
+
215
+ // Connect to MQTT
216
+ await mqttClient.connect()
217
+
218
+ // Auto-disconnect timer if specified
219
+ if (mqttStopAfter) {
220
+ setTimeout(async () => {
221
+ console.log(`\n⏰ ${mqttStopAfter} seconds elapsed, disconnecting...`)
222
+ await cleanup()
223
+ }, mqttStopAfter * 1000)
224
+ }
225
+
226
+ // Keep process alive (unless auto-disconnect is set)
227
+ if (!mqttStopAfter) {
228
+ await new Promise(() => {}) // Keep alive indefinitely
229
+ }
230
+ } else {
231
+ process.exit(0)
232
+ }
233
+ } catch (error) {
234
+ handleCliError(error)
235
+ }
236
+ }
237
+
238
+ // Run the main function
239
+ await main().catch(handleCliError)
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=groups.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["groups.js"],"names":[],"mappings":""}
package/bin/groups.js ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @import { ArgscloptsParseArgsOptionsConfig } from 'argsclopts'
5
+ */
6
+
7
+ import { printHelpText } from 'argsclopts'
8
+ import { parseArgs } from 'node:util'
9
+ import { pkg } from '../lib/pkg.cjs'
10
+ import {
11
+ getCommonOptions,
12
+ loadTokensFromEnv,
13
+ createYotoClient,
14
+ handleCliError,
15
+ printHeader
16
+ } from './lib/cli-helpers.js'
17
+
18
+ /** @type {ArgscloptsParseArgsOptionsConfig} */
19
+ const options = {
20
+ ...getCommonOptions(),
21
+ 'group-id': {
22
+ type: 'string',
23
+ short: 'g',
24
+ help: 'Specific group ID to fetch'
25
+ }
26
+ }
27
+
28
+ const args = parseArgs({ options, strict: false })
29
+
30
+ if (args.values['help']) {
31
+ await printHelpText({
32
+ options,
33
+ name: 'yoto-groups',
34
+ version: pkg.version,
35
+ exampleFn: ({ name }) => ` Yoto family library groups information helper\n\n Examples:\n ${name} # List all family library groups\n ${name} --group-id abc123 # Get specific group\n`
36
+ })
37
+ process.exit(0)
38
+ }
39
+
40
+ // Load tokens from environment
41
+ const { clientId, refreshToken, accessToken, envFile } = loadTokensFromEnv(args)
42
+
43
+ const groupId = args.values['group-id'] ? String(args.values['group-id']) : null
44
+
45
+ async function main () {
46
+ printHeader('Yoto Family Library Groups')
47
+
48
+ try {
49
+ // Create client
50
+ const client = createYotoClient({
51
+ clientId,
52
+ refreshToken,
53
+ accessToken,
54
+ outputFile: envFile
55
+ })
56
+
57
+ if (groupId) {
58
+ // Get specific group
59
+ console.log(`\nFetching group: ${groupId}\n`)
60
+ const group = await client.getGroup({ groupId })
61
+ console.dir(group, { depth: null, colors: true })
62
+ } else {
63
+ // Get all groups
64
+ console.log('\nFetching all family library groups...\n')
65
+ const groups = await client.getGroups()
66
+ console.dir(groups, { depth: null, colors: true })
67
+ }
68
+
69
+ process.exit(0)
70
+ } catch (error) {
71
+ handleCliError(error)
72
+ }
73
+ }
74
+
75
+ // Run the main function
76
+ await main().catch(err => {
77
+ console.error('\n❌ Unexpected error:')
78
+ console.error(err)
79
+ process.exit(1)
80
+ })
package/bin/icons.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=icons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["icons.js"],"names":[],"mappings":""}