cc-sessions 1.1.3 → 1.2.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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/bin/cc +149 -87
  4. data/bin/cc-bookmark +73 -13
  5. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06e2ca6f4ef6b30f3405ddb5be9af6f30c0c9f9d2b3d71882df8b4fc3f37d043
4
- data.tar.gz: b021936ca48295dbe300b366cc1fef360be465559dd4fc94195b67a96b8a2d0b
3
+ metadata.gz: a71cdec4c4feb8c680d3c29c2871393cd7af2255c3960f5a2b36eabc64fa9a7d
4
+ data.tar.gz: 03a816b0de6e0dd58017f816dc31e1c2b4acc7b77c159191dfa5f6e91faf6b6b
5
5
  SHA512:
6
- metadata.gz: 74a0948aba216611e7d6f6b0816e94f2371ab7861f3f23eff90f33631165149290036b106a902073e4d1131c3a6e7d4a3f7babf4046b9086e76543750490417d
7
- data.tar.gz: dc30be2e052937a75d2a8688583d26668f828ae65f94170527c05c57e5093d41b9657cd4c3c2734e2fb1b5d51df3b9e60cd5841c50b8a41fe75a4d99c0aa4809
6
+ metadata.gz: d9fc6662868b728dff1a09b8ae9962f7efed1f32166048758366a2fb72dbda40de0a432b02817a4b2a3c7e854df3e089a272abbc1da288276cf8f85c4213376f
7
+ data.tar.gz: 84073e656ccf0dad4aa8fd7e01ffc6768d5863dd12cda83475ec4332dbb2cd10a7f736ff9481399abc77289480a866a23847f11721663d3e24fc7f4c8abf0a1f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.0] - 2026-02-19
4
+
5
+ ### Added
6
+ - Session ID-based bookmarks (v2 format) for reliable session resume
7
+ - Auto-migration from v1 path-based bookmarks to v2 session IDs
8
+ - OSC 7 escape sequence on session resume for wezterm CWD tracking
9
+ - Reorder bookmarks with J/K (Shift+j/k) in interactive list
10
+ - `--resume <session_id>` used for direct session resume
11
+
12
+ ### Changed
13
+ - Bookmarks now keyed by session UUID instead of directory path
14
+ - `cc-bookmark` updated to detect and store session IDs
15
+ - Fallback to `path:<dir>` key when session ID unavailable
16
+
17
+ ## [1.1.4] - 2025-02-10
18
+
19
+ ### Fixed
20
+ - Bookmark path resolution when claude changes directory during session
21
+ - `cc -C` now resolves original session directory via ~/.claude/projects/
22
+
3
23
  ## [1.1.3] - 2025-02-04
4
24
 
5
25
  ### Added
data/bin/cc CHANGED
@@ -32,12 +32,11 @@ COMMAND_SOURCE = File.expand_path('../commands/bm.md', __dir__)
32
32
  COMMAND_DEST = File.expand_path('~/.claude/commands/bm.md')
33
33
  SETTINGS_FILE = File.expand_path('~/.claude/settings.json')
34
34
  BM_PERMISSION = 'Bash(cc-bookmark:*)'
35
+ CLAUDE_PROJECTS = File.expand_path('~/.claude/projects')
35
36
 
36
37
  def ensure_setup
