reposer 1.1.1 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2d182e993a62366b11a0717b1493ac0d5cdc3eb7919b2943d24991361e02aae
4
- data.tar.gz: 8ef47495a5f872a344f9e435d3144b0d3c3dfb1dfbc314b6bb59d04d86ba40f5
3
+ metadata.gz: acc16509f36a2ffb3c63ecbd7275d296c40f0e6b672991a34f795b7b4b3aeaf7
4
+ data.tar.gz: 2ddcc423700c0826b8068fb7d494b995b5df68b5f6dd5075c003dc661ef604d9
5
5
  SHA512:
6
- metadata.gz: 67f272b0d8c59d178839833edca2e7bcdb4f01e69c4a29703ea06683a9d978481bc2b5223fdf6805a25d8752bb8de24cdd3d8447ec6d8744c437b01d757e08e6
7
- data.tar.gz: 72d427439970c77e660ac48894df1cc0e5cb4ec7a250297467db547ef3893228bdcec7da90d2ffcd4f2ccf596148356548606fe116ba0b3ac92db4841cb40b9f
6
+ metadata.gz: e4c9d3615aa806706139e6b15db908c69e5e901491d1fd935999cd3a27b48f38c0ffac969473bc09b99e517dd4e7b4209d8edd4a85b754fe3f7071c147d9b22c
7
+ data.tar.gz: a022e0a8861813f8bed4737e6c8e5d70567fa5ba23e7e22641fcb653dd67b703d2421767bac0576cf4e5570daf503926c06043b0399bdbb104a42607d7414fa5
data/CHANGELOG.md CHANGED
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.0] - 2025-11-21
11
+
12
+ ### Added
13
+ - **20 Topics Generation**: AI now generates up to 20 relevant topics/tags instead of 5-8
14
+ - Enhanced topic generation for comprehensive repository tagging
15
+ - Includes language ecosystem, framework, architecture, deployment, and best practices topics
16
+ - Fallback template generates intelligent topics based on language, framework, and purpose
17
+ - **Emoji Support**: Automatic emoji inclusion for visual appeal
18
+ - Repository descriptions now include at least 2 relevant emojis
19
+ - README generation includes emojis in headers and sections
20
+ - Language-specific emojis (💎 Ruby, 🐍 Python, ⚡ JavaScript, etc.)
21
+ - Purpose-based emojis (🌐 API, 📊 Data, 🤖 AI/ML, etc.)
22
+ - **License Selection**: Interactive license type selection
23
+ - Support for MIT, Apache 2.0, GPL 3.0, BSD 3-Clause, MPL 2.0, Unlicense
24
+ - Custom/Other license option
25
+ - License passed through to GitHub repository creation
26
+ - README generation includes selected license
27
+
28
+ ### Fixed
29
+ - **GitHub Authentication**: Improved GitHub client token handling
30
+ - Now properly reads from `GITHUB_TOKEN` environment variable
31
+ - Better error messages for authentication failures
32
+ - Enhanced error handling for repository creation
33
+ - Support for GitHub API license templates
34
+ - **Topic Limits**: Removed artificial 8-topic limit, now supports up to 20
35
+ - **Context Propagation**: License now properly propagated through generation pipeline
36
+
37
+ ### Enhanced
38
+ - **AI Providers**: Both Gemini and Ollama providers updated
39
+ - Better prompts for emoji and topic generation
40
+ - License-aware README generation
41
+ - Improved formatting and structure
42
+ - **Fallback Templates**: Enhanced template-based generation
43
+ - More intelligent topic selection based on project characteristics
44
+ - Language ecosystem topics (npm, bundler, cargo, etc.)
45
+ - Framework-related topics (web, api, microservices, etc.)
46
+ - Purpose-based topic detection (ai, data, testing, security, etc.)
47
+
10
48
  ## [1.1.0] - 2025-01-20
11
49
 
12
50
  ### Changed - Gem Renamed
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reposer (1.1.1)
4
+ reposer (1.2.0)
5
5
  faraday (~> 2.0)
6
6
  octokit (~> 6.0)
7
7
  ostruct (~> 0.6)
