opencode-drive 0.1.2 → 0.1.3
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/README.md +57 -38
- package/package.json +1 -1
- package/src/cli/commands.ts +101 -68
- package/src/cli/control.ts +43 -0
- package/src/cli/describe.ts +4 -5
- package/src/cli/index.ts +83 -110
- package/src/cli/instance.ts +193 -148
- package/src/cli/mock-backend.ts +25 -0
- package/src/cli/registry.ts +41 -83
- package/src/cli/restart.ts +3 -66
- package/src/cli/script.ts +66 -0
- package/src/cli/send.ts +19 -39
- package/src/cli/start.ts +93 -68
- package/src/cli/stop.ts +4 -28
- package/src/cli/types.ts +7 -30
- package/src/client/backend.ts +94 -32
- package/src/client/protocol.ts +115 -29
- package/src/client/protocol.types.ts +17 -23
- package/src/experimental/driver.ts +29 -18
- package/src/experimental/flow-driver.ts +130 -43
- package/src/experimental/flows/properties.ts +19 -13
- package/src/experimental/hello-driver.ts +11 -4
- package/src/experimental/stale-running-driver.ts +45 -13
- package/src/index.ts +2 -0
- package/src/cli/driver-runner.ts +0 -39
- package/src/cli/driver.ts +0 -28
- package/src/experimental/cli-campaign.ts +0 -179
package/src/cli/index.ts
CHANGED
|
@@ -2,82 +2,87 @@
|
|
|
2
2
|
import { NodeRuntime, NodeServices } from "@effect/platform-node"
|
|
3
3
|
import { Effect, Option } from "effect"
|
|
4
4
|
import { Command, Flag } from "effect/unstable/cli"
|
|
5
|
-
import {
|
|
6
|
-
import { describe } from "./describe.js"
|
|
5
|
+
import { api } from "./api.js"
|
|
7
6
|
import { extractCommands } from "./parse.js"
|
|
7
|
+
import { send } from "./send.js"
|
|
8
8
|
import { start } from "./start.js"
|
|
9
|
-
import { stop } from "./stop.js"
|
|
10
|
-
import { api } from "./api.js"
|
|
11
9
|
import { restart } from "./restart.js"
|
|
12
10
|
import type { DriveCommand, SendOptions, StartOptions } from "./types.js"
|
|
13
11
|
|
|
14
12
|
const extracted = extract()
|
|
15
|
-
const name = Flag.string("name").pipe(Flag.optional, Flag.withDescription("Instance name"))
|
|
16
|
-
const driver = Flag.string("driver").pipe(Flag.optional, Flag.withDescription("TypeScript driver module"))
|
|
17
13
|
|
|
18
|
-
const startCommand = Command.make(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Flag.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Flag.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Flag.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
14
|
+
const startCommand = Command.make(
|
|
15
|
+
"start",
|
|
16
|
+
{
|
|
17
|
+
script: Flag.string("script").pipe(
|
|
18
|
+
Flag.optional,
|
|
19
|
+
Flag.withDescription("JavaScript or TypeScript automation module"),
|
|
20
|
+
),
|
|
21
|
+
visible: Flag.boolean("visible").pipe(
|
|
22
|
+
Flag.withDescription("Show OpenCode in the terminal"),
|
|
23
|
+
),
|
|
24
|
+
dev: Flag.string("dev").pipe(
|
|
25
|
+
Flag.optional,
|
|
26
|
+
Flag.withDescription("Path to an OpenCode development checkout"),
|
|
27
|
+
),
|
|
28
|
+
state: Flag.string("state").pipe(
|
|
29
|
+
Flag.optional,
|
|
30
|
+
Flag.withDescription("Simulation snapshot containing files/"),
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
(config) =>
|
|
34
|
+
execute(() =>
|
|
35
|
+
start(toStartOptions(config, extracted.commands, extracted.app)),
|
|
36
|
+
),
|
|
37
|
+
).pipe(
|
|
38
|
+
Command.withDescription("Launch a local simulated OpenCode instance"),
|
|
42
39
|
Command.withExamples([
|
|
43
|
-
{
|
|
44
|
-
|
|
40
|
+
{
|
|
41
|
+
command: "opencode-drive start",
|
|
42
|
+
description: "Launch headless OpenCode on the default ports",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
command: "opencode-drive start --visible",
|
|
46
|
+
description: "Launch visible OpenCode on the default ports",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
command: "opencode-drive start --script ./drive.ts",
|
|
50
|
+
description: "Launch headless OpenCode and run a script",
|
|
51
|
+
},
|
|
45
52
|
]),
|
|
46
53
|
)
|
|
47
54
|
|
|
48
|
-
const sendCommand = Command.make("send", {
|
|
49
|
-
execute(() => send(toSendOptions(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
execute(() => describe(Option.getOrUndefined(config.name)))).pipe(
|
|
61
|
-
Command.withDescription("Describe a registered OpenCode instance"),
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
const stopCommand = Command.make("stop", { name }, (config) =>
|
|
65
|
-
execute(() => stop(Option.getOrUndefined(config.name)))).pipe(
|
|
66
|
-
Command.withDescription("Stop a registered headless OpenCode instance"),
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
const restartCommand = Command.make("restart", { name }, (config) =>
|
|
70
|
-
execute(() => restart(Option.getOrUndefined(config.name)))).pipe(
|
|
71
|
-
Command.withDescription("Restart a registered OpenCode client"),
|
|
72
|
-
)
|
|
55
|
+
const sendCommand = Command.make("send", {}, () =>
|
|
56
|
+
execute(() => send(toSendOptions(extracted.commands, extracted.app))),
|
|
57
|
+
).pipe(
|
|
58
|
+
Command.withDescription("Send UI commands to OpenCode on the default port"),
|
|
59
|
+
Command.withExamples([
|
|
60
|
+
{
|
|
61
|
+
command:
|
|
62
|
+
'opencode-drive send --command.ui.type \'{"text":"hello"}\' --command.ui.state',
|
|
63
|
+
description: "Execute an ordered UI command batch",
|
|
64
|
+
},
|
|
65
|
+
]),
|
|
66
|
+
)
|
|
73
67
|
|
|
74
68
|
const apiCommand = Command.make("api", {}, () => execute(api)).pipe(
|
|
75
|
-
Command.withDescription("Print the OpenCode drive protocol"),
|
|
69
|
+
Command.withDescription("Print the OpenCode drive UI protocol"),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
const restartCommand = Command.make("restart", {}, () => execute(restart)).pipe(
|
|
73
|
+
Command.withDescription(
|
|
74
|
+
"Restart the active visible OpenCode instance and rerun its script",
|
|
75
|
+
),
|
|
76
76
|
)
|
|
77
77
|
|
|
78
78
|
const root = Command.make("opencode-drive").pipe(
|
|
79
79
|
Command.withDescription("Drive real and simulated OpenCode instances"),
|
|
80
|
-
Command.withSubcommands([
|
|
80
|
+
Command.withSubcommands([
|
|
81
|
+
startCommand,
|
|
82
|
+
sendCommand,
|
|
83
|
+
restartCommand,
|
|
84
|
+
apiCommand,
|
|
85
|
+
]),
|
|
81
86
|
)
|
|
82
87
|
|
|
83
88
|
Command.runWith(root, { version: "0.1.0" })(extracted.args).pipe(
|
|
@@ -87,81 +92,47 @@ Command.runWith(root, { version: "0.1.0" })(extracted.args).pipe(
|
|
|
87
92
|
|
|
88
93
|
function toStartOptions(
|
|
89
94
|
config: {
|
|
90
|
-
readonly
|
|
91
|
-
readonly driver: Option.Option<string>
|
|
92
|
-
readonly campaign: Option.Option<string>
|
|
93
|
-
readonly seed: number
|
|
94
|
-
readonly caseIndex: Option.Option<number>
|
|
95
|
-
readonly count: Option.Option<number>
|
|
96
|
-
readonly concurrency: number
|
|
95
|
+
readonly script: Option.Option<string>
|
|
97
96
|
readonly visible: boolean
|
|
98
|
-
readonly detach: boolean
|
|
99
97
|
readonly dev: Option.Option<string>
|
|
100
98
|
readonly state: Option.Option<string>
|
|
101
|
-
readonly anchor: Option.Option<string>
|
|
102
99
|
},
|
|
103
100
|
commands: ReadonlyArray<DriveCommand>,
|
|
104
101
|
app: ReadonlyArray<string>,
|
|
105
102
|
): StartOptions {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const visible = config.visible
|
|
103
|
+
if (commands.length > 0)
|
|
104
|
+
throw new Error("start does not accept command flags; use send or --script")
|
|
109
105
|
const options = {
|
|
110
106
|
kind: "start" as const,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
campaign,
|
|
114
|
-
seed: config.seed,
|
|
115
|
-
caseIndex: Option.getOrUndefined(config.caseIndex),
|
|
116
|
-
count: Option.getOrUndefined(config.count),
|
|
117
|
-
concurrency: config.concurrency,
|
|
118
|
-
visible,
|
|
119
|
-
detach: !visible && (config.detach || (driver === undefined && campaign === undefined && commands.length === 0)),
|
|
107
|
+
script: Option.getOrUndefined(config.script),
|
|
108
|
+
visible: config.visible,
|
|
120
109
|
dev: Option.getOrUndefined(config.dev),
|
|
121
110
|
state: Option.getOrUndefined(config.state),
|
|
122
|
-
anchor: Option.getOrUndefined(config.anchor),
|
|
123
111
|
command: app,
|
|
124
|
-
commands,
|
|
125
|
-
}
|
|
126
|
-
assertExecutionMode(options.driver, options.campaign, commands)
|
|
127
|
-
if (options.dev !== undefined && app.length > 0) throw new Error("--dev cannot be combined with a command after --")
|
|
128
|
-
if (options.caseIndex !== undefined && options.campaign === undefined) throw new Error("--case requires --campaign")
|
|
129
|
-
if (options.visible && options.campaign !== undefined && options.caseIndex === undefined) {
|
|
130
|
-
throw new Error("visible campaign runs require --case")
|
|
131
|
-
}
|
|
132
|
-
if (config.detach && (options.driver !== undefined || options.campaign !== undefined || commands.length > 0)) {
|
|
133
|
-
throw new Error("--detach cannot be combined with --driver, --campaign, or command flags")
|
|
134
112
|
}
|
|
113
|
+
if (options.dev !== undefined && app.length > 0)
|
|
114
|
+
throw new Error("--dev cannot be combined with a command after --")
|
|
135
115
|
return options
|
|
136
116
|
}
|
|
137
117
|
|
|
138
118
|
function toSendOptions(
|
|
139
|
-
config: { readonly name: Option.Option<string>; readonly driver: Option.Option<string> },
|
|
140
119
|
commands: ReadonlyArray<DriveCommand>,
|
|
141
120
|
app: ReadonlyArray<string>,
|
|
142
121
|
): SendOptions {
|
|
143
122
|
if (app.length > 0) throw new Error("send does not accept a command after --")
|
|
144
|
-
|
|
145
|
-
kind: "send" as const,
|
|
146
|
-
name: Option.getOrUndefined(config.name),
|
|
147
|
-
driver: Option.getOrUndefined(config.driver),
|
|
148
|
-
commands,
|
|
149
|
-
}
|
|
150
|
-
assertExecutionMode(options.driver, undefined, commands)
|
|
151
|
-
return options
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function assertExecutionMode(driver: string | undefined, campaign: string | undefined, commands: ReadonlyArray<DriveCommand>) {
|
|
155
|
-
const count = Number(driver !== undefined) + Number(campaign !== undefined) + Number(commands.length > 0)
|
|
156
|
-
if (count > 1) throw new Error("command flags, --driver, and --campaign are mutually exclusive")
|
|
123
|
+
return { kind: "send", commands }
|
|
157
124
|
}
|
|
158
125
|
|
|
159
126
|
function execute(task: () => Promise<void>) {
|
|
160
127
|
return Effect.tryPromise({ try: task, catch: (error) => error }).pipe(
|
|
161
|
-
Effect.catch((error) =>
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
128
|
+
Effect.catch((error) =>
|
|
129
|
+
Effect.sync(() => {
|
|
130
|
+
console.error(
|
|
131
|
+
`error: ${error instanceof Error ? error.message : String(error)}`,
|
|
132
|
+
)
|
|
133
|
+
process.exitCode = 1
|
|
134
|
+
}),
|
|
135
|
+
),
|
|
165
136
|
)
|
|
166
137
|
}
|
|
167
138
|
|
|
@@ -169,7 +140,9 @@ function extract() {
|
|
|
169
140
|
try {
|
|
170
141
|
return extractCommands(process.argv.slice(2))
|
|
171
142
|
} catch (error) {
|
|
172
|
-
console.error(
|
|
143
|
+
console.error(
|
|
144
|
+
`opencode-drive: ${error instanceof Error ? error.message : String(error)}`,
|
|
145
|
+
)
|
|
173
146
|
return process.exit(1)
|
|
174
147
|
}
|
|
175
148
|
}
|