@fro.bot/systematic 1.18.7 → 1.19.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.
Files changed (69) hide show
  1. package/agents/docs/ankane-readme-writer.md +67 -0
  2. package/agents/review/julik-frontend-races-reviewer.md +223 -0
  3. package/agents/review/kieran-python-reviewer.md +135 -0
  4. package/agents/review/schema-drift-detector.md +156 -0
  5. package/agents/workflow/every-style-editor.md +66 -0
  6. package/commands/agent-native-audit.md +4 -2
  7. package/commands/changelog.md +139 -0
  8. package/commands/create-agent-skill.md +5 -2
  9. package/commands/deepen-plan.md +50 -20
  10. package/commands/deploy-docs.md +120 -0
  11. package/commands/feature-video.md +352 -0
  12. package/commands/generate_command.md +164 -0
  13. package/commands/heal-skill.md +149 -0
  14. package/commands/lfg.md +14 -8
  15. package/commands/report-bug.md +151 -0
  16. package/commands/reproduce-bug.md +100 -0
  17. package/commands/resolve_parallel.md +36 -0
  18. package/commands/resolve_todo_parallel.md +37 -0
  19. package/commands/slfg.md +33 -0
  20. package/commands/test-browser.md +340 -0
  21. package/commands/test-xcode.md +333 -0
  22. package/commands/triage.md +311 -0
  23. package/commands/workflows/brainstorm.md +6 -1
  24. package/commands/workflows/compound.md +16 -13
  25. package/commands/workflows/plan.md +49 -1
  26. package/commands/workflows/review.md +28 -24
  27. package/commands/workflows/work.md +60 -25
  28. package/package.json +1 -1
  29. package/skills/andrew-kane-gem-writer/SKILL.md +185 -0
  30. package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
  31. package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
  32. package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
  33. package/skills/andrew-kane-gem-writer/references/resources.md +119 -0
  34. package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
  35. package/skills/dhh-rails-style/SKILL.md +186 -0
  36. package/skills/dhh-rails-style/references/architecture.md +653 -0
  37. package/skills/dhh-rails-style/references/controllers.md +303 -0
  38. package/skills/dhh-rails-style/references/frontend.md +510 -0
  39. package/skills/dhh-rails-style/references/gems.md +266 -0
  40. package/skills/dhh-rails-style/references/models.md +359 -0
  41. package/skills/dhh-rails-style/references/testing.md +338 -0
  42. package/skills/dspy-ruby/SKILL.md +738 -0
  43. package/skills/dspy-ruby/assets/config-template.rb +187 -0
  44. package/skills/dspy-ruby/assets/module-template.rb +300 -0
  45. package/skills/dspy-ruby/assets/signature-template.rb +221 -0
  46. package/skills/dspy-ruby/references/core-concepts.md +674 -0
  47. package/skills/dspy-ruby/references/observability.md +366 -0
  48. package/skills/dspy-ruby/references/optimization.md +603 -0
  49. package/skills/dspy-ruby/references/providers.md +418 -0
  50. package/skills/dspy-ruby/references/toolsets.md +502 -0
  51. package/skills/every-style-editor/SKILL.md +135 -0
  52. package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
  53. package/skills/gemini-imagegen/SKILL.md +238 -0
  54. package/skills/gemini-imagegen/requirements.txt +2 -0
  55. package/skills/gemini-imagegen/scripts/compose_images.py +157 -0
  56. package/skills/gemini-imagegen/scripts/edit_image.py +144 -0
  57. package/skills/gemini-imagegen/scripts/gemini_images.py +263 -0
  58. package/skills/gemini-imagegen/scripts/generate_image.py +133 -0
  59. package/skills/gemini-imagegen/scripts/multi_turn_chat.py +216 -0
  60. package/skills/rclone/SKILL.md +151 -0
  61. package/skills/rclone/scripts/check_setup.sh +60 -0
  62. package/skills/resolve-pr-parallel/SKILL.md +90 -0
  63. package/skills/resolve-pr-parallel/scripts/get-pr-comments +68 -0
  64. package/skills/resolve-pr-parallel/scripts/resolve-pr-thread +23 -0
  65. package/skills/setup/SKILL.md +168 -0
  66. package/skills/skill-creator/SKILL.md +211 -0
  67. package/skills/skill-creator/scripts/init_skill.py +303 -0
  68. package/skills/skill-creator/scripts/package_skill.py +110 -0
  69. package/skills/skill-creator/scripts/quick_validate.py +65 -0
