claude-inbox 0.2.0 → 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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +160 -152
  3. data/exe/claude-inbox +23 -4
  4. data/lib/claude_inbox/agents_client.rb +57 -55
  5. data/lib/claude_inbox/app.rb +42 -26
  6. data/lib/claude_inbox/config.rb +24 -0
  7. data/lib/claude_inbox/dialog.rb +3 -1
  8. data/lib/claude_inbox/images.rb +37 -6
  9. data/lib/claude_inbox/job_state.rb +23 -8
  10. data/lib/claude_inbox/keymap.rb +1 -1
  11. data/lib/claude_inbox/new_session_form.rb +29 -39
  12. data/lib/claude_inbox/painter.rb +32 -0
  13. data/lib/claude_inbox/palette.rb +4 -7
  14. data/lib/claude_inbox/peek.rb +2 -2
  15. data/lib/claude_inbox/poller.rb +4 -3
  16. data/lib/claude_inbox/pull_requests.rb +5 -5
  17. data/lib/claude_inbox/records.rb +7 -2
  18. data/lib/claude_inbox/remote/http.rb +137 -0
  19. data/lib/claude_inbox/remote/icon.png +0 -0
  20. data/lib/claude_inbox/remote/icon.svg +8 -0
  21. data/lib/claude_inbox/remote/listener.rb +436 -0
  22. data/lib/claude_inbox/remote/page.html +477 -0
  23. data/lib/claude_inbox/remote/pairing.rb +111 -0
  24. data/lib/claude_inbox/remote/pairing_dialog.rb +93 -0
  25. data/lib/claude_inbox/remote/start.rb +202 -0
  26. data/lib/claude_inbox/remote.rb +10 -0
  27. data/lib/claude_inbox/renderer.rb +57 -57
  28. data/lib/claude_inbox/session.rb +7 -21
  29. data/lib/claude_inbox/session_request.rb +151 -0
  30. data/lib/claude_inbox/sessions.rb +0 -1
  31. data/lib/claude_inbox/settings.rb +19 -7
  32. data/lib/claude_inbox/store.rb +0 -2
  33. data/lib/claude_inbox/subprocess.rb +9 -1
  34. data/lib/claude_inbox/terminal.rb +9 -3
  35. data/lib/claude_inbox/text.rb +7 -1
  36. data/lib/claude_inbox/trust.rb +23 -0
  37. data/lib/claude_inbox/vt_screen.rb +1 -1
  38. data/lib/claude_inbox.rb +1 -1
  39. metadata +14 -1
@@ -7,21 +7,33 @@ module ClaudeInbox
7
7
  # settings files it reads, most specific wins. Values are strings, or
8
8
  # nil when nothing sets them — then the CLI decides at launch.
9
9
  module Settings
10
- Defaults = Struct.new(:model, :effort, :permission_mode)
10
+ Defaults = Struct.new(:model, :effort, :permission_mode, :remote)
11
11
 
12
12
  def self.defaults(cwd, home: Dir.home)
13
- layers = [
14
- File.join(home, ".claude", "settings.json"),
15
- File.join(cwd, ".claude", "settings.json"),
16
- File.join(cwd, ".claude", "settings.local.json")
17
- ].map { |path| read(path) }
13
+ user = read(File.join(home, ".claude", "settings.json"))
14
+ project = read(File.join(cwd, ".claude", "settings.json"))
15
+ local = read(File.join(cwd, ".claude", "settings.local.json"))
16
+ layers = [user, project, local]
18
17
  Defaults.new(
19
18
  model: pick(layers, "model"),
20
19
  effort: pick(layers, "effortLevel"),
21
- permission_mode: pick(layers, "permissions", "defaultMode")
20
+ permission_mode: pick(layers, "permissions", "defaultMode"),
21
+ remote: remote_control(user, project, local, read(File.join(home, ".claude.json")))
22
22
  )
23
23
  end
24
24
 
25
+ # "Enable Remote Control for all sessions" in /config. The CLI lets a
26
+ # repo turn it off but only user scope turn it on, and still honours the
27
+ # copy older versions kept in ~/.claude.json.
28
+ def self.remote_control(user, project, local, legacy)
29
+ return "no" if project["remoteControlAtStartup"] == false || local["remoteControlAtStartup"] == false
30
+ [user, legacy].each do |h|
31
+ v = h["remoteControlAtStartup"]
32
+ return v ? "yes" : "no" if [true, false].include?(v)
33
+ end
34
+ nil
35
+ end
36
+
25
37
  def self.read(path)
