sight 0.3.0 → 0.4.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: 6e488680c7d4ecc42d8cd02b8d2731a315755b35df2a9cf319ed3f0d413e2809
4
- data.tar.gz: 375af2bad773014e984f0b89c3333c839dd84bfc8e8cd610357c78bcfd052962
3
+ metadata.gz: ff89f4bccb463b21835b299946bcf7a54b43f9792eb0b262954812c79633da19
4
+ data.tar.gz: 7dab4e4911a9bf145a5b0542ac993c6b5d3ce25af54b345e537291c5567db681
5
5
  SHA512:
6
- metadata.gz: 76d83afcd3ac25b084fc22661d9561fd75bd16ef662237b5cf2e1d784225a442ac682740a312a711ae169e16eac1f8d997ff3e68b44fb1222a00a9fd3e077c6a
7
- data.tar.gz: 934ab59d60fb58ac4029718705d1551fd6ede47ff0dfcadfefdf45fdd30eddc2060dcc815988060a796cf55cf993e0b9ba82b86426b1bc8043ed974631006099
6
+ metadata.gz: c591ba467ae6d6a64d1d54c188513a2afe743c40e767bca6892f9f30e6ff45ecfb1f134fcef62c420b4e7fb8c13a77fcd9c69e8f830c6cf1a7c79f8d4d61b82f
7
+ data.tar.gz: 131fd548a595de6b14288b2ae536b2275eb66fb50f6e08182875866bdee44c0acfbb8194a0ee515e510472bb72f38eeae34fa7c64f64e0a97b2e4e4433867775
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0] - 2026-03-06
4
+
5
+ ### Added
6
+
7
+ - Untracked file support with file status badges (new, modified, deleted)
8
+ - Color-coded status badges in file header
9
+ - Ctrl-f/b/d/u scroll navigation
10
+ - Scroll percentage indicator replacing line counter
11
+ - Hook system for AI agent integration (`sight install-hook <agent>`)
12
+ - Claude Code hook via `UserPromptSubmit`
13
+ - Cursor hook via `beforeSubmitPrompt`
14
+ - Highlighted commented hunks in gutter and status bar
15
+
3
16
  ## [0.3.0] - 2026-03-04
4
17
 
5
18
  ### Added
data/CLAUDE.md CHANGED
@@ -15,12 +15,20 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
15
15
 
16
16
  TUI for closing the loop on AI-generated code changes — browse diffs, jump between hunks, and annotate them. Entry point: `exe/sight` → `CLI.run` → `App.new(files).run`.
17
17
 
18
- **Data flow**: `Git.diff` (raw string) → `DiffParser.parse` (returns `DiffFile[]`) → `App` (curses TUI)
18
+ **Data flow**: `Git.diff` (raw string) → `DiffParser.parse` (returns `DiffFile[]`) → `App` (curses TUI).
19
+ Untracked files are added via `Git.untracked_files` → `DiffParser.build_untracked`.
19
20
 
20
- **Key structs** (all in `diff_parser.rb`): `DiffFile(path, hunks)`, `Hunk(context, lines)`, `DiffLine(type, content, lineno, old_lineno)`. `DisplayLine(type, text, lineno)` is the render-side equivalent in `display_line.rb`.
21
+ **Key structs** (all in `diff_parser.rb`): `DiffFile(path, hunks, status)`, `Hunk(context, lines)`, `DiffLine(type, content, lineno, old_lineno)`.
22
+ `DisplayLine(type, text, lineno)` is the render-side equivalent in `display_line.rb`.
23
+ `Annotation(file_path, type, hunk, comment)` in `annotation.rb` stores per-hunk comments; `AnnotationFormatter` serializes them for output.
21
24
 
22
25
  **App** renders per-file views with hunk-based navigation (j/k). Active hunk is highlighted; inactive hunks render in dark gray (color pair 5, color 240).
23
26
 
