diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index 309ec6d95096..b01323eaade5 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -20,6 +20,39 @@ export type ShareData = | { type: "session_diff"; data: unknown } | { type: "model"; data: unknown } +type SessionImportData = { + info: SDKSession + messages: Array<{ + info: Message + parts: Part[] + }> +} + +export async function readSessionImportFile(file: string) { + if (!(await Filesystem.exists(file))) { + return { + type: "not_found" as const, + file, + } + } + + const content = await Filesystem.readText(file) + const data = await Promise.resolve() + .then(() => JSON.parse(content) as SessionImportData) + .catch(() => undefined) + if (!data) { + return { + type: "unsupported" as const, + file, + } + } + + return { + type: "data" as const, + data, + } +} + /** Extract share ID from a share URL like https://opncd.ai/share/abc123 */ export function parseShareUrl(url: string): string | null { const match = url.match(/^https?:\/\/[^/]+\/share\/([a-zA-Z0-9_-]+)$/) @@ -86,15 +119,7 @@ export const ImportCommand = cmd({ }, handler: async (args) => { await bootstrap(process.cwd(), async () => { - let exportData: - | { - info: SDKSession - messages: Array<{ - info: Message - parts: Part[] - }> - } - | undefined + let exportData: SessionImportData | undefined const isUrl = args.file.startsWith("http://") || args.file.startsWith("https://") @@ -140,12 +165,18 @@ export const ImportCommand = cmd({ exportData = transformed } else { - exportData = await Filesystem.readJson>(args.file).catch(() => undefined) - if (!exportData) { + const result = await readSessionImportFile(args.file) + if (result.type === "not_found") { process.stdout.write(`File not found: ${args.file}`) process.stdout.write(EOL) return } + if (result.type === "unsupported") { + process.stdout.write(`Unsupported format: ${args.file}. Session data must be a JSON file.`) + process.stdout.write(EOL) + return + } + exportData = result.data } if (!exportData) { diff --git a/packages/opencode/test/cli/import.test.ts b/packages/opencode/test/cli/import.test.ts index d7c0241e6b0b..0f359ff15ccb 100644 --- a/packages/opencode/test/cli/import.test.ts +++ b/packages/opencode/test/cli/import.test.ts @@ -1,10 +1,12 @@ import { test, expect } from "bun:test" import { + readSessionImportFile, parseShareUrl, shouldAttachShareAuthHeaders, transformShareData, type ShareData, } from "../../src/cli/cmd/import" +import { tmpdir } from "../fixture/fixture" // parseShareUrl tests test("parses valid share URLs", () => { @@ -52,3 +54,31 @@ test("returns null for invalid share data", () => { expect(transformShareData([{ type: "message", data: {} as any }])).toBeNull() expect(transformShareData([{ type: "session", data: { id: "s" } as any }])).toBeNull() // no messages }) + +test("reports existing markdown import files as unsupported format", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + const file = `${dir}/session-ses_24ad.md` + await Bun.write(file, "# Session transcript") + return file + }, + }) + + const result = await readSessionImportFile(tmp.extra) + + expect(result).toEqual({ + type: "unsupported", + file: tmp.extra, + }) +}) + +test("reports missing import files as not found", async () => { + await using tmp = await tmpdir() + const file = `${tmp.path}/missing-session-file.json` + const result = await readSessionImportFile(file) + + expect(result).toEqual({ + type: "not_found", + file, + }) +})