26
38
  JSON.parse(File.read(path))
27
39
  rescue Errno::ENOENT, Errno::EACCES, JSON::ParserError
@@ -56,8 +56,6 @@ module ClaudeInbox
56
56
 
57
57
  DEFAULT_PATH = File.join(Dir.home, ".config", "claude-inbox", "state.json")
58
58
 
59
- attr_reader :path
60
-
61
59
  def initialize(path: DEFAULT_PATH, clock: -> { Time.now })
62
60
  @path = path
63
61
  @clock = clock
@@ -10,8 +10,16 @@ module ClaudeInbox
10
10
  def success? = status.success?
11
11
  end
12
12
 
13
+ # Children start in the environment from before Bundler, or every ruby a
14
+ # session runs, in any project, loads our Gemfile. Handed to exec rather
15
+ # than set on ENV, which the poll and background threads share.
16
+ CHILD_ENV = defined?(Bundler.unbundled_env) ? Bundler.unbundled_env.freeze : nil
17
+
13
18
  module_function
14
19
 
20
+ # argv as exec and Process.spawn take it, with CHILD_ENV in place of ours.
21
+ def command(*argv) = CHILD_ENV ? [CHILD_ENV, *argv, {unsetenv_others: true}] : argv
22
+
15
23
  def capture(*argv, chdir: nil)
16
24
  out_r, out_w = IO.pipe
17
25
  err_r, err_w = IO.pipe
@@ -23,7 +31,7 @@ module ClaudeInbox
23
31
  out_r.close
24
32
  err_r.close
25
33
  Dir.chdir(chdir) if chdir
26
- exec(*argv)
34
+ exec(*command(*argv))
27
35
  rescue SystemCallError => e
28
36
  $stderr.write(e.message)
29
37
  exit! 127
@@ -3,7 +3,7 @@
3
3
  require "io/console"
4
4
  require "tty-cursor"
5
5
  require "tty-screen"
6
- require_relative "renderer"
6
+ require_relative "painter"
7
7
 
8
8
  module ClaudeInbox
9
9
  # The screen the inbox draws on: the alt screen and the mouse and wheel
@@ -40,10 +40,16 @@ module ClaudeInbox
40
40
  @size = nil
41
41
  end
42
42
 
43
+ # The tty's mode is kept as found, not assumed: `cooked!` sets a stock
44
+ # mode that differs from a shell's (istrip on, imaxbel off), and the
45
+ # shell inherits whatever is left on quit.
43
46
  def enter
44
47
  @out.print ALT_ON, WHEEL_KEYS_ON, MOUSE_ON, PASTE_ON, TTY::Cursor.hide, TTY::Cursor.clear_screen
45
48
  @out.flush
46
- @input.raw! if @input.respond_to?(:raw!) && @input.tty?
49
+ if @input.respond_to?(:raw!) && @input.tty?
50
+ @mode = @input.console_mode
51
+ @input.raw!
52
+ end
47
53
  @restored = false
48
54
  resized
49
55
  end
@@ -52,7 +58,7 @@ module ClaudeInbox
52
58
  def restore
53
59
  return if @restored
54
60
  @restored = true
55
- @input.cooked! if @input.respond_to?(:cooked!) && @input.tty?
61
+ @input.console_mode = @mode if @mode && @input.tty?
56
62
  @out.print TTY::Cursor.show, PASTE_OFF, MOUSE_OFF, WHEEL_KEYS_OFF, ALT_OFF
57
63
  @out.flush
58
64
  rescue
@@ -13,6 +13,12 @@ module ClaudeInbox
13
13
 
14
14
  def strip_ansi(s) = s.gsub(ANSI, "")
15
15
 
16
+ # Control characters as \xNN, so quoted text can't reach the terminal
17
+ # as an escape. Bytes that aren't UTF-8 become U+FFFD.
18
+ def printable(s)
19
+ String.new(s.to_s, encoding: Encoding::UTF_8).scrub("\uFFFD").gsub(/[[:cntrl:]]/) { |c| format("\\x%02x", c.ord) }
20
+ end
21
+
16
22
  def width(s) = Unicode::DisplayWidth.of(strip_ansi(s))
17
23
 
18
24
  # Truncate plain (uncolored) text to `w` columns, appending an ellipsis