37
- # Create config directory
38
38
  FileUtils.mkdir_p(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
39
39
 
40
- # Install /bm command if not present
41
40
  unless File.exist?(COMMAND_DEST)
42
41
  if File.exist?(COMMAND_SOURCE)
43
42
  FileUtils.mkdir_p(File.dirname(COMMAND_DEST))
@@ -48,7 +47,6 @@ def ensure_setup
48
47
  end
49
48
  end
50
49
 
51
- # Add auto-accept permission for /bm command
52
50
  ensure_permission
53
51
  end
54
52
 
@@ -70,69 +68,86 @@ def ensure_permission
70
68
  puts
71
69
  end
72
70
  rescue JSON::ParserError
73
- # If settings.json is malformed, skip permission setup
74
71
  warn "Warning: Could not parse ~/.claude/settings.json - skipping permission setup"
75
72
  end
76
73
 
77
- def load_bookmarks
78
- return {} unless File.exist?(BOOKMARKS_FILE)
79
- JSON.parse(File.read(BOOKMARKS_FILE))
80
- rescue JSON::ParserError
81
- {}
74
+ def resolve_session_dir(cwd)
75
+ path = cwd
76
+ loop do
77
+ encoded = path.gsub('/', '-')
78
+ return path if Dir.exist?(File.join(CLAUDE_PROJECTS, encoded))
79
+ parent = File.dirname(path)
80
+ break if parent == path
81
+ path = parent
82
+ end
83
+ cwd
82
84
  end
83
85
 
84
- def find_tags_for_path(path, bookmarks)
85
- bookmarks[path] || []
86
- end
86
+ def detect_session_id(session_dir)
87
+ encoded = session_dir.gsub('/', '-')
88
+ project_dir = File.join(CLAUDE_PROJECTS, encoded)
89
+ return nil unless Dir.exist?(project_dir)
87
90
 
88
- def show_current_sessions
89
- # Find running claude processes (exclude grep itself and this script)
90
- pids = `pgrep -x claude 2>/dev/null`.split.map(&:to_i)
91
+ newest = Dir.glob(File.join(project_dir, '*.jsonl'))
92
+ .select { |f| File.file?(f) }
93
+ .max_by { |f| File.mtime(f) }
94
+ return nil unless newest
91
95
 
92
- if pids.empty?
93
- puts "No Claude Code sessions currently running."
94
- return
95
- end
96
-
97
- bookmarks = load_bookmarks
98
-
99
- puts "Currently running Claude Code sessions:\n\n"
96
+ File.basename(newest, '.jsonl')
97
+ end
100
98
 
101
- pids.each do |pid|
102
- # Get working directory from /proc
103
- cwd_link = "/proc/#{pid}/cwd"
104
- next unless File.exist?(cwd_link)
99
+ def load_bookmarks
100
+ unless File.exist?(BOOKMARKS_FILE)
101
+ return { 'version' => 2, 'sessions' => {} }
102
+ end
105
103
 
106
- begin
107
- cwd = File.readlink(cwd_link)
108
- tags = find_tags_for_path(cwd, bookmarks)
109
- tag_str = tags.empty? ? dim('(no tags)') : green(tags.join(', '))
104
+ data = JSON.parse(File.read(BOOKMARKS_FILE))
105
+ if data['version']
106
+ data
107
+ else
108
+ migrate_bookmarks(data)
109
+ end
110
+ rescue JSON::ParserError
111
+ { 'version' => 2, 'sessions' => {} }
112
+ end
110
113
 
111
- puts " PID #{cyan(pid.to_s)}: #{cwd}"
112
- puts " Tags: #{tag_str}"
113
- puts
114
- rescue Errno::EACCES
115
- # Can't read cwd for this process
116
- puts " PID #{cyan(pid.to_s)}: #{dim('(permission denied)')}"
117
- puts
118
- end
114
+ def migrate_bookmarks(old_data)
115
+ sessions = {}
116
+ old_data.each do |path, tags|
117
+ sid = detect_session_id(path)
118
+ key = sid || "path:#{path}"
119
+ sessions[key] = { 'path' => path, 'tags' => tags }
119
120
  end
121
+ new_data = { 'version' => 2, 'sessions' => sessions }
122
+ save_bookmarks(new_data)
123
+ new_data
124
+ end
125
+
126
+ def save_bookmarks(bookmarks)
127
+ FileUtils.mkdir_p(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
128
+ File.write(BOOKMARKS_FILE, JSON.pretty_generate(bookmarks))
120
129
  end
121
130
 
122
131
  def find_session_by_tag(tag)
123
132
  bookmarks = load_bookmarks
124
- bookmarks.each do |path, tags|
125
- return path if tags.include?(tag)
133
+ bookmarks['sessions'].each do |id, entry|
134
+ if entry['tags'].include?(tag)
135
+ return { id: id, path: entry['path'] }
136
+ end
126
137
  end
127
138
  nil
128
139
  end
129
140
 
141
+ def find_tags_for_path(path, bookmarks)
142
+ bookmarks['sessions'].each_value do |entry|
143
+ return entry['tags'] if entry['path'] == path
144
+ end
145
+ []
146
+ end
147
+
130
148
  def session_exists_in_dir?(dir)
131
- # Check for .claude directory with session files
132
149
  claude_dir = File.join(dir, '.claude')
133
150
  return false unless Dir.exist?(claude_dir)
134
-
135
- # Look for conversation files or other session indicators
136
151
  Dir.glob(File.join(claude_dir, '**/*')).any? { |f| File.file?(f) }
137
152
  end
138
153
 
@@ -140,32 +155,30 @@ def read_key
140
155
  input = $stdin.getch
141
156
  if input == "\e"
142
157
  begin
143
- input << $stdin.read_nonblock(3)
158
+ input << $stdin.read_nonblock(5)
144
159
  rescue IO::WaitReadable
145
160
  end
146
161
  end
147
162
  case input
148
- when "\e[A", "k" then :up
149
- when "\e[B", "j" then :down
150
- when "\r", "\n" then :enter
151
- when "q", "\e" then :quit
152
- when "d" then :delete
163
+ when "\e[A", "k" then :up
164
+ when "\e[B", "j" then :down
165
+ when "\e[1;2A", "K" then :move_up
166
+ when "\e[1;2B", "J" then :move_down
167
+ when "\r", "\n" then :enter
168
+ when "q", "\e" then :quit
169
+ when "d" then :delete
153
170
  else nil
154
171
  end
155
172
  end
156
173
 
157
- def save_bookmarks(bookmarks)
158
- File.write(BOOKMARKS_FILE, JSON.pretty_generate(bookmarks))
159
- end
160
-
161
174
  def delete_bookmark_by_tag(tag)
162
175
  bookmarks = load_bookmarks
163
- path = find_session_by_tag(tag)
176
+ match = find_session_by_tag(tag)
164
177
 
165
- if path
166
- bookmarks.delete(path)
178
+ if match
179
+ bookmarks['sessions'].delete(match[:id])
167
180
  save_bookmarks(bookmarks)
168
- puts "Deleted bookmark: #{path}"
181
+ puts "Deleted bookmark: #{match[:path]}"
169
182
  puts " (was tagged: #{tag})"
170
183
  else
171
184
  puts "No session found with tag '#{tag}'"
@@ -176,41 +189,38 @@ end
176
189
  def list_bookmarks
177
190
  bookmarks = load_bookmarks
178
191
 
179
- if bookmarks.empty?
192
+ if bookmarks['sessions'].empty?
180
193
  puts "No bookmarked sessions."
181
194
  puts
182
195
  puts "To bookmark a session, use '/bm tag1 tag2' in Claude Code."
183
196
  return
184
197
  end
185
198
 
186
- items = bookmarks.map do |path, tags|
187
- { path: path, tags: tags, exists: Dir.exist?(path) }
199
+ items = bookmarks['sessions'].map do |id, entry|
200
+ { id: id, path: entry['path'], tags: entry['tags'], exists: Dir.exist?(entry['path']) }
188
201
  end
189
202
 
190
203
  index = 0
191
- puts "Select session (↑/↓/j/k move, Enter select, d delete, q quit):\n\n"
204
+ puts "Select session (\u2191/\u2193/j/k move, J/K reorder, Enter select, d delete, q quit):\n\n"
192
205
 
193
- # Hide cursor
194
206
  print "\e[?25l"
195
207
 
196
208
  begin
197
209
  loop do
198
- # Render list
199
210
  items.each_with_index do |item, i|
200
211
  print clear_line
201
212
  tag_str = cyan(item[:tags].join(', '))
202
213
  path_str = dim(item[:path])
203
214
  missing = item[:exists] ? '' : red(' [NOT FOUND]')
204
- line = "#{tag_str} #{path_str}#{missing}"
215
+ line = "#{tag_str} \u2192 #{path_str}#{missing}"
205
216
 
206
217
  if i == index
207
- puts " #{line}"
218
+ puts "\u25b8 #{line}"
208
219
  else
209
220
  puts " #{line}"
210
221
  end
211
222
  end
212
223
 
213
- # Move cursor back to start
214
224
  print "\e[#{items.size}A"
215
225
 
216
226
  key = read_key
@@ -219,27 +229,35 @@ def list_bookmarks
219
229
  index = (index - 1) % items.size
220
230
  when :down
221
231
  index = (index + 1) % items.size
232
+ when :move_up
233
+ if index > 0
234
+ items[index], items[index - 1] = items[index - 1], items[index]
235
+ index -= 1
236
+ rebuild_and_save(items)
237
+ end
238
+ when :move_down
239
+ if index < items.size - 1
240
+ items[index], items[index + 1] = items[index + 1], items[index]
241
+ index += 1
242
+ rebuild_and_save(items)
243
+ end
222
244
  when :delete
223
- # Show confirmation
224
245
  old_size = items.size
225
- print "\e[#{old_size}B" # Move below list
246
+ print "\e[#{old_size}B"
226
247
  print clear_line
227
248
  print "Delete '#{items[index][:tags].join(', ')}'? (y/n) "
228
249
  confirm = $stdin.getch
229
250
  if confirm.downcase == 'y'
230
- bookmarks.delete(items[index][:path])
231
- save_bookmarks(bookmarks)
232
- items.delete_at(index)
251
+ deleted = items.delete_at(index)
252
+ rebuild_and_save(items)
233
253
  index = [index, items.size - 1].min
234
254
  if items.empty?
235
- # Clear list and confirmation line
236
255
  print "\e[#{old_size}A"
237
256
  (old_size + 1).times { print clear_line; puts }
238
257
  print "\e[#{old_size + 1}A"
239
258
  puts "All bookmarks deleted."
240
259
  return
241
260
  end
242
- # Clear old list (one more line than new size)
243
261
  print clear_line
244
262
  print "\e[#{old_size}A"
245
263
  (old_size).times { print clear_line; puts }
@@ -249,30 +267,65 @@ def list_bookmarks
249
267
  print "\e[#{old_size}A"
250
268
  end
251
269
  when :enter
252
- # Clear the menu
253
270
  (items.size).times { print clear_line; puts }
254
271
  print "\e[#{items.size}A"
255
272
 
256
273
  if items[index][:exists]
257
- print "\e[?25h" # Show cursor before exec
258
- resume_session(items[index][:path])
274
+ print "\e[?25h"
275
+ resume_session(items[index][:id], items[index][:path])
259
276
  else
260
277
  puts red("Directory not found: #{items[index][:path]}")
261
278
  end
262
279
  return
263
280
  when :quit
264
- # Clear the menu
265
281
  (items.size).times { print clear_line; puts }
266
282
  print "\e[#{items.size}A"
267
283
  return
268
284
  end
269
285
  end
270
286
  ensure
271
- # Always show cursor
272
287
  print "\e[?25h"
273
288
  end
274
289
  end
275
290
 
291
+ def rebuild_and_save(items)
292
+ sessions = {}
293
+ items.each { |item| sessions[item[:id]] = { 'path' => item[:path], 'tags' => item[:tags] } }
294
+ save_bookmarks({ 'version' => 2, 'sessions' => sessions })
295
+ end
296
+
297
+ def show_current_sessions
298
+ pids = `pgrep -x claude 2>/dev/null`.split.map(&:to_i)
299
+
300
+ if pids.empty?
301
+ puts "No Claude Code sessions currently running."
302
+ return
303
+ end
304
+
305
+ bookmarks = load_bookmarks
306
+
307
+ puts "Running sessions:\n\n"
308
+
309
+ pids.each do |pid|
310
+ cwd_link = "/proc/#{pid}/cwd"
311
+ next unless File.exist?(cwd_link)
312
+
313
+ begin
314
+ cwd = File.readlink(cwd_link)
315
+ session_dir = resolve_session_dir(cwd)
316
+ tags = find_tags_for_path(session_dir, bookmarks)
317
+ tag_str = tags.empty? ? dim('(no tags)') : green(tags.join(', '))
318
+
319
+ puts " PID #{cyan(pid.to_s)}: #{session_dir}"
320
+ puts " Tags: #{tag_str}"
321
+ puts
322
+ rescue Errno::EACCES
323
+ puts " PID #{cyan(pid.to_s)}: #{dim('(permission denied)')}"
324
+ puts
325
+ end
326
+ end
327
+ end
328
+
276
329
  def show_help
277
330
  puts <<~HELP
278
331
  CC - Claude Code Session Manager
@@ -307,20 +360,29 @@ def show_help
307
360
  HELP
308
361
  end
309
362
 
310
- def resume_session(path)
363
+ def resume_session(session_id, path)
311
364
  unless Dir.exist?(path)
312
365
  puts "Error: Directory not found: #{path}"
313
366
  exit 1
314
367
  end
315
368
 
316
369
  Dir.chdir(path) do
370
+ # Emit OSC 7 so wezterm knows the new cwd (for Alt+n, etc.)
371
+ host = `hostname`.strip
372
+ print "\033]7;file://#{host}#{path}\007"
317
373
  puts "Resuming session in: #{path}"
318
- # Use -c to continue most recent session in this directory (no picker)
319
- exec('claude', '-c')
374
+ if session_id.start_with?('path:')
375
+ exec('claude', '-c')
376
+ else
377
+ exec('claude', '--resume', session_id)
378
+ end
320
379
  end
321
380
  end
322
381
 
323
382
  def continue_or_start
383
+ # Emit OSC 7 so wezterm knows the cwd
384
+ host = `hostname`.strip
385
+ print "\033]7;file://#{host}#{Dir.pwd}\007"
324
386
  if session_exists_in_dir?(Dir.pwd)
325
387
  exec('claude', '-c')
326
388
  else
@@ -350,19 +412,19 @@ when nil
350
412
  continue_or_start
351
413
  else
352
414
  tag = ARGV[0]
353
- path = find_session_by_tag(tag)
415
+ match = find_session_by_tag(tag)
354
416
 
355
- if path
356
- resume_session(path)
417
+ if match
418
+ resume_session(match[:id], match[:path])
357
419
  else
358
420
  puts "No session found with tag '#{tag}'"
359
421
  puts
360
422
  puts "Available tags:"
361
423
  bookmarks = load_bookmarks
362
- if bookmarks.empty?
424
+ if bookmarks['sessions'].empty?
363
425
  puts " (none - use '/bm tag1 tag2' in Claude Code to bookmark)"
364
426
  else
365
- all_tags = bookmarks.values.flatten.uniq.sort
427
+ all_tags = bookmarks['sessions'].values.flat_map { |e| e['tags'] }.uniq.sort
366
428
  all_tags.each { |t| puts " #{t}" }
367
429
  end
368
430
  exit 1
data/bin/cc-bookmark CHANGED
@@ -2,34 +2,94 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # CC-Bookmark - Helper script for /bm command
5
- # Bookmarks the current directory with tags
5
+ # Bookmarks the current directory with tags, keyed by session ID
6
6
 
7
7
  require 'json'
8
8
  require 'fileutils'
9
9
 
10
10
  CONFIG_DIR = File.expand_path('~/.cc-sessions')
11
11
  BOOKMARKS_FILE = File.join(CONFIG_DIR, 'bookmarks.json')
12
+ CLAUDE_PROJECTS = File.expand_path('~/.claude/projects')
12
13
 
13
- FileUtils.mkdir_p(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
14
+ def resolve_session_dir(cwd)
15
+ path = cwd
16
+ loop do
17
+ encoded = path.gsub('/', '-')
18
+ return path if Dir.exist?(File.join(CLAUDE_PROJECTS, encoded))
19
+ parent = File.dirname(path)
20
+ break if parent == path
21
+ path = parent
22
+ end
23
+ cwd
24
+ end
25
+
26
+ def detect_session_id(session_dir)
27
+ encoded = session_dir.gsub('/', '-')
28
+ project_dir = File.join(CLAUDE_PROJECTS, encoded)
29
+ return nil unless Dir.exist?(project_dir)
30
+
31
+ newest = Dir.glob(File.join(project_dir, '*.jsonl'))
32
+ .select { |f| File.file?(f) }
33
+ .max_by { |f| File.mtime(f) }
34
+ return nil unless newest
35
+
36
+ File.basename(newest, '.jsonl')
37
+ end
38
+
39
+ def load_bookmarks
40
+ return { 'version' => 2, 'sessions' => {} } unless File.exist?(BOOKMARKS_FILE)
41
+
42
+ data = JSON.parse(File.read(BOOKMARKS_FILE))
43
+ if data['version']
44
+ data
45
+ else
46
+ migrate_bookmarks(data)
47
+ end
48
+ rescue JSON::ParserError
49
+ { 'version' => 2, 'sessions' => {} }
50
+ end
51
+
52
+ def migrate_bookmarks(old_data)
53
+ sessions = {}
54
+ old_data.each do |path, tags|
55
+ # Try to find a real session ID for this path
56
+ sid = detect_session_id(path)
57
+ key = sid || "path:#{path}"
58
+ sessions[key] = { 'path' => path, 'tags' => tags }
59
+ end
60
+ new_data = { 'version' => 2, 'sessions' => sessions }
61
+ save_bookmarks(new_data)
62
+ new_data
63
+ end
14
64
 
15
- bookmarks = if File.exist?(BOOKMARKS_FILE)
16
- JSON.parse(File.read(BOOKMARKS_FILE))
17
- else
18
- {}
19
- end
65
+ def save_bookmarks(bookmarks)
66
+ FileUtils.mkdir_p(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
67
+ File.write(BOOKMARKS_FILE, JSON.pretty_generate(bookmarks))
68
+ end
20
69
 
21
- cwd = Dir.pwd
70
+ FileUtils.mkdir_p(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
71
+
72
+ bookmarks = load_bookmarks
73
+ cwd = resolve_session_dir(Dir.pwd)
74
+ session_id = detect_session_id(cwd)
22
75
  tags = ARGV
23
76
 
24
77
  if tags.empty?
25
- if bookmarks[cwd]
26
- puts "Current bookmark: #{bookmarks[cwd].join(', ')}"
78
+ # Query mode: show tags for current session
79
+ if session_id
80
+ entry = bookmarks['sessions'][session_id]
81
+ if entry
82
+ puts "Current bookmark: #{entry['tags'].join(', ')}"
83
+ else
84
+ puts "No bookmark for this session. Usage: /bm tag1 tag2 ..."
85
+ end
27
86
  else
28
- puts "No bookmark for this directory. Usage: /bm tag1 tag2 ..."
87
+ puts "Could not detect session ID. Usage: /bm tag1 tag2 ..."
29
88
  end
30
89
  else
31
- bookmarks[cwd] = tags
32
- File.write(BOOKMARKS_FILE, JSON.pretty_generate(bookmarks))
90
+ key = session_id || "path:#{cwd}"
91
+ bookmarks['sessions'][key] = { 'path' => cwd, 'tags' => tags }
92
+ save_bookmarks(bookmarks)
33
93
  puts "Bookmarked: #{cwd}"
34
94
  puts "Tags: #{tags.join(', ')}"
35
95
  puts ""
metadata CHANGED
@@ -1,18 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cc-sessions
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.2.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-02-04 00:00:00.000000000 Z
11
+ date: 2026-02-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: A simple tool for bookmarking and resuming Claude Code sessions. Tag
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
15
- them with 'cc <tag>' from anywhere.
15
+ them with ''cc <tag>'' from anywhere. UPDATE v1.2.0: Session ID-based bookmarks,
16
+ auto-migration, OSC 7 for wezterm CWD tracking.'
16
17
  email:
17
18
  - g@isene.com
18
19
  executables: