1
0
Эх сурвалжийг харах

feat(cli): add console logout command

Victor Navarro 2 долоо хоног өмнө
parent
commit
5e6500b52f

+ 3 - 0
packages/cli/src/commands/commands.ts

@@ -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),
           },
         }),
+        Spec.make("logout", {
+          description: "Log out of OpenCode Console",
+        }),
       ],
     }),
     Spec.make("auth", {

+ 32 - 0
packages/cli/src/commands/handlers/console/logout.ts

@@ -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)
+  }),
+)

+ 1 - 0
packages/cli/src/index.ts

@@ -25,6 +25,7 @@ const Handlers = Runtime.handlers(Commands, {
   },
   console: {
     login: () => import("./commands/handlers/console/login"),
+    logout: () => import("./commands/handlers/console/logout"),
   },
   mcp: {
     list: () => import("./commands/handlers/mcp/list"),

+ 86 - 0
packages/cli/test/console.test.ts

@@ -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" })
+})