op-session-id 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +31 -0
  2. package/src/index.ts +115 -0
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "op-session-id",
4
+ "version": "0.1.1",
5
+ "description": "opencode TUI plugin: /id copies the current session ID; prints it again when opencode exits.",
6
+ "type": "module",
7
+ "main": "./src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./tui": "./src/index.ts"
11
+ },
12
+ "files": [
13
+ "src/index.ts"
14
+ ],
15
+ "scripts": {
16
+ "typecheck": "tsc --noEmit",
17
+ "test": "bun test",
18
+ "dev": "bun run scripts/dev-sandbox.ts"
19
+ },
20
+ "keywords": [
21
+ "opencode",
22
+ "opencode-plugin",
23
+ "session-id"
24
+ ],
25
+ "license": "MIT",
26
+ "devDependencies": {
27
+ "@opencode-ai/plugin": "^1.17.0",
28
+ "@types/bun": "^1.1.0",
29
+ "typescript": "^5"
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,115 @@
1
+ import { spawn } from "node:child_process"
2
+ import { platform } from "node:os"
3
+ import type { TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"
4
+
5
+ // Mirrors packages/tui/src/clipboard.ts writeOsc52: terminal-native clipboard
6
+ // that also works over ssh; tmux/screen need the DCS passthrough wrapper.
7
+ function writeOsc52(text: string) {
8
+ if (!process.stdout.isTTY) return
9
+ const sequence = `\x1b]52;c;${Buffer.from(text).toString("base64")}\x07`
10
+ try {
11
+ process.stdout.write(process.env.TMUX || process.env.STY ? `\x1bPtmux;\x1b${sequence}\x1b\\` : sequence)
12
+ } catch {
13
+ // Plugin command handlers run inside TUI dispatch; never throw from here.
14
+ }
15
+ }
16
+
17
+ function copy(text: string) {
18
+ // Clipboard writes only make sense from an interactive terminal; this also
19
+ // keeps hook-level tests from clobbering the real clipboard.
20
+ if (!process.stdout.isTTY) return
21
+ writeOsc52(text)
22
+ if (platform() !== "darwin") return
23
+ try {
24
+ const child = spawn("pbcopy", { stdio: ["pipe", "ignore", "ignore"] })
25
+ child.on("error", () => {})
26
+ child.stdin.end(text)
27
+ } catch {}
28
+ }
29
+
30
+ function routeSessionID(api: TuiPluginApi) {
31
+ const route = api.route.current
32
+ const sessionID = route.name === "session" ? route.params?.sessionID : undefined
33
+ return typeof sessionID === "string" ? sessionID : undefined
34
+ }
35
+
36
+ function readSessionID(properties: unknown) {
37
+ const record = properties && typeof properties === "object" ? (properties as Record<string, unknown>) : undefined
38
+ const sessionID = record?.sessionID ?? record?.sessionId ?? record?.session_id
39
+ return typeof sessionID === "string" ? sessionID : undefined
40
+ }
41
+
42
+ export async function installSessionIdPlugin(api: TuiPluginApi): Promise<void> {
43
+ let activeSessionID: string | undefined
44
+ const setActiveSession = (sessionID?: string) => {
45
+ if (sessionID) activeSessionID = sessionID
46
+ }
47
+
48
+ const current = () => routeSessionID(api) ?? activeSessionID
49
+
50
+ const showSessionID = () => {
51
+ const sessionID = current()
52
+ if (!sessionID) {
53
+ api.ui.toast({ message: "No session selected yet.", variant: "error" })
54
+ return
55
+ }
56
+ copy(sessionID)
57
+ api.ui.toast({ title: "Session ID", message: `${sessionID} (copied)`, variant: "success", duration: 8000 })
58
+ }
59
+
60
+ if (typeof api.keymap?.registerLayer === "function") {
61
+ api.keymap.registerLayer({
62
+ commands: [
63
+ {
64
+ namespace: "palette",
65
+ name: "session-id",
66
+ title: "Copy session ID",
67
+ desc: "Show the current session ID and copy it to the clipboard",
68
+ category: "Session",
69
+ slashName: "id",
70
+ run() {
71
+ showSessionID()
72
+ return true
73
+ },
74
+ },
75
+ ],
76
+ })
77
+ } else {
78
+ api.command?.register(() => [
79
+ {
80
+ title: "Copy session ID",
81
+ value: "session-id",
82
+ description: "Show the current session ID and copy it to the clipboard",
83
+ category: "Session",
84
+ slash: { name: "id" },
85
+ onSelect: showSessionID,
86
+ },
87
+ ])
88
+ }
89
+
90
+ api.event.on("tui.session.select", (event) => setActiveSession(event.properties.sessionID))
91
+ api.event.on("session.created", (event) => setActiveSession(event.properties.sessionID))
92
+ api.event.on("session.updated", (event) => setActiveSession(event.properties.sessionID))
93
+ api.event.on("session.status", (event) => setActiveSession(readSessionID(event.properties)))
94
+
95
+ // Best-effort: print the last session ID to the normal screen after the TUI
96
+ // tears down, so it is visible in scrollback once opencode closes.
97
+ const printOnExit = () => {
98
+ const sessionID = current()
99
+ if (!sessionID) return
100
+ try {
101
+ process.stderr.write(`\nopencode session: ${sessionID}\nresume: opencode --session ${sessionID}\n`)
102
+ } catch {}
103
+ }
104
+ process.on("exit", printOnExit)
105
+ api.lifecycle.onDispose(() => {
106
+ process.removeListener("exit", printOnExit)
107
+ })
108
+ }
109
+
110
+ const plugin: TuiPluginModule = {
111
+ id: "opencode-session-id",
112
+ tui: installSessionIdPlugin,
113
+ }
114
+
115
+ export default plugin