rails-informant 0.3.0 โ†’ 0.3.1

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: 71f2d05d2caf43bbaab7641118f09b86cafb4949d9f4c2be262a4773f6a88eab
4
- data.tar.gz: 8f3de02212167bfcbbb412d653af259eeed2a0e5907f75b8e1191d724faf112a
3
+ metadata.gz: 9190270b29cd61133c36e5083527afb433de74d02bde6a854c07ba0ee525686d
4
+ data.tar.gz: c9240093202eb13ac1fd85993ead95983abcced394efe40a0f49ad5cbe183f7d
5
5
  SHA512:
6
- metadata.gz: b7c2c8599fe28a7adef712a0cc4f989cdae99465a18b2120055c049dbf77136e243dee9f382fc8dee0ed6d6c7d2d6173a18b1e42a750d031a31268fd377758ff
7
- data.tar.gz: 59729b6bd6f564023810acd041e3d71f7cf930e8bb65dd88833495b6392d3eb979aa920b80650d4ffe6833f91675743b928a1162ecad4bf10c75c894759843ad
6
+ metadata.gz: 285bebc2912dfda97c99474e5a272a51c0959e7aea2c5739b051ccc3e661da69fca77960472d73e5acfc0d55d3cc5e7c8c56a4b48dd2a032abb0010150b8d2a5
7
+ data.tar.gz: 7e11cbeeba65c6e6735c28f3c9b2262a71a8c7a6991d6c5cc31679d2a8062fde7c825e79ea2ca07894328eccc78d58f441c57f2f7d1ebb5c447c573010c4b779
data/README.md CHANGED
@@ -132,16 +132,13 @@ The bundled `informant-mcp` executable connects Claude Code to your error data v
132
132
 
133
133
  The `rails_informant:skill` generator creates `.mcp.json` automatically. Set `INFORMANT_PRODUCTION_URL` and `INFORMANT_PRODUCTION_TOKEN` as environment variables (e.g., via `.envrc` + direnv). The MCP server inherits env vars from your shell.
134
134
 
135
- For multi-environment setups, create `~/.config/informant-mcp.yml`:
135
+ For multi-environment setups, add env vars for each environment:
136
136
 
137
- ```yaml
138
- environments:
139
- production:
140
- url: https://myapp.com
141
- token: ${INFORMANT_PRODUCTION_TOKEN}
142
- staging:
143
- url: https://staging.myapp.com
144
- token: ${INFORMANT_STAGING_TOKEN}
137
+ ```bash
138
+ export INFORMANT_PRODUCTION_URL=https://myapp.com
139
+ export INFORMANT_PRODUCTION_TOKEN=<token>
140
+ export INFORMANT_STAGING_URL=https://staging.myapp.com
141
+ export INFORMANT_STAGING_TOKEN=<token>
145
142
  ```
146
143
 
147
144
  ### Tools
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bash
2
+ # Informant: Check for unresolved production errors on Claude Code startup.
3
+ # Requires: curl, jq
4
+ # Env vars: INFORMANT_PRODUCTION_URL, INFORMANT_PRODUCTION_TOKEN
5
+ # INFORMANT_PRODUCTION_PATH_PREFIX (optional, default: /informant)
6
+
7
+ set -euo pipefail
8
+
9
+ # Silent exit if env vars are missing or URL is not HTTPS
10
+ [[ -z "${INFORMANT_PRODUCTION_URL:-}" ]] && exit 0
11
+ [[ -z "${INFORMANT_PRODUCTION_TOKEN:-}" ]] && exit 0
12
+ [[ "$INFORMANT_PRODUCTION_URL" == https://* ]] || exit 0
13
+
14
+ # Silent exit if jq is not installed
15
+ command -v jq >/dev/null 2>&1 || exit 0
16
+
17
+ path_prefix="${INFORMANT_PRODUCTION_PATH_PREFIX:-/informant}"
18
+ url="${INFORMANT_PRODUCTION_URL}${path_prefix}/api/v1/status"
19
+
20
+ # Fetch status (silent on failure)
21
+ response=$(curl -s -f \
22
+ --connect-timeout 3 \
23
+ --max-time 5 \
24
+ -H @- \
25
+ "$url" <<< "Authorization: Bearer ${INFORMANT_PRODUCTION_TOKEN}" \
26
+ 2>/dev/null) || exit 0
27
+
28
+ # Parse unresolved count
29
+ unresolved=$(echo "$response" | jq -r '.unresolved_count // 0') || exit 0
30
+ [[ "$unresolved" -eq 0 ]] && exit 0
31
+
32
+ # Format error summary
33
+ label="error"
34
+ [[ "$unresolved" -gt 1 ]] && label="errors"
35
+ echo "๐Ÿšจ Informant: ${unresolved} unresolved ${label} in production"
36
+
37
+ echo "$response" | jq -r '
38
+ .top_errors[]? |
39
+ " - \(.error_class) (\(.total_occurrences) \(if .total_occurrences == 1 then "occurrence" else "occurrences" end))"
40
+ ' 2>/dev/null || true
@@ -9,6 +9,11 @@ module RailsInformant
9
9
  copy_file "SKILL.md", ".claude/skills/informant/SKILL.md"
10
10
  end
11
11
 
12
+ def copy_hook_script
13
+ copy_file "informant-alerts.sh", ".claude/hooks/informant-alerts.sh"
14
+ chmod ".claude/hooks/informant-alerts.sh", 0o755
15
+ end
16
+
12
17
  def create_or_update_mcp_json
13
18
  mcp_path = File.join(destination_root, ".mcp.json")
14
19
  informant_entry = { "command" => "informant-mcp" }
@@ -27,14 +32,43 @@ module RailsInformant
27
32
  say "Could not parse existing .mcp.json โ€” skipping merge. Add the informant server manually.", :red
28
33
  end
29
34
 
35
+ def create_or_update_settings_json
36
+ settings_path = File.join(destination_root, ".claude", "settings.json")
37
+ hook_command = ".claude/hooks/informant-alerts.sh"
38
+
39
+ if File.exist?(settings_path)
40
+ existing = JSON.parse(File.read(settings_path))
41
+ existing["hooks"] ||= {}
42
+ existing["hooks"]["SessionStart"] ||= []
43
+
44
+ already_registered = existing["hooks"]["SessionStart"].any? do |entry|
45
+ entry["hooks"]&.any? { it["command"] == hook_command }
46
+ end
47
+
48
+ unless already_registered
49
+ existing["hooks"]["SessionStart"] << session_start_hook(hook_command)
50
+ end
51
+
52
+ create_file ".claude/settings.json", JSON.pretty_generate(existing) + "\n", force: true
53
+ else
54
+ create_file ".claude/settings.json", JSON.pretty_generate(
55
+ "hooks" => { "SessionStart" => [ session_start_hook(hook_command) ] }
56
+ ) + "\n"
57
+ end
58
+ rescue JSON::ParserError
59
+ say "Could not parse existing .claude/settings.json โ€” skipping hook setup.", :red
60
+ end
61
+
30
62
  def print_next_steps
31
63
  say ""
32
64
  say "Claude Code integration installed!", :green
33
65
  say ""
34
66
  say " Created .mcp.json"
35
67
  say " Created .claude/skills/informant/SKILL.md"
68
+ say " Created .claude/hooks/informant-alerts.sh"
69
+ say " Created .claude/settings.json (SessionStart hook)"
36
70
  say ""
37
- say "Next step โ€” set env vars so the MCP server can reach your app.", :yellow
71
+ say "Next step โ€” set env vars so the MCP server and startup alerts can reach your app.", :yellow
38
72
  say "Add to your .envrc (or export manually):"
39
73
  say ""
40
74
  say " export INFORMANT_PRODUCTION_URL=https://your-app.com"
@@ -43,6 +77,18 @@ module RailsInformant
43
77
  say "The token must match rails_informant.api_token in your Rails credentials."
44
78
  say "Add .envrc to .gitignore โ€” it contains secrets."
45
79
  say ""
80
+ say "Optional: install jq for startup error alerts (brew install jq)", :cyan
81
+ end
82
+
83
+ private
84
+
85
+ def session_start_hook(command)
86
+ {
87
+ "matcher" => "startup",
88
+ "hooks" => [
89
+ { "type" => "command", "command" => command, "timeout" => 10 }
90
+ ]
91
+ }
46
92
  end
47
93
  end
48
94
  end
@@ -1,16 +1,12 @@
1
- require "yaml"
2
-
3
1
  module RailsInformant
4
2
  module Mcp
5
3
  class Configuration
6
- CONFIG_PATH = File.expand_path("~/.config/informant-mcp.yml").freeze
7
-
8
- attr_reader :allow_insecure
9
-
10
4
  def initialize(allow_insecure: false)
11
5
  @allow_insecure = allow_insecure
12
- @environments = load_environments
6
+ @environments = load_from_env_vars
13
7
  @clients = {}
8
+
9
+ raise "No environments configured. Set INFORMANT_<ENV>_URL and INFORMANT_<ENV>_TOKEN environment variables." if @environments.empty?
14
10
  end
15
11
 
16
12
  def default_environment
@@ -34,12 +30,6 @@ module RailsInformant
34
30
 
35
31
  private
36
32
 
37
- def load_environments
38
- envs = load_from_yaml.merge(load_from_env_vars)
39
- raise "No environments configured. Set INFORMANT_<ENV>_URL and INFORMANT_<ENV>_TOKEN environment variables, or create ~/.config/informant-mcp.yml" if envs.empty?
40
- envs
41
- end
42
-
43
33
  def load_from_env_vars
44
34
  ENV.each_with_object({}) do |(key, _), envs|
45
35
  next unless (match = key.match(/\AINFORMANT_(.+)_URL\z/))
@@ -52,39 +42,6 @@ module RailsInformant
52
42
  }
53
43
  end
54
44
  end
55
-
56
- def load_from_yaml
57
- path = CONFIG_PATH
58
- return {} unless File.exist?(path)
59
-
60
- reject_insecure_permissions(path)
61
-
62
- yaml = YAML.safe_load_file(path)
63
- return {} unless yaml.is_a?(Hash) && yaml["environments"].is_a?(Hash)
64
-
65
- yaml["environments"].each_with_object({}) do |(name, config), envs|
66
- next unless config.is_a?(Hash)
67
- envs[name] = { path_prefix: config["path_prefix"], token: interpolate(config["token"]), url: config["url"] }
68
- end
69
- end
70
-
71
- def reject_insecure_permissions(path)
72
- mode = File.stat(path).mode
73
- return unless mode & 0o077 != 0
74
-
75
- message = "#{path} has insecure permissions (#{format('%04o', mode & 0o7777)}). Run: chmod 600 #{path}"
76
-
77
- if @allow_insecure
78
- warn "[RailsInformant] WARNING: #{message}"
79
- else
80
- raise "[RailsInformant] #{message} (use --allow-insecure to override)"
81
- end
82
- end
83
-
84
- def interpolate(value)
85
- return value unless value.is_a?(String)
86
- value.gsub(/\$\{(INFORMANT_\w+)\}/) { ENV[$1] || "" }
87
- end
88
45
  end
89
46
  end
90
47
  end
@@ -6,9 +6,11 @@ module RailsInformant
6
6
  end
7
7
 
8
8
  def call(env)
9
- @app.call(env).tap do
10
- if (exception = env["rails_informant.rescued_exception"])
11
- record_exception(exception, env: env)
9
+ @app.call(env).tap do |status, _headers, _body|
10
+ # Only record rescued exceptions that resulted in a server error.
11
+ # 4xx responses are expected application errors handled by ShowExceptions.
12
+ if (exception = env["rails_informant.rescued_exception"]) && status.to_i >= 500
13
+ record_exception exception, env:
12
14
  end
13
15
  end
14
16
  rescue StandardError => exception
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-informant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Lรณpez Prat
@@ -127,6 +127,7 @@ files:
127
127
  - exe/informant-mcp
128
128
  - lib/generators/rails_informant/install_generator.rb
129
129
  - lib/generators/rails_informant/skill/templates/SKILL.md
130
+ - lib/generators/rails_informant/skill/templates/informant-alerts.sh
130
131
  - lib/generators/rails_informant/skill_generator.rb
131
132
  - lib/generators/rails_informant/templates/create_informant_tables.rb.erb
132
133
  - lib/generators/rails_informant/templates/initializer.rb.erb