opencode-drive 0.1.3 → 0.1.5
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 +16 -96
- package/package.json +1 -1
- package/src/cli/commands.ts +4 -1
- package/src/cli/control.ts +112 -12
- package/src/cli/index.ts +78 -7
- package/src/cli/instance.ts +38 -29
- package/src/cli/list.ts +10 -0
- package/src/cli/logs.ts +9 -0
- package/src/cli/mock-backend.ts +27 -15
- package/src/cli/registry.ts +218 -23
- package/src/cli/response-generator.ts +334 -0
- package/src/cli/responses.ts +20 -0
- package/src/cli/restart.ts +1 -1
- package/src/cli/script.ts +11 -0
- package/src/cli/send.ts +4 -1
- package/src/cli/start.ts +130 -18
- package/src/cli/stop.ts +21 -4
- package/src/cli/types.ts +1 -1
- package/src/client/backend.ts +1 -1
- package/src/client/client.ts +86 -29
- package/src/client/protocol.ts +1 -0
- package/src/cli/describe.ts +0 -13
package/src/cli/send.ts
CHANGED
|
@@ -5,7 +5,10 @@ import { resolveInstance } from "./registry.js"
|
|
|
5
5
|
export async function send(options: SendOptions) {
|
|
6
6
|
if (options.commands.length === 0)
|
|
7
7
|
throw new Error("send requires at least one --command.ui.* flag")
|
|
8
|
-
const result = await executeCommands(
|
|
8
|
+
const result = await executeCommands(
|
|
9
|
+
(await resolveInstance(options.name)).endpoints.ui,
|
|
10
|
+
options.commands,
|
|
11
|
+
)
|
|
9
12
|
if (
|
|
10
13
|
options.commands.length === 1 &&
|
|
11
14
|
["ui.screenshot", "ui.end-record"].includes(
|
package/src/cli/start.ts
CHANGED
|
@@ -1,49 +1,101 @@
|
|
|
1
1
|
import { launchInstance } from "./instance.js"
|
|
2
|
+
import { mkdir, rm } from "node:fs/promises"
|
|
3
|
+
import { join } from "node:path"
|
|
2
4
|
import { connectMockBackend } from "./mock-backend.js"
|
|
5
|
+
import { createResponseSettings } from "./response-generator.js"
|
|
3
6
|
import { runScript } from "./script.js"
|
|
4
7
|
import { listenControl } from "./control.js"
|
|
8
|
+
import {
|
|
9
|
+
controlPath,
|
|
10
|
+
markReady,
|
|
11
|
+
markStarting,
|
|
12
|
+
register,
|
|
13
|
+
registryDirectory,
|
|
14
|
+
resolveInstance,
|
|
15
|
+
unregister,
|
|
16
|
+
} from "./registry.js"
|
|
5
17
|
import type { StartOptions } from "./types.js"
|
|
6
18
|
|
|
7
19
|
export async function start(options: StartOptions) {
|
|
20
|
+
if (!options.visible && !options.script && !options.daemon)
|
|
21
|
+
return startDetached(options)
|
|
22
|
+
const responses = createResponseSettings()
|
|
8
23
|
const instance = await launchInstance({
|
|
24
|
+
name: options.name,
|
|
9
25
|
command: options.command,
|
|
10
26
|
dev: options.dev,
|
|
11
27
|
state: options.state,
|
|
12
28
|
scripted: options.script !== undefined,
|
|
13
29
|
visible: options.visible,
|
|
14
30
|
})
|
|
31
|
+
await register({
|
|
32
|
+
version: 1,
|
|
33
|
+
name: options.name,
|
|
34
|
+
pid: process.pid,
|
|
35
|
+
startedAt: new Date().toISOString(),
|
|
36
|
+
cwd: process.cwd(),
|
|
37
|
+
artifacts: instance.artifacts,
|
|
38
|
+
visible: options.visible,
|
|
39
|
+
status: "starting",
|
|
40
|
+
endpoints: instance.endpoints,
|
|
41
|
+
control: controlPath(options.name),
|
|
42
|
+
}).catch(async (error) => {
|
|
43
|
+
await instance.stop()
|
|
44
|
+
throw error
|
|
45
|
+
})
|
|
15
46
|
const interrupt = () => void instance.stop()
|
|
16
47
|
let completed = false
|
|
17
48
|
let current: ReturnType<typeof run> | undefined
|
|
18
49
|
let restarting: Promise<void> | undefined
|
|
50
|
+
let stopping = false
|
|
19
51
|
process.once("SIGINT", interrupt)
|
|
20
52
|
process.once("SIGTERM", interrupt)
|
|
21
|
-
|
|
22
|
-
|
|
53
|
+
let closeControl: (() => Promise<void>) | undefined
|
|
54
|
+
try {
|
|
55
|
+
closeControl = await listenControl(controlPath(options.name), {
|
|
56
|
+
restart: () => {
|
|
23
57
|
if (restarting) return restarting
|
|
24
58
|
restarting = (async () => {
|
|
59
|
+
await markStarting(options.name, process.pid)
|
|
25
60
|
const previous = current
|
|
26
61
|
previous?.abort.abort(new Error("script restarted"))
|
|
27
62
|
await previous?.promise.catch(() => undefined)
|
|
28
63
|
await instance.restart()
|
|
29
|
-
current = run(options, instance)
|
|
64
|
+
current = run(options, instance, responses)
|
|
30
65
|
await current.ready
|
|
66
|
+
await markReady(options.name, process.pid)
|
|
31
67
|
})().finally(() => {
|
|
32
68
|
restarting = undefined
|
|
33
69
|
})
|
|
34
70
|
return restarting
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
71
|
+
},
|
|
72
|
+
stop: async () => {
|
|
73
|
+
stopping = true
|
|
74
|
+
current?.abort.abort(new Error("opencode-drive stopped"))
|
|
75
|
+
await instance.stop()
|
|
76
|
+
},
|
|
77
|
+
responses: async (input) => {
|
|
78
|
+
if (options.script)
|
|
79
|
+
throw new Error("responses are unavailable when --script owns the simulation backend")
|
|
80
|
+
return responses.update(input)
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
current = run(options, instance, responses)
|
|
84
|
+
await current.ready
|
|
85
|
+
await markReady(options.name, process.pid)
|
|
39
86
|
if (options.visible) {
|
|
40
87
|
const status = await instance.wait()
|
|
41
|
-
if (status !== 0) process.exitCode = status
|
|
88
|
+
if (status !== 0 && !stopping) process.exitCode = status
|
|
42
89
|
return
|
|
43
90
|
}
|
|
44
91
|
while (true) {
|
|
45
92
|
const active: NonNullable<typeof current> = current
|
|
46
93
|
await active.promise
|
|
94
|
+
if (stopping) break
|
|
95
|
+
if (restarting) {
|
|
96
|
+
await restarting
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
47
99
|
if (active !== current) continue
|
|
48
100
|
completed = true
|
|
49
101
|
break
|
|
@@ -54,14 +106,76 @@ export async function start(options: StartOptions) {
|
|
|
54
106
|
current?.abort.abort(new Error("opencode-drive stopped"))
|
|
55
107
|
await closeControl?.()
|
|
56
108
|
await instance.stop()
|
|
109
|
+
await unregister(options.name, process.pid)
|
|
57
110
|
if (options.script && !options.visible)
|
|
58
111
|
report(instance, completed ? "completed" : undefined)
|
|
59
112
|
}
|
|
60
113
|
}
|
|
61
114
|
|
|
115
|
+
async function startDetached(options: StartOptions) {
|
|
116
|
+
const existing = await resolveInstance(options.name, { ready: false }).catch(() => undefined)
|
|
117
|
+
if (existing)
|
|
118
|
+
throw new Error(`drive instance "${options.name}" is already running`)
|
|
119
|
+
const ownerLog = join(registryDirectory(), `${options.name}.log`)
|
|
120
|
+
await mkdir(registryDirectory(), { recursive: true })
|
|
121
|
+
await rm(ownerLog, { force: true })
|
|
122
|
+
const child = Bun.spawn(
|
|
123
|
+
[
|
|
124
|
+
process.execPath,
|
|
125
|
+
process.argv[1]!,
|
|
126
|
+
"start",
|
|
127
|
+
"--daemon",
|
|
128
|
+
"--name",
|
|
129
|
+
options.name,
|
|
130
|
+
...(options.script ? ["--script", options.script] : []),
|
|
131
|
+
...(options.dev ? ["--dev", options.dev] : []),
|
|
132
|
+
...(options.state ? ["--state", options.state] : []),
|
|
133
|
+
...(options.command.length ? ["--", ...options.command] : []),
|
|
134
|
+
],
|
|
135
|
+
{
|
|
136
|
+
cwd: process.cwd(),
|
|
137
|
+
env: process.env,
|
|
138
|
+
stdin: "ignore",
|
|
139
|
+
stdout: "ignore",
|
|
140
|
+
stderr: Bun.file(ownerLog),
|
|
141
|
+
},
|
|
142
|
+
)
|
|
143
|
+
child.unref()
|
|
144
|
+
const deadline = Date.now() + 60_000
|
|
145
|
+
while (Date.now() < deadline) {
|
|
146
|
+
const manifest = await resolveInstance(options.name).catch(() => undefined)
|
|
147
|
+
if (manifest?.pid === child.pid) {
|
|
148
|
+
report({
|
|
149
|
+
artifacts: manifest.artifacts,
|
|
150
|
+
logs: `${manifest.artifacts}/logs`,
|
|
151
|
+
})
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
if (child.exitCode !== null)
|
|
155
|
+
throw new Error(
|
|
156
|
+
`detached instance exited with status ${child.exitCode}; see ${ownerLog}`,
|
|
157
|
+
)
|
|
158
|
+
await Bun.sleep(50)
|
|
159
|
+
}
|
|
160
|
+
await terminateOwner(child)
|
|
161
|
+
throw new Error(
|
|
162
|
+
`timed out starting drive instance "${options.name}"; see ${ownerLog}`,
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function terminateOwner(child: Bun.Subprocess) {
|
|
167
|
+
if (child.exitCode !== null) return
|
|
168
|
+
child.kill("SIGTERM")
|
|
169
|
+
const deadline = Date.now() + 1_000
|
|
170
|
+
while (child.exitCode === null && Date.now() < deadline) await Bun.sleep(25)
|
|
171
|
+
if (child.exitCode === null) child.kill("SIGKILL")
|
|
172
|
+
await child.exited
|
|
173
|
+
}
|
|
174
|
+
|
|
62
175
|
function run(
|
|
63
176
|
options: StartOptions,
|
|
64
177
|
instance: Awaited<ReturnType<typeof launchInstance>>,
|
|
178
|
+
responses: ReturnType<typeof createResponseSettings>,
|
|
65
179
|
) {
|
|
66
180
|
const abort = new AbortController()
|
|
67
181
|
const child = instance.child
|
|
@@ -86,26 +200,25 @@ function run(
|
|
|
86
200
|
if (options.visible) {
|
|
87
201
|
await Promise.race([
|
|
88
202
|
child.exited,
|
|
89
|
-
new Promise<void>((resolve) =>
|
|
203
|
+
new Promise<void>((resolve) =>
|
|
90
204
|
abort.signal.addEventListener("abort", () => resolve(), {
|
|
91
205
|
once: true,
|
|
92
|
-
})
|
|
93
|
-
|
|
206
|
+
}),
|
|
207
|
+
),
|
|
94
208
|
])
|
|
95
209
|
}
|
|
96
210
|
return
|
|
97
211
|
}
|
|
98
|
-
const mock = await connectMockBackend(instance.endpoints.backend)
|
|
212
|
+
const mock = await connectMockBackend(instance.endpoints.backend, responses)
|
|
99
213
|
ready()
|
|
100
214
|
abort.signal.addEventListener("abort", () => mock.close(), { once: true })
|
|
101
|
-
if (!options.visible) report(instance)
|
|
102
215
|
const status = await Promise.race([
|
|
103
216
|
child.exited,
|
|
104
|
-
new Promise<number>((resolve) =>
|
|
217
|
+
new Promise<number>((resolve) =>
|
|
105
218
|
abort.signal.addEventListener("abort", () => resolve(0), {
|
|
106
219
|
once: true,
|
|
107
|
-
})
|
|
108
|
-
|
|
220
|
+
}),
|
|
221
|
+
),
|
|
109
222
|
])
|
|
110
223
|
mock.close()
|
|
111
224
|
if (status !== 0 && !abort.signal.aborted) process.exitCode = status
|
|
@@ -114,10 +227,9 @@ function run(
|
|
|
114
227
|
}
|
|
115
228
|
|
|
116
229
|
function report(
|
|
117
|
-
instance:
|
|
230
|
+
instance: { readonly artifacts: string; readonly logs: string },
|
|
118
231
|
status?: string,
|
|
119
232
|
) {
|
|
120
233
|
if (status) console.error(`opencode-drive: ${status}`)
|
|
121
234
|
console.error(`opencode-drive: artifacts ${instance.artifacts}`)
|
|
122
|
-
console.error(`opencode-drive: logs ${instance.logs}`)
|
|
123
235
|
}
|
package/src/cli/stop.ts
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import { request } from "./control.js"
|
|
2
|
-
import { resolveInstance } from "./registry.js"
|
|
2
|
+
import { manifestPath, resolveInstance } from "./registry.js"
|
|
3
3
|
|
|
4
|
-
export async function stop(name
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export async function stop(name?: string) {
|
|
5
|
+
const manifest = await resolveInstance(name)
|
|
6
|
+
await request(manifest.control, "stop")
|
|
7
|
+
const deadline = Date.now() + 10_000
|
|
8
|
+
while (Date.now() < deadline) {
|
|
9
|
+
const current: unknown = await Bun.file(manifestPath(manifest.name))
|
|
10
|
+
.json()
|
|
11
|
+
.catch(() => undefined)
|
|
12
|
+
if (
|
|
13
|
+
typeof current !== "object" ||
|
|
14
|
+
current === null ||
|
|
15
|
+
!("pid" in current) ||
|
|
16
|
+
current.pid !== manifest.pid
|
|
17
|
+
) {
|
|
18
|
+
console.log("success")
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
await Bun.sleep(25)
|
|
22
|
+
}
|
|
23
|
+
throw new Error(`timed out stopping drive instance "${manifest.name}"`)
|
|
7
24
|
}
|
package/src/cli/types.ts
CHANGED
package/src/client/backend.ts
CHANGED
package/src/client/client.ts
CHANGED
|
@@ -1,21 +1,48 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Frontend,
|
|
3
|
-
type JsonRpc,
|
|
4
|
-
} from "./protocol.js"
|
|
1
|
+
import { Frontend, type JsonRpc } from "./protocol.js"
|
|
5
2
|
|
|
6
3
|
const defaultPort = 40900
|
|
7
4
|
|
|
8
5
|
type Methods = {
|
|
9
|
-
readonly "ui.screenshot": {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
readonly "ui.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
readonly "ui.
|
|
18
|
-
|
|
6
|
+
readonly "ui.screenshot": {
|
|
7
|
+
readonly params: undefined
|
|
8
|
+
readonly result: Frontend.Screenshot
|
|
9
|
+
}
|
|
10
|
+
readonly "ui.state": {
|
|
11
|
+
readonly params: undefined
|
|
12
|
+
readonly result: Frontend.State
|
|
13
|
+
}
|
|
14
|
+
readonly "ui.start-record": {
|
|
15
|
+
readonly params: undefined
|
|
16
|
+
readonly result: Frontend.StartRecord
|
|
17
|
+
}
|
|
18
|
+
readonly "ui.end-record": {
|
|
19
|
+
readonly params: undefined
|
|
20
|
+
readonly result: Frontend.EndRecord
|
|
21
|
+
}
|
|
22
|
+
readonly "ui.type": {
|
|
23
|
+
readonly params: Frontend.TypeParams
|
|
24
|
+
readonly result: Frontend.State
|
|
25
|
+
}
|
|
26
|
+
readonly "ui.press": {
|
|
27
|
+
readonly params: Frontend.PressParams
|
|
28
|
+
readonly result: Frontend.State
|
|
29
|
+
}
|
|
30
|
+
readonly "ui.enter": {
|
|
31
|
+
readonly params: undefined
|
|
32
|
+
readonly result: Frontend.State
|
|
33
|
+
}
|
|
34
|
+
readonly "ui.arrow": {
|
|
35
|
+
readonly params: Frontend.ArrowParams
|
|
36
|
+
readonly result: Frontend.State
|
|
37
|
+
}
|
|
38
|
+
readonly "ui.focus": {
|
|
39
|
+
readonly params: Frontend.FocusParams
|
|
40
|
+
readonly result: Frontend.State
|
|
41
|
+
}
|
|
42
|
+
readonly "ui.click": {
|
|
43
|
+
readonly params: Frontend.ClickParams
|
|
44
|
+
readonly result: Frontend.State
|
|
45
|
+
}
|
|
19
46
|
}
|
|
20
47
|
|
|
21
48
|
type MethodName = keyof Methods
|
|
@@ -73,12 +100,20 @@ export class SimulationClient {
|
|
|
73
100
|
this.socket = socket
|
|
74
101
|
this.url = url
|
|
75
102
|
this.timeout = timeout
|
|
76
|
-
socket.addEventListener("message", (event) =>
|
|
77
|
-
|
|
78
|
-
|
|
103
|
+
socket.addEventListener("message", (event) =>
|
|
104
|
+
this.onMessage(String(event.data)),
|
|
105
|
+
)
|
|
106
|
+
socket.addEventListener("close", () =>
|
|
107
|
+
this.rejectAll(new SimulationError("connection closed")),
|
|
108
|
+
)
|
|
109
|
+
socket.addEventListener("error", () =>
|
|
110
|
+
this.rejectAll(new SimulationError("connection error")),
|
|
111
|
+
)
|
|
79
112
|
}
|
|
80
113
|
|
|
81
|
-
static async connect(
|
|
114
|
+
static async connect(
|
|
115
|
+
options?: SimulationClientOptions,
|
|
116
|
+
): Promise<SimulationClient> {
|
|
82
117
|
const timeout = options?.timeout ?? 30_000
|
|
83
118
|
if (options?.url !== undefined) {
|
|
84
119
|
return new SimulationClient(await open(options.url), options.url, timeout)
|
|
@@ -100,7 +135,10 @@ export class SimulationClient {
|
|
|
100
135
|
}
|
|
101
136
|
|
|
102
137
|
/** Raw JSON-RPC call. Prefer the typed wrappers below. */
|
|
103
|
-
async call<M extends MethodName>(
|
|
138
|
+
async call<M extends MethodName>(
|
|
139
|
+
method: M,
|
|
140
|
+
params?: Methods[M]["params"],
|
|
141
|
+
): Promise<Methods[M]["result"]> {
|
|
104
142
|
if (this.socket.readyState !== WebSocket.OPEN) {
|
|
105
143
|
throw new SimulationError("connection is not open", method)
|
|
106
144
|
}
|
|
@@ -112,7 +150,14 @@ export class SimulationClient {
|
|
|
112
150
|
}, this.timeout)
|
|
113
151
|
this.pending.set(id, { method, resolve, reject, timer })
|
|
114
152
|
})
|
|
115
|
-
this.socket.send(
|
|
153
|
+
this.socket.send(
|
|
154
|
+
JSON.stringify({
|
|
155
|
+
jsonrpc: "2.0",
|
|
156
|
+
id,
|
|
157
|
+
method,
|
|
158
|
+
...(params === undefined ? {} : { params }),
|
|
159
|
+
}),
|
|
160
|
+
)
|
|
116
161
|
// The server contract types each method's result; the cast happens once
|
|
117
162
|
// here rather than at every call site.
|
|
118
163
|
return (await promise) as Methods[M]["result"]
|
|
@@ -142,15 +187,23 @@ export class SimulationClient {
|
|
|
142
187
|
return this.call("ui.type", { text })
|
|
143
188
|
}
|
|
144
189
|
|
|
145
|
-
pressKey(
|
|
146
|
-
|
|
190
|
+
pressKey(
|
|
191
|
+
key: string,
|
|
192
|
+
modifiers?: Frontend.KeyModifiers,
|
|
193
|
+
): Promise<Frontend.State> {
|
|
194
|
+
return this.call("ui.press", {
|
|
195
|
+
key: key === "escape" ? "\u001b" : key,
|
|
196
|
+
...(modifiers === undefined ? {} : { modifiers }),
|
|
197
|
+
})
|
|
147
198
|
}
|
|
148
199
|
|
|
149
200
|
pressEnter(): Promise<Frontend.State> {
|
|
150
201
|
return this.call("ui.enter")
|
|
151
202
|
}
|
|
152
203
|
|
|
153
|
-
pressArrow(
|
|
204
|
+
pressArrow(
|
|
205
|
+
direction: "up" | "down" | "left" | "right",
|
|
206
|
+
): Promise<Frontend.State> {
|
|
154
207
|
return this.call("ui.arrow", { direction })
|
|
155
208
|
}
|
|
156
209
|
|
|
@@ -165,7 +218,7 @@ export class SimulationClient {
|
|
|
165
218
|
// ── lifecycle ─────────────────────────────────────────────────────────
|
|
166
219
|
|
|
167
220
|
close(): void {
|
|
168
|
-
this.socket.
|
|
221
|
+
this.socket.terminate()
|
|
169
222
|
}
|
|
170
223
|
|
|
171
224
|
private onMessage(data: string) {
|
|
@@ -175,7 +228,8 @@ export class SimulationClient {
|
|
|
175
228
|
if (waiter === undefined) return
|
|
176
229
|
this.pending.delete(message.id)
|
|
177
230
|
clearTimeout(waiter.timer)
|
|
178
|
-
if (message.error)
|
|
231
|
+
if (message.error)
|
|
232
|
+
waiter.reject(new SimulationError(message.error.message, waiter.method))
|
|
179
233
|
else waiter.resolve(message.result)
|
|
180
234
|
}
|
|
181
235
|
|
|
@@ -199,14 +253,16 @@ function parseResponse(data: string): JsonRpc.Response | undefined {
|
|
|
199
253
|
if (!("jsonrpc" in value) || value.jsonrpc !== "2.0") return undefined
|
|
200
254
|
if (!("id" in value)) return undefined
|
|
201
255
|
const id = value.id
|
|
202
|
-
if (typeof id !== "number" && typeof id !== "string" && id !== null)
|
|
256
|
+
if (typeof id !== "number" && typeof id !== "string" && id !== null)
|
|
257
|
+
return undefined
|
|
203
258
|
const result = "result" in value ? value.result : undefined
|
|
204
259
|
const error = "error" in value ? value.error : undefined
|
|
205
260
|
if (error !== undefined) {
|
|
206
261
|
if (typeof error !== "object" || error === null) return undefined
|
|
207
262
|
const code = "code" in error ? error.code : undefined
|
|
208
263
|
const message = "message" in error ? error.message : undefined
|
|
209
|
-
if (typeof code !== "number" || typeof message !== "string")
|
|
264
|
+
if (typeof code !== "number" || typeof message !== "string")
|
|
265
|
+
return undefined
|
|
210
266
|
return { jsonrpc: "2.0", id, error: { code, message } }
|
|
211
267
|
}
|
|
212
268
|
return { jsonrpc: "2.0", id, result: result as JsonRpc.Response["result"] }
|
|
@@ -232,5 +288,6 @@ function open(url: string): Promise<WebSocket> {
|
|
|
232
288
|
})
|
|
233
289
|
}
|
|
234
290
|
|
|
235
|
-
export const connectSimulation = (
|
|
236
|
-
|
|
291
|
+
export const connectSimulation = (
|
|
292
|
+
options?: SimulationClientOptions,
|
|
293
|
+
): Promise<SimulationClient> => SimulationClient.connect(options)
|
package/src/client/protocol.ts
CHANGED
package/src/cli/describe.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { resolveInstance } from "./registry.js"
|
|
2
|
-
|
|
3
|
-
export async function describe(name: string) {
|
|
4
|
-
const manifest = await resolveInstance(name)
|
|
5
|
-
console.log([
|
|
6
|
-
`PID: ${manifest.pid}`,
|
|
7
|
-
`Visible: ${manifest.visible}`,
|
|
8
|
-
`Artifacts: ${manifest.artifacts}`,
|
|
9
|
-
`UI: ${manifest.endpoints.ui}`,
|
|
10
|
-
`Backend: ${manifest.endpoints.backend}`,
|
|
11
|
-
`Logs: ${manifest.artifacts}/logs`,
|
|
12
|
-
].join("\n"))
|
|
13
|
-
}
|