Skip to content

Commit aa57c42

Browse files
committed
feat: add context ingestion to plugins
1 parent 4294077 commit aa57c42

File tree

7 files changed

+373
-263
lines changed

7 files changed

+373
-263
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ walkdir = "2.4"
2929
glob = "0.3"
3030
regex = "1.10"
3131
uuid = { version = "1.6", features = ["v4"] }
32+
tempfile = "3.0"
3233

3334
[dev-dependencies]
3435
tempfile = "3.0"

docs/plugins.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,152 @@ The plugin system follows the same pattern as Git's external subcommands:
88

99
- Any executable named `repos-<plugin>` in your `PATH` becomes a plugin
1010
- When you run `repos <plugin> <args>`, the tool automatically finds and executes `repos-<plugin>` with the provided arguments
11+
- **NEW**: The core `repos` CLI automatically handles common options (`--config`, `--tag`, `--exclude-tag`, `--debug`) and passes filtered context to plugins via environment variables
1112
- This provides complete isolation, crash safety, and the ability to write plugins in any language
1213

14+
## Context Injection (Simplified Plugin Development)
15+
16+
As of version 0.0.10, plugins can opt into receiving pre-processed context from the core `repos` CLI. This means plugins don't need to:
17+
18+
- Parse `--config`, `--tag`, `--exclude-tag`, `--debug` options themselves
19+
- Load and parse the YAML configuration file
20+
- Apply tag filtering logic
21+
22+
### How Context Injection Works
23+
24+
When you run:
25+
26+
```bash
27+
repos health --config custom.yaml --tag flow --exclude-tag deprecated prs
28+
```
29+
30+
The core CLI:
31+
32+
1. Parses `--config`, `--tag`, `--exclude-tag` options
33+
2. Loads the config file
34+
3. Applies tag filtering (28 repos → 5 repos matching criteria)
35+
4. Serializes filtered repositories to a temp JSON file
36+
5. Sets environment variables:
37+
- `REPOS_PLUGIN_PROTOCOL=1` (indicates context injection is available)
38+
- `REPOS_FILTERED_REPOS_FILE=/tmp/repos-xxx.json` (path to filtered repos)
39+
- `REPOS_DEBUG=1` (if --debug flag was passed)
40+
- `REPOS_TOTAL_REPOS=28` (total repos in config)
41+
- `REPOS_FILTERED_COUNT=5` (repos after filtering)
42+
6. Executes `repos-health prs` with only plugin-specific args
43+
44+
### Using Context Injection in Your Plugin
45+
46+
**Rust Example:**
47+
48+
```rust
49+
use anyhow::Result;
50+
use repos::{Repository, load_plugin_context, is_debug_mode};
51+
52+
#[tokio::main]
53+
async fn main() -> Result<()> {
54+
// Try to load injected context
55+
let repos = if let Some(repos) = load_plugin_context()? {
56+
// New protocol: use pre-filtered repos from core CLI
57+
let debug = is_debug_mode();
58+
if debug {
59+
eprintln!("Using injected context with {} repos", repos.len());
60+
}
61+
repos
62+
} else {
63+
// Legacy fallback: parse args and load config manually
64+
// (for backwards compatibility when run directly)
65+
load_config_manually()?
66+
};
67+
68+
// Now just implement your plugin logic
69+
for repo in repos {
70+
println!("Processing: {}", repo.name);
71+
// Your plugin functionality here
72+
}
73+
74+
Ok(())
75+
}
76+
```
77+
78+
**Python Example:**
79+
80+
```python
81+
#!/usr/bin/env python3
82+
import os
83+
import json
84+
import sys
85+
86+
def main():
87+
# Check if context injection is available
88+
if os.environ.get('REPOS_PLUGIN_PROTOCOL') == '1':
89+
# Load pre-filtered repositories
90+
repos_file = os.environ.get('REPOS_FILTERED_REPOS_FILE')
91+
with open(repos_file, 'r') as f:
92+
repos = json.load(f)
93+
94+
debug = os.environ.get('REPOS_DEBUG') == '1'
95+
if debug:
96+
total = os.environ.get('REPOS_TOTAL_REPOS', '?')
97+
print(f"Using injected context: {len(repos)}/{total} repos", file=sys.stderr)
98+
else:
99+
# Legacy fallback: parse args and load config manually
100+
repos = load_config_manually()
101+
102+
# Implement plugin logic with filtered repos
103+
for repo in repos:
104+
print(f"Processing: {repo['name']}")
105+
# Your plugin functionality here
106+
107+
if __name__ == '__main__':
108+
main()
109+
```
110+
111+
**Bash Example:**
112+
113+
```bash
114+
#!/bin/bash
115+
116+
# Check if context injection is available
117+
if [ "$REPOS_PLUGIN_PROTOCOL" = "1" ]; then
118+
# Load pre-filtered repositories
119+
REPOS=$(cat "$REPOS_FILTERED_REPOS_FILE")
120+
121+
if [ "$REPOS_DEBUG" = "1" ]; then
122+
echo "Using injected context: $REPOS_FILTERED_COUNT/$REPOS_TOTAL_REPOS repos" >&2
123+
fi
124+
125+
# Process filtered repos using jq
126+
echo "$REPOS" | jq -r '.[] | .name' | while read -r repo_name; do
127+
echo "Processing: $repo_name"
128+
# Your plugin functionality here
129+
done
130+
else
131+
# Legacy fallback: parse args and load config manually
132+
# (for backwards compatibility when run directly)
133+
echo "Loading config manually..." >&2
134+
# ... manual config loading logic ...
135+
fi
136+
```
137+
138+
### Benefits of Context Injection
139+
140+
1. **Less boilerplate**: No need to parse common CLI options
141+
2. **Consistent behavior**: Filtering works the same across all plugins
142+
3. **Better performance**: Config loaded once, not per plugin
143+
4. **Backwards compatible**: Plugins still work when run directly
144+
5. **Language agnostic**: Available via environment variables
145+
146+
### Supported Common Options
147+
148+
When invoking plugins through `repos <plugin>`, these options are automatically handled:
149+
150+
- `--config <path>` or `-c <path>`: Custom config file
151+
- `--tag <tag>` or `-t <tag>`: Filter repos by tag (can be repeated)
152+
- `--exclude-tag <tag>` or `-e <tag>`: Exclude repos by tag (can be repeated)
153+
- `--debug` or `-d`: Enable debug output
154+
155+
All other arguments are passed to the plugin as-is.
156+
13157
## Creating a Plugin
14158

15159
To create a plugin:

0 commit comments

Comments
 (0)