featureparity 0.0.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.
data/lib/fp/config.rb ADDED
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'fileutils'
5
+
6
+ module Fp
7
+ # Handles configuration: profiles, API keys, API URL.
8
+ # Priority: FP_API_KEY env var > --profile flag > FP_PROFILE env var > default profile
9
+ class Config
10
+ DEFAULT_API_URL = 'https://api.featureparity.dev'
11
+ CONFIG_DIR = File.expand_path('~/.config/fp')
12
+ CONFIG_FILE = File.join(CONFIG_DIR, 'config.yml')
13
+
14
+ attr_reader :api_key, :api_url, :profile_name
15
+
16
+ def initialize(profile: nil)
17
+ @profile_name = resolve_profile_name(profile)
18
+ @api_key = resolve_api_key
19
+ @api_url = resolve_api_url
20
+ end
21
+
22
+ def valid?
23
+ !@api_key.nil? && !@api_key.empty?
24
+ end
25
+
26
+ def validation_error
27
+ return nil if valid?
28
+
29
+ <<~MSG
30
+ No API key found. Set FP_API_KEY environment variable or add a profile:
31
+ fp profile add <name> --api-key fp_...
32
+ MSG
33
+ end
34
+
35
+ class << self
36
+ def load_profiles
37
+ return {} unless File.exist?(CONFIG_FILE)
38
+
39
+ config = YAML.safe_load_file(CONFIG_FILE, permitted_classes: [Symbol]) || {}
40
+ config['profiles'] || {}
41
+ rescue StandardError => e
42
+ warn "Warning: Could not load config file: #{e.message}"
43
+ {}
44
+ end
45
+
46
+ def save_profile(name, api_key:, api_url: nil)
47
+ FileUtils.mkdir_p(CONFIG_DIR)
48
+
49
+ # Ensure config file has restricted permissions
50
+ unless File.exist?(CONFIG_FILE)
51
+ File.write(CONFIG_FILE, "---\nprofiles: {}\n")
52
+ File.chmod(0o600, CONFIG_FILE)
53
+ end
54
+
55
+ config = YAML.safe_load_file(CONFIG_FILE, permitted_classes: [Symbol]) || {}
56
+ config['profiles'] ||= {}
57
+ config['profiles'][name] = {
58
+ 'api_key' => api_key
59
+ }
60
+ config['profiles'][name]['api_url'] = api_url if api_url
61
+
62
+ File.write(CONFIG_FILE, YAML.dump(config))
63
+ File.chmod(0o600, CONFIG_FILE)
64
+ end
65
+
66
+ def list_profiles
67
+ load_profiles.keys
68
+ end
69
+
70
+ def profile_exists?(name)
71
+ load_profiles.key?(name)
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def resolve_profile_name(explicit_profile)
78
+ # Explicit --profile flag takes precedence over FP_PROFILE env var
79
+ explicit_profile || ENV['FP_PROFILE']
80
+ end
81
+
82
+ def resolve_api_key
83
+ # FP_API_KEY env var always wins
84
+ return ENV['FP_API_KEY'] if ENV['FP_API_KEY'] && !ENV['FP_API_KEY'].empty?
85
+
86
+ # Otherwise, use profile if specified
87
+ return nil unless @profile_name
88
+
89
+ profiles = self.class.load_profiles
90
+ profile = profiles[@profile_name]
91
+ return nil unless profile
92
+
93
+ profile['api_key']
94
+ end
95
+
96
+ def resolve_api_url
97
+ # FP_API_URL env var takes precedence
98
+ return ENV['FP_API_URL'] if ENV['FP_API_URL'] && !ENV['FP_API_URL'].empty?
99
+
100
+ # Then profile-specific URL
101
+ if @profile_name
102
+ profiles = self.class.load_profiles
103
+ profile = profiles[@profile_name]
104
+ return profile['api_url'] if profile && profile['api_url']
105
+ end
106
+
107
+ # Finally, default
108
+ DEFAULT_API_URL
109
+ end
110
+ end
111
+ end
data/lib/fp/output.rb ADDED
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'csv'
5
+
6
+ module Fp
7
+ # Output formatting for CLI responses
8
+ # Follows Fizzy CLI conventions: { ok: true/false, data: ..., error: ... }
9
+ class Output
10
+ def initialize(json: false)
11
+ @json = json
12
+ end
13
+
14
+ def json?
15
+ @json
16
+ end
17
+
18
+ def success(data, summary: nil)
19
+ if @json
20
+ puts JSON.pretty_generate({ ok: true, data: data })
21
+ else
22
+ yield if block_given?
23
+ puts summary if summary
24
+ end
25
+ end
26
+
27
+ def error(message, status: nil)
28
+ if @json
29
+ result = { ok: false, error: message }
30
+ result[:status] = status if status
31
+ puts JSON.pretty_generate(result)
32
+ else
33
+ warn "Error: #{message}"
34
+ end
35
+ end
36
+
37
+ def table(headers, rows)
38
+ return if rows.empty?
39
+
40
+ if @json
41
+ # For JSON output, return data as array of hashes
42
+ data = rows.map { |row| headers.zip(row).to_h }
43
+ puts JSON.pretty_generate({ ok: true, data: data })
44
+ else
45
+ # ASCII table
46
+ widths = headers.map.with_index do |header, i|
47
+ [header.to_s.length, *rows.map { |r| r[i].to_s.length }].max
48
+ end
49
+
50
+ separator = '+' + widths.map { |w| '-' * (w + 2) }.join('+') + '+'
51
+ format_row = lambda do |row|
52
+ '| ' + row.map.with_index { |cell, i| cell.to_s.ljust(widths[i]) }.join(' | ') + ' |'
53
+ end
54
+
55
+ puts separator
56
+ puts format_row.call(headers)
57
+ puts separator
58
+ rows.each { |row| puts format_row.call(row) }
59
+ puts separator
60
+ end
61
+ end
62
+
63
+ def csv(headers, rows)
64
+ csv_string = CSV.generate do |csv|
65
+ csv << headers
66
+ rows.each { |row| csv << row }
67
+ end
68
+ puts csv_string
69
+ end
70
+ end
71
+ end
data/lib/fp/version.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fp
4
+ VERSION = '0.0.1'
5
+ end
data/lib/fp.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fp/version'
4
+ require_relative 'fp/config'
5
+ require_relative 'fp/client'
6
+ require_relative 'fp/output'
7
+ require_relative 'fp/commands'
8
+ require_relative 'fp/cli'
9
+
10
+ module Fp
11
+ class Error < StandardError; end
12
+ class AuthError < Error; end
13
+ class APIError < Error; end
14
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: featureparity
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Stowzilla
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-08-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: net-http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: yaml
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: uri
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.12'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.12'
69
+ - !ruby/object:Gem::Dependency
70
+ name: csv
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: Command-line interface for FeatureParity - report evidence, propose requirements,
84
+ view parity matrix.
85
+ email:
86
+ - team@stowzilla.com
87
+ executables:
88
+ - fp
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - bin/fp
93
+ - lib/fp.rb
94
+ - lib/fp/cli.rb
95
+ - lib/fp/client.rb
96
+ - lib/fp/commands.rb
97
+ - lib/fp/commands/base.rb
98
+ - lib/fp/commands/help.rb
99
+ - lib/fp/commands/list.rb
100
+ - lib/fp/commands/matrix.rb
101
+ - lib/fp/commands/profile.rb
102
+ - lib/fp/commands/projects.rb
103
+ - lib/fp/commands/propose.rb
104
+ - lib/fp/commands/report.rb
105
+ - lib/fp/commands/setup.rb
106
+ - lib/fp/commands/show.rb
107
+ - lib/fp/commands/surfaces.rb
108
+ - lib/fp/commands/version.rb
109
+ - lib/fp/config.rb
110
+ - lib/fp/output.rb
111
+ - lib/fp/version.rb
112
+ homepage: https://featureparity.dev
113
+ licenses:
114
+ - MIT
115
+ metadata:
116
+ rubygems_mfa_required: 'true'
117
+ homepage_uri: https://featureparity.dev
118
+ source_code_uri: https://github.com/stowzilla/feature_parity
119
+ changelog_uri: https://github.com/stowzilla/feature_parity/blob/master/cli/CHANGELOG.md
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '3.0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubygems_version: 3.5.22
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: FeatureParity CLI
139
+ test_files: []