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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c2adba72c82bfd3b3b92c29bf497cc0c486b16261edc38c103135a7391525a4
4
- data.tar.gz: fa079e0580d51496bf4d5f4af242b291847854a1d6dd6f17918730b463bb2c9d
3
+ metadata.gz: 52c6ed94883c7981bdbc6bc0a2faa9d800dbe81fd4af0ee7af41dda777e95421
4
+ data.tar.gz: 1724ced66383451b1d914285b996a8d2c8bc82643fb08a0eb674a8c0c23a6e06
5
5
  SHA512:
6
- metadata.gz: d8a67b4261fb520b7b64c3c039dcabd5f47f1ae6ca377d1fa4d0a6d0dd9ac2574a83cc0024923273a754527c9e0cba96c75e0923918d1fd997ba62f8f1dd6ae7
7
- data.tar.gz: 79218840d95ce17d7a62d40e2f4cfca09498973b784677175ec1cbf67a9ed801074fad18a716dba2a16f89c7499a030b53e3c9c5fbf1b5f412b7cbd0530cd628
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sxn (0.2.3)
4
+ sxn (0.3.0)
5
5
  async (~> 2.0)
6
6
  bcrypt (~> 3.1)
7
7
  dry-configurable (~> 1.0)
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.add(session_name)
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
- def current
68
- Commands::Sessions.new.current
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
@@ -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