@geastack/cli 0.1.52 → 0.1.54

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 (44) hide show
  1. package/README.md +14 -1
  2. package/docs/ESP32-WAVESHARE-AMOLED-QUICKSTART.md +2 -2
  3. package/docs/NPX-COMMANDS.md +3 -1
  4. package/docs/SETUP.md +81 -15
  5. package/docs/SPEC.md +8 -5
  6. package/package.json +2 -3
  7. package/src/apps/app-index-writer.mjs +15 -0
  8. package/src/apps/apple-icons.mjs +147 -0
  9. package/src/apps/bundle-writer.mjs +156 -0
  10. package/src/apps/launcher-catalog.mjs +114 -0
  11. package/src/apps/openai-icons.mjs +356 -0
  12. package/src/apps/zip-writer.mjs +104 -0
  13. package/src/ble/ble-ota.swift +291 -0
  14. package/src/boards/config.mjs +114 -0
  15. package/src/boards/custom-target.mjs +309 -0
  16. package/src/boards/resolve.mjs +145 -0
  17. package/src/boards/targets.mjs +45 -0
  18. package/src/boards/usb.mjs +252 -0
  19. package/src/chips.mjs +1 -1
  20. package/src/commands/apps.mjs +204 -0
  21. package/src/commands/board.mjs +402 -0
  22. package/src/commands/boards.mjs +334 -0
  23. package/src/commands/doctor.mjs +91 -0
  24. package/src/context.mjs +50 -54
  25. package/src/device/device.mjs +96 -0
  26. package/src/device/image.mjs +115 -0
  27. package/src/device/serial.mjs +430 -0
  28. package/src/device/wifi.mjs +190 -0
  29. package/src/esp32/build.mjs +321 -0
  30. package/src/esp32/capabilities.mjs +54 -0
  31. package/src/esp32/flash.mjs +181 -0
  32. package/src/esp32/idf-env.mjs +171 -0
  33. package/src/esp32/ota.mjs +80 -0
  34. package/src/esp32/partitions.mjs +59 -0
  35. package/src/esp32/sdkconfig.mjs +103 -0
  36. package/src/esp32/wifi-config.mjs +72 -0
  37. package/src/gea.mjs +89 -427
  38. package/src/geaos/adapter.mjs +119 -0
  39. package/src/heap-report.mjs +1276 -0
  40. package/src/manifest.mjs +135 -36
  41. package/src/rp2350/adapter.mjs +155 -0
  42. package/src/serial-devices.mjs +32 -20
  43. package/src/setup-wizard.mjs +11 -12
  44. package/src/taurus/adapter.mjs +52 -0
