Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
# claude-code-sessions

Monorepo for Claude Code session management tools
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
![Unofficial](https://img.shields.io/badge/unofficial-community%20project-orange)

Browse, search, rename, split, and clean up [Claude Code](https://claude.ai/code) sessions — via MCP server, Web UI, or VSCode extension.

> **Note**: This is a community project and is not affiliated with or endorsed by Anthropic.

## Packages

| Package | npm | Description |
| -------------------------------------- | ----------------------- | ------------ |
| [@claude-sessions/core](packages/core) | `@claude-sessions/core` | Core library |
| [@claude-sessions/web](packages/web) | `@claude-sessions/web` | Web UI |
| [claude-sessions-mcp](packages/mcp) | `claude-sessions-mcp` | MCP server |
| Package | Version | Description |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
| [@claude-sessions/core](packages/core) | [![npm](https://img.shields.io/npm/v/@claude-sessions/core)](https://www.npmjs.com/package/@claude-sessions/core) | Core library |
| [@claude-sessions/web](packages/web) | [![npm](https://img.shields.io/npm/v/@claude-sessions/web)](https://www.npmjs.com/package/@claude-sessions/web) | Web UI |
| [claude-sessions-mcp](packages/mcp) | [![npm](https://img.shields.io/npm/v/claude-sessions-mcp)](https://www.npmjs.com/package/claude-sessions-mcp) | MCP server |
| [claude-sessions](packages/vscode-extension) | [![VS Marketplace](https://img.shields.io/visual-studio-marketplace/v/es6kr.claude-sessions)](https://marketplace.visualstudio.com/items?itemName=es6kr.claude-sessions)<br>[![Open VSX](https://img.shields.io/open-vsx/v/es6kr/claude-sessions)](https://open-vsx.org/extension/es6kr/claude-sessions) | VSCode extension |

## Installation

Expand Down
3 changes: 3 additions & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"url": "https://github.com/es6kr/claude-code-sessions",
"directory": "packages/web"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"bin": {
"claude-sessions-web": "./dist/cli.js"
},
Expand Down
75 changes: 75 additions & 0 deletions packages/web/src/lib/components/MessageItem.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf'
import MessageItem from './MessageItem.svelte'

const noop = () => {}

const { Story } = defineMeta({
title: 'Components/MessageItem',
component: MessageItem,
tags: ['autodocs'],
args: {
sessionId: 'test-session',
onDelete: noop,
},
})
</script>

<Story
name="Slash Command Without Args"
args={{
msg: {
uuid: 'msg-1',
type: 'human',
timestamp: new Date().toISOString(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use fixed timestamps in stories to keep snapshots deterministic.

Using new Date().toISOString() introduces run-to-run UI diffs in Storybook visual regression flows. Prefer a static timestamp.

💡 Proposed fix
 <script module>
   import { defineMeta } from '@storybook/addon-svelte-csf'
   import MessageItem from './MessageItem.svelte'
 
   const noop = () => {}
+  const fixedTimestamp = '2026-01-20T10:00:00.000Z'
@@
       uuid: 'msg-1',
       type: 'human',
-      timestamp: new Date().toISOString(),
+      timestamp: fixedTimestamp,
@@
       uuid: 'msg-2',
       type: 'human',
-      timestamp: new Date().toISOString(),
+      timestamp: fixedTimestamp,
@@
       uuid: 'msg-3',
       type: 'human',
-      timestamp: new Date().toISOString(),
+      timestamp: fixedTimestamp,

Also applies to: 42-42, 63-63

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/lib/components/MessageItem.stories.svelte` at line 24,
Replace dynamic timestamps in MessageItem.stories.svelte (the timestamp prop
assignments that currently use new Date().toISOString()) with a fixed ISO
timestamp string (e.g. "2023-01-01T00:00:00.000Z") so Storybook snapshots are
deterministic; update each occurrence (the timestamp props in the story objects)
to use the fixed string.

message: { content: '<command-name>/vsix</command-name>' },
},
}}
>
{#snippet children(args)}
<div class="p-4 bg-gh-canvas text-gh-text max-w-2xl">
<MessageItem {...args} />
</div>
{/snippet}
</Story>

<Story
name="Slash Command With Args"
args={{
msg: {
uuid: 'msg-2',
type: 'human',
timestamp: new Date().toISOString(),
message: {
content:
'<command-name>/session</command-name>\n<command-args>repair --dry-run e15f9f9a</command-args>',
},
},
}}
>
{#snippet children(args)}
<div class="p-4 bg-gh-canvas text-gh-text max-w-2xl">
<MessageItem {...args} />
</div>
{/snippet}
</Story>

<Story
name="Slash Command With Short Args"
args={{
msg: {
uuid: 'msg-3',
type: 'human',
timestamp: new Date().toISOString(),
message: {
content: '<command-name>/skill-name</command-name>\n<command-args>topic</command-args>',
},
},
}}
>
{#snippet children(args)}
<div class="p-4 bg-gh-canvas text-gh-text max-w-2xl">
<MessageItem {...args} />
</div>
{/snippet}
</Story>
7 changes: 6 additions & 1 deletion packages/web/src/lib/components/MessageItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,12 @@
class="p-3 rounded-lg bg-gh-accent/15 border-l-3 border-l-gh-accent group relative"
>
<div class="flex justify-between items-center text-xs text-gh-text-secondary">
<span class="font-semibold text-gh-accent">{commandData.name || 'Command'}</span>
<span>
<span class="font-semibold text-gh-accent">{commandData.name || 'Command'}</span>
{#if commandData.args}
<span class="text-gh-text-secondary">{commandData.args}</span>
{/if}
Comment on lines +297 to +300
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add an explicit separator between command name and args.

commandData.args is rendered directly after the command name, which can concatenate text in the UI. Add a space or margin before args.

💡 Proposed fix
         <span class="font-semibold text-gh-accent">{commandData.name || 'Command'}</span>
         {`#if` commandData.args}
-          <span class="text-gh-text-secondary">{commandData.args}</span>
+          <span class="ml-1 text-gh-text-secondary">{commandData.args}</span>
         {/if}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span class="font-semibold text-gh-accent">{commandData.name || 'Command'}</span>
{#if commandData.args}
<span class="text-gh-text-secondary">{commandData.args}</span>
{/if}
<span class="font-semibold text-gh-accent">{commandData.name || 'Command'}</span>
{`#if` commandData.args}
<span class="ml-1 text-gh-text-secondary">{commandData.args}</span>
{/if}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/lib/components/MessageItem.svelte` around lines 297 - 300,
The command args are rendered immediately after the command name in
MessageItem.svelte which can cause concatenated text; update the {`#if`
commandData.args} branch so the args rendering includes an explicit separator
(e.g., prepend a space or wrap in a span with a margin class) before
{commandData.args} to visually separate it from the command name (target the
span with class "text-gh-text-secondary" that currently renders
commandData.args).

</span>
<div class="flex items-center gap-2">
<span>{formatDate(msg.timestamp)}</span>
{@render splitButton()}
Expand Down
45 changes: 45 additions & 0 deletions packages/web/src/lib/components/MessageList.stories.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,35 @@
},
]

// Slash command with args
const slashCommandMessages = [
{
type: 'human',
uuid: 'cmd-1',
parentUuid: null,
timestamp: '2026-01-20T10:00:00.000Z',
message: {
content:
'<command-name>/session</command-name>\n<command-args>repair --dry-run e15f9f9a</command-args>',
},
},
{
type: 'assistant',
uuid: 'cmd-2',
parentUuid: 'cmd-1',
timestamp: '2026-01-20T10:00:05.000Z',
message: {
role: 'assistant',
content: [
{
type: 'text',
text: 'Running session repair in dry-run mode for e15f9f9a...\n\nNo issues found. The session chain is intact.',
},
],
},
},
]

// Empty messages
const emptyMessages = []

Expand Down Expand Up @@ -236,6 +265,22 @@
{/snippet}
</Story>

<Story
name="Slash Command With Args"
args={{
sessionId: mockSessionId,
messages: slashCommandMessages,
onDeleteMessage: handleDelete,
onSplitSession: handleSplit,
}}
>
{#snippet children(args)}
<div class="max-w-4xl">
<MessageList {...args} />
</div>
{/snippet}
</Story>

<Story
name="Empty Messages"
args={{
Expand Down