@@ -57,7 +63,7 @@ module ClaudeInbox
57
63
  s = truncate(strip_ansi(s), w)
58
64
  cur = width(s)
59
65
  end
60
- s + (" " * (w - cur))
66
+ s + (" " * [w - cur, 0].max)
61
67
  end
62
68
 
63
69
  # Greedy word wrap on display width. Words wider than `w` are split.
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module ClaudeInbox
6
+ # The directories whose trust dialog has been accepted, read from where
7
+ # the CLI keeps the answer (see docs/cli-quirks.md). For choosing which
8
+ # directories to offer and in what order, never as a gate: whether a
9
+ # session may start somewhere stays the CLI's call.
10
+ module Trust
11
+ def self.projects(path: File.join(Dir.home, ".claude.json"))
12
+ data = JSON.parse(File.read(path))
13
+ projects = data["projects"] if data.is_a?(Hash)
14
+ return [] unless projects.is_a?(Hash)
15
+ projects.filter_map do |dir, entry|
16
+ dir if dir.start_with?("/") && !dir.include?("\0") &&
17
+ entry.is_a?(Hash) && entry["hasTrustDialogAccepted"] == true
18
+ end
19
+ rescue SystemCallError, IOError, JSON::ParserError
20
+ []
21
+ end
22
+ end
23
+ end
@@ -90,7 +90,7 @@ module ClaudeInbox
90
90
  n = nums[0] || 1
91
91
  case final
92
92
  when "H", "f"
93
- @row = ((nums[0] || 1) - 1).clamp(0, @rows - 1)
93
+ @row = (n - 1).clamp(0, @rows - 1)
94
94
  @col = ((nums[1] || 1) - 1).clamp(0, @cols - 1)
95
95
  when "A" then @row = [@row - n, 0].max
96
96
  when "B" then @row = [@row + n, @rows - 1].min
data/lib/claude_inbox.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeInbox
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
6
6
 
7
7
  require_relative "claude_inbox/palette"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude-inbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Byron
@@ -107,6 +107,7 @@ files:
107
107
  - lib/claude_inbox.rb
108
108
  - lib/claude_inbox/agents_client.rb
109
109
  - lib/claude_inbox/app.rb
110
+ - lib/claude_inbox/config.rb
110
111
  - lib/claude_inbox/debug.rb
111
112
  - lib/claude_inbox/dialog.rb
112
113
  - lib/claude_inbox/images.rb
@@ -115,6 +116,7 @@ files:
115
116
  - lib/claude_inbox/logs.rb
116
117
  - lib/claude_inbox/mouse.rb
117
118
  - lib/claude_inbox/new_session_form.rb
119
+ - lib/claude_inbox/painter.rb
118
120
  - lib/claude_inbox/palette.rb
119
121
  - lib/claude_inbox/paste.rb
120
122
  - lib/claude_inbox/peek.rb
@@ -123,8 +125,18 @@ files:
123
125
  - lib/claude_inbox/rate_limits.rb
124
126
  - lib/claude_inbox/reaper.rb
125
127
  - lib/claude_inbox/records.rb
128
+ - lib/claude_inbox/remote.rb
129
+ - lib/claude_inbox/remote/http.rb
130
+ - lib/claude_inbox/remote/icon.png
131
+ - lib/claude_inbox/remote/icon.svg
132
+ - lib/claude_inbox/remote/listener.rb
133
+ - lib/claude_inbox/remote/page.html
134
+ - lib/claude_inbox/remote/pairing.rb
135
+ - lib/claude_inbox/remote/pairing_dialog.rb
136
+ - lib/claude_inbox/remote/start.rb
126
137
  - lib/claude_inbox/renderer.rb
127
138
  - lib/claude_inbox/session.rb
139
+ - lib/claude_inbox/session_request.rb
128
140
  - lib/claude_inbox/sessions.rb
129
141
  - lib/claude_inbox/settings.rb
130
142
  - lib/claude_inbox/slash_commands.rb
@@ -138,6 +150,7 @@ files:
138
150
  - lib/claude_inbox/text.rb
139
151
  - lib/claude_inbox/text_buffer.rb
140
152
  - lib/claude_inbox/theme.rb
153
+ - lib/claude_inbox/trust.rb
141
154
  - lib/claude_inbox/vt_screen.rb
142
155
  homepage: https://github.com/jordanbyron/claude-inbox
143
156
  licenses: