SLOPSHOPPER

session-dir-button

A band above the prompt showing the current session's directory, with a Copy button that puts the path on the clipboard.

newbandtoast
v0.1.0no licenseupdated 2026-10-09dcr-lol/claude-session-mod/session-dir-button
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · session-dir-button
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM Session dir /Users/dev/.claude/projects/app/preview [ Copy ] ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
Session dir /Users/dev/.claude/projects/app/preview [ Copy ]
README

session-dir-button

A Claude Code mod that adds a band above the prompt showing the current session's directory, with a Copy button that puts the path on the clipboard:

Session dir  C:\Users\you\.claude\projects\C--Users-you-repo\<session-id>  [Copy]

The path is the session's transcript path without .jsonl (~/.claude/projects/<project>/<session-id>). It follows /clear and resumes. Claude Code only creates that folder once the session writes something there, such as subagent transcripts or large tool outputs, so it may not exist yet.

Install

At the prompt of a terminal Claude Code session:

/plugin install session-dir-button --marketplace dcr-lol/claude-session-mod

Answer y to add the marketplace, then pick a scope.

Develop

claude plugin validate session-dir-button
claude plugin test session-dir-button
claude --plugin-dir session-dir-button
Source 2 files
hooks/register.tsx 79 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { SessionDir } from '../types'
5
6const BACKSLASH = String.fromCharCode(92)
7
8const dir = atom({ plugin: 'session-dir-button', key: 'dir' } as const, null as SessionDir)
9
10// The session's own folder sits beside its transcript, named by its id:
11// ~/.claude/projects/<project>/<session-id>.jsonl -> .../<project>/<session-id>
12function sessionDirOf(transcriptPath: string): string {
13  return transcriptPath.endsWith('.jsonl') ? transcriptPath.slice(0, -'.jsonl'.length) : transcriptPath
14}
15
16// Mirrors how Claude Code names a project folder (every non-alphanumeric
17// character of the cwd becomes '-') and adds the session id. Used until a
18// classic event brings the real transcript path.
19async function guessDir($: EngineInterface, cwd: string): Promise<string> {
20  const isWindows = cwd.charAt(1) === ":"
21  const sep = isWindows ? BACKSLASH : '/'
22  const home = (isWindows ? await $.env.get('USERPROFILE') : await $.env.get('HOME')) ?? ''
23  const configDir = (await $.env.get('CLAUDE_CONFIG_DIR')) ?? `${home}${sep}.claude`
24
25  const project = cwd.replace(/[^a-zA-Z0-9]/g, '-')
26
27  return `${configDir}${sep}projects${sep}${project}${sep}${await $.session.id()}`
28}
29
30async function fromTranscript($: EngineInterface, transcriptPath: string): Promise<void> {
31  if (transcriptPath) {
32    await update($, dir, () => sessionDirOf(transcriptPath))
33  }
34}
35
36export const register: Register = on => {
37  on('session.start', async ($, e, next) => {
38    const guess = await guessDir($, e.cwd)
39    await update($, dir, () => guess)
40
41    return next(e)
42  })
43
44  on('classic.SessionStart', async ($, e, next) => {
45    await fromTranscript($, e.transcript_path).catch(() => undefined)
46
47    return next(e)
48  })
49
50  on('classic.UserPromptSubmit', async ($, e, next) => {
51    await fromTranscript($, e.transcript_path).catch(() => undefined)
52
53    return next(e)
54  })
55
56  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
57    const path = await read($, dir)
58
59    if (e.props.hasSurvey || path === null) {
60      return next(e)
61    }
62
63    const { Box, Button, Text } = $.ui.resolve(e)
64
65    async function copy(surface: typeof e.surface): Promise<void> {
66      const copied = await $.ui.copy({ text: path as string, surface })
67      $.ui.toast(copied.isCopied ? 'Session directory copied' : `Could not copy: ${copied.reason}`)
68    }
69
70    return (
71      <Box flexDirection="row" gap={1}>
72        <Text dimColor>Session dir</Text>
73        <Text key="path">{path}</Text>
74        <Button key="copy" label="Copy" onPress={press => copy(press.surface)} />
75      </Box>
76    )
77  })
78}
79
types/index.d.ts 8 lines
1export type SessionDir = string | null
2
3declare module 'claude-code' {
4  interface PluginState {
5    'session-dir-button': { dir: SessionDir }
6  }
7}
8