summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx10
-rw-r--r--packages/opencode/src/cli/cmd/tui/routes/session/index.tsx102
2 files changed, 112 insertions, 0 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
index a4c1f33d9..172ae8a02 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
@@ -244,6 +244,16 @@ export function Autocomplete(props: {
onSelect: () => command.trigger("session.rename"),
},
{
+ display: "/copy",
+ description: "copy session transcript to clipboard",
+ onSelect: () => command.trigger("session.copy"),
+ },
+ {
+ display: "/export",
+ description: "export session transcript to file",
+ onSelect: () => command.trigger("session.export"),
+ },
+ {
display: "/timeline",
description: "jump to message",
onSelect: () => command.trigger("session.timeline"),
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index ccd12819a..9fa844b7c 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -65,6 +65,9 @@ import parsers from "../../../../../../parsers-config.ts"
import { Clipboard } from "../../util/clipboard"
import { Toast, useToast } from "../../ui/toast"
import { useKV } from "../../context/kv.tsx"
+import { Editor } from "../../util/editor"
+import { Global } from "@/global"
+import fs from "fs/promises"
addDefaultParsers(parsers.parsers)
@@ -447,6 +450,105 @@ export function Session() {
},
},
{
+ title: "Copy session transcript",
+ value: "session.copy",
+ keybind: "session_copy",
+ category: "Session",
+ onSelect: async (dialog) => {
+ try {
+ // Format session transcript as markdown
+ const sessionData = session()
+ const sessionMessages = messages()
+
+ let transcript = `# ${sessionData.title}\n\n`
+ transcript += `**Session ID:** ${sessionData.id}\n`
+ transcript += `**Created:** ${new Date(sessionData.time.created).toLocaleString()}\n`
+ transcript += `**Updated:** ${new Date(sessionData.time.updated).toLocaleString()}\n\n`
+ transcript += `---\n\n`
+
+ for (const msg of sessionMessages) {
+ const parts = sync.data.part[msg.id] ?? []
+ const role = msg.role === "user" ? "User" : "Assistant"
+ transcript += `## ${role}\n\n`
+
+ for (const part of parts) {
+ if (part.type === "text" && !part.synthetic) {
+ transcript += `${part.text}\n\n`
+ } else if (part.type === "tool") {
+ transcript += `\`\`\`\nTool: ${part.tool}\n\`\`\`\n\n`
+ }
+ }
+
+ transcript += `---\n\n`
+ }
+
+ // Copy to clipboard
+ await Clipboard.copy(transcript)
+ toast.show({ message: "Session transcript copied to clipboard!", variant: "success" })
+ } catch (error) {
+ toast.show({ message: "Failed to copy session transcript", variant: "error" })
+ }
+ dialog.clear()
+ },
+ },
+ {
+ title: "Export session transcript to file",
+ value: "session.export",
+ keybind: "session_export",
+ category: "Session",
+ onSelect: async (dialog) => {
+ try {
+ // Format session transcript as markdown
+ const sessionData = session()
+ const sessionMessages = messages()
+
+ let transcript = `# ${sessionData.title}\n\n`
+ transcript += `**Session ID:** ${sessionData.id}\n`
+ transcript += `**Created:** ${new Date(sessionData.time.created).toLocaleString()}\n`
+ transcript += `**Updated:** ${new Date(sessionData.time.updated).toLocaleString()}\n\n`
+ transcript += `---\n\n`
+
+ for (const msg of sessionMessages) {
+ const parts = sync.data.part[msg.id] ?? []
+ const role = msg.role === "user" ? "User" : "Assistant"
+ transcript += `## ${role}\n\n`
+
+ for (const part of parts) {
+ if (part.type === "text" && !part.synthetic) {
+ transcript += `${part.text}\n\n`
+ } else if (part.type === "tool") {
+ transcript += `\`\`\`\nTool: ${part.tool}\n\`\`\`\n\n`
+ }
+ }
+
+ transcript += `---\n\n`
+ }
+
+ // Save to file in data directory
+ const exportDir = path.join(Global.Path.data, "exports")
+ await fs.mkdir(exportDir, { recursive: true })
+
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
+ const filename = `session-${sessionData.id.slice(0, 8)}-${timestamp}.md`
+ const filepath = path.join(exportDir, filename)
+
+ await Bun.write(filepath, transcript)
+
+ // Open with EDITOR if available
+ const result = await Editor.open({ value: transcript, renderer })
+ if (result !== undefined) {
+ // User edited the file, save the changes
+ await Bun.write(filepath, result)
+ }
+
+ toast.show({ message: `Session exported to ${filename}`, variant: "success" })
+ } catch (error) {
+ toast.show({ message: "Failed to export session", variant: "error" })
+ }
+ dialog.clear()
+ },
+ },
+ {
title: "Next child session",
value: "session.child.next",
keybind: "session_child_cycle",