claude-inbox 0.12.1 → 0.12.2

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: 58a50d40ba0f8df6b8b53f3613a75b5982871eaa3c6f9d0c29c46411a1e70323
4
- data.tar.gz: f8abcc49a7a5d61d339189f79c0f7a62d800b98531ce1e82060efda0a91e0d2e
3
+ metadata.gz: 39ef2bafdac9f7b1c8ba1d4e6f28f5105a5fd5c42320f627caf61fd07f9c314d
4
+ data.tar.gz: 0ed5aadb2d02b61b1eabca62a7de74820f7e884f5ffdc6f0bbb68382ba26cc13
5
5
  SHA512:
6
- metadata.gz: 1c994b3434e3e45fbf60f6c472227fb97610ae8355bb0dd166fb49c83713025fc8be70d2f06660e4475aa0606604a6860ce063c2d721fb836c048e7f18393b86
7
- data.tar.gz: 591b90b883b64fc4494d8dec115958c618cf88ad13fd5cd3e9a125cbba6364df80b7c370d6d49c6db97bb74e65905882ede46bf9fdcab4b2201d39bc1e582d19
6
+ metadata.gz: 393709c2bc4fc083ded424de49ca3ad102bcf41d75fd72b5c445d8b1f23f7e80f7df0ac1623d4081994e14699168db7c58a2bc64f486b00dd83941c2d691600f
7
+ data.tar.gz: fb06b9a05422bff8e3a5474169a11574041219ae700e30a5178a1e507e74df95cd0c069a1c395a070a7f7856bbe6bcdd6380c2a6011a03ff36a1210c05cdae2e
@@ -3,17 +3,15 @@
3
3
  require "json"
4
4
 
5
5
  module ClaudeInbox
6
- # What `claude` will use when a flag is left off: read from the same
7
- # settings files it reads, most specific wins. Values are strings, or
8
- # nil when nothing sets them — then the CLI decides at launch.
6
+ # What a `claude` session in a directory gets from the settings files it
7
+ # reads, most specific wins: the defaults for flags left off, and the
8
+ # plugins it loads.
9
9
  module Settings
10
+ # Strings, or nil when nothing sets them: then the CLI decides at launch.
10
11
  Defaults = Struct.new(:model, :effort, :permission_mode, :remote)
11
12
 
12
13
  def self.defaults(cwd, home: Dir.home)
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]
14
+ user, project, local = layers = layers(cwd, home)
17
15
  Defaults.new(
18
16
  model: pick(layers, "model"),
19
17
  effort: pick(layers, "effortLevel"),
@@ -22,6 +20,23 @@ module ClaudeInbox
22
20
  )
23
21
  end
24
22
 
23
+ # The "name@marketplace" keys of the plugins a session in cwd loads.
24
+ # Its install scope plays no part; see docs/cli-quirks.md.
25
+ def self.enabled_plugins(cwd, home: Dir.home)
26
+ layers(cwd, home).each_with_object({}) do |h, on|
27
+ plugins = h["enabledPlugins"]
28
+ on.merge!(plugins) if plugins.is_a?(Hash)
29
+ end.select { |_, v| v == true }.keys
30
+ end
31
+
32
+ def self.layers(cwd, home)
33
+ [
34
+ read(File.join(home, ".claude", "settings.json")),
35
+ read(File.join(cwd, ".claude", "settings.json")),
36
+ read(File.join(cwd, ".claude", "settings.local.json"))
37
+ ]
38
+ end
39
+
25
40
  # "Enable Remote Control for all sessions" in /config. The CLI lets a
26
41
  # repo turn it off but only user scope turn it on, and still honours the
27
42
  # copy older versions kept in ~/.claude.json.
@@ -47,6 +62,6 @@ module ClaudeInbox
47
62
  end
48
63
  nil
49
64
  end
50
- private_class_method :remote_control, :pick
65
+ private_class_method :layers, :remote_control, :pick
51
66
  end
52
67
  end
@@ -6,10 +6,11 @@ module ClaudeInbox
6
6
  # The slash commands `claude` would offer at its own prompt, read from the
7
7
  # same places it reads them: `.claude/commands/*.md` and
8
8
  # `.claude/skills/*/SKILL.md` under the project and the home directory,
9
- # the skills and commands of installed plugins (named `plugin:name`), and
10
- # the skills claude.ai syncs down (`anthropic-skills:name`). Built-ins
11
- # live inside the CLI and are not listed; typing one still works, since
12
- # the prompt goes to `claude --bg` verbatim.
9
+ # the skills and commands of the plugins enabled there (named
10
+ # `plugin:name`), and the skills claude.ai syncs down
11
+ # (`anthropic-skills:name`). Built-ins live inside the CLI and are not
12
+ # listed; typing one still works, since the prompt goes to `claude --bg`
13
+ # verbatim.
13
14
  #
14
15
  # Pure: filesystem in, plain values out. Nothing here runs `claude`.
15
16
  module SlashCommands
@@ -27,7 +28,7 @@ module ClaudeInbox
27
28
  [
28
29
  *from_dir(File.join(cwd, ".claude")),
29
30
  *from_dir(File.join(home, ".claude")),
30
- *plugins(home),
31
+ *plugins(cwd, home),
31
32
  *synced(home)
32
33
  ].uniq(&:name).sort_by(&:name)
33
34
  end
@@ -65,9 +66,10 @@ module ClaudeInbox
65
66
 
66
67
  # `installed_plugins.json` maps "name@marketplace" to where the plugin
67
68
  # was unpacked: one entry, or a list of them, one per install scope.
68
- def plugins(home)
69
+ def plugins(cwd, home)
69
70
  installed = Settings.read(File.join(home, ".claude", "plugins", "installed_plugins.json"))
70
- (installed["plugins"] || {}).flat_map do |key, entries|
71
+ enabled = Settings.enabled_plugins(cwd, home:)
72
+ (installed["plugins"] || {}).slice(*enabled).flat_map do |key, entries|
71
73
  plugin = key.split("@").first
72
74
  Array(entries).filter_map { |e| e["installPath"] if e.is_a?(Hash) }.uniq.flat_map do |root|
73
75
  from_dir(root, prefix: plugin)
@@ -10,15 +10,27 @@ module ClaudeInbox
10
10
  def success? = status.success?
11
11
  end
12
12
 
13
+ # `asdf exec`, which the ruby shim runs us through, puts the ruby it
14
+ # picked ahead of every shim on PATH and names it in ASDF_INSTALL_PATH.
15
+ # Left in place, every session runs our ruby whatever its project's
16
+ # .tool-versions says.
17
+ def self.unpinned(env)
18
+ install = env["ASDF_INSTALL_PATH"] or return env
19
+ prefix = File.join(install, "")
20
+ path = env.fetch("PATH", "").split(File::PATH_SEPARATOR).reject { |dir| dir.start_with?(prefix) }
21
+ env.reject { |k, _| k.start_with?("ASDF_INSTALL_") }.merge("PATH" => path.join(File::PATH_SEPARATOR))
22
+ end
23
+
13
24
  # 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
25
+ # session runs, in any project, loads our Gemfile, and from before asdf
26
+ # pinned our ruby. Handed to exec rather than set on ENV, which the poll
27
+ # and background threads share.
28
+ CHILD_ENV = unpinned(defined?(Bundler.unbundled_env) ? Bundler.unbundled_env : ENV.to_h).freeze
17
29
 
18
30
  module_function
19
31
 
20
32
  # 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
33
+ def command(*argv) = [CHILD_ENV, *argv, {unsetenv_others: true}]
22
34
 
23
35
  # `input` is written before any output is read, so it is for the few
24
36
  # bytes a clipboard takes, not a stream.
data/lib/claude_inbox.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeInbox
4
- VERSION = "0.12.1"
4
+ VERSION = "0.12.2"
5
5
  end
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.12.1
4
+ version: 0.12.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Byron