sxn 0.2.3 → 0.3.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 +47 -0
- data/Gemfile.lock +1 -1
- data/lib/sxn/CLI.rb +107 -4
- data/lib/sxn/commands/init.rb +136 -0
- data/lib/sxn/commands/sessions.rb +307 -7
- data/lib/sxn/commands/templates.rb +230 -0
- data/lib/sxn/commands/worktrees.rb +17 -1
- data/lib/sxn/commands.rb +1 -0
- data/lib/sxn/config/templates_config.rb +153 -0
- data/lib/sxn/config.rb +1 -0
- data/lib/sxn/core/config_manager.rb +4 -0
- data/lib/sxn/core/project_manager.rb +19 -2
- data/lib/sxn/core/rules_manager.rb +61 -3
- data/lib/sxn/core/session_config.rb +96 -0
- data/lib/sxn/core/session_manager.rb +29 -3
- data/lib/sxn/core/template_manager.rb +187 -0
- data/lib/sxn/core/worktree_manager.rb +38 -11
- data/lib/sxn/core.rb +2 -0
- data/lib/sxn/errors.rb +34 -2
- data/lib/sxn/ui/prompt.rb +4 -0
- data/lib/sxn/ui/table.rb +18 -1
- data/lib/sxn/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52c6ed94883c7981bdbc6bc0a2faa9d800dbe81fd4af0ee7af41dda777e95421
|
|
4
|
+
data.tar.gz: 1724ced66383451b1d914285b996a8d2c8bc82643fb08a0eb674a8c0c23a6e06
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ec072a5436b75710595263f5667828a83d0c5c42aa44f8cde622550762b30fd1ce3b244636bbf57df70b89eeda2c765abbfd04b6b80ff3c4998358e8bad4afe2
|
|
7
|
+
data.tar.gz: d1d7fa0c3acc68746767376f8f201004c74e8930ce8069b69aa4ad080b7338d4f21da7743cfd704e20746e942ddeca497fc7baa7f81c3a4d7c638e3b6a8437eb
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,50 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] - 2025-12-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Session templates support for creating sessions from predefined configurations
|
|
12
|
+
- `sxn templates list` to view available templates
|
|
13
|
+
- `sxn templates show <name>` to view template details
|
|
14
|
+
- `--template` option for `sxn sessions add` to create sessions from templates
|
|
15
|
+
- TemplateManager for template operations and validation
|
|
16
|
+
- TemplatesConfig for loading templates from `templates.yml`
|
|
17
|
+
- Session template error classes for better error handling
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Project rules now correctly apply when creating sessions from templates
|
|
21
|
+
|
|
22
|
+
## [0.2.5] - 2025-11-30
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- `sxn enter` command to quickly navigate to current session directory
|
|
26
|
+
- `sxn current enter` subcommand as alternative way to enter session
|
|
27
|
+
- `--path` option for `sxn current` to output only the session path
|
|
28
|
+
- `sxn shell` command to install shell integration (idempotent)
|
|
29
|
+
- Auto-detects shell type (bash/zsh)
|
|
30
|
+
- Installs `sxn-enter` function to shell config
|
|
31
|
+
- Supports `--uninstall` to remove integration
|
|
32
|
+
- Supports `--shell-type` to specify shell explicitly
|
|
33
|
+
|
|
34
|
+
## [0.2.4] - 2025-11-30
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- Interactive worktree wizard after session creation
|
|
38
|
+
- Prompts to add worktrees with descriptive explanations
|
|
39
|
+
- Supports adding multiple worktrees in sequence
|
|
40
|
+
- Explains branch options including remote tracking syntax
|
|
41
|
+
- `--skip-worktree` flag to bypass the wizard when creating sessions
|
|
42
|
+
- `--verbose` flag for worktree debugging with detailed git output
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- Sessions now automatically switch to newly created session (no need to run `sxn use` afterwards)
|
|
46
|
+
- Improved project manager to safely handle nil projects configuration
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
- Fixed test mocks for verbose parameter in worktree operations
|
|
50
|
+
- Fixed version spec to support semver pre-release format
|
|
51
|
+
|
|
8
52
|
## [0.2.3] - 2025-09-16
|
|
9
53
|
|
|
10
54
|
### Added
|
|
@@ -71,6 +115,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
71
115
|
- Initial placeholder release
|
|
72
116
|
- Basic gem structure
|
|
73
117
|
|
|
118
|
+
[0.3.0]: https://github.com/idl3/sxn/compare/v0.2.5...v0.3.0
|
|
119
|
+
[0.2.5]: https://github.com/idl3/sxn/compare/v0.2.4...v0.2.5
|
|
120
|
+
[0.2.4]: https://github.com/idl3/sxn/compare/v0.2.3...v0.2.4
|
|
74
121
|
[0.2.3]: https://github.com/idl3/sxn/compare/v0.2.1...v0.2.3
|
|
75
122
|
[0.2.1]: https://github.com/idl3/sxn/compare/v0.2.0...v0.2.1
|
|
76
123
|
[0.2.0]: https://github.com/idl3/sxn/compare/v0.1.0...v0.2.0
|
data/Gemfile.lock
CHANGED
data/lib/sxn/CLI.rb
CHANGED
|
@@ -41,8 +41,12 @@ module Sxn
|
|
|
41
41
|
desc "add SESSION_NAME", "Create a new session (shortcut for 'sxn sessions add')"
|
|
42
42
|
option :description, type: :string, aliases: "-d", desc: "Session description"
|
|
43
43
|
option :linear_task, type: :string, aliases: "-l", desc: "Linear task ID"
|
|
44
|
+
option :branch, type: :string, aliases: "-b", desc: "Default branch for worktrees"
|
|
45
|
+
option :template, type: :string, aliases: "-t", desc: "Template to use for worktree creation"
|
|
44
46
|
def add(session_name)
|
|
45
|
-
Commands::Sessions.new
|
|
47
|
+
cmd = Commands::Sessions.new
|
|
48
|
+
cmd.options = options
|
|
49
|
+
cmd.add(session_name)
|
|
46
50
|
rescue Sxn::Error => e
|
|
47
51
|
handle_error(e)
|
|
48
52
|
end
|
|
@@ -62,10 +66,102 @@ module Sxn
|
|
|
62
66
|
handle_error(e)
|
|
63
67
|
end
|
|
64
68
|
|
|
65
|
-
desc "current", "Show current session (shortcut for 'sxn sessions current')"
|
|
69
|
+
desc "current [SUBCOMMAND]", "Show current session (shortcut for 'sxn sessions current')"
|
|
66
70
|
option :verbose, type: :boolean, aliases: "-v", desc: "Show detailed information"
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
option :path, type: :boolean, aliases: "-p", desc: "Output only the session path"
|
|
72
|
+
def current(subcommand = nil)
|
|
73
|
+
Commands::Sessions.new.current(subcommand)
|
|
74
|
+
rescue Sxn::Error => e
|
|
75
|
+
handle_error(e)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
desc "enter", "Enter current session directory (outputs cd command for shell eval)"
|
|
79
|
+
long_desc <<-LONGDESC
|
|
80
|
+
Outputs a cd command to navigate to the current session directory.
|
|
81
|
+
|
|
82
|
+
Usage with shell eval:
|
|
83
|
+
eval "$(sxn enter)"
|
|
84
|
+
|
|
85
|
+
Or install shell integration for easier use:
|
|
86
|
+
sxn shell
|
|
87
|
+
|
|
88
|
+
Then simply run:
|
|
89
|
+
sxn-enter
|
|
90
|
+
LONGDESC
|
|
91
|
+
def enter
|
|
92
|
+
Commands::Sessions.new.enter
|
|
93
|
+
rescue Sxn::Error => e
|
|
94
|
+
handle_error(e)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
desc "up", "Navigate to project root from session (outputs cd command for shell eval)"
|
|
98
|
+
long_desc <<-LONGDESC
|
|
99
|
+
Outputs a cd command to navigate to the project root from within a session.
|
|
100
|
+
|
|
101
|
+
The project root is determined from the .sxnrc file in the session directory,
|
|
102
|
+
which points back to the parent .sxn folder.
|
|
103
|
+
|
|
104
|
+
Usage with shell eval:
|
|
105
|
+
eval "$(sxn up)"
|
|
106
|
+
|
|
107
|
+
Or install shell integration for easier use:
|
|
108
|
+
sxn shell
|
|
109
|
+
|
|
110
|
+
Then simply run:
|
|
111
|
+
sxn-up
|
|
112
|
+
LONGDESC
|
|
113
|
+
def up
|
|
114
|
+
require "shellwords"
|
|
115
|
+
|
|
116
|
+
session_config = Sxn::Core::SessionConfig.find_from_path(Dir.pwd)
|
|
117
|
+
|
|
118
|
+
unless session_config
|
|
119
|
+
warn "Not in a session directory."
|
|
120
|
+
warn ""
|
|
121
|
+
warn "This command works when run from within a session folder."
|
|
122
|
+
warn "Session folders contain a .sxnrc file that points back to the project."
|
|
123
|
+
warn ""
|
|
124
|
+
warn "Tip: Add this function to your shell profile for easier navigation:"
|
|
125
|
+
warn ""
|
|
126
|
+
warn " sxn-up() { eval \"$(sxn up 2>/dev/null)\" || sxn up; }"
|
|
127
|
+
exit(1)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
project_root = session_config.project_root
|
|
131
|
+
|
|
132
|
+
unless project_root && File.directory?(project_root)
|
|
133
|
+
warn "Could not determine project root from .sxnrc"
|
|
134
|
+
warn "parent_sxn_path: #{session_config.parent_sxn_path || "nil"}"
|
|
135
|
+
exit(1)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Output the cd command for shell integration
|
|
139
|
+
puts "cd #{Shellwords.escape(project_root)}"
|
|
140
|
+
rescue Sxn::Error => e
|
|
141
|
+
handle_error(e)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
desc "shell", "Install shell integration (sxn-enter function)"
|
|
145
|
+
option :shell_type, type: :string, enum: %w[bash zsh auto], default: "auto",
|
|
146
|
+
desc: "Shell type (bash, zsh, or auto-detect)"
|
|
147
|
+
option :uninstall, type: :boolean, default: false, desc: "Remove shell integration"
|
|
148
|
+
long_desc <<-LONGDESC
|
|
149
|
+
Installs shell integration to your shell configuration file (.zshrc or .bashrc).
|
|
150
|
+
|
|
151
|
+
This adds the sxn-enter function which allows you to quickly navigate
|
|
152
|
+
to your current session directory.
|
|
153
|
+
|
|
154
|
+
The installation is idempotent - running it multiple times will not
|
|
155
|
+
add duplicate entries.
|
|
156
|
+
|
|
157
|
+
Examples:
|
|
158
|
+
sxn shell # Auto-detect shell and install
|
|
159
|
+
sxn shell --shell-type=zsh # Install for zsh specifically
|
|
160
|
+
sxn shell --uninstall # Remove shell integration
|
|
161
|
+
LONGDESC
|
|
162
|
+
map "shell" => :install_shell_wrapper
|
|
163
|
+
def install_shell_wrapper
|
|
164
|
+
Commands::Init.new.invoke(:install_shell, [], options)
|
|
69
165
|
rescue Sxn::Error => e
|
|
70
166
|
handle_error(e)
|
|
71
167
|
end
|
|
@@ -122,6 +218,13 @@ module Sxn
|
|
|
122
218
|
handle_error(e)
|
|
123
219
|
end
|
|
124
220
|
|
|
221
|
+
desc "templates SUBCOMMAND", "Manage session templates"
|
|
222
|
+
def templates(subcommand = nil, *args)
|
|
223
|
+
Commands::Templates.start([subcommand, *args].compact)
|
|
224
|
+
rescue Sxn::Error => e
|
|
225
|
+
handle_error(e)
|
|
226
|
+
end
|
|
227
|
+
|
|
125
228
|
desc "status", "Show overall sxn status"
|
|
126
229
|
def status
|
|
127
230
|
show_status
|
data/lib/sxn/commands/init.rb
CHANGED
|
@@ -8,6 +8,34 @@ module Sxn
|
|
|
8
8
|
class Init < Thor
|
|
9
9
|
include Thor::Actions
|
|
10
10
|
|
|
11
|
+
# Shell integration marker - used to identify sxn shell functions
|
|
12
|
+
SHELL_MARKER = "# sxn shell integration"
|
|
13
|
+
SHELL_MARKER_END = "# end sxn shell integration"
|
|
14
|
+
|
|
15
|
+
# Shell function that gets installed
|
|
16
|
+
SHELL_FUNCTION = <<~SHELL.freeze
|
|
17
|
+
#{SHELL_MARKER}
|
|
18
|
+
sxn-enter() {
|
|
19
|
+
local cmd
|
|
20
|
+
cmd="$(sxn enter 2>/dev/null)"
|
|
21
|
+
if [ $? -eq 0 ] && [ -n "$cmd" ]; then
|
|
22
|
+
eval "$cmd"
|
|
23
|
+
else
|
|
24
|
+
sxn enter
|
|
25
|
+
fi
|
|
26
|
+
}
|
|
27
|
+
sxn-up() {
|
|
28
|
+
local cmd
|
|
29
|
+
cmd="$(sxn up 2>/dev/null)"
|
|
30
|
+
if [ $? -eq 0 ] && [ -n "$cmd" ]; then
|
|
31
|
+
eval "$cmd"
|
|
32
|
+
else
|
|
33
|
+
sxn up
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
#{SHELL_MARKER_END}
|
|
37
|
+
SHELL
|
|
38
|
+
|
|
11
39
|
desc "init [FOLDER]", "Initialize sxn in a project folder"
|
|
12
40
|
option :force, type: :boolean, desc: "Force initialization even if already initialized"
|
|
13
41
|
option :auto_detect, type: :boolean, default: true, desc: "Automatically detect and register projects"
|
|
@@ -55,8 +83,116 @@ module Sxn
|
|
|
55
83
|
end
|
|
56
84
|
end
|
|
57
85
|
|
|
86
|
+
desc "install_shell", "Install shell integration (sxn-enter function)"
|
|
87
|
+
option :shell_type, type: :string, enum: %w[bash zsh auto], default: "auto",
|
|
88
|
+
desc: "Shell type (bash, zsh, or auto-detect)"
|
|
89
|
+
option :uninstall, type: :boolean, default: false, desc: "Remove shell integration"
|
|
90
|
+
def install_shell
|
|
91
|
+
@ui.section("Shell Integration")
|
|
92
|
+
|
|
93
|
+
shell_type = detect_shell_type
|
|
94
|
+
rc_file = shell_rc_file(shell_type)
|
|
95
|
+
|
|
96
|
+
unless rc_file
|
|
97
|
+
@ui.error("Could not determine shell configuration file")
|
|
98
|
+
@ui.info("Supported shells: bash, zsh")
|
|
99
|
+
exit(1)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if options[:uninstall]
|
|
103
|
+
uninstall_shell_integration(rc_file, shell_type)
|
|
104
|
+
else
|
|
105
|
+
install_shell_integration(rc_file, shell_type)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
58
109
|
private
|
|
59
110
|
|
|
111
|
+
def detect_shell_type
|
|
112
|
+
shell_opt = options[:shell_type] || options[:shell] || "auto"
|
|
113
|
+
return shell_opt unless shell_opt == "auto"
|
|
114
|
+
|
|
115
|
+
# Check SHELL environment variable
|
|
116
|
+
current_shell = ENV.fetch("SHELL", "")
|
|
117
|
+
if current_shell.include?("zsh")
|
|
118
|
+
"zsh"
|
|
119
|
+
elsif current_shell.include?("bash")
|
|
120
|
+
"bash"
|
|
121
|
+
else
|
|
122
|
+
# Default to bash
|
|
123
|
+
"bash"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def shell_rc_file(shell_type)
|
|
128
|
+
home = Dir.home
|
|
129
|
+
case shell_type
|
|
130
|
+
when "zsh"
|
|
131
|
+
File.join(home, ".zshrc")
|
|
132
|
+
when "bash"
|
|
133
|
+
# Prefer .bashrc, fall back to .bash_profile on macOS
|
|
134
|
+
bashrc = File.join(home, ".bashrc")
|
|
135
|
+
bash_profile = File.join(home, ".bash_profile")
|
|
136
|
+
File.exist?(bashrc) ? bashrc : bash_profile
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def shell_integration_installed?(rc_file)
|
|
141
|
+
return false unless File.exist?(rc_file)
|
|
142
|
+
|
|
143
|
+
content = File.read(rc_file)
|
|
144
|
+
content.include?(SHELL_MARKER)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def install_shell_integration(rc_file, _shell_type)
|
|
148
|
+
if shell_integration_installed?(rc_file)
|
|
149
|
+
@ui.info("Shell integration already installed in #{rc_file}")
|
|
150
|
+
@ui.info("Use --uninstall to remove it first if you want to reinstall")
|
|
151
|
+
return
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Ensure rc file exists
|
|
155
|
+
FileUtils.touch(rc_file) unless File.exist?(rc_file)
|
|
156
|
+
|
|
157
|
+
# Append shell function
|
|
158
|
+
File.open(rc_file, "a") do |f|
|
|
159
|
+
f.puts "" # Add blank line before
|
|
160
|
+
f.puts SHELL_FUNCTION
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
@ui.success("Installed shell integration to #{rc_file}")
|
|
164
|
+
@ui.newline
|
|
165
|
+
@ui.info("The following functions were added:")
|
|
166
|
+
@ui.newline
|
|
167
|
+
puts " sxn-enter - Navigate to current session directory"
|
|
168
|
+
puts " sxn-up - Navigate to project root from session"
|
|
169
|
+
@ui.newline
|
|
170
|
+
@ui.recovery_suggestion("Run 'source #{rc_file}' or restart your shell to use them")
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def uninstall_shell_integration(rc_file, _shell_type)
|
|
174
|
+
unless File.exist?(rc_file)
|
|
175
|
+
@ui.info("Shell configuration file not found: #{rc_file}")
|
|
176
|
+
return
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
unless shell_integration_installed?(rc_file)
|
|
180
|
+
@ui.info("Shell integration not installed in #{rc_file}")
|
|
181
|
+
return
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Read file and remove sxn block
|
|
185
|
+
content = File.read(rc_file)
|
|
186
|
+
# Remove the block between markers (including blank line before)
|
|
187
|
+
pattern = /\n?#{Regexp.escape(SHELL_MARKER)}.*?#{Regexp.escape(SHELL_MARKER_END)}\n?/m
|
|
188
|
+
new_content = content.gsub(pattern, "\n")
|
|
189
|
+
|
|
190
|
+
File.write(rc_file, new_content)
|
|
191
|
+
|
|
192
|
+
@ui.success("Removed shell integration from #{rc_file}")
|
|
193
|
+
@ui.recovery_suggestion("Run 'source #{rc_file}' or restart your shell")
|
|
194
|
+
end
|
|
195
|
+
|
|
60
196
|
def determine_sessions_folder(folder)
|
|
61
197
|
return folder if folder && !options[:quiet]
|
|
62
198
|
|