data/exe/repo-composer ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/repose"
5
+
6
+ Repose::CLI.start(ARGV)
@@ -121,33 +121,38 @@ module Repose
121
121
 
122
122
  def build_description_prompt(context)
123
123
  <<~PROMPT
124
- Generate a concise, professional GitHub repository description (max 100 characters) for:
124
+ Generate a concise, professional GitHub repository description (max 120 characters) for:
125
125
 
126
126
  Repository name: #{context[:name]}
127
127
  Language: #{context[:language]}
128
128
  Framework: #{context[:framework]}
129
129
  Purpose: #{context[:purpose]}
130
130
 
131
- Return only the description text, no quotes or extra formatting.
131
+ IMPORTANT: Include at least 2 relevant emojis that represent the project's purpose or technology.
132
+ Example format: "🚀 Fast API server for data processing 📊"
133
+
134
+ Return only the description text with emojis, no quotes or extra formatting.
132
135
  PROMPT
133
136
  end
134
137
 
135
138
  def build_topics_prompt(context)
136
139
  <<~PROMPT
137
- Generate 5-8 relevant GitHub topics (keywords) for this repository:
140
+ Generate 20 relevant GitHub topics (keywords) for this repository:
138
141
 
139
142
  Repository name: #{context[:name]}
140
143
  Language: #{context[:language]}
141
144
  Framework: #{context[:framework]}
142
145
  Purpose: #{context[:purpose]}
143
146
 
144
- Return topics as comma-separated lowercase words (e.g., javascript, react, api, nodejs).
145
- No quotes, no explanations, just the comma-separated list.
147
+ Include topics for: language, framework, use-case, architecture, deployment, testing, best practices.
148
+ Return topics as comma-separated lowercase words (e.g., javascript, react, api, nodejs, docker, ci-cd).
149
+ No quotes, no explanations, just the comma-separated list of 20 topics.
146
150
  PROMPT
147
151
  end
148
152
 
149
153
  def build_readme_prompt(context)
150
154
  title = context[:name].split(/[-_]/).map(&:capitalize).join(" ")
155
+ license = context[:license] || "MIT"
151
156
 
152
157
  <<~PROMPT
153
158
  Generate a comprehensive README.md for a GitHub repository with these details:
@@ -156,17 +161,18 @@ module Repose
156
161
  Language: #{context[:language]}
157
162
  Framework: #{context[:framework]}
158
163
  Purpose: #{context[:purpose]}
164
+ License: #{license}
159
165
 
