@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.
- package/dist/bin.js +3 -3
- package/dist/bin.js.map +1 -1
- package/dist/broker-host.d.ts +2 -1
- package/dist/broker-host.d.ts.map +1 -1
- package/dist/broker-host.js +65 -40
- package/dist/broker-host.js.map +1 -1
- package/dist/broker-result.d.ts +7 -0
- package/dist/broker-result.d.ts.map +1 -0
- package/dist/broker-result.js +10 -0
- package/dist/broker-result.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -32
- package/dist/index.js.map +1 -1
- package/dist/intent/dispatch.d.ts +13 -0
- package/dist/intent/dispatch.d.ts.map +1 -0
- package/dist/intent/dispatch.js +180 -0
- package/dist/intent/dispatch.js.map +1 -0
- package/dist/intent/env.d.ts +11 -0
- package/dist/intent/env.d.ts.map +1 -0
- package/dist/intent/env.js +10 -0
- package/dist/intent/env.js.map +1 -0
- package/dist/intent/evaluate.d.ts +21 -0
- package/dist/intent/evaluate.d.ts.map +1 -0
- package/dist/intent/evaluate.js +115 -0
- package/dist/intent/evaluate.js.map +1 -0
- package/dist/intent/log.d.ts +8 -0
- package/dist/intent/log.d.ts.map +1 -0
- package/dist/intent/log.js +37 -0
- package/dist/intent/log.js.map +1 -0
- package/dist/intent/tree.d.ts +13 -0
- package/dist/intent/tree.d.ts.map +1 -0
- package/dist/intent/tree.js +99 -0
- package/dist/intent/tree.js.map +1 -0
- package/dist/local-client.d.ts +1 -0
- package/dist/local-client.d.ts.map +1 -1
- package/dist/local-client.js +23 -15
- package/dist/local-client.js.map +1 -1
- package/package.json +6 -6
- package/src/bin.ts +4 -3
- package/src/broker-host.ts +68 -38
- package/src/broker-result.ts +14 -0
- package/src/index.ts +71 -35
- package/src/intent/dispatch.ts +276 -0
- package/src/intent/env.ts +13 -0
- package/src/intent/evaluate.ts +155 -0
- package/src/intent/log.ts +47 -0
- package/src/intent/tree.ts +108 -0
- package/src/local-client.ts +31 -15
package/dist/local-client.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
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 =
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/local-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-client.js","sourceRoot":"","sources":["../src/local-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAE,
|
|
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.
|
|
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.
|
|
27
|
-
"@crosshands/runtime": "0.
|
|
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-
|
|
36
|
-
"@crosshands/platform-windows": "0.
|
|
37
|
-
"@crosshands/platform-
|
|
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 {
|
|
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),
|
package/src/broker-host.ts
CHANGED
|
@@ -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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
121
|
+
return broker.request({ operation, input, deadlineMs: Math.max(1, deadlineAt - Date.now()) })
|
|
105
122
|
}
|
|
106
|
-
|
|
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
|
|
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
|
|
277
|
-
const
|
|
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 (
|
|
283
|
-
|
|
284
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
:
|
|
540
|
-
await
|
|
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 {
|
|
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'
|