diff --git a/packages/opencode/src/cli/cmd/agent.ts b/packages/opencode/src/cli/cmd/agent.ts index 60526a62008b..1f1b5e7231f7 100644 --- a/packages/opencode/src/cli/cmd/agent.ts +++ b/packages/opencode/src/cli/cmd/agent.ts @@ -1,5 +1,6 @@ import { cmd } from "./cmd" import * as prompts from "@clack/prompts" +import { createSpinner } from "../prompt" import { UI } from "../ui" import { Global } from "@opencode-ai/core/global" import { Agent } from "../../agent/agent" @@ -124,7 +125,7 @@ const AgentCreateCommand = effectCmd({ } // Generate agent - const spinner = prompts.spinner() + const spinner = createSpinner() spinner.start("Generating agent configuration...") const model = args.model ? Provider.parseModel(args.model) : undefined const generated = await Effect.runPromise(agentSvc.generate({ description, model })).catch((error) => { diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index ea5b35ef7868..422f514ca636 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -2,6 +2,7 @@ import path from "path" import { exec } from "child_process" import { Filesystem } from "@/util/filesystem" import * as prompts from "@clack/prompts" +import { createSpinner } from "../prompt" import { map, pipe, sortBy, values } from "remeda" import { Octokit } from "@octokit/rest" import { graphql } from "@octokit/graphql" @@ -325,7 +326,7 @@ export const GithubInstallCommand = effectCmd({ } async function installGitHubApp() { - const s = prompts.spinner() + const s = createSpinner() s.start("Installing GitHub app") // Get installation diff --git a/packages/opencode/src/cli/cmd/mcp.ts b/packages/opencode/src/cli/cmd/mcp.ts index 2ae7cece6a27..2b77d93961a0 100644 --- a/packages/opencode/src/cli/cmd/mcp.ts +++ b/packages/opencode/src/cli/cmd/mcp.ts @@ -5,6 +5,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js" import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js" import * as prompts from "@clack/prompts" +import { createSpinner } from "../prompt" import { UI } from "../ui" import { MCP } from "../../mcp" import { McpAuth } from "../../mcp/auth" @@ -253,7 +254,7 @@ export const McpAuthCommand = effectCmd({ prompts.log.warn(`${serverName} has expired credentials. Re-authenticating...`) } - const spinner = prompts.spinner() + const spinner = createSpinner() spinner.start("Starting OAuth flow...") // Subscribe to browser open failure events to show URL for manual opening @@ -665,7 +666,7 @@ export const McpDebugCommand = effectCmd({ } } - const spinner = prompts.spinner() + const spinner = createSpinner() spinner.start("Testing connection...") // Test basic HTTP connectivity first diff --git a/packages/opencode/src/cli/cmd/plug.ts b/packages/opencode/src/cli/cmd/plug.ts index 1529e9b71df3..733e041210ea 100644 --- a/packages/opencode/src/cli/cmd/plug.ts +++ b/packages/opencode/src/cli/cmd/plug.ts @@ -1,4 +1,5 @@ -import { intro, log, outro, spinner } from "@clack/prompts" +import { intro, log, outro } from "@clack/prompts" +import { createSpinner } from "../prompt" import { Effect } from "effect" import { ConfigPaths } from "@/config/paths" @@ -45,7 +46,7 @@ export type PlugCtx = { } const defaultPlugDeps: PlugDeps = { - spinner: () => spinner(), + spinner: () => createSpinner(), log: { error: (msg) => log.error(msg), info: (msg) => log.info(msg), diff --git a/packages/opencode/src/cli/cmd/uninstall.ts b/packages/opencode/src/cli/cmd/uninstall.ts index 0afdc518545d..0eb5506ce41c 100644 --- a/packages/opencode/src/cli/cmd/uninstall.ts +++ b/packages/opencode/src/cli/cmd/uninstall.ts @@ -1,6 +1,7 @@ import type { Argv } from "yargs" import { UI } from "../ui" import * as prompts from "@clack/prompts" +import { createSpinner } from "../prompt" import { Installation } from "../../installation" import { Global } from "@opencode-ai/core/global" import fs from "fs/promises" @@ -142,7 +143,7 @@ async function showRemovalSummary(targets: RemovalTargets, method: Installation. } async function executeUninstall(method: Installation.Method, targets: RemovalTargets) { - const spinner = prompts.spinner() + const spinner = createSpinner() const errors: string[] = [] for (const dir of targets.directories) { diff --git a/packages/opencode/src/cli/cmd/upgrade.ts b/packages/opencode/src/cli/cmd/upgrade.ts index 3c1604a0b835..8f7ce01696cc 100644 --- a/packages/opencode/src/cli/cmd/upgrade.ts +++ b/packages/opencode/src/cli/cmd/upgrade.ts @@ -1,6 +1,7 @@ import type { Argv } from "yargs" import { UI } from "../ui" import * as prompts from "@clack/prompts" +import { createSpinner } from "../prompt" import { Installation } from "../../installation" import { InstallationVersion } from "@opencode-ai/core/installation/version" @@ -37,7 +38,7 @@ export const UpgradeCommand = { ], initialValue: false, }) - if (!install) { + if (prompts.isCancel(install) || !install) { prompts.outro("Done") return } @@ -52,7 +53,7 @@ export const UpgradeCommand = { } prompts.log.info(`From ${InstallationVersion} → ${target}`) - const spinner = prompts.spinner() + const spinner = createSpinner() spinner.start("Upgrading...") const err = await Installation.upgrade(method, target).catch((err) => err) if (err) { diff --git a/packages/opencode/src/cli/effect/prompt.ts b/packages/opencode/src/cli/effect/prompt.ts index 2713f1a5b87a..0664b4ac9afb 100644 --- a/packages/opencode/src/cli/effect/prompt.ts +++ b/packages/opencode/src/cli/effect/prompt.ts @@ -1,5 +1,6 @@ import * as prompts from "@clack/prompts" import { Effect, Option } from "effect" +import { createSpinner } from "../prompt" export const intro = (msg: string) => Effect.sync(() => prompts.intro(msg)) export const outro = (msg: string) => Effect.sync(() => prompts.outro(msg)) @@ -29,7 +30,7 @@ export const password = (opts: Parameters[0]) => Effect.promise(() => prompts.password(opts)).pipe(Effect.map((result) => optional(result))) export const spinner = () => { - const s = prompts.spinner() + const s = createSpinner() return { start: (msg: string) => Effect.sync(() => s.start(msg)), stop: (msg: string, code?: number) => Effect.sync(() => s.stop(msg, code)), diff --git a/packages/opencode/src/cli/prompt.ts b/packages/opencode/src/cli/prompt.ts new file mode 100644 index 000000000000..237586ee2bcd --- /dev/null +++ b/packages/opencode/src/cli/prompt.ts @@ -0,0 +1,5 @@ +import * as prompts from "@clack/prompts" + +const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const + +export const createSpinner = () => prompts.spinner({ frames: [...SPINNER_FRAMES] })