@crosshands/cli 0.1.6 → 0.2.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 (49) hide show
  1. package/dist/bin.js +3 -3
  2. package/dist/bin.js.map +1 -1
  3. package/dist/broker-host.d.ts +2 -1
  4. package/dist/broker-host.d.ts.map +1 -1
  5. package/dist/broker-host.js +65 -40
  6. package/dist/broker-host.js.map +1 -1
  7. package/dist/broker-result.d.ts +7 -0
  8. package/dist/broker-result.d.ts.map +1 -0
  9. package/dist/broker-result.js +10 -0
  10. package/dist/broker-result.js.map +1 -0
  11. package/dist/index.d.ts +6 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +60 -32
  14. package/dist/index.js.map +1 -1
  15. package/dist/intent/dispatch.d.ts +13 -0
  16. package/dist/intent/dispatch.d.ts.map +1 -0
  17. package/dist/intent/dispatch.js +180 -0
  18. package/dist/intent/dispatch.js.map +1 -0
  19. package/dist/intent/env.d.ts +11 -0
  20. package/dist/intent/env.d.ts.map +1 -0
  21. package/dist/intent/env.js +10 -0
  22. package/dist/intent/env.js.map +1 -0
  23. package/dist/intent/evaluate.d.ts +21 -0
  24. package/dist/intent/evaluate.d.ts.map +1 -0
  25. package/dist/intent/evaluate.js +115 -0
  26. package/dist/intent/evaluate.js.map +1 -0
  27. package/dist/intent/log.d.ts +8 -0
  28. package/dist/intent/log.d.ts.map +1 -0
  29. package/dist/intent/log.js +37 -0
  30. package/dist/intent/log.js.map +1 -0
  31. package/dist/intent/tree.d.ts +13 -0
  32. package/dist/intent/tree.d.ts.map +1 -0
  33. package/dist/intent/tree.js +99 -0
  34. package/dist/intent/tree.js.map +1 -0
  35. package/dist/local-client.d.ts +1 -0
  36. package/dist/local-client.d.ts.map +1 -1
  37. package/dist/local-client.js +23 -15
  38. package/dist/local-client.js.map +1 -1
  39. package/package.json +6 -6
  40. package/src/bin.ts +4 -3
  41. package/src/broker-host.ts +68 -38
  42. package/src/broker-result.ts +14 -0
  43. package/src/index.ts +71 -35
  44. package/src/intent/dispatch.ts +276 -0
  45. package/src/intent/env.ts +13 -0
  46. package/src/intent/evaluate.ts +155 -0
  47. package/src/intent/log.ts +47 -0
  48. package/src/intent/tree.ts +108 -0
  49. package/src/local-client.ts +31 -15
@@ -1,10 +1,10 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { createHash } from 'node:crypto';
3
- import { chmod, lstat, mkdir, readFile } from 'node:fs/promises';
2
+ import { lstat, readFile } from 'node:fs/promises';
4
3
  import { homedir } from 'node:os';
5
4
  import { isAbsolute, join, resolve } from 'node:path';
6
5
  import { CONTRACT_VERSIONS, createComputerError } from '@crosshands/contract';
