diff --git a/src/cli/repl-commands/adapter-cli-reference.ts b/src/cli/repl-commands/adapter-cli-reference.ts new file mode 100644 index 0000000..73dbbf5 --- /dev/null +++ b/src/cli/repl-commands/adapter-cli-reference.ts @@ -0,0 +1,93 @@ +import type { Adapter } from "../../core/pipeline/types.js"; +import { colors } from "../colors.js"; + +export interface AdapterLoginCommandSpec { + command: string; + args: string[]; +} + +interface AdapterCliReferenceSection { + title: string; + commands: string[]; + note?: string; +} + +export const getAdapterLoginCommandSpec = ( + adapter: Adapter, +): AdapterLoginCommandSpec => { + if (adapter === "codex") { + return { command: "codex", args: ["login"] }; + } + + if (adapter === "gemini") { + return { command: "gemini", args: [] }; + } + + return { command: "claude", args: ["auth", "login"] }; +}; + +export const getLoginHint = (adapter: Adapter): string => { + const spec = getAdapterLoginCommandSpec(adapter); + return [spec.command, ...spec.args].join(" ").trim(); +}; + +export const getLogoutHint = (adapter: Adapter): string => + adapter === "claude" ? "claude auth logout" : `${adapter} logout`; + +const getAdapterCliReferenceSections = (): AdapterCliReferenceSection[] => [ + { + title: "Codex CLI", + commands: [ + "codex login", + "codex login status", + "codex debug models", + "codex /goal ", + "codex /goal pause | resume | clear", + "codex logout", + ], + note: "로그인 / 상태 / 모델 조회 / 목표 관리(/goal)는 Codex 원본 CLI에서 처리합니다.", + }, + { + title: "Gemini CLI", + commands: ["gemini"], + note: "인증 진입은 gemini로 하고, 모델 선택은 detoks /gms가 맡습니다.", + }, + { + title: "Claude Code", + commands: [ + "claude auth login", + "claude auth status --json", + "claude --model ", + "claude auth logout", + ], + note: "Claude는 auth 후 detoks /claude-models가 모델 선택을 맡고, 실행 시 --model로 전달됩니다.", + }, +]; + +export const formatAdapterCliReference = (): string => { + const lines: string[] = [ + "", + `${colors.title("외부 adapter CLI 참고")}`, + "", + ]; + + for (const section of getAdapterCliReferenceSections()) { + lines.push(` ${colors.boldText(section.title)}`); + for (const command of section.commands) { + lines.push(` ${colors.muted(command)}`); + } + if (section.note) { + lines.push(` ${colors.muted(section.note)}`); + } + lines.push(""); + } + + lines.push( + colors.muted( + " detoks 명령은 REPL 안에서, adapter CLI 원본 명령은 외부 터미널에서 사용하세요.", + ), + ); + lines.push(""); + + return `${lines.join("\n")}\n`; +}; diff --git a/src/cli/repl-commands/index.ts b/src/cli/repl-commands/index.ts index b4deb29..55c8cce 100644 --- a/src/cli/repl-commands/index.ts +++ b/src/cli/repl-commands/index.ts @@ -47,6 +47,13 @@ import { import { loadLastCustomModel, saveCustomModel } from "../model-setup/custom-store.js"; import { parseHfRepoInput, listGgufFiles, HfRepoError } from "../model-setup/hf-repo.js"; import { promptLine } from "../interactive/prompt-line.js"; +import { + formatAdapterCliReference, + getAdapterLoginCommandSpec, + getLoginHint, + getLogoutHint, +} from "./adapter-cli-reference.js"; +export { formatAdapterCliReference, getAdapterLoginCommandSpec } from "./adapter-cli-reference.js"; export interface SlashCommand { name: string; @@ -211,92 +218,6 @@ export const isSlashCommand = ( return input.startsWith("/") && getSlashCommand(input, adapter) !== null; }; -export const getAdapterLoginCommandSpec = ( - adapter: Adapter, -): { command: string; args: string[] } => { - if (adapter === "codex") { - return { command: "codex", args: ["login"] }; - } - - if (adapter === "gemini") { - return { command: "gemini", args: [] }; - } - - return { command: "claude", args: ["auth", "login"] }; -}; - -const getLoginHint = (adapter: Adapter): string => { - const spec = getAdapterLoginCommandSpec(adapter); - return [spec.command, ...spec.args].join(" ").trim(); -}; - -const getLogoutHint = (adapter: Adapter): string => - adapter === "claude" ? "claude auth logout" : `${adapter} logout`; - -interface AdapterCliReferenceSection { - title: string; - commands: string[]; - note?: string; -} - -const getAdapterCliReferenceSections = (): AdapterCliReferenceSection[] => [ - { - title: "Codex CLI", - commands: [ - "codex login", - "codex login status", - "codex debug models", - "codex /goal ", - "codex /goal pause | resume | clear", - "codex logout", - ], - note: "로그인 / 상태 / 모델 조회 / 목표 관리(/goal)는 Codex 원본 CLI에서 처리합니다.", - }, - { - title: "Gemini CLI", - commands: ["gemini"], - note: "인증 진입은 gemini로 하고, 모델 선택은 detoks /gms가 맡습니다.", - }, - { - title: "Claude Code", - commands: [ - "claude auth login", - "claude auth status --json", - "claude --model ", - "claude auth logout", - ], - note: "Claude는 auth 후 detoks /claude-models가 모델 선택을 맡고, 실행 시 --model로 전달됩니다.", - }, -]; - -export const formatAdapterCliReference = (): string => { - const lines: string[] = [ - "", - `${colors.title("외부 adapter CLI 참고")}`, - "", - ]; - - for (const section of getAdapterCliReferenceSections()) { - lines.push(` ${colors.boldText(section.title)}`); - for (const command of section.commands) { - lines.push(` ${colors.muted(command)}`); - } - if (section.note) { - lines.push(` ${colors.muted(section.note)}`); - } - lines.push(""); - } - - lines.push( - colors.muted( - " detoks 명령은 REPL 안에서, adapter CLI 원본 명령은 외부 터미널에서 사용하세요.", - ), - ); - lines.push(""); - - return `${lines.join("\n")}\n`; -}; - const buildSlashCommandMenuOptions = ( adapter: Adapter, ): SelectOption[] => diff --git a/tests/ts/unit/cli/repl-commands/adapter-cli-reference.test.ts b/tests/ts/unit/cli/repl-commands/adapter-cli-reference.test.ts new file mode 100644 index 0000000..073e9c4 --- /dev/null +++ b/tests/ts/unit/cli/repl-commands/adapter-cli-reference.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest"; +import { + formatAdapterCliReference, + getAdapterLoginCommandSpec, + getLoginHint, + getLogoutHint, +} from "../../../../../src/cli/repl-commands/adapter-cli-reference.js"; + +describe("adapter CLI reference helpers", () => { + it("maps adapters to login and logout hints", () => { + expect(getAdapterLoginCommandSpec("codex")).toEqual({ + command: "codex", + args: ["login"], + }); + expect(getAdapterLoginCommandSpec("gemini")).toEqual({ + command: "gemini", + args: [], + }); + expect(getAdapterLoginCommandSpec("claude")).toEqual({ + command: "claude", + args: ["auth", "login"], + }); + + expect(getLoginHint("codex")).toBe("codex login"); + expect(getLoginHint("gemini")).toBe("gemini"); + expect(getLoginHint("claude")).toBe("claude auth login"); + + expect(getLogoutHint("codex")).toBe("codex logout"); + expect(getLogoutHint("gemini")).toBe("gemini logout"); + expect(getLogoutHint("claude")).toBe("claude auth logout"); + }); + + it("formats the external adapter CLI reference", () => { + const reference = formatAdapterCliReference(); + + expect(reference).toContain("외부 adapter CLI 참고"); + expect(reference).toContain("Codex CLI"); + expect(reference).toContain("codex debug models"); + expect(reference).toContain("Gemini CLI"); + expect(reference).toContain("gemini"); + expect(reference).toContain("Claude Code"); + expect(reference).toContain("claude auth status --json"); + expect(reference).toContain("detoks 명령은 REPL 안에서"); + }); +});