feat(cli): add console logout command
This commit is contained in:
parent
b985d2eb8e
commit
5e6500b52f
4 changed files with 122 additions and 0 deletions
|
|
@ -79,6 +79,9 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
|
||||||
url: Argument.string("url").pipe(Argument.withDescription("Console server URL"), Argument.optional),
|
url: Argument.string("url").pipe(Argument.withDescription("Console server URL"), Argument.optional),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
Spec.make("logout", {
|
||||||
|
description: "Log out of OpenCode Console",
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
Spec.make("auth", {
|
Spec.make("auth", {
|
||||||
|
|
|
||||||
32
packages/cli/src/commands/handlers/console/logout.ts
Normal file
32
packages/cli/src/commands/handlers/console/logout.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { EOL } from "node:os"
|
||||||
|
import { Effect } from "effect"
|
||||||
|
import { OpenCode } from "@opencode-ai/client"
|
||||||
|
import { Service } from "@opencode-ai/client/effect/service"
|
||||||
|
import { Commands } from "../../commands"
|
||||||
|
import { Runtime } from "../../../framework/runtime"
|
||||||
|
import { ServiceConfig } from "../../../services/service-config"
|
||||||
|
|
||||||
|
const integrationID = "opencode"
|
||||||
|
const location = { directory: process.cwd() }
|
||||||
|
|
||||||
|
export default Runtime.handler(
|
||||||
|
Commands.commands.console.commands.logout,
|
||||||
|
Effect.fn("cli.console.logout")(function* () {
|
||||||
|
const endpoint = yield* Service.ensure(yield* ServiceConfig.options())
|
||||||
|
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||||
|
const found = yield* Effect.promise(() => client.integration.get({ integrationID, location }))
|
||||||
|
const credentials = found.data?.connections.filter((connection) => connection.type === "credential") ?? []
|
||||||
|
|
||||||
|
if (credentials.length === 0) {
|
||||||
|
process.stdout.write("Not logged in" + EOL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
yield* Effect.forEach(
|
||||||
|
credentials,
|
||||||
|
(connection) => Effect.promise(() => client.credential.remove({ credentialID: connection.id, location })),
|
||||||
|
{ discard: true },
|
||||||
|
)
|
||||||
|
process.stdout.write("Logged out from OpenCode Console" + EOL)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
@ -25,6 +25,7 @@ const Handlers = Runtime.handlers(Commands, {
|
||||||
},
|
},
|
||||||
console: {
|
console: {
|
||||||
login: () => import("./commands/handlers/console/login"),
|
login: () => import("./commands/handlers/console/login"),
|
||||||
|
logout: () => import("./commands/handlers/console/logout"),
|
||||||
},
|
},
|
||||||
mcp: {
|
mcp: {
|
||||||
list: () => import("./commands/handlers/mcp/list"),
|
list: () => import("./commands/handlers/mcp/list"),
|
||||||
|
|
|
||||||
86
packages/cli/test/console.test.ts
Normal file
86
packages/cli/test/console.test.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import { afterEach, expect, test } from "bun:test"
|
||||||
|
import fs from "node:fs/promises"
|
||||||
|
import os from "node:os"
|
||||||
|
import path from "node:path"
|
||||||
|
import { OPENCODE_VERSION } from "../src/version"
|
||||||
|
|
||||||
|
const cleanup: Array<() => Promise<void>> = []
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await Promise.all(cleanup.splice(0).map((fn) => fn()))
|
||||||
|
})
|
||||||
|
|
||||||
|
async function cli(args: string[], env?: Record<string, string>) {
|
||||||
|
const child = Bun.spawn([process.execPath, "run", "src/index.ts", ...args], {
|
||||||
|
cwd: path.join(import.meta.dir, ".."),
|
||||||
|
env: { ...process.env, ...env },
|
||||||
|
stdout: "pipe",
|
||||||
|
stderr: "pipe",
|
||||||
|
})
|
||||||
|
const [stdout, stderr, exitCode] = await Promise.all([
|
||||||
|
new Response(child.stdout).text(),
|
||||||
|
new Response(child.stderr).text(),
|
||||||
|
child.exited,
|
||||||
|
])
|
||||||
|
return { stdout, stderr, exitCode }
|
||||||
|
}
|
||||||
|
|
||||||
|
test("registers the console logout command", async () => {
|
||||||
|
const result = await cli(["console", "--help"])
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(0)
|
||||||
|
expect(result.stdout).toContain("logout Log out of OpenCode Console")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("removes stored OpenCode Console credentials", async () => {
|
||||||
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-console-logout-"))
|
||||||
|
const removed: string[] = []
|
||||||
|
const server = Bun.serve({
|
||||||
|
port: 0,
|
||||||
|
fetch(request) {
|
||||||
|
const url = new URL(request.url)
|
||||||
|
if (url.pathname === "/api/health") {
|
||||||
|
return Response.json({ healthy: true, version: OPENCODE_VERSION, pid: process.pid })
|
||||||
|
}
|
||||||
|
if (request.method === "GET" && url.pathname === "/api/integration/opencode") {
|
||||||
|
return Response.json({
|
||||||
|
location: { directory: process.cwd(), project: { id: "global", directory: "/" } },
|
||||||
|
data: {
|
||||||
|
id: "opencode",
|
||||||
|
name: "OpenCode",
|
||||||
|
methods: [],
|
||||||
|
connections: removed.length === 0 ? [{ type: "credential", id: "cred_test", label: "default" }] : [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (request.method === "DELETE" && url.pathname === "/api/credential/cred_test") {
|
||||||
|
removed.push(url.pathname)
|
||||||
|
return new Response(null, { status: 204 })
|
||||||
|
}
|
||||||
|
return new Response(null, { status: 404 })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
cleanup.push(async () => {
|
||||||
|
server.stop(true)
|
||||||
|
await fs.rm(root, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
await fs.mkdir(path.join(root, "state", "opencode"), { recursive: true })
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(root, "state", "opencode", "service-local.json"),
|
||||||
|
JSON.stringify({ version: OPENCODE_VERSION, url: server.url.toString(), pid: process.pid }),
|
||||||
|
)
|
||||||
|
const env = {
|
||||||
|
XDG_CACHE_HOME: path.join(root, "cache"),
|
||||||
|
XDG_CONFIG_HOME: path.join(root, "config"),
|
||||||
|
XDG_DATA_HOME: path.join(root, "data"),
|
||||||
|
XDG_STATE_HOME: path.join(root, "state"),
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await cli(["console", "logout"], env)
|
||||||
|
|
||||||
|
expect(result).toMatchObject({ exitCode: 0, stdout: "Logged out from OpenCode Console\n" })
|
||||||
|
expect(removed).toEqual(["/api/credential/cred_test"])
|
||||||
|
|
||||||
|
const repeated = await cli(["console", "logout"], env)
|
||||||
|
expect(repeated).toMatchObject({ exitCode: 0, stdout: "Not logged in\n" })
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue