zwischen 0.1.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.
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require "fileutils"
5
+ require_relative "credentials"
6
+ require_relative "hooks"
7
+ require_relative "config"
8
+
9
+ module Zwischen
10
+ class Setup
11
+ def self.run
12
+ new.run
13
+ end
14
+
15
+ def self.uninstall
16
+ new.uninstall
17
+ end
18
+
19
+ def initialize
20
+ @shell = Thor::Shell::Color.new
21
+ end
22
+
23
+ def run
24
+ @shell.say("\nšŸ›”ļø Installing Zwischen security layer...\n", :bold)
25
+
26
+ check_tools
27
+ configure_credentials
28
+ install_hook
29
+ create_config
30
+
31
+ @shell.say(" āœ“ Done!", :green)
32
+ @shell.say("\nZwischen will now scan automatically before pushes.")
33
+ @shell.say("Run 'zwischen scan' to test it now.\n")
34
+ end
35
+
36
+ def uninstall
37
+ @shell.say("\nšŸ—‘ļø Zwischen Uninstall\n", :bold)
38
+
39
+ project_root = Dir.pwd
40
+ hook_path = Hooks.hook_path(project_root)
41
+
42
+ # Remove hook
43
+ if Hooks.installed?(project_root)
44
+ if @shell.yes?("Remove git hook?", default: true)
45
+ if Hooks.uninstall(project_root)
46
+ @shell.say(" āœ“ Removed .git/hooks/pre-push", :green)
47
+ else
48
+ @shell.say(" āœ— Failed to remove hook", :red)
49
+ end
50
+ end
51
+ else
52
+ @shell.say(" ↳ No Zwischen hook found", :yellow)
53
+ end
54
+
55
+ # Remove config
56
+ config_path = File.join(project_root, Config::CONFIG_FILE)
57
+ if File.exist?(config_path)
58
+ if @shell.yes?("Remove project config (.zwischen.yml)?", default: false)
59
+ File.delete(config_path)
60
+ @shell.say(" āœ“ Removed .zwischen.yml", :green)
61
+ else
62
+ @shell.say(" ↳ Kept .zwischen.yml", :yellow)
63
+ end
64
+ end
65
+
66
+ # Remove credentials
67
+ if File.exist?(Credentials.credentials_path)
68
+ if @shell.yes?("Remove global credentials (~/.zwischen/credentials)?", default: false)
69
+ File.delete(Credentials.credentials_path)
70
+ @shell.say(" āœ“ Removed credentials", :green)
71
+ else
72
+ @shell.say(" ↳ Kept credentials", :yellow)
73
+ end
74
+ end
75
+
76
+ @shell.say("\nāœ… Zwischen uninstalled from this project.\n", :green)
77
+ end
78
+
79
+ private
80
+
81
+ def check_tools
82
+ installer = Installer.new
83
+
84
+ # Auto-install gitleaks if missing
85
+ unless installer.gitleaks_available?
86
+ @shell.say(" ↳ Installing gitleaks...", :yellow)
87
+ if installer.auto_install_gitleaks
88
+ @shell.say(" āœ“ Installed gitleaks to ~/.zwischen/bin/", :green)
89
+ else
90
+ @shell.say(" āš ļø Could not auto-install gitleaks", :yellow)
91
+ @shell.say(" → #{installer.preferred_command('gitleaks')}", :yellow)
92
+ end
93
+ else
94
+ @shell.say(" āœ“ gitleaks available", :green)
95
+ end
96
+
97
+ # Check semgrep (optional, not auto-installed)
98
+ if installer.check_tool("semgrep")
99
+ @shell.say(" āœ“ semgrep available", :green)
100
+ else
101
+ @shell.say(" ↳ semgrep not found (optional)", :yellow)
102
+ @shell.say(" → #{installer.preferred_command('semgrep')}", :yellow)
103
+ end
104
+ end
105
+
106
+ def configure_credentials
107
+ api_key = ENV["ANTHROPIC_API_KEY"]
108
+ return unless api_key && !api_key.strip.empty?
109
+
110
+ Credentials.save(api_key: api_key)
111
+ @shell.say(" āœ“ Credentials stored in ~/.zwischen/credentials - never committed", :green)
112
+ end
113
+
114
+ def install_hook
115
+ project_root = Dir.pwd
116
+ git_dir = File.join(project_root, ".git")
117
+
118
+ unless File.directory?(git_dir)
119
+ @shell.say(" āš ļø No .git directory found. Skipping hook installation.", :yellow)
120
+ return false
121
+ end
122
+
123
+ hook_path = Hooks.hook_path(project_root)
124
+
125
+ if File.exist?(hook_path)
126
+ if Hooks.zwischen_hook?(hook_path)
127
+ @shell.say(" āœ“ Pre-push hook already installed", :green)
128
+ return true
129
+ end
130
+
131
+ backup_path = "#{hook_path}.zwischen.backup"
132
+ if File.exist?(backup_path)
133
+ timestamp = Time.now.strftime("%Y%m%d%H%M%S")
134
+ backup_path = "#{backup_path}.#{timestamp}"
135
+ end
136
+ FileUtils.cp(hook_path, backup_path)
137
+ @shell.say(" āœ“ Backed up existing hook to #{backup_path}", :green)
138
+ end
139
+
140
+ if Hooks.install(project_root)
141
+ @shell.say(" āœ“ Installing pre-push hook", :green)
142
+ true
143
+ else
144
+ @shell.say(" āœ— Failed to install hook", :red)
145
+ false
146
+ end
147
+ end
148
+
149
+ def create_config
150
+ project_root = Dir.pwd
151
+ config_path = File.join(project_root, Config::CONFIG_FILE)
152
+
153
+ if File.exist?(config_path)
154
+ @shell.say(" āœ“ Config already exists (.zwischen.yml)", :green)
155
+ return false
156
+ end
157
+
158
+ if Config.init(project_root, quiet: true)
159
+ @shell.say(" āœ“ Creating config (.zwischen.yml)", :green)
160
+ true
161
+ else
162
+ @shell.say(" āœ— Failed to create config", :red)
163
+ false
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zwischen
4
+ VERSION = "0.1.0"
5
+ end
data/lib/zwischen.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "zwischen/version"
4
+
5
+ module Zwischen
6
+ class Error < StandardError; end
7
+ end
8
+
9
+ # Load all modules when gem is required
10
+ require_relative "zwischen/config"
11
+ require_relative "zwischen/project_detector"
12
+ require_relative "zwischen/finding/finding"
13
+ require_relative "zwischen/finding/aggregator"
14
+ require_relative "zwischen/scanner/base"
15
+ require_relative "zwischen/scanner/gitleaks"
16
+ require_relative "zwischen/scanner/semgrep"
17
+ require_relative "zwischen/scanner/orchestrator"
18
+ require_relative "zwischen/installer"
19
+ require_relative "zwischen/credentials"
20
+ require_relative "zwischen/git_diff"
21
+ require_relative "zwischen/hooks"
22
+ require_relative "zwischen/setup"
23
+ require_relative "zwischen/ai/anthropic_client"
24
+ require_relative "zwischen/ai/ollama_client"
25
+ require_relative "zwischen/ai/openai_client"
26
+ require_relative "zwischen/ai/analyzer"
27
+ require_relative "zwischen/reporter/terminal"
28
+ require_relative "zwischen/reporter/sarif"
29
+ require_relative "zwischen/cli"
data/zwischen.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/zwischen/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "zwischen"
7
+ spec.version = Zwischen::VERSION
8
+ spec.authors = ["Conner Jordan"]
9
+ spec.email = ["connercharlesjordan@gmail.com"]
10
+
11
+ spec.summary = "AI-augmented security scanning CLI for vibe coders"
12
+ spec.description = "Orchestrates Gitleaks and Semgrep scanners, aggregates findings, and uses AI to prioritize and explain security issues"
13
+ spec.homepage = "https://github.com/cjordan223/zwischen"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.3.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+
21
+ spec.files = Dir["lib/**/*", "bin/**/*", "*.md", "*.gemspec", ".zwischen.yml.example"].reject do |f|
22
+ File.directory?(f) || f.start_with?(*%w[spec/ test/ features/ .git .github])
23
+ end
24
+ spec.bindir = "bin"
25
+ spec.executables = ["zwischen"]
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_dependency "thor", "~> 1.3"
29
+ spec.add_dependency "faraday", "~> 2.0"
30
+ spec.add_dependency "colorize", "~> 0.8.1"
31
+
32
+ spec.add_development_dependency "rspec", "~> 3.12"
33
+ spec.add_development_dependency "rake", "~> 13.0"
34
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zwischen
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Conner Jordan
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: thor
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: colorize
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.8.1
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 0.8.1
54
+ - !ruby/object:Gem::Dependency
55
+ name: rspec
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.12'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.12'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '13.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '13.0'
82
+ description: Orchestrates Gitleaks and Semgrep scanners, aggregates findings, and
83
+ uses AI to prioritize and explain security issues
84
+ email:
85
+ - connercharlesjordan@gmail.com
86
+ executables:
87
+ - zwischen
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".zwischen.yml.example"
92
+ - CHANGELOG.md
93
+ - DEVELOPMENT.md
94
+ - README.md
95
+ - TESTING.md
96
+ - bin/zwischen
97
+ - lib/zwischen.rb
98
+ - lib/zwischen/ai/analyzer.rb
99
+ - lib/zwischen/ai/anthropic_client.rb
100
+ - lib/zwischen/ai/base_client.rb
101
+ - lib/zwischen/ai/ollama_client.rb
102
+ - lib/zwischen/ai/openai_client.rb
103
+ - lib/zwischen/cli.rb
104
+ - lib/zwischen/config.rb
105
+ - lib/zwischen/credentials.rb
106
+ - lib/zwischen/finding/aggregator.rb
107
+ - lib/zwischen/finding/finding.rb
108
+ - lib/zwischen/git_diff.rb
109
+ - lib/zwischen/hooks.rb
110
+ - lib/zwischen/installer.rb
111
+ - lib/zwischen/project_detector.rb
112
+ - lib/zwischen/reporter/sarif.rb
113
+ - lib/zwischen/reporter/terminal.rb
114
+ - lib/zwischen/scanner/base.rb
115
+ - lib/zwischen/scanner/gitleaks.rb
116
+ - lib/zwischen/scanner/orchestrator.rb
117
+ - lib/zwischen/scanner/semgrep.rb
118
+ - lib/zwischen/setup.rb
119
+ - lib/zwischen/version.rb
120
+ - zwischen.gemspec
121
+ homepage: https://github.com/cjordan223/zwischen
122
+ licenses:
123
+ - MIT
124
+ metadata:
125
+ homepage_uri: https://github.com/cjordan223/zwischen
126
+ source_code_uri: https://github.com/cjordan223/zwischen
127
+ changelog_uri: https://github.com/cjordan223/zwischen/blob/main/CHANGELOG.md
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: 3.3.0
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubygems_version: 3.6.9
143
+ specification_version: 4
144
+ summary: AI-augmented security scanning CLI for vibe coders
145
+ test_files: []