160
- Include these sections:
161
- - Title and brief description
162
- - Features (3-5 bullet points)
163
- - Installation instructions (language-specific)
164
- - Usage examples with code blocks
165
- - Contributing guidelines
166
- - License (MIT)
166
+ Include these sections with relevant emojis:
167
+ - Title with emoji and brief description with emojis
168
+ - Features section (3-5 bullet points with emojis)
169
+ - 🚀 Installation instructions (language-specific)
170
+ - 💻 Usage examples with code blocks
171
+ - 🤝 Contributing guidelines
172
+ - 📄 License (#{license})
167
173
 
168
- Use proper Markdown formatting. Be concise and professional.
169
- Return only the README content, no extra commentary.
174
+ Use proper Markdown formatting with emojis throughout for visual appeal.
175
+ Be concise and professional. Return only the README content, no extra commentary.
170
176
  PROMPT
171
177
  end
172
178
 
@@ -185,8 +191,8 @@ module Repose
185
191
  # Split by commas and clean up
186
192
  topics = text.split(",").map(&:strip).map(&:downcase)
187
193
 
188
- # Remove duplicates and limit to 8
189
- topics.uniq.first(8)
194
+ # Remove duplicates and limit to 20
195
+ topics.uniq.first(20)
190
196
  end
191
197
  end
192
198
  end
@@ -132,32 +132,37 @@ module Repose
132
132
 
133
133
  def build_description_prompt(context)
134
134
  <<~PROMPT
135
- Generate a concise GitHub repository description (max 100 characters) for:
135
+ Generate a concise GitHub repository description (max 120 characters) for:
136
136
 
137
137
  Repository: #{context[:name]}
138
138
  Language: #{context[:language]}
139
139
  Framework: #{context[:framework]}
140
140
  Purpose: #{context[:purpose]}
141
141
 
142
- Return ONLY the description text with no quotes or formatting.
142
+ IMPORTANT: Include at least 2 relevant emojis that represent the project.
143
+ Example: "🚀 Fast API server for data processing 📊"
144
+
145
+ Return ONLY the description text with emojis, no quotes or formatting.
143
146
  PROMPT
144
147
  end
145
148
 
146
149
  def build_topics_prompt(context)
147
150
  <<~PROMPT
148
- Generate 5-8 GitHub topics for:
151
+ Generate 20 GitHub topics for:
149
152
 
150
153
  Repository: #{context[:name]}
151
154
  Language: #{context[:language]}
152
155
  Framework: #{context[:framework]}
153
156
  Purpose: #{context[:purpose]}
154
157
 
155
- Return ONLY comma-separated lowercase keywords (e.g., python, api, docker, cli).
158
+ Include topics for: language, framework, use-case, architecture, deployment, testing.
159
+ Return ONLY comma-separated lowercase keywords (e.g., python, api, docker, cli, testing, ci-cd).
156
160
  PROMPT
157
161
  end
158
162
 
159
163
  def build_readme_prompt(context)
160
164
  title = context[:name].split(/[-_]/).map(&:capitalize).join(" ")
165
+ license = context[:license] || "MIT"
161
166
 
162
167
  <<~PROMPT
163
168
  Create a GitHub README.md for:
@@ -166,17 +171,18 @@ module Repose
166
171
  Language: #{context[:language]}
167
172
  Framework: #{context[:framework]}
168
173
  Purpose: #{context[:purpose]}
174
+ License: #{license}
169
175
 
170
- Include:
171
- - Title (# #{title})
172
- - Brief description
173
- - Features (3-5 bullet points)
174
- - Installation (#{context[:language]}-specific commands)
175
- - Usage with code examples
176
- - Contributing
177
- - MIT License
176
+ Include sections with emojis:
177
+ - Title with emoji (# 🚀 #{title})
178
+ - Brief description with emojis
179
+ - Features (3-5 bullet points with emojis)
180
+ - 🚀 Installation (#{context[:language]}-specific commands)
181
+ - 💻 Usage with code examples
182
+ - 🤝 Contributing
183
+ - 📄 License (#{license})
178
184
 
179
- Use proper Markdown. Return ONLY the README content.
185
+ Use proper Markdown with emojis. Return ONLY the README content.
180
186
  PROMPT
181
187
  end
182
188
 
@@ -196,8 +202,8 @@ module Repose
196
202
  # Extract comma-separated values
197
203
  topics = text.split(",").map(&:strip).map(&:downcase)
198
204
 
199
- # Remove duplicates, filter out empty, limit to 8
200
- topics.reject(&:empty?).uniq.first(8)
205
+ # Remove duplicates, filter out empty, limit to 20
206
+ topics.reject(&:empty?).uniq.first(20)
201
207
  end
202
208
  end
203
209
  end
@@ -16,7 +16,8 @@ module Repose
16
16
  name: context[:name],
17
17
  description: generate_description(context),
18
18
  topics: generate_topics(context),
19
- readme: generate_readme(context)
19
+ readme: generate_readme(context),
20
+ license: context[:license]
20
21
  }
21
22
  end
22
23
 
@@ -93,40 +94,84 @@ module Repose
93
94
  end
94
95
 
95
96
  def generate_fallback_description(context)
96
- # Fallback description generation without AI
97
- base_desc = "A #{context[:language]}"
97
+ # Fallback description generation without AI - with emojis
98
+ emoji = select_emoji_for_language(context[:language])
99
+ purpose_emoji = select_emoji_for_purpose(context[:purpose])
100
+
101
+ base_desc = "#{emoji} A #{context[:language]}"
98
102
  base_desc += " #{context[:framework]}" if context[:framework]
99
103
  base_desc += " project"
100
104
  base_desc += " for #{context[:purpose]}" if context[:purpose] && !context[:purpose].empty?
105
+ base_desc += " #{purpose_emoji}" if purpose_emoji
101
106
 
102
107
  base_desc.capitalize
103
108
  end
104
109
 
105
110
  def generate_fallback_topics(context)
106
- # Basic topic generation without AI
111
+ # Enhanced topic generation without AI - generate up to 20 relevant topics
107
112
  topics = []
108
113
  topics << context[:language].downcase if context[:language]
109
114
  topics << context[:framework].downcase if context[:framework]
110
115
 
111
- # Add some common topics based on name patterns
116
+ # Language ecosystem topics
117
+ language_topics = language_ecosystem_topics(context[:language])
118
+ topics.concat(language_topics)
119
+
120
+ # Framework-specific topics
121
+ if context[:framework]
122
+ framework_topics = framework_related_topics(context[:framework])
123
+ topics.concat(framework_topics)
124
+ end
125
+
126
+ # Add topics based on name patterns
112
127
  name_lower = context[:name].downcase
113
128
  topics << "api" if name_lower.include?("api")
114
- topics << "web" if name_lower.include?("web") || context[:framework]&.downcase&.include?("rails")
129
+ topics << "rest" if name_lower.include?("api") || name_lower.include?("rest")
130
+ topics << "graphql" if name_lower.include?("graphql")
131
+ topics << "web" if name_lower.include?("web") || context[:framework]&.downcase&.match?(/(rails|django|flask|express)/)
115
132
  topics << "cli" if name_lower.include?("cli") || name_lower.include?("command")
116
133
  topics << "tool" if name_lower.include?("tool") || name_lower.include?("util")
134
+ topics << "library" if name_lower.include?("lib")
135
+ topics << "microservice" if name_lower.include?("micro") || name_lower.include?("service")
136
+ topics << "automation" if name_lower.include?("auto") || name_lower.include?("script")
137
+ topics << "devops" if name_lower.include?("devops") || name_lower.include?("deploy")
138
+ topics << "docker" if name_lower.include?("docker") || name_lower.include?("container")
139
+ topics << "kubernetes" if name_lower.include?("k8s") || name_lower.include?("kube")
140
+
141
+ # Purpose-based topics
142
+ if context[:purpose]
143
+ purpose_lower = context[:purpose].downcase
144
+ topics << "ai" if purpose_lower.match?(/(ai|artificial|intelligence|ml|machine|learning)/)
145
+ topics << "data" if purpose_lower.match?(/(data|analytics|etl)/)
146
+ topics << "testing" if purpose_lower.match?(/(test|qa|quality)/)
147
+ topics << "monitoring" if purpose_lower.match?(/(monitor|observ|metric)/)
148
+ topics << "security" if purpose_lower.match?(/(secur|auth|encrypt)/)
149
+ end
150
+
151
+ # General best practice topics
152
+ topics.concat(["opensource", "development", "best-practices"])
117
153
 
118
- topics.uniq.first(8)
154
+ topics.uniq.first(20)
119
155
  end
120
156
 
121
157
  def generate_fallback_readme(context)
122
158
  title = context[:name].split(/[-_]/).map(&:capitalize).join(" ")
159
+ emoji = select_emoji_for_language(context[:language])
160
+ license = context[:license] || "MIT"
123
161
 
124
162
  <<~README
125
- # #{title}
163
+ # #{emoji} #{title}
126
164
 
127
- A #{context[:language]} #{context[:framework] ? "#{context[:framework]} " : ""}project#{context[:purpose] && !context[:purpose].empty? ? " for #{context[:purpose]}" : ""}.
165
+ 🚀 A #{context[:language]} #{context[:framework] ? "#{context[:framework]} " : ""}project#{context[:purpose] && !context[:purpose].empty? ? " for #{context[:purpose]}" : ""}.
128
166
 
129
- ## Installation
167
+ ## ✨ Features
168
+
169
+ - 🛠️ Modern #{context[:language]} development
170
+ #{context[:framework] ? "- 🏛️ Built with #{context[:framework]}" : ""}
171
+ - 📚 Comprehensive documentation
172
+ - ✅ Production-ready code
173
+
174
+ ## 🚀 Installation
130
175
 
131
176
  ```bash
132
177
  git clone https://github.com/yourusername/#{context[:name]}.git
@@ -135,20 +180,20 @@ module Repose
135
180
 
136
181
  #{language_specific_install_instructions(context[:language])}
137
182
 
138
- ## Usage
183
+ ## 💻 Usage
139
184
 
140
185
  More documentation coming soon!
141
186
 
142
- ## Contributing
187
+ ## 🤝 Contributing
143
188
 
144
189
  1. Fork the repository
145
190
  2. Create a feature branch
146
191
  3. Make your changes
147
192
  4. Submit a pull request
148
193
 
149
- ## License
194
+ ## 📄 License
150
195
 
151
- This project is licensed under the MIT License.
196
+ This project is licensed under the #{license} License.
152
197
  README
153
198
  end
154
199
 
@@ -168,5 +213,92 @@ module Repose
168
213
  ""
169
214
  end
170
215
  end
216
+
217
+ def select_emoji_for_language(language)
218
+ emojis = {
219
+ "ruby" => "💎",
220
+ "python" => "🐍",
221
+ "javascript" => "⚡",
222
+ "typescript" => "📘",
223
+ "go" => "🚀",
224
+ "rust" => "🦀",
225
+ "java" => "☕",
226
+ "kotlin" => "🎯",
227
+ "swift" => "🍎",
228
+ "php" => "🐘",
229
+ "c" => "⚙️",
230
+ "c++" => "⚙️",
231
+ "c#" => "💠",
232
+ "scala" => "🎸",
233
+ "mojo" => "🔥"
234
+ }
235
+ emojis[language&.downcase] || "🚀"
236
+ end
237
+
238
+ def select_emoji_for_purpose(purpose)
239
+ return nil unless purpose && !purpose.empty?
240
+
241
+ purpose_lower = purpose.downcase
242
+ if purpose_lower.match?(/(api|rest|graphql)/)
243
+ "🌐"
244
+ elsif purpose_lower.match?(/(data|analytics|etl)/)
245
+ "📊"
246
+ elsif purpose_lower.match?(/(ai|ml|machine|learning)/)
247
+ "🤖"
248
+ elsif purpose_lower.match?(/(web|website|frontend)/)
249
+ "🎨"
250
+ elsif purpose_lower.match?(/(cli|command|terminal)/)
251
+ "💻"
252
+ elsif purpose_lower.match?(/(test|testing|qa)/)
253
+ "✅"
254
+ elsif purpose_lower.match?(/(deploy|devops|automation)/)
255
+ "⚙️"
256
+ elsif purpose_lower.match?(/(monitor|observ|metric)/)
257
+ "📈"
258
+ elsif purpose_lower.match?(/(secur|auth|encrypt)/)
259
+ "🔐"
260
+ elsif purpose_lower.match?(/(game|gaming)/)
261
+ "🎮"
262
+ elsif purpose_lower.match?(/(chat|message|communication)/)
263
+ "💬"
264
+ else
265
+ "✨"
266
+ end
267
+ end
268
+
269
+ def language_ecosystem_topics(language)
270
+ topics_map = {
271
+ "ruby" => ["gem", "bundler", "rails", "ruby-on-rails"],
272
+ "python" => ["pip", "pypi", "django", "flask"],
273
+ "javascript" => ["npm", "nodejs", "webpack", "babel"],
274
+ "typescript" => ["npm", "nodejs", "webpack", "types"],
275
+ "go" => ["golang", "modules", "concurrent"],
276
+ "rust" => ["cargo", "crates", "systems-programming"],
277
+ "java" => ["maven", "gradle", "jvm", "spring"],
278
+ "kotlin" => ["gradle", "jvm", "android"],
279
+ "swift" => ["cocoapods", "spm", "ios"],
280
+ "php" => ["composer", "laravel", "symfony"],
281
+ "c#" => ["dotnet", "nuget", "asp-net"],
282
+ "scala" => ["sbt", "jvm", "functional"]
283
+ }
284
+ topics_map[language&.downcase] || []
285
+ end
286
+
287
+ def framework_related_topics(framework)
288
+ framework_lower = framework&.downcase
289
+ topics = []
290
+
291
+ # Web frameworks
292
+ topics.concat(["web", "mvc", "backend"]) if framework_lower&.match?(/(rails|django|flask|express|spring)/)
293
+ topics.concat(["web", "frontend", "spa"]) if framework_lower&.match?(/(react|vue|angular)/)
294
+
295
+ # API frameworks
296
+ topics.concat(["api", "rest", "microservices"]) if framework_lower&.match?(/(fastapi|gin|echo|actix)/)
297
+
298
+ # Full-stack frameworks
299
+ topics.concat(["fullstack", "ssr"]) if framework_lower&.match?(/(next|nuxt)/)
300
+
301
+ topics
302
+ end
171
303
  end
172
304
  end
data/lib/repose/cli.rb CHANGED
@@ -20,6 +20,7 @@ module Repose
20
20
  Examples:
21
21
  $ repose create my-awesome-project
22
22
  $ repose create web-scraper --language ruby --framework rails
23
+ $ repose create api-server --license apache-2.0
23
24
  DESC
24
25
  option :language, type: :string, desc: "Primary programming language"
25
26
  option :framework, type: :string, desc: "Framework or library to use"
@@ -27,6 +28,7 @@ module Repose
27
28
  option :private, type: :boolean, default: false, desc: "Create private repository"
28
29
  option :template, type: :string, desc: "Repository template to use"
29
30
  option :topics, type: :array, desc: "Custom topics/tags"
31
+ option :license, type: :string, desc: "License type (mit, apache-2.0, gpl-3.0, bsd-3-clause, unlicense, etc.)"
30
32
  option :dry_run, type: :boolean, default: false, desc: "Preview without creating"
31
33
  def create(name = nil)
32
34
  pastel = Pastel.new
@@ -102,12 +104,13 @@ module Repose
102
104
  language: options[:language],
103
105
  framework: options[:framework],
104
106
  description: options[:description],
105
- topics: options[:topics] || []
107
+ topics: options[:topics] || [],
108
+ license: options[:license]
106
109
  }
107
110
 
108
111
  # Interactive prompts for missing context
109
112
  unless context[:language]
110
- languages = %w[c c++ c# go java javascript kotlin mojo php python ruby rust scala typescript]
113
+ languages = %w[c c++ c# go java javascript kotlin mojo php python ruby rust scala swift typescript]
111
114
  context[:language] = prompt.select("Primary programming language:", languages, per_page: 14)
112
115
  end
113
116
 
@@ -119,6 +122,24 @@ module Repose
119
122
  end
120
123
  end
121
124
 
125
+ # License selection
126
+ unless context[:license]
127
+ licenses = [
128
+ { name: "MIT License (Permissive, most popular)", value: "mit" },
129
+ { name: "Apache 2.0 (Permissive with patent grant)", value: "apache-2.0" },
130
+ { name: "GPL 3.0 (Copyleft, strong)", value: "gpl-3.0" },
131
+ { name: "BSD 3-Clause (Permissive)", value: "bsd-3-clause" },
132
+ { name: "Mozilla Public License 2.0", value: "mpl-2.0" },
133
+ { name: "Unlicense (Public Domain)", value: "unlicense" },
134
+ { name: "Other/Custom", value: "other" }
135
+ ]
136
+ context[:license] = prompt.select("Choose a license:", licenses)
137
+
138
+ if context[:license] == "other"
139
+ context[:license] = prompt.ask("Enter license name:", default: "MIT")
140
+ end
141
+ end
142
+
122
143
  # Additional context
123
144
  context[:purpose] = prompt.ask("What will this project do? (optional):")
124
145
 
@@ -164,7 +185,8 @@ module Repose
164
185
  description: content[:description],
165
186
  private: options[:private],
166
187
  topics: content[:topics],
167
- readme: content[:readme]
188
+ readme: content[:readme],
189
+ license: content[:license]
168
190
  )
169
191
 
170
192
  spinner.success("✅")
@@ -5,20 +5,35 @@ require "octokit"
5
5
  module Repose
6
6
  class GitHubClient
7
7
  def initialize
8
- @client = Octokit::Client.new(access_token: Repose.config.github_token)
8
+ token = Repose.config.github_token || ENV["GITHUB_TOKEN"]
9
+
10
+ raise Errors::ConfigurationError, "GitHub token not configured. Set GITHUB_TOKEN environment variable or run 'repose configure'" if token.nil? || token.empty?
11
+
12
+ @client = Octokit::Client.new(access_token: token)
13
+ @client.auto_paginate = true
9
14
  end
10
15
 
11
- def create_repository(name:, description:, private: false, topics: [], readme: nil)
12
- # Create the repository
13
- repo = @client.create_repository(name, {
16
+ def create_repository(name:, description:, private: false, topics: [], readme: nil, license: nil)
17
+ # Create the repository with license template if specified
18
+ repo_options = {
14
19
  description: description,
15
20
  private: private,
16
- auto_init: false # We'll create our own README
17
- })
21
+ auto_init: false, # We'll create our own README
22
+ has_issues: true,
23
+ has_wiki: true,
24
+ has_projects: true
25
+ }
26
+
27
+ # Add license template if specified
28
+ if license && !license.empty?
29
+ repo_options[:license_template] = normalize_license_key(license)
30
+ end
31
+
32
+ repo = @client.create_repository(name, repo_options)
18
33
 
19
34
  # Add topics if provided
20
35
  if topics.any?
21
- @client.replace_all_topics(repo.full_name, topics.map(&:downcase))
36
+ @client.replace_all_topics(repo.full_name, topics.map(&:downcase).uniq)
22
37
  end
23
38
 
24
39
  # Create README if provided
@@ -26,27 +41,57 @@ module Repose
26
41
  @client.create_contents(
27
42
  repo.full_name,
28
43
  "README.md",
29
- "Initial README",
44
+ "Initial commit: Add README",
30
45
  readme,
31
- branch: repo.default_branch
46
+ branch: repo.default_branch || "main"
32
47
  )
33
48
  end
34
49
 
35
50
  repo
51
+ rescue Octokit::Unauthorized => e
52
+ raise Errors::AuthenticationError, "GitHub authentication failed. Check your token permissions: #{e.message}"
53
+ rescue Octokit::UnprocessableEntity => e
54
+ raise Errors::GitHubError, "Repository creation failed (repository may already exist): #{e.message}"
36
55
  rescue Octokit::Error => e
37
56
  raise Errors::GitHubError, "GitHub API error: #{e.message}"
38
57
  end
39
58
 
40
59
  def repository_exists?(name)
41
- @client.repository?("#{@client.user.login}/#{name}")
60
+ username = @client.user.login
61
+ @client.repository?("#{username}/#{name}")
42
62
  rescue Octokit::NotFound
43
63
  false
64
+ rescue Octokit::Unauthorized => e
65
+ raise Errors::AuthenticationError, "GitHub authentication failed: #{e.message}"
44
66
  end
45
67
 
46
68
  def user_info
47
69
  @client.user
70
+ rescue Octokit::Unauthorized => e
71
+ raise Errors::AuthenticationError, "Failed to authenticate with GitHub. Check your token: #{e.message}"
48
72
  rescue Octokit::Error => e
49
73
  raise Errors::GitHubError, "Failed to fetch user info: #{e.message}"
50
74
  end
75
+
76
+ private
77
+
78
+ def normalize_license_key(license)
79
+ # GitHub API uses specific license keys
80
+ license_map = {
81
+ "mit" => "mit",
82
+ "apache" => "apache-2.0",
83
+ "apache-2.0" => "apache-2.0",
84
+ "gpl" => "gpl-3.0",
85
+ "gpl-3.0" => "gpl-3.0",
86
+ "bsd" => "bsd-3-clause",
87
+ "bsd-3-clause" => "bsd-3-clause",
88
+ "mpl" => "mpl-2.0",
89
+ "mpl-2.0" => "mpl-2.0",
90
+ "unlicense" => "unlicense"
91
+ }
92
+
93
+ normalized = license_map[license.downcase] || license.downcase
94
+ normalized
95
+ end
51
96
  end
52
97
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Repose
4
- VERSION = "1.1.1"
4
+ VERSION = "1.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reposer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wesley Scholl
@@ -144,6 +144,7 @@ files:
144
144
  - demo.rb
145
145
  - demo_ai_providers.rb
146
146
  - demo_interactive.rb
147
+ - exe/repo-composer
147
148
  - exe/repose
148
149
  - exe/reposer
149
150
  - lib/repose.rb