27
+ **Hook system**: Unified via `sight install-hook <agent>` / `sight uninstall-hook <agent>` (agent: `claude` or `cursor`).
28
+ `ClaudeHookInstaller` manages a Claude Code `UserPromptSubmit` hook in `~/.config/claude/settings.json`.
29
+ `CursorHookInstaller` manages a Cursor `beforeSubmitPrompt` hook in `~/.cursor/hooks.json`.
30
+ Both read `.git/sight/pending-review`, output annotations, and delete the file. Hidden subcommands: `hook-run`, `cursor-hook-run`.
31
+
24
32
  ## Conventions
25
33
 
26
34
  - Ruby >= 3.2, uses `frozen_string_literal` in all files
data/README.md CHANGED
@@ -24,10 +24,30 @@ sight
24
24
  | `k` | Previous hunk |
25
25
  | `n` | Next file |
26
26
  | `p` | Previous file |
27
+ | `Ctrl-F` / `Ctrl-B` | Scroll full page down / up |
28
+ | `Ctrl-D` / `Ctrl-U` | Scroll half page down / up |
27
29
  | `?` | Toggle help |
28
30
  | `c` | Comment on hunk |
29
31
  | `q` / `Esc` | Quit |
30
32
 
33
+ ### Agent Integration
34
+
35
+ Install a hook so annotations are automatically fed as context in your next message:
36
+
37
+ ```bash
38
+ sight install-hook claude # Claude Code (~/.config/claude/settings.json)
39
+ sight install-hook cursor # Cursor (~/.cursor/hooks.json)
40
+ ```
41
+
42
+ When you quit sight after annotating, the next message you send will include your annotations.
43
+
44
+ To remove:
45
+
46
+ ```bash
47
+ sight uninstall-hook claude
48
+ sight uninstall-hook cursor
49
+ ```
50
+
31
51
  ## Development
32
52
 