@@ -0,0 +1,402 @@
1
+ import path from 'node:path'
2
+
3
+ import { flag, option, optionList } from '../args.mjs'
4
+ import { resolveBoardSelection } from '../boards/resolve.mjs'
5
+ import { createChildEnv } from '../context.mjs'
6
+ import { chooseTransport, openDevice, saveScreenshot } from '../device/device.mjs'
7
+ import { geadev } from '../device/serial.mjs'
8
+ import { ExitCode, fail } from '../errors.mjs'
9
+ import { buildEsp32Firmware, buildImages, esp32BuildDir, fullCleanEsp32, requireEspIdf } from '../esp32/build.mjs'
10
+ import { eraseSlot, flashFirmware, flashImageSet, flashOptions, postFlashRestartNote, restoreBootMetadata, stageImage } from '../esp32/flash.mjs'
11
+ import { bleOta, otaEraseSlot, otaFlash, otaStage, waitForReboot } from '../esp32/ota.mjs'
12
+ import { manifestRequestsBleOta } from '../esp32/capabilities.mjs'
13
+ import { runGeaos } from '../geaos/adapter.mjs'
14
+ import { assertTargetEnabled, assertValidApp, resolveRequestedApp } from '../manifest.mjs'
15
+ import { buildRp2350, flashRp2350, rp2350BuildDir } from '../rp2350/adapter.mjs'
16
+ import { runTargetHook } from '../taurus/adapter.mjs'
17
+
18
+ // Every board-facing command: resolve the alias, pick the adapter, run.
19
+
20
+ export function selectBoard(ctx, parsed, needs = {}) {
21
+ const boardName = option(parsed, 'board', '')
22
+ const targetName = option(parsed, 'target', '')
23
+ if (!boardName && !targetName) fail('--board <alias> is required (see gea boards list).', ExitCode.usage)
24
+ try {
25
+ return resolveBoardSelection({
26
+ ctx,
27
+ boardName,
28
+ targetName,
29
+ requestedPort: option(parsed, 'port', ''),
30
+ requestedHost: option(parsed, 'host', ''),
31
+ needs,
32
+ deferUsbPort: true
33
+ })
34
+ } catch (error) {
35
+ fail(error.message, ExitCode.usage)
36
+ }
37
+ }
38
+
39
+ function optionalApp(ctx, parsed, rest, selection, { required = false } = {}) {
40
+ const requested = option(parsed, 'app') || rest[0]
41
+ let app = null
42
+ if (requested || required) {
43
+ app = resolveRequestedApp(ctx, parsed, rest)
44
+ } else {
45
+ try {
46
+ app = resolveRequestedApp(ctx, parsed, [])
47
+ } catch {
48
+ app = null
49
+ }
50
+ }
51
+ if (!app) return null
52
+ assertValidApp(app)
53
+ assertTargetEnabled(ctx, app, selection.boardName || selection.target)
54
+ return app
55
+ }
56
+
57
+ function bleOtaRequested(parsed, app, env) {
58
+ return option(parsed, 'transport') === 'ble' || env.GEA_EMBEDDED_BLE_OTA === '1' || manifestRequestsBleOta(app?.packageJson)
59
+ }
60
+
61
+ function io(parsed, options) {
62
+ return {
63
+ env: options.env,
64
+ dryRun: flag(parsed, 'dry-run'),
65
+ stdout: options.stdout,
66
+ stderr: options.stderr
67
+ }
68
+ }
69
+
70
+ // ---- build ------------------------------------------------------------------
71
+
72
+ export async function buildCommand(ctx, parsed, rest, options) {
73
+ const selection = selectBoard(ctx, parsed)
74
+ const app = optionalApp(ctx, parsed, rest, selection)
75
+ const base = io(parsed, options)
76
+ const env = createChildEnv(ctx, base.env)
77
+ switch (selection.adapter) {
78
+ case 'esp32-idf': {
79
+ buildEsp32Firmware({ ctx, selection, app, env, bleOta: bleOtaRequested(parsed, app, env), dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr, configureOnly: flag(parsed, 'configure-only') })
80
+ return 0
81
+ }
82
+ case 'rp2350-pico':
83
+ buildRp2350({ ctx, selection, app, env, dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr, configureOnly: flag(parsed, 'configure-only') })
84
+ return 0
85
+ case 'geaos-linux':
86
+ case 'geaos-arm64':
87
+ return runGeaos({ ctx, selection, action: 'build', app, positionals: rest, env, dryRun: base.dryRun, stdout: base.stdout })
88
+ case 'taurus-s3':
89
+ return runTargetHook({ ctx, selection, action: 'build', app, env, dryRun: base.dryRun, stdout: base.stdout })
90
+ default:
91
+ fail(`Unknown adapter '${selection.adapter}' for target '${selection.target}'.`, ExitCode.usage)
92
+ }
93
+ }
94
+
95
+ export async function cleanCommand(ctx, parsed, rest, options) {
96
+ const selection = selectBoard(ctx, parsed)
97
+ const app = optionalApp(ctx, parsed, rest, selection)
98
+ const base = io(parsed, options)
99
+ if (selection.adapter === 'esp32-idf') {
100
+ fullCleanEsp32({ ctx, selection, app, env: base.env, stdout: base.stdout })
101
+ return 0
102
+ }
103
+ if (selection.adapter === 'rp2350-pico') {
104
+ const dir = rp2350BuildDir(ctx, selection)
105
+ base.stdout(`Removing build artifacts in ${dir}...`)
106
+ const { rmSync } = await import('node:fs')
107
+ rmSync(dir, { recursive: true, force: true })
108
+ return 0
109
+ }
110
+ fail(`'clean' is not supported for ${selection.adapter} boards.`, ExitCode.usage)
111
+ }
112
+
113
+ // ---- flash / run --------------------------------------------------------------
114
+
115
+ async function flashEsp32(ctx, parsed, rest, options, selection, { monitor }) {
116
+ const base = io(parsed, options)
117
+ const env = createChildEnv(ctx, base.env)
118
+ const idf = requireEspIdf(env, base.stdout)
119
+ const flashEnv = idf.env
120
+ const opts = flashOptions(flashEnv, { manualBoot: flag(parsed, 'manual-boot'), noReset: option(parsed, 'reset') === false, baud: option(parsed, 'flash-baud', '') })
121
+ const common = { idf, selection, options: opts, port: selection.port, env: flashEnv, dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr }
122
+ const slotImages = optionList(parsed, 'slot-image')
123
+ const eraseSlotName = option(parsed, 'erase-slot', '')
124
+ const slot = option(parsed, 'slot', '')
125
+ const explicitImage = option(parsed, 'image', '')
126
+ const app = explicitImage && !option(parsed, 'app') && !rest[0] ? null : optionalApp(ctx, parsed, rest, selection, { required: !explicitImage && !slotImages.length && !eraseSlotName && !flag(parsed, 'restore-boot') })
127
+
128
+ const buildDir = esp32BuildDir(ctx, selection, app?.id, flashEnv)
129
+ const images = { ...buildImages(buildDir), buildDir }
130
+
131
+ if (eraseSlotName) {
132
+ await eraseSlot({ ...common, slot: eraseSlotName })
133
+ return 0
134
+ }
135
+ if (flag(parsed, 'restore-boot')) {
136
+ await restoreBootMetadata({ ...common, images })
137
+ return 0
138
+ }
139
+ if (slotImages.length) {
140
+ await flashImageSet({ ...common, images, slotImages })
141
+ postFlashRestartNote(selection, base.stderr)
142
+ return 0
143
+ }
144
+
145
+ let image = explicitImage ? path.resolve(ctx.cwd, explicitImage) : images.app
146
+ if (!explicitImage && !flag(parsed, 'no-build')) {
147
+ buildEsp32Firmware({ ctx, selection, app, env, bleOta: bleOtaRequested(parsed, app, env), dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr })
148
+ }
149
+ const appLabel = app?.id || 'prebuilt image'
150
+ if (slot) {
151
+ await stageImage({ ...common, image, slot, appLabel })
152
+ return 0
153
+ }
154
+ await flashFirmware({ ...common, images, appImage: image, appLabel })
155
+ if (!monitor) {
156
+ postFlashRestartNote(selection, base.stderr)
157
+ return 0
158
+ }
159
+ return monitorCommand(ctx, parsed, rest, options, selection)
160
+ }
161
+
162
+ export async function flashCommand(ctx, parsed, rest, options, { monitor = false } = {}) {
163
+ const selection = selectBoard(ctx, parsed, { usbPort: true })
164
+ const base = io(parsed, options)
165
+ const env = createChildEnv(ctx, base.env)
166
+ switch (selection.adapter) {
167
+ case 'esp32-idf':
168
+ return flashEsp32(ctx, parsed, rest, options, selection, { monitor })
169
+ case 'rp2350-pico': {
170
+ const app = optionalApp(ctx, parsed, rest, selection)
171
+ const { uf2 } = buildRp2350({ ctx, selection, app, env, dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr })
172
+ flashRp2350({ selection, uf2, env, dryRun: base.dryRun, stdout: base.stdout })
173
+ return monitor ? monitorCommand(ctx, parsed, rest, options, selection) : 0
174
+ }
175
+ case 'geaos-linux':
176
+ case 'geaos-arm64': {
177
+ const app = optionalApp(ctx, parsed, rest, selection)
178
+ return runGeaos({ ctx, selection, action: monitor ? 'flash-monitor' : 'flash', app, positionals: rest, env, dryRun: base.dryRun, stdout: base.stdout })
179
+ }
180
+ case 'taurus-s3': {
181
+ const app = optionalApp(ctx, parsed, rest, selection)
182
+ return runTargetHook({ ctx, selection, action: 'flash', app, env, dryRun: base.dryRun, stdout: base.stdout })
183
+ }
184
+ default:
185
+ fail(`Unknown adapter '${selection.adapter}' for target '${selection.target}'.`, ExitCode.usage)
186
+ }
187
+ }
188
+
189
+ // ---- ota ------------------------------------------------------------------------
190
+
191
+ export async function otaCommand(ctx, parsed, rest, options) {
192
+ const transport = option(parsed, 'transport', 'wifi')
193
+ if (transport !== 'wifi' && transport !== 'ble') fail("--transport must be 'wifi' or 'ble'.", ExitCode.usage)
194
+ const selection = selectBoard(ctx, parsed, transport === 'wifi' ? { otaHost: true } : {})
195
+ if (selection.adapter !== 'esp32-idf') fail(`OTA is only available for ESP32 boards (board '${selection.boardName}' is ${selection.adapter}).`, ExitCode.usage)
196
+ const base = io(parsed, options)
197
+ const env = createChildEnv(ctx, base.env)
198
+ const slot = option(parsed, 'slot', '')
199
+ const eraseSlotName = option(parsed, 'erase-slot', '')
200
+ const explicitImage = option(parsed, 'image', '')
201
+
202
+ if (eraseSlotName) {
203
+ await otaEraseSlot({ selection, host: selection.host, slot: eraseSlotName, dryRun: base.dryRun, stdout: base.stdout })
204
+ return 0
205
+ }
206
+
207
+ const app = explicitImage && !option(parsed, 'app') && !rest[0] ? null : optionalApp(ctx, parsed, rest, selection, { required: !explicitImage })
208
+ let image = explicitImage ? path.resolve(ctx.cwd, explicitImage) : ''
209
+ if (!explicitImage) {
210
+ const prepared = flag(parsed, 'no-build')
211
+ ? { images: buildImages(esp32BuildDir(ctx, selection, app.id, env)) }
212
+ : buildEsp32Firmware({ ctx, selection, app, env, bleOta: transport === 'ble' || bleOtaRequested(parsed, app, env), dryRun: base.dryRun, stdout: base.stdout, stderr: base.stderr })
213
+ image = prepared.images.app
214
+ }
215
+
216
+ if (transport === 'ble') {
217
+ bleOta({ cliPackageRoot: ctx.cliPackageRoot, image, deviceName: option(parsed, 'device', ''), env, dryRun: base.dryRun, stdout: base.stdout })
218
+ return 0
219
+ }
220
+ if (slot) {
221
+ await otaStage({ selection, host: selection.host, image, slot, boot: flag(parsed, 'boot'), reboot: flag(parsed, 'reboot'), appLabel: app?.id || 'prebuilt image', dryRun: base.dryRun, stdout: base.stdout })
222
+ return 0
223
+ }
224
+ await otaFlash({ host: selection.host, image, dryRun: base.dryRun, stdout: base.stdout })
225
+ if (flag(parsed, 'monitor') || flag(parsed, 'logs')) {
226
+ if (base.dryRun) return 0
227
+ await waitForReboot({ host: selection.host, stdout: base.stdout })
228
+ return logsCommand(ctx, { ...parsed, options: { ...parsed.options, transport: 'wifi', follow: true } }, rest, options, selection)
229
+ }
230
+ return 0
231
+ }
232
+
233
+ // ---- monitor / logs / screenshot ---------------------------------------------
234
+
235
+ function abortOnSigint() {
236
+ const controller = new AbortController()
237
+ const onSigint = () => controller.abort()
238
+ process.once('SIGINT', onSigint)
239
+ return { signal: controller.signal, release: () => process.removeListener('SIGINT', onSigint) }
240
+ }
241
+
242
+ async function withDevice(ctx, parsed, options, selection, transport, fn) {
243
+ const base = io(parsed, options)
244
+ if (base.dryRun) {
245
+ base.stdout(`[dry-run] ${transport} device: ${transport === 'wifi' ? option(parsed, 'host', '') || selection.otaHost : selection.port || `usb serial ${selection.usbSerial}`}`)
246
+ return 0
247
+ }
248
+ const device = await openDevice({
249
+ selection,
250
+ transport,
251
+ host: option(parsed, 'host', ''),
252
+ port: selection.port,
253
+ env: base.env,
254
+ trace: flag(parsed, 'trace'),
255
+ stderr: base.stderr,
256
+ waitSeconds: Number(option(parsed, 'wait', base.env.GEA_ESP32_MONITOR_WAIT_SECONDS || 0))
257
+ })
258
+ try {
259
+ return (await fn(device, base)) ?? 0
260
+ } finally {
261
+ await device.close()
262
+ }
263
+ }
264
+
265
+ export async function monitorCommand(ctx, parsed, rest, options, preselected = null) {
266
+ const selection = preselected || selectBoard(ctx, parsed, { usbPort: true })
267
+ if (selection.adapter === 'geaos-linux' || selection.adapter === 'geaos-arm64') {
268
+ const base = io(parsed, options)
269
+ return runGeaos({ ctx, selection, action: 'monitor', positionals: rest, env: createChildEnv(ctx, base.env), dryRun: base.dryRun, stdout: base.stdout })
270
+ }
271
+ return withDevice(ctx, parsed, options, selection, 'usb', async (device, base) => {
272
+ base.stderr(`Opening serial monitor on ${device.description}... (Ctrl+C to exit)`)
273
+ const { signal, release } = abortOnSigint()
274
+ try {
275
+ await device.logs({ write: (line) => base.stdout(line), timestamps: flag(parsed, 'timestamps'), logFile: option(parsed, 'log-file', ''), signal })
276
+ } finally {
277
+ release()
278
+ }
279
+ })
280
+ }
281
+
282
+ export async function logsCommand(ctx, parsed, rest, options, preselected = null) {
283
+ const selection = preselected || selectBoard(ctx, parsed, {})
284
+ const transport = chooseTransport(option(parsed, 'transport', 'auto'), selection, { host: option(parsed, 'host', '') })
285
+ if (transport === 'usb') return monitorCommand(ctx, parsed, rest, options, preselected || selectBoard(ctx, parsed, { usbPort: true }))
286
+ return withDevice(ctx, parsed, options, selection, 'wifi', async (device, base) => {
287
+ const follow = flag(parsed, 'follow')
288
+ base.stderr(`Connecting to diagnostics stream at ${device.host}:8081${follow ? ' (Ctrl+C to exit)' : ''}`)
289
+ const { signal, release } = abortOnSigint()
290
+ try {
291
+ await device.logs({ follow, write: (chunk) => process.stdout.write(chunk), timeoutMs: Number(option(parsed, 'timeout', 10)) * 1000, signal })
292
+ } finally {
293
+ release()
294
+ }
295
+ })
296
+ }
297
+
298
+ export async function screenshotCommand(ctx, parsed, rest, options) {
299
+ const selection = selectBoard(ctx, parsed, {})
300
+ const transport = chooseTransport(option(parsed, 'transport', 'auto'), selection, { host: option(parsed, 'host', '') })
301
+ const file = path.resolve(ctx.cwd, rest[0] || option(parsed, 'out', '') || 'screenshot.png')
302
+ const usbSelection = transport === 'usb' ? selectBoard(ctx, parsed, { usbPort: true }) : selection
303
+ return withDevice(ctx, parsed, options, usbSelection, transport, async (device, base) => {
304
+ const timeoutMs = Number(option(parsed, 'timeout', transport === 'wifi' ? 30 : 12)) * 1000
305
+ const shot = await saveScreenshot(device, file, { timeoutMs, legacy: flag(parsed, 'legacy') })
306
+ base.stdout(`Saved ${shot.width}x${shot.height} screenshot${shot.app ? ` of ${shot.app}` : ''} from ${device.description} to ${file}`)
307
+ })
308
+ }
309
+
310
+ // ---- devctl -------------------------------------------------------------------------
311
+
312
+ const devctlUsage = `gea devctl <verb> [args] --board <alias> [--transport auto|usb|wifi]
313
+
314
+ Verbs (USB, GEADEV protocol):
315
+ ping | app | state | mem | summary | i2cscan | reboot
316
+ node <class> hit <x> <y> tap <x> <y> [holdMs]
317
+ drag <x1> <y1> <x2> <y2> [steps] [delayMs] swipe <x> <y1> <y2>
318
+ back key <code> notify <text>
319
+ storage get <key> | storage set <key> <value>
320
+ set-default <app-id> set-time [epochSeconds]
321
+ brightness [0-100] ls [path] rm <path>
322
+ push <local> <remote> [--base64] pull <remote> <local>
323
+ playfile <path>
324
+ Verbs (WiFi):
325
+ hbm on|off -- high-brightness mode (POST /display/hbm)`
326
+
327
+ export async function devctlCommand(ctx, parsed, rest, options) {
328
+ const verb = rest[0]
329
+ const args = rest.slice(1)
330
+ if (!verb || verb === 'help') {
331
+ options.stdout(devctlUsage)
332
+ return verb ? 0 : ExitCode.usage
333
+ }
334
+ const selection = selectBoard(ctx, parsed, {})
335
+ const wifiVerbs = new Set(['hbm'])
336
+ const requested = option(parsed, 'transport', wifiVerbs.has(verb) ? 'wifi' : 'usb')
337
+ const transport = chooseTransport(requested, selection, { host: option(parsed, 'host', '') })
338
+ const usbSelection = transport === 'usb' ? selectBoard(ctx, parsed, { usbPort: true }) : selection
339
+ return withDevice(ctx, parsed, options, usbSelection, transport, async (device, base) => {
340
+ if (verb === 'hbm') {
341
+ const state = args[0]
342
+ if (!['on', 'off', '1', '0'].includes(state)) fail('devctl hbm expects on|off.', ExitCode.usage)
343
+ const reply = await device.hbm(state === 'on' || state === '1')
344
+ base.stdout(JSON.stringify(reply))
345
+ return 0
346
+ }
347
+ if (device.kind !== 'usb') fail(`devctl ${verb} needs the USB transport.`, ExitCode.usage)
348
+ const d = device.serial
349
+ const num = (value, name) => {
350
+ const n = Number(value)
351
+ if (!Number.isFinite(n)) fail(`devctl ${verb}: ${name} must be a number.`, ExitCode.usage)
352
+ return n
353
+ }
354
+ const need = (count) => {
355
+ if (args.length < count) fail(`devctl ${verb} needs ${count} argument(s).\n${devctlUsage}`, ExitCode.usage)
356
+ }
357
+ const print = (value) => base.stdout(String(value))
358
+ switch (verb) {
359
+ case 'ping': print(await geadev.ping(d)); break
360
+ case 'app': print(await geadev.app(d)); break
361
+ case 'state': print(await geadev.state(d)); break
362
+ case 'mem': print(await geadev.mem(d)); break
363
+ case 'summary': print(await geadev.summary(d)); break
364
+ case 'i2cscan': print(await geadev.i2cScan(d)); break
365
+ case 'reboot': print(await geadev.reboot(d)); break
366
+ case 'node': need(1); print(await geadev.node(d, args[0])); break
367
+ case 'hit': need(2); print(await geadev.hit(d, num(args[0], 'x'), num(args[1], 'y'))); break
368
+ case 'tap': need(2); print(await geadev.tap(d, num(args[0], 'x'), num(args[1], 'y'), args[2] ? num(args[2], 'holdMs') : 80)); break
369
+ case 'drag': need(4); print(await geadev.drag(d, num(args[0], 'x1'), num(args[1], 'y1'), num(args[2], 'x2'), num(args[3], 'y2'), args[4] ? num(args[4], 'steps') : 6, args[5] ? num(args[5], 'delayMs') : 24)); break
370
+ case 'swipe': need(3); print(await geadev.swipe(d, num(args[0], 'x'), num(args[1], 'y1'), num(args[2], 'y2'))); break
371
+ case 'back': print(await geadev.back(d)); break
372
+ case 'key': need(1); print(await geadev.key(d, args[0])); break
373
+ case 'notify': need(1); print(await geadev.notify(d, args.join(' '))); break
374
+ case 'storage':
375
+ need(2)
376
+ if (args[0] === 'get') print((await geadev.storageGet(d, args[1])).value)
377
+ else if (args[0] === 'set') { need(3); print(await geadev.storageSet(d, args[1], args.slice(2).join(' '))) }
378
+ else fail('devctl storage expects get <key> or set <key> <value>.', ExitCode.usage)
379
+ break
380
+ case 'set-default': need(1); print(await geadev.setDefault(d, args[0])); break
381
+ case 'set-time': print(await geadev.setTime(d, args[0] ? num(args[0], 'epoch') : Math.floor(Date.now() / 1000))); break
382
+ case 'brightness': print(await geadev.brightness(d, args[0] === undefined ? undefined : num(args[0], 'value'))); break
383
+ case 'ls': print(await geadev.ls(d, args[0] || '/sdcard')); break
384
+ case 'rm': need(1); print(await geadev.rm(d, args[0])); break
385
+ case 'push': need(2); print(await geadev.pushFile(d, path.resolve(ctx.cwd, args[0]), args[1], { base64: flag(parsed, 'base64'), stderr: base.stderr })); break
386
+ case 'pull': need(2); print(await geadev.pullFile(d, args[0], path.resolve(ctx.cwd, args[1]))); break
387
+ case 'playfile': need(1); print(await geadev.playFile(d, args[0])); break
388
+ default:
389
+ fail(`Unknown devctl verb '${verb}'.\n${devctlUsage}`, ExitCode.usage)
390
+ }
391
+ return 0
392
+ })
393
+ }
394
+
395
+ // ---- geaos device passthrough -------------------------------------------------
396
+
397
+ export async function geaosDeviceCommand(ctx, parsed, rest, options) {
398
+ const selection = selectBoard(ctx, parsed, { usbPort: true })
399
+ if (selection.adapter !== 'geaos-linux' && selection.adapter !== 'geaos-arm64') fail(`'${rest[0]}' is a geaos device action; board '${selection.boardName}' is ${selection.adapter}.`, ExitCode.usage)
400
+ const base = io(parsed, options)
401
+ return runGeaos({ ctx, selection, action: rest[0], positionals: rest.slice(1), env: createChildEnv(ctx, base.env), dryRun: base.dryRun, stdout: base.stdout })
402
+ }