@@ -0,0 +1,263 @@
1
+ """
2
+ Gemini Image Generation Library
3
+
4
+ A simple Python library for generating and editing images with the Gemini API.
5
+
6
+ Usage:
7
+ from gemini_images import GeminiImageGenerator
8
+
9
+ gen = GeminiImageGenerator()
10
+ gen.generate("A sunset over mountains", "sunset.png")
11
+ gen.edit("input.png", "Add clouds", "output.png")
12
+
13
+ Environment:
14
+ GEMINI_API_KEY - Required API key
15
+ """
16
+
17
+ import os
18
+ from pathlib import Path
19
+ from typing import Literal
20
+
21
+ from PIL import Image
22
+ from google import genai
23
+ from google.genai import types
24
+
25
+
26
+ AspectRatio = Literal["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]
27
+ ImageSize = Literal["1K", "2K", "4K"]
28
+ Model = Literal["gemini-2.5-flash-image", "gemini-3-pro-image-preview"]
29
+
30
+
31
+ class GeminiImageGenerator:
32
+ """High-level interface for Gemini image generation."""
33
+
34
+ FLASH = "gemini-2.5-flash-image"
35
+ PRO = "gemini-3-pro-image-preview"
36
+
37
+ def __init__(self, api_key: str | None = None, model: Model = FLASH):
38
+ """Initialize the generator.
39
+
40
+ Args:
41
+ api_key: Gemini API key (defaults to GEMINI_API_KEY env var)
42
+ model: Default model to use
43
+ """
44
+ self.api_key = api_key or os.environ.get("GEMINI_API_KEY")
45
+ if not self.api_key:
46
+ raise EnvironmentError("GEMINI_API_KEY not set")
47
+
48
+ self.client = genai.Client(api_key=self.api_key)
49
+ self.model = model
50
+
51
+ def _build_config(
52
+ self,
53
+ aspect_ratio: AspectRatio | None = None,
54
+ image_size: ImageSize | None = None,
55
+ google_search: bool = False,
56
+ ) -> types.GenerateContentConfig:
57
+ """Build generation config."""
58
+ kwargs = {"response_modalities": ["TEXT", "IMAGE"]}
59
+
60
+ img_config = {}
61
+ if aspect_ratio:
62
+ img_config["aspect_ratio"] = aspect_ratio
63
+ if image_size:
64
+ img_config["image_size"] = image_size
65
+
66
+ if img_config:
67
+ kwargs["image_config"] = types.ImageConfig(**img_config)
68
+
69
+ if google_search:
70
+ kwargs["tools"] = [{"google_search": {}}]
71
+
72
+ return types.GenerateContentConfig(**kwargs)
73
+
74
+ def generate(
75
+ self,
76
+ prompt: str,
77
+ output: str | Path,
78
+ *,
79
+ model: Model | None = None,
80
+ aspect_ratio: AspectRatio | None = None,
81
+ image_size: ImageSize | None = None,
82
+ google_search: bool = False,
83
+ ) -> tuple[Path, str | None]:
84
+ """Generate an image from a text prompt.
85
+
86
+ Args:
87
+ prompt: Text description
88
+ output: Output file path
89
+ model: Override default model
90
+ aspect_ratio: Output aspect ratio
91
+ image_size: Output resolution
92
+ google_search: Enable Google Search grounding (Pro only)
93
+
94
+ Returns:
95
+ Tuple of (output path, optional text response)
96
+ """
97
+ output = Path(output)
98
+ config = self._build_config(aspect_ratio, image_size, google_search)
99
+
100
+ response = self.client.models.generate_content(
101
+ model=model or self.model,
102
+ contents=[prompt],
103
+ config=config,
104
+ )
105
+
106
+ text = None
107
+ for part in response.parts:
108
+ if part.text:
109
+ text = part.text
110
+ elif part.inline_data:
111
+ part.as_image().save(output)
112
+
113
+ return output, text
114
+
115
+ def edit(
116
+ self,
117
+ input_image: str | Path | Image.Image,
118
+ instruction: str,
119
+ output: str | Path,
120
+ *,
121
+ model: Model | None = None,
122
+ aspect_ratio: AspectRatio | None = None,
123
+ image_size: ImageSize | None = None,
124
+ ) -> tuple[Path, str | None]:
125
+ """Edit an existing image.
126
+
127
+ Args:
128
+ input_image: Input image (path or PIL Image)
129
+ instruction: Edit instruction
130
+ output: Output file path
131
+ model: Override default model
132
+ aspect_ratio: Output aspect ratio
133
+ image_size: Output resolution
134
+
135
+ Returns:
136
+ Tuple of (output path, optional text response)
137
+ """
138
+ output = Path(output)
139
+
140
+ if isinstance(input_image, (str, Path)):
141
+ input_image = Image.open(input_image)
142
+
143
+ config = self._build_config(aspect_ratio, image_size)
144
+
145
+ response = self.client.models.generate_content(
146
+ model=model or self.model,
147
+ contents=[instruction, input_image],
148
+ config=config,
149
+ )
150
+
151
+ text = None
152
+ for part in response.parts:
153
+ if part.text:
154
+ text = part.text
155
+ elif part.inline_data:
156
+ part.as_image().save(output)
157
+
158
+ return output, text
159
+
160
+ def compose(
161
+ self,
162
+ instruction: str,
163
+ images: list[str | Path | Image.Image],
164
+ output: str | Path,
165
+ *,
166
+ model: Model | None = None,
167
+ aspect_ratio: AspectRatio | None = None,
168
+ image_size: ImageSize | None = None,
169
+ ) -> tuple[Path, str | None]:
170
+ """Compose multiple images into one.
171
+
172
+ Args:
173
+ instruction: Composition instruction
174
+ images: List of input images (up to 14)
175
+ output: Output file path
176
+ model: Override default model (Pro recommended)
177
+ aspect_ratio: Output aspect ratio
178
+ image_size: Output resolution
179
+
180
+ Returns:
181
+ Tuple of (output path, optional text response)
182
+ """
183
+ output = Path(output)
184
+
185
+ # Load images
186
+ loaded = []
187
+ for img in images:
188
+ if isinstance(img, (str, Path)):
189
+ loaded.append(Image.open(img))
190
+ else:
191
+ loaded.append(img)
192
+
193
+ config = self._build_config(aspect_ratio, image_size)
194
+ contents = [instruction] + loaded
195
+
196
+ response = self.client.models.generate_content(
197
+ model=model or self.PRO, # Pro recommended for composition
198
+ contents=contents,
199
+ config=config,
200
+ )
201
+
202
+ text = None
203
+ for part in response.parts:
204
+ if part.text:
205
+ text = part.text
206
+ elif part.inline_data:
207
+ part.as_image().save(output)
208
+
209
+ return output, text
210
+
211
+ def chat(self) -> "ImageChat":
212
+ """Start an interactive chat session for iterative refinement."""
213
+ return ImageChat(self.client, self.model)
214
+
215
+
216
+ class ImageChat:
217
+ """Multi-turn chat session for iterative image generation."""
218
+
219
+ def __init__(self, client: genai.Client, model: Model):
220
+ self.client = client
221
+ self.model = model
222
+ self._chat = client.chats.create(
223
+ model=model,
224
+ config=types.GenerateContentConfig(response_modalities=["TEXT", "IMAGE"]),
225
+ )
226
+ self.current_image: Image.Image | None = None
227
+
228
+ def send(
229
+ self,
230
+ message: str,
231
+ image: Image.Image | str | Path | None = None,
232
+ ) -> tuple[Image.Image | None, str | None]:
233
+ """Send a message and optionally an image.
234
+
235
+ Returns:
236
+ Tuple of (generated image or None, text response or None)
237
+ """
238
+ contents = [message]
239
+ if image:
240
+ if isinstance(image, (str, Path)):
241
+ image = Image.open(image)
242
+ contents.append(image)
243
+
244
+ response = self._chat.send_message(contents)
245
+
246
+ text = None
247
+ img = None
248
+ for part in response.parts:
249
+ if part.text:
250
+ text = part.text
251
+ elif part.inline_data:
252
+ img = part.as_image()
253
+ self.current_image = img
254
+
255
+ return img, text
256
+
257
+ def reset(self):
258
+ """Reset the chat session."""
259
+ self._chat = self.client.chats.create(
260
+ model=self.model,
261
+ config=types.GenerateContentConfig(response_modalities=["TEXT", "IMAGE"]),
262
+ )
263
+ self.current_image = None
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generate images from text prompts using Gemini API.
4
+
5
+ Usage:
6
+ python generate_image.py "prompt" output.png [--model MODEL] [--aspect RATIO] [--size SIZE]
7
+
8
+ Examples:
9
+ python generate_image.py "A cat in space" cat.png
10
+ python generate_image.py "A logo for Acme Corp" logo.png --model gemini-3-pro-image-preview --aspect 1:1
11
+ python generate_image.py "Epic landscape" landscape.png --aspect 16:9 --size 2K
12
+
13
+ Environment:
14
+ GEMINI_API_KEY - Required API key
15
+ """
16
+
17
+ import argparse
18
+ import os
19
+ import sys
20
+
21
+ from google import genai
22
+ from google.genai import types
23
+
24
+
25
+ def generate_image(
26
+ prompt: str,
27
+ output_path: str,
28
+ model: str = "gemini-2.5-flash-image",
29
+ aspect_ratio: str | None = None,
30
+ image_size: str | None = None,
31
+ ) -> str | None:
32
+ """Generate an image from a text prompt.
33
+
34
+ Args:
35
+ prompt: Text description of the image to generate
36
+ output_path: Path to save the generated image
37
+ model: Gemini model to use
38
+ aspect_ratio: Aspect ratio (1:1, 16:9, 9:16, etc.)
39
+ image_size: Resolution (1K, 2K, 4K - 4K only for pro model)
40
+
41
+ Returns:
42
+ Any text response from the model, or None
43
+ """
44
+ api_key = os.environ.get("GEMINI_API_KEY")
45
+ if not api_key:
46
+ raise EnvironmentError("GEMINI_API_KEY environment variable not set")
47
+
48
+ client = genai.Client(api_key=api_key)
49
+
50
+ # Build config
51
+ config_kwargs = {"response_modalities": ["TEXT", "IMAGE"]}
52
+
53
+ image_config_kwargs = {}
54
+ if aspect_ratio:
55
+ image_config_kwargs["aspect_ratio"] = aspect_ratio
56
+ if image_size:
57
+ image_config_kwargs["image_size"] = image_size
58
+
59
+ if image_config_kwargs:
60
+ config_kwargs["image_config"] = types.ImageConfig(**image_config_kwargs)
61
+
62
+ config = types.GenerateContentConfig(**config_kwargs)
63
+
64
+ response = client.models.generate_content(
65
+ model=model,
66
+ contents=[prompt],
67
+ config=config,
68
+ )
69
+
70
+ text_response = None
71
+ image_saved = False
72
+
73
+ for part in response.parts:
74
+ if part.text is not None:
75
+ text_response = part.text
76
+ elif part.inline_data is not None:
77
+ image = part.as_image()
78
+ image.save(output_path)
79
+ image_saved = True
80
+
81
+ if not image_saved:
82
+ raise RuntimeError("No image was generated. Check your prompt and try again.")
83
+
84
+ return text_response
85
+
86
+
87
+ def main():
88
+ parser = argparse.ArgumentParser(
89
+ description="Generate images from text prompts using Gemini API",
90
+ formatter_class=argparse.RawDescriptionHelpFormatter,
91
+ epilog=__doc__
92
+ )
93
+ parser.add_argument("prompt", help="Text prompt describing the image")
94
+ parser.add_argument("output", help="Output file path (e.g., output.png)")
95
+ parser.add_argument(
96
+ "--model", "-m",
97
+ default="gemini-2.5-flash-image",
98
+ choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"],
99
+ help="Model to use (default: gemini-2.5-flash-image)"
100
+ )
101
+ parser.add_argument(
102
+ "--aspect", "-a",
103
+ choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"],
104
+ help="Aspect ratio"
105
+ )
106
+ parser.add_argument(
107
+ "--size", "-s",
108
+ choices=["1K", "2K", "4K"],
109
+ help="Image resolution (4K only available with pro model)"
110
+ )
111
+
112
+ args = parser.parse_args()
113
+
114
+ try:
115
+ text = generate_image(
116
+ prompt=args.prompt,
117
+ output_path=args.output,
118
+ model=args.model,
119
+ aspect_ratio=args.aspect,
120
+ image_size=args.size,
121
+ )
122
+
123
+ print(f"Image saved to: {args.output}")
124
+ if text:
125
+ print(f"Model response: {text}")
126
+
127
+ except Exception as e:
128
+ print(f"Error: {e}", file=sys.stderr)
129
+ sys.exit(1)
130
+
131
+
132
+ if __name__ == "__main__":
133
+ main()
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Interactive multi-turn image generation and refinement using Gemini API.
4
+
5
+ Usage:
6
+ python multi_turn_chat.py [--model MODEL] [--output-dir DIR]
7
+
8
+ This starts an interactive session where you can:
9
+ - Generate images from prompts
10
+ - Iteratively refine images through conversation
11
+ - Load existing images for editing
12
+ - Save images at any point
13
+
14
+ Commands:
15
+ /save [filename] - Save current image
16
+ /load <path> - Load an image into the conversation
17
+ /clear - Start fresh conversation
18
+ /quit - Exit
19
+
20
+ Environment:
21
+ GEMINI_API_KEY - Required API key
22
+ """
23
+
24
+ import argparse
25
+ import os
26
+ import sys
27
+ from datetime import datetime
28
+ from pathlib import Path
29
+
30
+ from PIL import Image
31
+ from google import genai
32
+ from google.genai import types
33
+
34
+
35
+ class ImageChat:
36
+ """Interactive chat session for image generation and refinement."""
37
+
38
+ def __init__(
39
+ self,
40
+ model: str = "gemini-2.5-flash-image",
41
+ output_dir: str = ".",
42
+ ):
43
+ api_key = os.environ.get("GEMINI_API_KEY")
44
+ if not api_key:
45
+ raise EnvironmentError("GEMINI_API_KEY environment variable not set")
46
+
47
+ self.client = genai.Client(api_key=api_key)
48
+ self.model = model
49
+ self.output_dir = Path(output_dir)
50
+ self.output_dir.mkdir(parents=True, exist_ok=True)
51
+
52
+ self.chat = None
53
+ self.current_image = None
54
+ self.image_count = 0
55
+
56
+ self._init_chat()
57
+
58
+ def _init_chat(self):
59
+ """Initialize or reset the chat session."""
60
+ config = types.GenerateContentConfig(
61
+ response_modalities=["TEXT", "IMAGE"]
62
+ )
63
+ self.chat = self.client.chats.create(
64
+ model=self.model,
65
+ config=config,
66
+ )
67
+ self.current_image = None
68
+
69
+ def send_message(self, message: str, image: Image.Image | None = None) -> tuple[str | None, Image.Image | None]:
70
+ """Send a message and optionally an image, return response text and image."""
71
+ contents = []
72
+ if message:
73
+ contents.append(message)
74
+ if image:
75
+ contents.append(image)
76
+
77
+ if not contents:
78
+ return None, None
79
+
80
+ response = self.chat.send_message(contents)
81
+
82
+ text_response = None
83
+ image_response = None
84
+
85
+ for part in response.parts:
86
+ if part.text is not None:
87
+ text_response = part.text
88
+ elif part.inline_data is not None:
89
+ image_response = part.as_image()
90
+ self.current_image = image_response
91
+
92
+ return text_response, image_response
93
+
94
+ def save_image(self, filename: str | None = None) -> str | None:
95
+ """Save the current image to a file."""
96
+ if self.current_image is None:
97
+ return None
98
+
99
+ if filename is None:
100
+ self.image_count += 1
101
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
102
+ filename = f"image_{timestamp}_{self.image_count}.png"
103
+
104
+ filepath = self.output_dir / filename
105
+ self.current_image.save(filepath)
106
+ return str(filepath)
107
+
108
+ def load_image(self, path: str) -> Image.Image:
109
+ """Load an image from disk."""
110
+ img = Image.open(path)
111
+ self.current_image = img
112
+ return img
113
+
114
+
115
+ def main():
116
+ parser = argparse.ArgumentParser(
117
+ description="Interactive multi-turn image generation",
118
+ formatter_class=argparse.RawDescriptionHelpFormatter,
119
+ epilog=__doc__
120
+ )
121
+ parser.add_argument(
122
+ "--model", "-m",
123
+ default="gemini-2.5-flash-image",
124
+ choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"],
125
+ help="Model to use"
126
+ )
127
+ parser.add_argument(
128
+ "--output-dir", "-o",
129
+ default=".",
130
+ help="Directory to save images"
131
+ )
132
+
133
+ args = parser.parse_args()
134
+
135
+ try:
136
+ chat = ImageChat(model=args.model, output_dir=args.output_dir)
137
+ except Exception as e:
138
+ print(f"Error initializing: {e}", file=sys.stderr)
139
+ sys.exit(1)
140
+
141
+ print(f"Gemini Image Chat ({args.model})")
142
+ print("Commands: /save [name], /load <path>, /clear, /quit")
143
+ print("-" * 50)
144
+
145
+ while True:
146
+ try:
147
+ user_input = input("\nYou: ").strip()
148
+ except (EOFError, KeyboardInterrupt):
149
+ print("\nGoodbye!")
150
+ break
151
+
152
+ if not user_input:
153
+ continue
154
+
155
+ # Handle commands
156
+ if user_input.startswith("/"):
157
+ parts = user_input.split(maxsplit=1)
158
+ cmd = parts[0].lower()
159
+ arg = parts[1] if len(parts) > 1 else None
160
+
161
+ if cmd == "/quit":
162
+ print("Goodbye!")
163
+ break
164
+
165
+ elif cmd == "/clear":
166
+ chat._init_chat()
167
+ print("Conversation cleared.")
168
+ continue
169
+
170
+ elif cmd == "/save":
171
+ path = chat.save_image(arg)
172
+ if path:
173
+ print(f"Image saved to: {path}")
174
+ else:
175
+ print("No image to save.")
176
+ continue
177
+
178
+ elif cmd == "/load":
179
+ if not arg:
180
+ print("Usage: /load <path>")
181
+ continue
182
+ try:
183
+ chat.load_image(arg)
184
+ print(f"Loaded: {arg}")
185
+ print("You can now describe edits to make.")
186
+ except Exception as e:
187
+ print(f"Error loading image: {e}")
188
+ continue
189
+
190
+ else:
191
+ print(f"Unknown command: {cmd}")
192
+ continue
193
+
194
+ # Send message to model
195
+ try:
196
+ # If we have a loaded image and this is first message, include it
197
+ image_to_send = None
198
+ if chat.current_image and not chat.chat.history:
199
+ image_to_send = chat.current_image
200
+
201
+ text, image = chat.send_message(user_input, image_to_send)
202
+
203
+ if text:
204
+ print(f"\nGemini: {text}")
205
+
206
+ if image:
207
+ # Auto-save
208
+ path = chat.save_image()
209
+ print(f"\n[Image generated: {path}]")
210
+
211
+ except Exception as e:
212
+ print(f"\nError: {e}")
213
+
214
+
215
+ if __name__ == "__main__":
216
+ main()