33
53
  ```bash
data/lib/sight/app.rb CHANGED
@@ -53,6 +53,7 @@ module Sight
53
53
  Curses.init_pair(3, Curses::COLOR_CYAN, -1)
54
54
  Curses.init_pair(4, Curses::COLOR_YELLOW, -1)
55
55
  Curses.init_pair(5, 240, -1)
56
+ Curses.init_pair(6, Curses::COLOR_MAGENTA, -1)
56
57
  end
57
58
 
58
59
  def lines
@@ -70,11 +71,19 @@ module Sight
70
71
  end
71
72
 
72
73
  def render_header(win, width)
73
- path = files[file_idx].path
74
+ file = files[file_idx]
75
+ badge = "[#{file.status || :modified}]"
76
+ path = file.path
77
+ gap = width - path.length - badge.length
74
78
  win.setpos(0, 0)
75
- win.attron(color_for(:header)) { win.addstr(path[0, width]) }
79
+ if gap >= 1
80
+ win.attron(color_for(:header)) { win.addstr("#{path}#{" " * gap}") }
81
+ win.attron(badge_color(file.status)) { win.addstr(badge) }
82
+ else
83
+ win.attron(color_for(:header)) { win.addstr(path[0, width]) }
84
+ end
76
85
  win.setpos(1, 0)
77
- win.attron(color_for(:header)) { win.addstr("\u2500" * width) }
86
+ win.attron(Curses.color_pair(0) | Curses::A_BOLD) { win.addstr("\u2500" * width) }
78
87
  end
79
88
 
80
89
  def render_content(win, width)
@@ -89,13 +98,21 @@ module Sight
89
98
  lines.size
90
99
  end
91
100
 
101
+ commented_lines = commented_hunk_lines
102
+
92
103
  scroll_height.times do |row|
93
104
  idx = offset + row
94
105
  break if idx >= lines.size
95
106
  line = lines[idx]
96
107
  win.setpos(row + 2, 0)
97
108
  active = idx >= selected_start && idx < selected_end
98
- win.attron(dim) { win.addstr("#{format_gutter(line.type, line.lineno, gutter)} \u2502 ") }
109
+ gutter_str = format_gutter(line.type, line.lineno, gutter)
110
+ commented = commented_lines.include?(idx)
111
+ separator = commented ? "\u2503" : "\u2502"
112
+ sep_attr = commented ? Curses.color_pair(4) : dim
113
+ win.attron(dim) { win.addstr("#{gutter_str} ") }
114
+ win.attron(sep_attr) { win.addstr(separator) }
115
+ win.attron(dim) { win.addstr(" ") }
99
116
  attr = active ? color_for(line.type) : Curses.color_pair(5)
100
117
  win.attron(attr) { win.addstr(line.text[0, content_width]) }
101
118
  end
@@ -104,7 +121,13 @@ module Sight
104
121
  def render_status_bar(win, width)
105
122
  win.setpos(Curses.lines - 1, 0)
106
123
  win.attron(Curses.color_pair(4) | Curses::A_REVERSE) do
107
- status = " File #{file_idx + 1}/#{files.size} | Hunk #{hunk_idx + 1}/#{hunk_offsets.size} | Line #{offset + 1}/#{lines.size} "
124
+ percent = if lines.size <= scroll_height
125
+ 100
126
+ else
127
+ ((offset + scroll_height) * 100.0 / lines.size).ceil.clamp(0, 100)
128
+ end
129
+ commented = hunk_commented?(file_idx, hunk_idx) ? " [commented]" : ""
130
+ status = " File #{file_idx + 1}/#{files.size} | Hunk #{hunk_idx + 1}/#{hunk_offsets.size}#{commented} | #{percent}% "
108
131
  win.addstr(status.ljust(width))
109
132
  end
110
133
  end
@@ -123,11 +146,20 @@ module Sight
123
146
  case type
124
147
  when :add then Curses.color_pair(1)
125
148
  when :del then Curses.color_pair(2)
126
- when :header then Curses.color_pair(4) | Curses::A_BOLD
149
+ when :header then Curses.color_pair(0) | Curses::A_BOLD
127
150
  else Curses.color_pair(0)
128
151
  end
129
152
  end
130
153
 
154
+ def badge_color(status)
155
+ case status
156
+ when :added then Curses.color_pair(1) | Curses::A_BOLD
157
+ when :deleted then Curses.color_pair(2) | Curses::A_BOLD
158
+ when :untracked then Curses.color_pair(6) | Curses::A_BOLD
159
+ else Curses.color_pair(4) | Curses::A_BOLD
160
+ end
161
+ end
162
+
131
163
  def scroll_height
132
164
  Curses.lines - 3
133
165
  end
@@ -147,6 +179,10 @@ module Sight
147
179
  when "k" then jump_hunk(-1)
148
180
  when "n" then jump_file(1)
149
181
  when "p" then jump_file(-1)
182
+ when 6 then scroll(scroll_height)
183
+ when 2 then scroll(-scroll_height)
184
+ when 4 then scroll(scroll_height / 2)
185
+ when 21 then scroll(-scroll_height / 2)
150
186
  when "c" then annotate_hunk
151
187
  when "?" then show_help
152
188
  end
@@ -156,6 +192,10 @@ module Sight
156
192
  HELP_KEYS = [
157
193
  ["j", "Next hunk"],
158
194
  ["k", "Previous hunk"],
195
+ ["C-f", "Page down"],
196
+ ["C-b", "Page up"],
197
+ ["C-d", "Half page down"],
198
+ ["C-u", "Half page up"],
159
199
  ["n", "Next file"],
160
200
  ["p", "Previous file"],
161
201
  ["q / Esc", "Quit"],
@@ -271,6 +311,27 @@ module Sight
271
311
  end
272
312
  end
273
313
 
314
+ def hunk_commented?(file_index, hunk_index)
315
+ path = files[file_index].path
316
+ hunk = files[file_index].hunks[hunk_index]
317
+ annotations.any? { |a| a.file_path == path && a.hunk.equal?(hunk) }
318
+ end
319
+
320
+ def commented_hunk_lines
321
+ result = []
322
+ hunk_offsets.each_with_index do |offset, hunk_index|
323
+ next unless hunk_commented?(file_idx, hunk_index)
324
+ hunk_end = (hunk_index + 1 < hunk_offsets.size) ? hunk_offsets[hunk_index + 1] : lines.size
325
+ (offset...hunk_end).each { |i| result << i }
326
+ end
327
+ result
328
+ end
329
+
330
+ def scroll(delta)
331
+ max = [0, lines.size - scroll_height].max
332
+ self.offset = (offset + delta).clamp(0, max)
333
+ end
334
+
274
335
  def jump_hunk(delta)
275
336
  return if hunk_offsets.empty?
276
337
  self.hunk_idx = (hunk_idx + delta).clamp(0, hunk_offsets.size - 1)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+
6
+ module Sight
7
+ module ClaudeHookInstaller
8
+ HOOK_COMMAND = "sight hook-run"
9
+
10
+ module_function
11
+
12
+ def settings_path
13
+ File.join(Dir.home, ".config", "claude", "settings.json")
14
+ end
15
+
16
+ def install(path: settings_path)
17
+ settings = if File.exist?(path)
18
+ JSON.parse(File.read(path))
19
+ else
20
+ {}
21
+ end
22
+
23
+ hooks = settings["hooks"] ||= {}
24
+ prompt_hooks = hooks["UserPromptSubmit"] ||= []
25
+
26
+ if prompt_hooks.any? { |h| hook_is_sight?(h) }
27
+ puts "sight hook already installed"
28
+ return
29
+ end
30
+
31
+ prompt_hooks << {
32
+ "matcher" => "*",
33
+ "hooks" => [{"type" => "command", "command" => HOOK_COMMAND}]
34
+ }
35
+
36
+ FileUtils.mkdir_p(File.dirname(path))
37
+ File.write(path, JSON.pretty_generate(settings) + "\n")
38
+ puts "Installed sight hook into #{path}"
39
+ end
40
+
41
+ def uninstall(path: settings_path)
42
+ unless File.exist?(path)
43
+ puts "No settings file found"
44
+ return
45
+ end
46
+
47
+ settings = JSON.parse(File.read(path))
48
+ prompt_hooks = settings.dig("hooks", "UserPromptSubmit")
49
+
50
+ unless prompt_hooks&.any? { |h| hook_is_sight?(h) }
51
+ puts "No sight hook found"
52
+ return
53
+ end
54
+
55
+ prompt_hooks.reject! { |h| hook_is_sight?(h) }
56
+ File.write(path, JSON.pretty_generate(settings) + "\n")
57
+ puts "Uninstalled sight hook"
58
+ end
59
+
60
+ def hook_is_sight?(entry)
61
+ Array(entry["hooks"]).any? { |h| h["command"]&.include?("sight") }
62
+ end
63
+ end
64
+ end
data/lib/sight/cli.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module Sight
4
6
  module CLI
5
7
  module_function
@@ -11,7 +13,9 @@ module Sight
11
13
  puts
12
14
  puts "Keys: j/k hunks, n/p files, c comment, ? help, q quit"
13
15
  puts
14
- puts "Annotations are printed to stdout on quit."
16
+ puts "Subcommands:"
17
+ puts " install-hook <agent> Install hook (claude, cursor)"
18
+ puts " uninstall-hook <agent> Remove hook (claude, cursor)"
15
19
  return
16
20
  end
17
21
 
@@ -20,19 +24,103 @@ module Sight
20
24
  return
21
25
  end
22
26
 
23
- raw = Git.diff
24
- if raw.empty?
25
- warn "No diff output"
27
+ if argv[0] == "install-hook"
28
+ install_hook(argv[1])
29
+ return
30
+ end
31
+
32
+ if argv[0] == "uninstall-hook"
33
+ uninstall_hook(argv[1])
26
34
  return
27
35
  end
28
36
 
37
+ if argv.include?("hook-run")
38
+ run_hook
39
+ return
40
+ end
41
+
42
+ if argv.include?("cursor-hook-run")
43
+ run_cursor_hook
44
+ return
45
+ end
46
+
47
+ Git.clear_pending_review
48
+
49
+ raw = Git.diff
29
50
  files = DiffParser.parse(raw)
51
+
52
+ Git.untracked_files.each do |path|
53
+ content = Git.file_content(path)
54
+ next unless content.valid_encoding? && !content.include?("\x00")
55
+ files << DiffParser.build_untracked(path, content)
56
+ end
57
+
58
+ if files.empty?
59
+ warn "No changes"
60
+ return
61
+ end
62
+
30
63
  app = App.new(files)
31
64
  app.run
32
65
 
33
66
  unless app.annotations.empty?
34
- puts AnnotationFormatter.format(app.annotations)
67
+ formatted = AnnotationFormatter.format(app.annotations)
68
+ puts formatted
69
+ Git.save_pending_review(formatted)
35
70
  end
36
71
  end
72
+
73
+ AGENTS = {
74
+ "claude" => ClaudeHookInstaller,
75
+ "cursor" => CursorHookInstaller
76
+ }.freeze
77
+
78
+ def install_hook(agent)
79
+ installer = AGENTS[agent]
80
+ unless installer
81
+ warn "Unknown agent: #{agent.inspect}. Use: claude, cursor"
82
+ return
83
+ end
84
+ installer.install
85
+ end
86
+
87
+ def uninstall_hook(agent)
88
+ installer = AGENTS[agent]
89
+ unless installer
90
+ warn "Unknown agent: #{agent.inspect}. Use: claude, cursor"
91
+ return
92
+ end
93
+ installer.uninstall
94
+ end
95
+
96
+ def run_hook
97
+ git_dir = Git.repo_dir
98
+ rescue Error
99
+ nil
100
+ else
101
+ file = File.join(git_dir, "sight", "pending-review")
102
+ return unless File.exist?(file)
103
+
104
+ content = File.read(file)
105
+ puts "The user has just finished reviewing your code changes in sight. Here are their annotations:"
106
+ puts
107
+ puts content
108
+ File.delete(file)
109
+ end
110
+
111
+ def run_cursor_hook
112
+ $stdin.read
113
+ git_dir = Git.repo_dir
114
+ rescue Error
115
+ nil
116
+ else
117
+ file = File.join(git_dir, "sight", "pending-review")
118
+ return unless File.exist?(file)
119
+
120
+ content = File.read(file)
121
+ message = "The user has just finished reviewing your code changes in sight. Here are their annotations:\n\n#{content}"
122
+ puts JSON.generate(user_message: message)
123
+ File.delete(file)
124
+ end
37
125
  end
38
126
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+
6
+ module Sight
7
+ module CursorHookInstaller
8
+ HOOK_COMMAND = "sight cursor-hook-run"
9
+
10
+ module_function
11
+
12
+ def hooks_path
13
+ File.join(Dir.home, ".cursor", "hooks.json")
14
+ end
15
+
16
+ def install(path: hooks_path)
17
+ config = if File.exist?(path)
18
+ JSON.parse(File.read(path))
19
+ else
20
+ {"version" => 1, "hooks" => {}}
21
+ end
22
+
23
+ config["version"] ||= 1
24
+ hooks = config["hooks"] ||= {}
25
+ prompt_hooks = hooks["beforeSubmitPrompt"] ||= []
26
+
27
+ if prompt_hooks.any? { |h| hook_is_sight?(h) }
28
+ puts "sight hook already installed"
29
+ return
30
+ end
31
+
32
+ prompt_hooks << {"command" => HOOK_COMMAND}
33
+
34
+ FileUtils.mkdir_p(File.dirname(path))
35
+ File.write(path, JSON.pretty_generate(config) + "\n")
36
+ puts "Installed sight hook into #{path}"
37
+ end
38
+
39
+ def uninstall(path: hooks_path)
40
+ unless File.exist?(path)
41
+ puts "No hooks file found"
42
+ return
43
+ end
44
+
45
+ config = JSON.parse(File.read(path))
46
+ prompt_hooks = config.dig("hooks", "beforeSubmitPrompt")
47
+
48
+ unless prompt_hooks&.any? { |h| hook_is_sight?(h) }
49
+ puts "No sight hook found"
50
+ return
51
+ end
52
+
53
+ prompt_hooks.reject! { |h| hook_is_sight?(h) }
54
+ File.write(path, JSON.pretty_generate(config) + "\n")
55
+ puts "Uninstalled sight hook"
56
+ end
57
+
58
+ def hook_is_sight?(entry)
59
+ entry["command"]&.include?("sight")
60
+ end
61
+ end
62
+ end
@@ -3,7 +3,7 @@
3
3
  module Sight
4
4
  DiffLine = Struct.new(:type, :content, :lineno, :old_lineno, keyword_init: true)
5
5
  Hunk = Struct.new(:context, :lines, keyword_init: true)
6
- DiffFile = Struct.new(:path, :hunks, keyword_init: true)
6
+ DiffFile = Struct.new(:path, :hunks, :status, keyword_init: true)
7
7
 
8
8
  module DiffParser
9
9
  module_function
@@ -19,10 +19,14 @@ module Sight
19
19
  if line.start_with?("diff --git ")
20
20
  current_hunk = nil
21
21
  path = line.split(" b/", 2).last
22
- current_file = DiffFile.new(path: path, hunks: [])
22
+ current_file = DiffFile.new(path: path, hunks: [], status: :modified)
23
23
  files << current_file
24
24
  elsif current_file.nil?
25
25
  next
26
+ elsif line.start_with?("new file mode")
27
+ current_file.status = :added
28
+ elsif line.start_with?("deleted file mode")
29
+ current_file.status = :deleted
26
30
  elsif line.start_with?("@@ ")
27
31
  context, new_start, old_start = parse_hunk_header(line)
28
32
  new_lineno = new_start
@@ -48,6 +52,14 @@ module Sight
48
52
  files
49
53
  end
50
54
 
55
+ def build_untracked(path, content)
56
+ lines = content.lines(chomp: true).each_with_index.map do |text, i|
57
+ DiffLine.new(type: :add, content: "+#{text}", lineno: i + 1, old_lineno: nil)
58
+ end
59
+ hunk = Hunk.new(context: nil, lines: lines)
60
+ DiffFile.new(path: path, hunks: [hunk], status: :untracked)
61
+ end
62
+
51
63
  def parse_hunk_header(line)
52
64
  match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@(.*)/)
53
65
  return [nil, 1, 1] unless match
data/lib/sight/git.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Sight
4
6
  module Git
5
7
  module_function
@@ -10,6 +12,33 @@ module Sight
10
12
  output
11
13
  end
12
14
 
15
+ def untracked_files
16
+ output, success = run_cmd(["git", "ls-files", "--others", "--exclude-standard"])
17
+ return [] unless success
18
+ output.lines(chomp: true).reject(&:empty?)
19
+ end
20
+
21
+ def file_content(path)
22
+ File.read(path, mode: "rb")
23
+ end
24
+
25
+ def repo_dir
26
+ output, success = run_cmd(["git", "rev-parse", "--git-dir"])
27
+ raise Error, "not a git repository" unless success
28
+ output.strip
29
+ end
30
+
31
+ def save_pending_review(content)
32
+ dir = File.join(repo_dir, "sight")
33
+ FileUtils.mkdir_p(dir)
34
+ File.write(File.join(dir, "pending-review"), content)
35
+ end
36
+
37
+ def clear_pending_review
38
+ path = File.join(repo_dir, "sight", "pending-review")
39
+ File.delete(path) if File.exist?(path)
40
+ end
41
+
13
42
  def run_cmd(cmd, err: IO::NULL)
14
43
  output = IO.popen(cmd, err: err, &:read)
15
44
  [output, $?.success?]
data/lib/sight/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sight
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/sight.rb CHANGED
@@ -6,6 +6,8 @@ require_relative "sight/diff_parser"
6
6
  require_relative "sight/git"
7
7
  require_relative "sight/annotation"
8
8
  require_relative "sight/annotation_formatter"
9
+ require_relative "sight/claude_hook_installer"
10
+ require_relative "sight/cursor_hook_installer"
9
11
  require_relative "sight/cli"
10
12
  require_relative "sight/app"
11
13
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sight
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ariel Rzezak
@@ -43,7 +43,9 @@ files:
43
43
  - lib/sight/annotation.rb
44
44
  - lib/sight/annotation_formatter.rb
45
45
  - lib/sight/app.rb
46
+ - lib/sight/claude_hook_installer.rb
46
47
  - lib/sight/cli.rb
48
+ - lib/sight/cursor_hook_installer.rb
47
49
  - lib/sight/diff_parser.rb
48
50
  - lib/sight/display_line.rb
49
51
  - lib/sight/git.rb