cc-sessions 1.0.1 → 1.1.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +23 -13
- data/bin/cc +144 -10
- data/bin/cc-bookmark +37 -0
- data/commands/bm.md +7 -35
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb7625607c8b67ff39212aed3d7b9bcb8a9cf5fcbae0751a554b1a09427baea1
|
|
4
|
+
data.tar.gz: a40ac44c23feca33a5b91eeefb4d7665a0b74a4d7e3fe54f544cb2a1dc01e5e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8516229845736a7311f5d597c3218d6bff92533835b8a0b0f16851e05c503a62c98bbd2c388ee69ab42622961c57ca82efa539539b536f91cccb9d109a14133f
|
|
7
|
+
data.tar.gz: aa67d7f92b1d3882b10696f2b3fb4638ce902849d46c8069ce538f49d74aa9ff71ebc632c3e4864efcdd767c8c764c5a92e73b6659b39b7c1b02315118d6eb6b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.0] - 2025-01-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Interactive session picker with `cc -l` (no external dependencies)
|
|
7
|
+
- Delete bookmarks with `d` key in list (with confirmation)
|
|
8
|
+
- Delete bookmarks from CLI with `cc -d <tag>`
|
|
9
|
+
- Vim-style navigation (j/k) in addition to arrow keys
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- `cc -l` now shows interactive list with hidden cursor
|
|
13
|
+
- Zero external dependencies (removed tty-prompt)
|
|
14
|
+
|
|
15
|
+
## [1.0.2] - 2025-01-23
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `cc-bookmark` helper script for reliable permission matching
|
|
19
|
+
- SVG logo
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- `/bm` command now uses `cc-bookmark` script (auto-accepts without prompts)
|
|
23
|
+
- Updated README with badges and proper logo placement
|
|
24
|
+
|
|
3
25
|
## [1.0.1] - 2025-01-23
|
|
4
26
|
|
|
5
27
|
### Added
|
data/README.md
CHANGED
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
# CC-sessions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
 [](https://badge.fury.io/rb/cc-sessions) 
|
|
4
|
+
|
|
5
|
+
<img src="img/cc-sessions_logo.svg" align="left" width="150" height="150">
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
A simple tool for bookmarking and resuming Claude Code sessions with tags.
|
|
6
8
|
|
|
7
9
|
Claude Code sessions are tied to directories, making it hard to remember where
|
|
8
10
|
you were working on specific projects. This tool lets you tag sessions with
|
|
9
11
|
meaningful names and quickly resume them.
|
|
10
12
|
|
|
13
|
+
<br clear="left"/>
|
|
14
|
+
|
|
11
15
|
## Features
|
|
12
16
|
|
|
13
17
|
- **Bookmark sessions** with `/bm tag1 tag2` inside Claude Code
|
|
14
18
|
- **Resume sessions** with `cc tag` from anywhere
|
|
15
19
|
- **List bookmarks** with `cc -l`
|
|
16
|
-
- **Auto-install** the `/bm` command on first run
|
|
20
|
+
- **Auto-install** the `/bm` command and permission on first run
|
|
17
21
|
|
|
18
22
|
## Installation
|
|
19
23
|
|
|
24
|
+
### From RubyGems (Recommended)
|
|
25
|
+
|
|
20
26
|
```bash
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
cd CC-sessions
|
|
27
|
+
gem install cc-sessions
|
|
28
|
+
```
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
export PATH="$HOME/path/to/CC-sessions/bin:$PATH"
|
|
30
|
+
### From Source
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/isene/CC-sessions.git
|
|
34
|
+
cd CC-sessions
|
|
35
|
+
./install.sh
|
|
30
36
|
```
|
|
31
37
|
|
|
32
38
|
## Usage
|
|
@@ -60,8 +66,11 @@ cc -h
|
|
|
60
66
|
|
|
61
67
|
### First Run
|
|
62
68
|
|
|
63
|
-
On first run, `cc` automatically
|
|
64
|
-
|
|
69
|
+
On first run, `cc` automatically:
|
|
70
|
+
1. Installs the `/bm` command to `~/.claude/commands/`
|
|
71
|
+
2. Adds auto-accept permission to `~/.claude/settings.json`
|
|
72
|
+
|
|
73
|
+
This enables `/bm` to work without confirmation prompts.
|
|
65
74
|
|
|
66
75
|
## Files
|
|
67
76
|
|
|
@@ -69,6 +78,7 @@ This enables the `/bm` command inside Claude Code.
|
|
|
69
78
|
|------|---------|
|
|
70
79
|
| `~/.cc-sessions/bookmarks.json` | Stores your bookmarks |
|
|
71
80
|
| `~/.claude/commands/bm.md` | The `/bm` command definition |
|
|
81
|
+
| `~/.claude/settings.json` | Permission for auto-accept |
|
|
72
82
|
|
|
73
83
|
## Example Workflow
|
|
74
84
|
|
|
@@ -91,4 +101,4 @@ cc rtfm # Instantly back in that session
|
|
|
91
101
|
|
|
92
102
|
## License
|
|
93
103
|
|
|
94
|
-
|
|
104
|
+
This software is released into the public domain under [The Unlicense](https://unlicense.org/).
|
data/bin/cc
CHANGED
|
@@ -15,13 +15,21 @@
|
|
|
15
15
|
|
|
16
16
|
require 'json'
|
|
17
17
|
require 'fileutils'
|
|
18
|
+
require 'io/console'
|
|
19
|
+
|
|
20
|
+
# Simple ANSI helpers
|
|
21
|
+
def cyan(s) = "\e[36m#{s}\e[0m"
|
|
22
|
+
def dim(s) = "\e[2m#{s}\e[0m"
|
|
23
|
+
def red(s) = "\e[31m#{s}\e[0m"
|
|
24
|
+
def rev(s) = "\e[7m#{s}\e[0m"
|
|
25
|
+
def clear_line = "\e[2K\r"
|
|
18
26
|
|
|
19
27
|
CONFIG_DIR = File.expand_path('~/.cc-sessions')
|
|
20
28
|
BOOKMARKS_FILE = File.join(CONFIG_DIR, 'bookmarks.json')
|
|
21
29
|
COMMAND_SOURCE = File.expand_path('../commands/bm.md', __dir__)
|
|
22
30
|
COMMAND_DEST = File.expand_path('~/.claude/commands/bm.md')
|
|
23
31
|
SETTINGS_FILE = File.expand_path('~/.claude/settings.json')
|
|
24
|
-
BM_PERMISSION = 'Bash(
|
|
32
|
+
BM_PERMISSION = 'Bash(cc-bookmark:*)'
|
|
25
33
|
|
|
26
34
|
def ensure_setup
|
|
27
35
|
# Create config directory
|
|
@@ -88,6 +96,43 @@ def session_exists_in_dir?(dir)
|
|
|
88
96
|
Dir.glob(File.join(claude_dir, '**/*')).any? { |f| File.file?(f) }
|
|
89
97
|
end
|
|
90
98
|
|
|
99
|
+
def read_key
|
|
100
|
+
input = $stdin.getch
|
|
101
|
+
if input == "\e"
|
|
102
|
+
begin
|
|
103
|
+
input << $stdin.read_nonblock(3)
|
|
104
|
+
rescue IO::WaitReadable
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
case input
|
|
108
|
+
when "\e[A", "k" then :up
|
|
109
|
+
when "\e[B", "j" then :down
|
|
110
|
+
when "\r", "\n" then :enter
|
|
111
|
+
when "q", "\e" then :quit
|
|
112
|
+
when "d" then :delete
|
|
113
|
+
else nil
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def save_bookmarks(bookmarks)
|
|
118
|
+
File.write(BOOKMARKS_FILE, JSON.pretty_generate(bookmarks))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def delete_bookmark_by_tag(tag)
|
|
122
|
+
bookmarks = load_bookmarks
|
|
123
|
+
path = find_session_by_tag(tag)
|
|
124
|
+
|
|
125
|
+
if path
|
|
126
|
+
bookmarks.delete(path)
|
|
127
|
+
save_bookmarks(bookmarks)
|
|
128
|
+
puts "Deleted bookmark: #{path}"
|
|
129
|
+
puts " (was tagged: #{tag})"
|
|
130
|
+
else
|
|
131
|
+
puts "No session found with tag '#{tag}'"
|
|
132
|
+
exit 1
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
91
136
|
def list_bookmarks
|
|
92
137
|
bookmarks = load_bookmarks
|
|
93
138
|
|
|
@@ -98,14 +143,93 @@ def list_bookmarks
|
|
|
98
143
|
return
|
|
99
144
|
end
|
|
100
145
|
|
|
101
|
-
|
|
102
|
-
|
|
146
|
+
items = bookmarks.map do |path, tags|
|
|
147
|
+
{ path: path, tags: tags, exists: Dir.exist?(path) }
|
|
148
|
+
end
|
|
103
149
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
150
|
+
index = 0
|
|
151
|
+
puts "Select session (↑/↓/j/k move, Enter select, d delete, q quit):\n\n"
|
|
152
|
+
|
|
153
|
+
# Hide cursor
|
|
154
|
+
print "\e[?25l"
|
|
155
|
+
|
|
156
|
+
begin
|
|
157
|
+
loop do
|
|
158
|
+
# Render list
|
|
159
|
+
items.each_with_index do |item, i|
|
|
160
|
+
print clear_line
|
|
161
|
+
tag_str = cyan(item[:tags].join(', '))
|
|
162
|
+
path_str = dim(item[:path])
|
|
163
|
+
missing = item[:exists] ? '' : red(' [NOT FOUND]')
|
|
164
|
+
line = "#{tag_str} → #{path_str}#{missing}"
|
|
165
|
+
|
|
166
|
+
if i == index
|
|
167
|
+
puts "▸ #{line}"
|
|
168
|
+
else
|
|
169
|
+
puts " #{line}"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Move cursor back to start
|
|
174
|
+
print "\e[#{items.size}A"
|
|
175
|
+
|
|
176
|
+
key = read_key
|
|
177
|
+
case key
|
|
178
|
+
when :up
|
|
179
|
+
index = (index - 1) % items.size
|
|
180
|
+
when :down
|
|
181
|
+
index = (index + 1) % items.size
|
|
182
|
+
when :delete
|
|
183
|
+
# Show confirmation
|
|
184
|
+
old_size = items.size
|
|
185
|
+
print "\e[#{old_size}B" # Move below list
|
|
186
|
+
print clear_line
|
|
187
|
+
print "Delete '#{items[index][:tags].join(', ')}'? (y/n) "
|
|
188
|
+
confirm = $stdin.getch
|
|
189
|
+
if confirm.downcase == 'y'
|
|
190
|
+
bookmarks.delete(items[index][:path])
|
|
191
|
+
save_bookmarks(bookmarks)
|
|
192
|
+
items.delete_at(index)
|
|
193
|
+
index = [index, items.size - 1].min
|
|
194
|
+
if items.empty?
|
|
195
|
+
# Clear list and confirmation line
|
|
196
|
+
print "\e[#{old_size}A"
|
|
197
|
+
(old_size + 1).times { print clear_line; puts }
|
|
198
|
+
print "\e[#{old_size + 1}A"
|
|
199
|
+
puts "All bookmarks deleted."
|
|
200
|
+
return
|
|
201
|
+
end
|
|
202
|
+
# Clear old list (one more line than new size)
|
|
203
|
+
print clear_line
|
|
204
|
+
print "\e[#{old_size}A"
|
|
205
|
+
(old_size).times { print clear_line; puts }
|
|
206
|
+
print "\e[#{old_size}A"
|
|
207
|
+
else
|
|
208
|
+
print clear_line
|
|
209
|
+
print "\e[#{old_size}A"
|
|
210
|
+
end
|
|
211
|
+
when :enter
|
|
212
|
+
# Clear the menu
|
|
213
|
+
(items.size).times { print clear_line; puts }
|
|
214
|
+
print "\e[#{items.size}A"
|
|
215
|
+
|
|
216
|
+
if items[index][:exists]
|
|
217
|
+
print "\e[?25h" # Show cursor before exec
|
|
218
|
+
resume_session(items[index][:path])
|
|
219
|
+
else
|
|
220
|
+
puts red("Directory not found: #{items[index][:path]}")
|
|
221
|
+
end
|
|
222
|
+
return
|
|
223
|
+
when :quit
|
|
224
|
+
# Clear the menu
|
|
225
|
+
(items.size).times { print clear_line; puts }
|
|
226
|
+
print "\e[#{items.size}A"
|
|
227
|
+
return
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
ensure
|
|
231
|
+
# Always show cursor
|
|
232
|
+
print "\e[?25h"
|
|
109
233
|
end
|
|
110
234
|
end
|
|
111
235
|
|
|
@@ -118,7 +242,8 @@ def show_help
|
|
|
118
242
|
USAGE:
|
|
119
243
|
cc Continue session in current directory, or start new
|
|
120
244
|
cc <tag> Resume session bookmarked with <tag>
|
|
121
|
-
cc -l, --list List all bookmarked sessions
|
|
245
|
+
cc -l, --list List all bookmarked sessions (interactive)
|
|
246
|
+
cc -d, --delete <tag> Delete bookmark matching <tag>
|
|
122
247
|
cc -h, --help Show this help
|
|
123
248
|
|
|
124
249
|
BOOKMARKING (inside Claude Code):
|
|
@@ -127,7 +252,8 @@ def show_help
|
|
|
127
252
|
EXAMPLES:
|
|
128
253
|
cc rtfm Resume session tagged 'rtfm'
|
|
129
254
|
cc Continue in current dir or start fresh
|
|
130
|
-
cc -l Show
|
|
255
|
+
cc -l Show interactive list (d to delete, Enter to select)
|
|
256
|
+
cc -d rtfm Delete bookmark tagged 'rtfm'
|
|
131
257
|
|
|
132
258
|
FILES:
|
|
133
259
|
~/.cc-sessions/bookmarks.json Bookmark storage
|
|
@@ -168,6 +294,14 @@ when '-h', '--help'
|
|
|
168
294
|
show_help
|
|
169
295
|
when '-l', '--list'
|
|
170
296
|
list_bookmarks
|
|
297
|
+
when '-d', '--delete'
|
|
298
|
+
if ARGV[1]
|
|
299
|
+
delete_bookmark_by_tag(ARGV[1])
|
|
300
|
+
else
|
|
301
|
+
puts "Usage: cc -d <tag>"
|
|
302
|
+
puts "Delete bookmark matching the given tag."
|
|
303
|
+
exit 1
|
|
304
|
+
end
|
|
171
305
|
when nil
|
|
172
306
|
continue_or_start
|
|
173
307
|
else
|
data/bin/cc-bookmark
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# CC-Bookmark - Helper script for /bm command
|
|
5
|
+
# Bookmarks the current directory with tags
|
|
6
|
+
|
|
7
|
+
require 'json'
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
|
|
10
|
+
CONFIG_DIR = File.expand_path('~/.cc-sessions')
|
|
11
|
+
BOOKMARKS_FILE = File.join(CONFIG_DIR, 'bookmarks.json')
|
|
12
|
+
|
|
13
|
+
FileUtils.mkdir_p(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
|
|
14
|
+
|
|
15
|
+
bookmarks = if File.exist?(BOOKMARKS_FILE)
|
|
16
|
+
JSON.parse(File.read(BOOKMARKS_FILE))
|
|
17
|
+
else
|
|
18
|
+
{}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
cwd = Dir.pwd
|
|
22
|
+
tags = ARGV
|
|
23
|
+
|
|
24
|
+
if tags.empty?
|
|
25
|
+
if bookmarks[cwd]
|
|
26
|
+
puts "Current bookmark: #{bookmarks[cwd].join(', ')}"
|
|
27
|
+
else
|
|
28
|
+
puts "No bookmark for this directory. Usage: /bm tag1 tag2 ..."
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
bookmarks[cwd] = tags
|
|
32
|
+
File.write(BOOKMARKS_FILE, JSON.pretty_generate(bookmarks))
|
|
33
|
+
puts "Bookmarked: #{cwd}"
|
|
34
|
+
puts "Tags: #{tags.join(', ')}"
|
|
35
|
+
puts ""
|
|
36
|
+
puts "Resume later with: cc #{tags.first}"
|
|
37
|
+
end
|
data/commands/bm.md
CHANGED
|
@@ -10,44 +10,16 @@ The user provides space-separated tags after `/bm`. For example:
|
|
|
10
10
|
|
|
11
11
|
## What to Do
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
2. Parse the tags from the command arguments (the text after `/bm`)
|
|
15
|
-
3. Store the bookmark in `~/.cc-sessions/bookmarks.json`
|
|
16
|
-
4. If no tags provided, show current bookmark for this directory (if any)
|
|
17
|
-
|
|
18
|
-
## Implementation
|
|
19
|
-
|
|
20
|
-
Run this bash command, replacing `$TAGS` with the actual tags provided:
|
|
13
|
+
Run the cc-bookmark command with the provided tags:
|
|
21
14
|
|
|
22
15
|
```bash
|
|
23
|
-
|
|
24
|
-
file = File.expand_path("~/.cc-sessions/bookmarks.json")
|
|
25
|
-
bookmarks = File.exist?(file) ? JSON.parse(File.read(file)) : {}
|
|
26
|
-
cwd = Dir.pwd
|
|
27
|
-
tags = ARGV
|
|
28
|
-
if tags.empty?
|
|
29
|
-
if bookmarks[cwd]
|
|
30
|
-
puts "Current bookmark: #{bookmarks[cwd].join(", ")}"
|
|
31
|
-
else
|
|
32
|
-
puts "No bookmark for this directory. Usage: /bm tag1 tag2 ..."
|
|
33
|
-
end
|
|
34
|
-
else
|
|
35
|
-
bookmarks[cwd] = tags
|
|
36
|
-
File.write(file, JSON.pretty_generate(bookmarks))
|
|
37
|
-
puts "Bookmarked: #{cwd}"
|
|
38
|
-
puts "Tags: #{tags.join(", ")}"
|
|
39
|
-
puts ""
|
|
40
|
-
puts "Resume later with: cc #{tags.first}"
|
|
41
|
-
end
|
|
42
|
-
' $TAGS
|
|
16
|
+
cc-bookmark $TAGS
|
|
43
17
|
```
|
|
44
18
|
|
|
45
|
-
|
|
19
|
+
Replace `$TAGS` with the actual tags provided by the user.
|
|
46
20
|
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
Bookmarked: /path/to/session
|
|
50
|
-
Tags: tag1, tag2, tag3
|
|
21
|
+
## Example
|
|
51
22
|
|
|
52
|
-
|
|
53
|
-
|
|
23
|
+
User types: `/bm rtfm ruby`
|
|
24
|
+
|
|
25
|
+
Run: `cc-bookmark rtfm ruby`
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cc-sessions
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Geir Isene
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A simple tool for bookmarking and resuming Claude Code sessions. Tag
|
|
14
14
|
sessions with meaningful names using /bm inside Claude Code, then quickly resume
|
|
@@ -17,6 +17,7 @@ email:
|
|
|
17
17
|
- g@isene.com
|
|
18
18
|
executables:
|
|
19
19
|
- cc
|
|
20
|
+
- cc-bookmark
|
|
20
21
|
extensions: []
|
|
21
22
|
extra_rdoc_files: []
|
|
22
23
|
files:
|
|
@@ -24,6 +25,7 @@ files:
|
|
|
24
25
|
- LICENSE
|
|
25
26
|
- README.md
|
|
26
27
|
- bin/cc
|
|
28
|
+
- bin/cc-bookmark
|
|
27
29
|
- commands/bm.md
|
|
28
30
|
homepage: https://github.com/isene/CC-sessions
|
|
29
31
|
licenses:
|