try-cli 1.7.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 (9) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +259 -0
  4. data/VERSION +1 -0
  5. data/bin/try +4 -0
  6. data/lib/fuzzy.rb +133 -0
  7. data/lib/tui.rb +892 -0
  8. data/try.rb +1281 -0
  9. metadata +53 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a6b118821db0d2fc53ac1fdb38f5574d13bee02e25b39d7362f51ab7aa128976
4
+ data.tar.gz: a3eb513bf97a2a9fe0e082d1b3166049c320739261158768a40880aa9d04ca54
5
+ SHA512:
6
+ metadata.gz: bbcc44f8afa56ae146d95a03dadec98bea06b0726fafed760926d9662c30321f19632c171b766956878c88b0b5e318f201c31894a73be571509d0ec0eda91ebf
7
+ data.tar.gz: 6759723862c35789b434bb0339397334b25cb046d06bba86632b8639eeaf2bb593cc65ff82aef891e122bc966d38d1c98c48357807a4a63e643f985944514442
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tobi Lutke
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,259 @@
1
+ # try - fresh directories for every vibe
2
+
3
+ *Your experiments deserve a home.* 🏠
4
+
5
+ > For everyone who constantly creates new projects for little experiments, a one-file Ruby script to quickly manage and navigate to keep them somewhat organized
6
+
7
+ Ever find yourself with 50 directories named `test`, `test2`, `new-test`, `actually-working-test`, scattered across your filesystem? Or worse, just coding in `/tmp` and losing everything?
8
+
9
+ **try** is here for your beautifully chaotic mind.
10
+
11
+ # What it does
12
+
13
+ ![Fuzzy Search Demo](assets/try-fuzzy-search-demo.gif)
14
+
15
+ *[View interactive version on asciinema](https://asciinema.org/a/ve8AXBaPhkKz40YbqPTlVjqgs)*
16
+
17
+ Instantly navigate through all your experiment directories with:
18
+ - **Fuzzy search** that just works
19
+ - **Smart sorting** - recently used stuff bubbles to the top
20
+ - **Auto-dating** - creates directories like `2025-08-17-redis-experiment`
21
+ - **Zero config** - just one Ruby file, no dependencies
22
+
23
+ ## Quick Start
24
+
25
+ ```bash
26
+ curl -sL https://raw.githubusercontent.com/tobi/try/refs/heads/main/try.rb > ~/.local/try.rb
27
+
28
+ # Make "try" executable so it can be run directly
29
+ chmod +x ~/.local/try.rb
30
+
31
+ # Add to your shell (bash/zsh)
32
+ echo 'eval "$(ruby ~/.local/try.rb init ~/src/tries)"' >> ~/.zshrc
33
+
34
+ # for fish shell users
35
+ echo 'eval (~/.local/try.rb init ~/src/tries | string collect)' >> ~/.config/fish/config.fish
36
+ ```
37
+
38
+ ## The Problem
39
+
40
+ You're learning Redis. You create `/tmp/redis-test`. Then `~/Desktop/redis-actually`. Then `~/projects/testing-redis-again`. Three weeks later you can't find that brilliant connection pooling solution you wrote at 2am.
41
+
42
+ ## The Solution
43
+
44
+ All your experiments in one place, with instant fuzzy search:
45
+
46
+ ```bash
47
+ $ try pool
48
+ → 2025-08-14-redis-connection-pool 2h, 18.5
49
+ 2025-08-03-thread-pool 3d, 12.1
50
+ 2025-07-22-db-pooling 2w, 8.3
51
+ + Create new: pool
52
+ ```
53
+
54
+ Type, arrow down, enter. You're there.
55
+
56
+ ## Features
57
+
58
+ ### 🎯 Smart Fuzzy Search
59
+ Not just substring matching - it's smart:
60
+ - `rds` matches `redis-server`
61
+ - `connpool` matches `connection-pool`
62
+ - Recent stuff scores higher
63
+ - Shorter names win on equal matches
64
+
65
+ ### ⏰ Time-Aware
66
+ - Shows how long ago you touched each project
67
+ - Recently accessed directories float to the top
68
+ - Perfect for "what was I working on yesterday?"
69
+
70
+ ### 🎨 Pretty TUI
71
+ - Clean, minimal interface
72
+ - Highlights matches as you type
73
+ - Shows scores so you know why things are ranked
74
+ - Dark mode by default (because obviously)
75
+
76
+ ### 📁 Organized Chaos
77
+ - Everything lives in `~/src/tries` (configurable via `TRY_PATH`)
78
+ - Auto-prefixes with dates: `2025-08-17-your-idea`
79
+ - Skip the date prompt if you already typed a name
80
+
81
+ ### Shell Integration
82
+
83
+ - Bash/Zsh:
84
+
85
+ ```bash
86
+ # default is ~/src/tries
87
+ eval "$(~/.local/try.rb init)"
88
+ # or pick a path
89
+ eval "$(~/.local/try.rb init ~/src/tries)"
90
+ ```
91
+
92
+ - Fish:
93
+
94
+ ```fish
95
+ eval (~/.local/try.rb init | string collect)
96
+ # or pick a path
97
+ eval (~/.local/try.rb init ~/src/tries | string collect)
98
+ ```
99
+
100
+ Notes:
101
+ - The runtime commands printed by `try` are shell-neutral (absolute paths, quoted). Only the small wrapper function differs per shell.
102
+
103
+ ## Usage
104
+
105
+ ```bash
106
+ try # Browse all experiments
107
+ try redis # Jump to redis experiment or create new
108
+ try new api # Start with "2025-08-17-new-api"
109
+ try . [name] # Create a dated worktree dir for current repo
110
+ try ./path/to/repo [name] # Use another repo as the worktree source
111
+ try worktree dir [name] # Same as above, explicit CLI form
112
+ try clone https://github.com/user/repo.git # Clone repo into date-prefixed directory
113
+ try https://github.com/user/repo.git # Shorthand for clone (same as above)
114
+ try --help # See all options
115
+ ```
116
+
117
+ Notes on worktrees (`try .` / `try worktree dir`):
118
+ - With a custom [name], uses that; otherwise uses cwd’s basename. Both are prefixed with today’s date.
119
+ - Inside a Git repo: adds a detached HEAD git worktree to the created directory.
120
+ - Outside a repo: simply creates the directory and changes into it.
121
+
122
+ ### Git Repository Cloning
123
+
124
+ **try** can automatically clone git repositories into properly named experiment directories:
125
+
126
+ ```bash
127
+ # Clone with auto-generated directory name
128
+ try clone https://github.com/tobi/try.git
129
+ # Creates: 2025-08-27-tobi-try
130
+
131
+ # Clone with custom name
132
+ try clone https://github.com/tobi/try.git my-fork
133
+ # Creates: my-fork
134
+
135
+ # Shorthand syntax (no need to type 'clone')
136
+ try https://github.com/tobi/try.git
137
+ # Creates: 2025-08-27-tobi-try
138
+ ```
139
+
140
+ Supported git URI formats:
141
+ - `https://github.com/user/repo.git` (HTTPS GitHub)
142
+ - `git@github.com:user/repo.git` (SSH GitHub)
143
+ - `https://gitlab.com/user/repo.git` (GitLab)
144
+ - `git@host.com:user/repo.git` (SSH other hosts)
145
+
146
+ The `.git` suffix is automatically removed from URLs when generating directory names.
147
+
148
+ ### Keyboard Shortcuts
149
+
150
+ - `↑/↓` or `Ctrl-P/N/J/K` - Navigate
151
+ - `Enter` - Select or create
152
+ - `Backspace` - Delete character
153
+ - `Ctrl-D` - Delete directory (with confirmation)
154
+ - `ESC` - Cancel
155
+ - Just type to filter
156
+
157
+ ## Configuration
158
+
159
+ Set `TRY_PATH` to change where experiments are stored:
160
+
161
+ ```bash
162
+ export TRY_PATH=~/code/sketches
163
+ ```
164
+
165
+ Default: `~/src/tries`
166
+
167
+ ## Nix
168
+
169
+ ### Quick start
170
+
171
+ ```bash
172
+ nix run github:tobi/try
173
+ nix run github:tobi/try -- --help
174
+ nix run github:tobi/try init ~/my-tries
175
+ ```
176
+
177
+ ### Home Manager
178
+
179
+ ```nix
180
+ {
181
+ inputs.try.url = "github:tobi/try";
182
+
183
+ imports = [ inputs.try.homeManagerModules.default ];
184
+
185
+ programs.try = {
186
+ enable = true;
187
+ path = "~/experiments"; # optional, defaults to ~/src/tries
188
+ };
189
+ }
190
+ ```
191
+
192
+ ## Homebrew
193
+
194
+ ### Quick start
195
+
196
+ ```bash
197
+ brew tap tobi/try https://github.com/tobi/try
198
+ brew install try
199
+ ```
200
+
201
+ After installation, add to your shell:
202
+
203
+ - Bash/Zsh:
204
+
205
+ ```bash
206
+ # default is ~/src/tries
207
+ eval "$(try init)"
208
+ # or pick a path
209
+ eval "$(try init ~/src/tries)"
210
+ ```
211
+
212
+ - Fish:
213
+
214
+ ```fish
215
+ eval "(try init | string collect)"
216
+ # or pick a path
217
+ eval "(try init ~/src/tries | string collect)"
218
+ ```
219
+
220
+ ## Why Ruby?
221
+
222
+ - One file, no dependencies
223
+ - Works on any system with Ruby (macOS has it built-in)
224
+ - Fast enough for thousands of directories
225
+ - Easy to hack on
226
+
227
+ ## The Philosophy
228
+
229
+ Your brain doesn't work in neat folders. You have ideas, you try things, you context-switch like a caffeinated squirrel. This tool embraces that.
230
+
231
+ Every experiment gets a home. Every home is instantly findable. Your 2am coding sessions are no longer lost to the void.
232
+
233
+ ## FAQ
234
+
235
+ **Q: Why not just use `cd` and `ls`?**
236
+ A: Because you have 200 directories and can't remember if you called it `test-redis`, `redis-test`, or `new-redis-thing`.
237
+
238
+ **Q: Why not use `fzf`?**
239
+ A: fzf is great for files. This is specifically for project directories, with time-awareness and auto-creation built in.
240
+
241
+ **Q: Can I use this for real projects?**
242
+ A: You can, but it's designed for experiments. Real projects deserve real names in real locations.
243
+
244
+ **Q: What if I have thousands of experiments?**
245
+ A: First, welcome to the club. Second, it handles it fine - the scoring algorithm ensures relevant stuff stays on top.
246
+
247
+ ## Contributing
248
+
249
+ It's one file. If you want to change something, just edit it. Send a PR if you think others would like it too.
250
+
251
+ ## License
252
+
253
+ MIT - Do whatever you want with it.
254
+
255
+ ---
256
+
257
+ *Built for developers with ADHD by developers with ADHD.*
258
+
259
+ *Your experiments deserve a home.* 🏠
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.7.1
data/bin/try ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../try'
data/lib/fuzzy.rb ADDED
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Fuzzy string matching with scoring and highlight positions
4
+ #
5
+ # Usage:
6
+ # entries = [
7
+ # { text: "2024-01-15-project", base_score: 3.2 },
8
+ # { text: "2024-02-20-another", base_score: 1.5 },
9
+ # ]
10
+ # fuzzy = Fuzzy.new(entries)
11
+ #
12
+ # # Get all matches
13
+ # fuzzy.match("proj").each do |entry, positions, score|
14
+ # puts "#{entry[:text]} score=#{score} highlight=#{positions.inspect}"
15
+ # end
16
+ #
17
+ # # Limit results
18
+ # fuzzy.match("proj").limit(10).each { |entry, positions, score| ... }
19
+ #
20
+ class Fuzzy
21
+ Entry = Data.define(:data, :text, :text_lower, :base_score)
22
+
23
+ def initialize(entries)
24
+ @entries = entries.map do |e|
25
+ text = e[:text] || e["text"]
26
+ Entry.new(
27
+ data: e,
28
+ text: text,
29
+ text_lower: text.downcase,
30
+ base_score: e[:base_score] || e["base_score"] || 0.0
31
+ )
32
+ end
33
+ end
34
+
35
+ # Returns a MatchResult enumerator for the query
36
+ def match(query)
37
+ MatchResult.new(@entries, query.to_s)
38
+ end
39
+
40
+ # Enumerator wrapper that supports .limit() and .each
41
+ class MatchResult
42
+ include Enumerable
43
+
44
+ def initialize(entries, query)
45
+ @entries = entries
46
+ @query = query
47
+ @query_lower = query.downcase
48
+ @query_chars = @query_lower.chars
49
+ @limit = nil
50
+ end
51
+
52
+ # Set maximum number of results
53
+ def limit(n)
54
+ @limit = n
55
+ self
56
+ end
57
+
58
+ # Iterate over matches: yields (entry_data, highlight_positions, score)
59
+ def each(&block)
60
+ return enum_for(:each) unless block_given?
61
+
62
+ results = []
63
+
64
+ @entries.each do |entry|
65
+ score, positions = calculate_match(entry)
66
+ next if score.nil? # No match
67
+
68
+ results << [entry.data, positions, score]
69
+ end
70
+
71
+ # Sort by score descending
72
+ results.sort_by! { |_, _, score| -score }
73
+
74
+ # Apply limit
75
+ results = results.first(@limit) if @limit
76
+
77
+ results.each(&block)
78
+ end
79
+
80
+ private
81
+
82
+ # Pre-computed sqrt values for proximity bonus (gap 0-15)
83
+ SQRT_TABLE = (0..16).map { |n| 2.0 / Math.sqrt(n + 1) }.freeze
84
+
85
+ def calculate_match(entry)
86
+ positions = []
87
+ score = entry.base_score
88
+
89
+ # Empty query = match all with base score only
90
+ return [score, positions] if @query.empty?
91
+
92
+ text = entry.text_lower
93
+ query_chars = @query_chars
94
+ query_len = query_chars.length
95
+
96
+ last_pos = -1
97
+ pos = 0
98
+
99
+ query_chars.each do |qc|
100
+ # Find next occurrence of query char starting from pos
101
+ found = text.index(qc, pos)
102
+ return nil unless found # No match
103
+
104
+ positions << found
105
+
106
+ # Base match point
107
+ score += 1.0
108
+
109
+ # Word boundary bonus (start of string or after non-alphanumeric)
110
+ if found == 0 || text[found - 1].match?(/[^a-z0-9]/)
111
+ score += 1.0
112
+ end
113
+
114
+ # Proximity bonus (consecutive chars score higher)
115
+ if last_pos >= 0
116
+ gap = found - last_pos - 1
117
+ score += gap < 16 ? SQRT_TABLE[gap] : (2.0 / Math.sqrt(gap + 1))
118
+ end
119
+
120
+ last_pos = found
121
+ pos = found + 1
122
+ end
123
+
124
+ # Density bonus: prefer shorter spans
125
+ score *= (query_len.to_f / (last_pos + 1)) if last_pos >= 0
126
+
127
+ # Length penalty: shorter strings score higher
128
+ score *= (10.0 / (entry.text.length + 10.0))
129
+
130
+ [score, positions]
131
+ end
132
+ end
133
+ end