7
- import { LocalControlClient, brokerEndpoint } from '@crosshands/runtime';
6
+ import { LocalControlClient, brokerEndpoint, ensurePrivateDirectory, graphicalSessionKey } from '@crosshands/runtime';
7
+ import { unwrapBrokerResult } from './broker-result.js';
8
8
  export function localClientPaths() {
9
9
  const uid = process.getuid?.();
10
10
  const osIdentity = process.env.CROSSHANDS_OS_IDENTITY ??
@@ -16,7 +16,7 @@ export function localClientPaths() {
16
16
  process.env.SECURITYSESSIONID ??
17
17
  process.env.SESSIONNAME ??
18
18
  `interactive:${osIdentity}`;
19
- const sessionKey = createHash('sha256').update(graphicalSessionId).digest('hex').slice(0, 12);
19
+ const sessionKey = graphicalSessionKey(graphicalSessionId);
20
20
  const runtimeDirectory = process.env.CROSSHANDS_RUNTIME_DIR ?? defaultRuntimeDirectory(sessionKey);
21
21
  const identity = { osIdentity, graphicalSessionId };
22
22
  return {
@@ -48,14 +48,13 @@ function defaultRuntimeDirectory(sessionKey) {
48
48
  }
49
49
  return join(localAppData, 'CrossHands', 'runtime', sessionKey);
50
50
  }
51
- async function prepareRuntimeDirectory(path) {
52
- await mkdir(path, { recursive: true, mode: 0o700 });
53
- const info = await lstat(path);
54
- if (!info.isDirectory() || info.isSymbolicLink())
55
- throw createComputerError('provider_unavailable', 'CrossHands runtime path is unsafe');
56
- if (process.getuid !== undefined && info.uid !== process.getuid())
57
- throw createComputerError('provider_unavailable', 'CrossHands runtime path has another owner');
58
- await chmod(path, 0o700);
51
+ function prepareRuntimeDirectory(path) {
52
+ try {
53
+ ensurePrivateDirectory(path);
54
+ }
55
+ catch (cause) {
56
+ throw createComputerError('provider_unavailable', cause instanceof Error ? cause.message : 'CrossHands directory is unsafe');
57
+ }
59
58
  }
60
59
  async function readSecureToken(path) {
61
60
  const info = await lstat(path);
@@ -74,12 +73,21 @@ async function connect(paths) {
74
73
  identity: paths.identity
75
74
  });
76
75
  }
76
+ export function brokerSpawnEnv(env) {
77
+ const next = { ...env };
78
+ delete next.CROSSHANDS_JEV;
79
+ for (const key of Object.keys(next)) {
80
+ if (key === 'TYPESAFE_API_KEY' || key.startsWith('TYPESAFE_'))
81
+ delete next[key];
82
+ }
83
+ return next;
84
+ }
77
85
  function defaultSpawnBroker(entrypoint) {
78
86
  const child = spawn(process.execPath, [entrypoint, 'broker'], {
79
87
  detached: true,
80
88
  stdio: 'ignore',
81
89
  windowsHide: true,
82
- env: process.env
90
+ env: brokerSpawnEnv(process.env)
83
91
  });
84
92
  child.unref();
85
93
  }
@@ -91,7 +99,7 @@ function brokerIsAbsent(cause) {
91
99
  }
92
100
  export async function createProductionBrokerClient(options = {}) {
93
101
  const paths = options.paths ?? localClientPaths();
94
- await prepareRuntimeDirectory(paths.runtimeDirectory);
102
+ prepareRuntimeDirectory(paths.runtimeDirectory);
95
103
  try {
96
104
  const control = await connect(paths);
97
105
  return controlAdapter(control);
@@ -124,7 +132,7 @@ export async function createProductionBrokerClient(options = {}) {
124
132
  }
125
133
  function controlAdapter(control) {
126
134
  return {
127
- request: async (operation, input) => control.request({ operation, input }, { deadlineMs: 30_000 }),
135
+ request: async (operation, input) => unwrapBrokerResult(await control.request({ operation, input }, { deadlineMs: 30_000 })),
128
136
  close: () => control.close()
129
137
  };
130
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"local-client.js","sourceRoot":"","sources":["../src/local-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"local-client.js","sourceRoot":"","sources":["../src/local-client.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crosshands/cli",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "Agent-agnostic local computer-use CLI",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -23,8 +23,8 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@crosshands/contract": "0.1.6",
27
- "@crosshands/runtime": "0.1.6"
26
+ "@crosshands/contract": "0.2.0",
27
+ "@crosshands/runtime": "0.2.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "24.10.1",
@@ -32,9 +32,9 @@
32
32
  "vitest": "4.1.5"
33
33
  },
34
34
  "optionalDependencies": {
35
- "@crosshands/platform-darwin": "0.1.6",
36
- "@crosshands/platform-windows": "0.1.6",
37
- "@crosshands/platform-linux": "0.1.6"
35
+ "@crosshands/platform-linux": "0.2.0",
36
+ "@crosshands/platform-windows": "0.2.0",
37
+ "@crosshands/platform-darwin": "0.2.0"
38
38
  },
39
39
  "engines": {
40
40
  "node": ">=22"
package/src/bin.ts CHANGED
@@ -3,9 +3,7 @@
3
3
  import { stdin, stderr, stdout } from 'node:process'
4
4
  import { StringDecoder } from 'node:string_decoder'
5
5
 
6
- import { runBrokerHost } from './broker-host.js'
7
- import { runCli, type CliIo } from './index.js'
8
- import { createProductionBrokerClient } from './local-client.js'
6
+ import type { CliIo } from './index.js'
9
7
 
10
8
  async function readStdin(): Promise<string> {
11
9
  const decoder = new StringDecoder('utf8')
@@ -25,9 +23,12 @@ async function readStdin(): Promise<string> {
25
23
  async function main(): Promise<number> {
26
24
  const argv = process.argv.slice(2)
27
25
  if (argv[0] === 'broker') {
26
+ const { runBrokerHost } = await import('./broker-host.js')
28
27
  await runBrokerHost()
29
28
  return 0
30
29
  }
30
+ const { runCli } = await import('./index.js')
31
+ const { createProductionBrokerClient } = await import('./local-client.js')
31
32
  const io: CliIo = {
32
33
  stdin: readStdin,
33
34
  stdout: (value) => stdout.write(value),
@@ -1,3 +1,5 @@
1
+ import { randomUUID } from 'node:crypto'
2
+
1
3
  import {
2
4
  CONTRACT_VERSIONS,
3
5
  createComputerError,
@@ -7,9 +9,13 @@ import {
7
9
  type ReferenceBindings
8
10
  } from '@crosshands/contract'
9
11
  import {
12
+ JsonlDiagnosticsWriter,
10
13
  LocalBroker,
11
14
  LocalControlServer,
15
+ diagnosticsDirectory,
16
+ emitDiagnostic,
12
17
  type BrokerEndpoint,
18
+ type ControlHandshakeEvent,
13
19
  type LocalControlIdentity,
14
20
  type LocalControlRequest,
15
21
  type StableAppIdentity,
@@ -25,6 +31,7 @@ type ProviderModule = {
25
31
  endpoint: BrokerEndpoint
26
32
  identity: LocalControlIdentity
27
33
  handler: (request: LocalControlRequest) => Promise<unknown>
34
+ onHandshake?: (event: ControlHandshakeEvent) => void
28
35
  }) => Promise<{ start(): Promise<void>; close(): Promise<void> }>
29
36
  inspectTarget?: (
30
37
  operation: ComputerOperationName,
@@ -76,9 +83,16 @@ export async function runBrokerHost(): Promise<void> {
76
83
  const providerModule = await loadProviderModule()
77
84
  const paths = localClientPaths()
78
85
  const peer = { ...paths.identity, verified: true, local: true }
86
+ const generation = `broker-${randomUUID()}`
87
+ const diagnostics = new JsonlDiagnosticsWriter({
88
+ directory: diagnosticsDirectory(paths.identity.graphicalSessionId),
89
+ generation
90
+ })
79
91
  const broker = new LocalBroker({
80
92
  identity: peer,
93
+ generation,
81
94
  providerFactory: () => providerModule.createProvider(),
95
+ diagnostics,
82
96
  ...(providerModule.inspectTarget === undefined
83
97
  ? {}
84
98
  : {
@@ -88,45 +102,61 @@ export async function runBrokerHost(): Promise<void> {
88
102
  ): Promise<TargetInspection | null> => providerModule.inspectTarget!(operation, input)
89
103
  })
90
104
  })
91
- await broker.connect({ peer, versions: CONTRACT_VERSIONS })
92
- const handler = async ({ payload, deadlineAt }: LocalControlRequest): Promise<unknown> => {
93
- if (payload === null || typeof payload !== 'object') throw new Error('Invalid broker request')
94
- const record = payload as Record<string, unknown>
95
- if (typeof record.operation !== 'string') throw new Error('Missing operation')
96
- const operation = record.operation as ComputerOperationName
97
- let input: unknown
98
- try {
99
- input = parseOperationInput(operation, record.input)
100
- } catch (cause) {
101
- if (cause instanceof Error && cause.name === 'ZodError') {
102
- throw createComputerError('invalid_argument', 'Invalid input for the selected operation')
105
+ try {
106
+ await broker.connect({ peer, versions: CONTRACT_VERSIONS })
107
+ const handler = async ({ payload, deadlineAt }: LocalControlRequest): Promise<unknown> => {
108
+ if (payload === null || typeof payload !== 'object') throw new Error('Invalid broker request')
109
+ const record = payload as Record<string, unknown>
110
+ if (typeof record.operation !== 'string') throw new Error('Missing operation')
111
+ const operation = record.operation as ComputerOperationName
112
+ let input: unknown
113
+ try {
114
+ input = parseOperationInput(operation, record.input)
115
+ } catch (cause) {
116
+ if (cause instanceof Error && cause.name === 'ZodError') {
117
+ throw createComputerError('invalid_argument', 'Invalid input for the selected operation')
118
+ }
119
+ throw cause
103
120
  }
104
- throw cause
121
+ return broker.request({ operation, input, deadlineMs: Math.max(1, deadlineAt - Date.now()) })
105
122
  }
106
- return broker.request({ operation, input, deadlineMs: Math.max(1, deadlineAt - Date.now()) })
123
+ const onHandshake = (event: ControlHandshakeEvent): void => {
124
+ emitDiagnostic(diagnostics, {
125
+ kind: 'client.handshake',
126
+ ...broker.diagnosticEnvelope(),
127
+ accepted: event.accepted,
128
+ requestId: event.requestId,
129
+ ...(event.code === undefined ? {} : { code: event.code })
130
+ })
131
+ }
132
+ const server =
133
+ paths.endpoint.transport === 'named-pipe'
134
+ ? await (providerModule.createControlServer?.({
135
+ endpoint: paths.endpoint,
136
+ identity: paths.identity,
137
+ handler,
138
+ onHandshake
139
+ }) ??
140
+ Promise.reject(
141
+ new Error('The Windows payload has no native DACL and peer-token control relay')
142
+ ))
143
+ : new LocalControlServer({
144
+ endpoint: paths.endpoint,
145
+ runtimeDirectory: paths.runtimeDirectory,
146
+ tokenFile: paths.tokenFile,
147
+ identity: paths.identity,
148
+ handler,
149
+ onHandshake
150
+ })
151
+ await server.start()
152
+ emitDiagnostic(diagnostics, { kind: 'broker.start', ...broker.diagnosticEnvelope() })
153
+ await new Promise<void>((resolve) => {
154
+ const stop = (): void => resolve()
155
+ process.once('SIGINT', stop)
156
+ process.once('SIGTERM', stop)
157
+ })
158
+ await server.close()
159
+ } finally {
160
+ await broker.close('signal')
107
161
  }
108
- const server =
109
- paths.endpoint.transport === 'named-pipe'
110
- ? await (providerModule.createControlServer?.({
111
- endpoint: paths.endpoint,
112
- identity: paths.identity,
113
- handler
114
- }) ??
115
- Promise.reject(
116
- new Error('The Windows payload has no native DACL and peer-token control relay')
117
- ))
118
- : new LocalControlServer({
119
- endpoint: paths.endpoint,
120
- runtimeDirectory: paths.runtimeDirectory,
121
- tokenFile: paths.tokenFile,
122
- identity: paths.identity,
123
- handler
124
- })
125
- await server.start()
126
- await new Promise<void>((resolve) => {
127
- const stop = (): void => resolve()
128
- process.once('SIGINT', stop)
129
- process.once('SIGTERM', stop)
130
- })
131
- await server.close()
132
162
  }
@@ -0,0 +1,14 @@
1
+ export type BrokerResultEnvelope = {
2
+ requestId: string
3
+ result: unknown
4
+ }
5
+
6
+ export function isBrokerResultEnvelope(value: unknown): value is BrokerResultEnvelope {
7
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return false
8
+ const record = value as Record<string, unknown>
9
+ return typeof record.requestId === 'string' && 'result' in record
10
+ }
11
+
12
+ export function unwrapBrokerResult(value: unknown): unknown {
13
+ return isBrokerResultEnvelope(value) ? value.result : value
14
+ }
package/src/index.ts CHANGED
@@ -5,6 +5,10 @@ import { dirname, resolve } from 'node:path'
5
5
 
6
6
  import type { ComputerOperationName } from '@crosshands/contract'
7
7
 
8
+ import { unwrapBrokerResult } from './broker-result.js'
9
+ import { dispatchPublicOperation } from './intent/dispatch.js'
10
+ import { createCliJevLogger } from './intent/log.js'
11
+
8
12
  export type CliBrokerClient = {
9
13
  request(operation: ComputerOperationName, input: unknown): Promise<unknown>
10
14
  close(): Promise<void>
@@ -52,8 +56,10 @@ const ALLOWED: Record<string, readonly string[]> = {
52
56
  'get-app-state': [
53
57
  'json',
54
58
  'app',
59
+ 'context',
55
60
  'window-id',
56
61
  'window-index',
62
+ 'goal',
57
63
  'no-screenshot',
58
64
  'restore-window',
59
65
  'screenshot-output'
@@ -68,6 +74,7 @@ const ALLOWED: Record<string, readonly string[]> = {
68
74
  'click-count',
69
75
  'mouse-button',
70
76
  'modifiers',
77
+ 'goal',
71
78
  'no-screenshot',
72
79
  'restore-window',
73
80
  'screenshot-output'
@@ -78,6 +85,7 @@ const ALLOWED: Record<string, readonly string[]> = {
78
85
  'context',
79
86
  'element-index',
80
87
  'action',
88
+ 'goal',
81
89
  'no-screenshot',
82
90
  'restore-window',
83
91
  'screenshot-output'
@@ -91,6 +99,7 @@ const ALLOWED: Record<string, readonly string[]> = {
91
99
  'y',
92
100
  'direction',
93
101
  'pages',
102
+ 'goal',
94
103
  'no-screenshot',
95
104
  'restore-window',
96
105
  'screenshot-output'
@@ -146,6 +155,7 @@ const ALLOWED: Record<string, readonly string[]> = {
146
155
  'element-index',
147
156
  'value',
148
157
  'value-stdin',
158
+ 'goal',
149
159
  'no-screenshot',
150
160
  'restore-window',
151
161
  'screenshot-output'
@@ -165,7 +175,10 @@ const EXIT_CODES: Record<string, number> = {
165
175
  action_not_supported: 7,
166
176
  timeout: 8,
167
177
  version_incompatible: 9,
168
- session_unavailable: 10
178
+ session_unavailable: 10,
179
+ goal_mismatch: 5,
180
+ policy_unavailable: 5,
181
+ intent_unavailable: 2
169
182
  }
170
183
 
171
184
  class CliError extends Error {
@@ -252,12 +265,6 @@ function contextToken(flags: Flags): string {
252
265
  return stringFlag(flags, 'context', true)!
253
266
  }
254
267
 
255
- function elementTarget(flags: Flags, name = 'element-index'): unknown {
256
- const index = numberFlag(flags, name, { integer: true, min: 0 })
257
- if (index === undefined) throw new CliError('invalid_argument', `Missing required --${name}`)
258
- return { kind: 'element', elementIndex: index }
259
- }
260
-
261
268
  function chordTokens(raw: string): string[] {
262
269
  return raw.split('+')
263
270
  }
@@ -271,17 +278,32 @@ function parseClickModifiers(flags: Flags): string[] | undefined {
271
278
  return modifiers
272
279
  }
273
280
 
274
- function pointOrElement(flags: Flags): unknown {
281
+ function optionalGoal(flags: Flags): { goal?: string } {
282
+ const goal = stringFlag(flags, 'goal')
283
+ return goal === undefined ? {} : { goal }
284
+ }
285
+
286
+ function actionTarget(flags: Flags, options: { coordinates?: boolean } = {}): unknown {
275
287
  const element = numberFlag(flags, 'element-index', { integer: true, min: 0 })
276
- const x = numberFlag(flags, 'x')
277
- const y = numberFlag(flags, 'y')
288
+ const allowCoordinates = options.coordinates === true
289
+ const x = allowCoordinates ? numberFlag(flags, 'x') : undefined
290
+ const y = allowCoordinates ? numberFlag(flags, 'y') : undefined
278
291
  const hasCoordinates = x !== undefined || y !== undefined
279
292
  if (element !== undefined && hasCoordinates)
280
293
  throw new CliError('invalid_argument', 'Choose an element index or coordinates, not both')
281
294
  if (element !== undefined) return { kind: 'element', elementIndex: element }
282
- if (x === undefined || y === undefined)
283
- throw new CliError('invalid_argument', 'Coordinates require both --x and --y')
284
- return { kind: 'coordinate', x, y }
295
+ if (hasCoordinates) {
296
+ if (x === undefined || y === undefined)
297
+ throw new CliError('invalid_argument', 'Coordinates require both --x and --y')
298
+ return { kind: 'coordinate', x, y }
299
+ }
300
+ if (stringFlag(flags, 'goal') !== undefined) return { kind: 'intent' }
301
+ throw new CliError(
302
+ 'invalid_argument',
303
+ allowCoordinates
304
+ ? 'Choose an element index, coordinates, or --goal'
305
+ : 'Missing required --element-index'
306
+ )
285
307
  }
286
308
 
287
309
  async function protectedText(flags: Flags, name: 'text' | 'value', io: CliIo): Promise<string> {
@@ -314,10 +336,19 @@ async function operationInput(command: string, flags: Flags, io: CliIo): Promise
314
336
  case 'list-windows':
315
337
  return { app: stringFlag(flags, 'app', true) }
316
338
  case 'get-app-state': {
339
+ const context = stringFlag(flags, 'context')
317
340
  const window = windowSelector(flags)
341
+ if (context !== undefined) {
342
+ return {
343
+ contextToken: context,
344
+ ...optionalGoal(flags),
345
+ ...observationFlags(flags)
346
+ }
347
+ }
318
348
  return {
319
349
  app: stringFlag(flags, 'app', true),
320
350
  ...(window === undefined ? {} : { window }),
351
+ ...optionalGoal(flags),
321
352
  ...observationFlags(flags)
322
353
  }
323
354
  }
@@ -329,7 +360,8 @@ async function operationInput(command: string, flags: Flags, io: CliIo): Promise
329
360
  const modifiers = parseClickModifiers(flags)
330
361
  return {
331
362
  ...common(),
332
- target: pointOrElement(flags),
363
+ target: actionTarget(flags, { coordinates: true }),
364
+ ...optionalGoal(flags),
333
365
  ...(clickCount === undefined ? {} : { clickCount }),
334
366
  ...(button === undefined ? {} : { button }),
335
367
  ...(modifiers === undefined ? {} : { modifiers })
@@ -338,8 +370,9 @@ async function operationInput(command: string, flags: Flags, io: CliIo): Promise
338
370
  case 'perform-secondary-action':
339
371
  return {
340
372
  ...common(),
341
- target: elementTarget(flags),
342
- action: stringFlag(flags, 'action', true)
373
+ target: actionTarget(flags),
374
+ action: stringFlag(flags, 'action', true),
375
+ ...optionalGoal(flags)
343
376
  }
344
377
  case 'scroll': {
345
378
  const direction = stringFlag(flags, 'direction', true)!
@@ -348,8 +381,9 @@ async function operationInput(command: string, flags: Flags, io: CliIo): Promise
348
381
  const pages = numberFlag(flags, 'pages', { integer: true, min: 1 })
349
382
  return {
350
383
  ...common(),
351
- target: pointOrElement(flags),
384
+ target: actionTarget(flags, { coordinates: true }),
352
385
  direction,
386
+ ...optionalGoal(flags),
353
387
  ...(pages === undefined ? {} : { pages })
354
388
  }
355
389
  }
@@ -403,8 +437,9 @@ async function operationInput(command: string, flags: Flags, io: CliIo): Promise
403
437
  case 'set-value':
404
438
  return {
405
439
  ...common(),
406
- target: elementTarget(flags),
407
- value: await protectedText(flags, 'value', io)
440
+ target: actionTarget(flags),
441
+ value: await protectedText(flags, 'value', io),
442
+ ...optionalGoal(flags)
408
443
  }
409
444
  default:
410
445
  throw new CliError('invalid_argument', `Unknown computer command: ${command}`)
@@ -428,19 +463,9 @@ function readiness(capabilities: unknown): string {
428
463
  }
429
464
 
430
465
  async function runDoctor(client: CliBrokerClient): Promise<unknown> {
431
- const capabilities = await client.request('capabilities', {})
466
+ const capabilities = unwrapBrokerResult(await client.request('capabilities', {}))
432
467
  const permissions = await client.request('permissions', {})
433
- const capabilityResult =
434
- capabilities !== null && typeof capabilities === 'object' && 'result' in capabilities
435
- ? (capabilities as Record<string, unknown>).result
436
- : capabilities
437
- return { readiness: readiness(capabilityResult), checks: { capabilities, permissions } }
438
- }
439
-
440
- function publicBrokerResult(value: unknown): unknown {
441
- if (value !== null && typeof value === 'object' && 'requestId' in value && 'result' in value)
442
- return (value as Record<string, unknown>).result
443
- return value
468
+ return { readiness: readiness(capabilities), checks: { capabilities, permissions } }
444
469
  }
445
470
 
446
471
  function serializedError(cause: unknown): Record<string, unknown> {
@@ -524,6 +549,7 @@ async function exportScreenshot(value: unknown, destination: string): Promise<vo
524
549
 
525
550
  export async function runCli(argv: string[], io: CliIo, client: CliBrokerClient): Promise<number> {
526
551
  let json = false
552
+ const log = createCliJevLogger()
527
553
  try {
528
554
  if (argv[0] !== 'computer')
529
555
  throw new CliError('invalid_argument', 'Usage: crosshands computer <command> --json')
@@ -536,8 +562,13 @@ export async function runCli(argv: string[], io: CliIo, client: CliBrokerClient)
536
562
  const brokerResult =
537
563
  operation === 'doctor'
538
564
  ? await runDoctor(client)
539
- : publicBrokerResult(
540
- await client.request(operation, await operationInput(command, flags, io))
565
+ : unwrapBrokerResult(
566
+ await dispatchPublicOperation(
567
+ client,
568
+ operation,
569
+ await operationInput(command, flags, io),
570
+ { env: process.env, ...(log === undefined ? {} : { log }) }
571
+ )
541
572
  )
542
573
  const result = structuredClone(brokerResult)
543
574
  const screenshotOutput = stringFlag(flags, 'screenshot-output')
@@ -550,9 +581,14 @@ export async function runCli(argv: string[], io: CliIo, client: CliBrokerClient)
550
581
  if (!json) io.stderr(`${String(error.message)}\n`)
551
582
  return EXIT_CODES[String(error.code)] ?? 1
552
583
  } finally {
553
- await client.close().catch(() => undefined)
584
+ await Promise.all([client.close().catch(() => undefined), log?.close().catch(() => undefined)])
554
585
  }
555
586
  }
556
587
 
557
- export { createProductionBrokerClient, localClientPaths } from './local-client.js'
588
+ export { unwrapBrokerResult } from './broker-result.js'
589
+ export { createProductionBrokerClient, localClientPaths, brokerSpawnEnv } from './local-client.js'
558
590
  export type { LocalClientPaths, ProductionClientOptions } from './local-client.js'
591
+ export { dispatchPublicOperation } from './intent/dispatch.js'
592
+ export { parseJevEnv } from './intent/env.js'
593
+ export { createCliJevLogger, createJevLogger, hashGoal } from './intent/log.js'
594
+ export type { JevLogger } from './intent/log.js'