ai-agent-rules 0.11.0__py3-none-any.whl → 0.15.8__py3-none-any.whl
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.
Potentially problematic release.
This version of ai-agent-rules might be problematic. Click here for more details.
- {ai_agent_rules-0.11.0.dist-info → ai_agent_rules-0.15.8.dist-info}/METADATA +91 -6
- {ai_agent_rules-0.11.0.dist-info → ai_agent_rules-0.15.8.dist-info}/RECORD +27 -16
- ai_rules/agents/claude.py +3 -1
- ai_rules/agents/cursor.py +70 -0
- ai_rules/agents/goose.py +4 -1
- ai_rules/bootstrap/__init__.py +8 -4
- ai_rules/bootstrap/installer.py +95 -23
- ai_rules/bootstrap/updater.py +183 -43
- ai_rules/cli.py +360 -42
- ai_rules/config/AGENTS.md +5 -4
- ai_rules/config/claude/CLAUDE.md +1 -0
- ai_rules/config/claude/commands/agents-md.md +422 -0
- ai_rules/config/claude/settings.json +7 -4
- ai_rules/config/cursor/keybindings.json +14 -0
- ai_rules/config/cursor/settings.json +81 -0
- ai_rules/config/goose/.goosehints +1 -0
- ai_rules/config/profiles/default.yaml +6 -0
- ai_rules/config/profiles/work.yaml +11 -0
- ai_rules/config.py +55 -46
- ai_rules/mcp.py +2 -3
- ai_rules/profiles.py +187 -0
- ai_rules/state.py +47 -0
- ai_rules/utils.py +35 -0
- {ai_agent_rules-0.11.0.dist-info → ai_agent_rules-0.15.8.dist-info}/WHEEL +0 -0
- {ai_agent_rules-0.11.0.dist-info → ai_agent_rules-0.15.8.dist-info}/entry_points.txt +0 -0
- {ai_agent_rules-0.11.0.dist-info → ai_agent_rules-0.15.8.dist-info}/licenses/LICENSE +0 -0
- {ai_agent_rules-0.11.0.dist-info → ai_agent_rules-0.15.8.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ai-agent-rules
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.8
|
|
4
4
|
Summary: Manage user-level AI agent configurations
|
|
5
5
|
Author-email: Will Pfleger <pfleger.will@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -47,14 +47,14 @@ Manage AI agent configurations through symlinks. Keep all your configs in one gi
|
|
|
47
47
|
|
|
48
48
|
## Overview
|
|
49
49
|
|
|
50
|
-
Consolidates config files for AI coding agents (Claude Code, Goose) into a single source of truth via symlinks:
|
|
50
|
+
Consolidates config files for AI coding agents (Claude Code, Cursor, Goose) into a single source of truth via symlinks:
|
|
51
51
|
|
|
52
52
|
- Git-tracked configs synced across machines
|
|
53
53
|
- Edit once, apply everywhere
|
|
54
54
|
- Exclude specific files (e.g., company-managed)
|
|
55
55
|
- Per-agent customizations
|
|
56
56
|
|
|
57
|
-
**Supported:** Claude Code (settings, agents, commands), Goose (hints, config), Shared (AGENTS.md)
|
|
57
|
+
**Supported:** Claude Code (settings, agents, commands), Cursor (settings, keybindings), Goose (hints, config), Shared (AGENTS.md)
|
|
58
58
|
|
|
59
59
|
## Installation
|
|
60
60
|
|
|
@@ -75,6 +75,16 @@ This will:
|
|
|
75
75
|
|
|
76
76
|
After setup, you can run `ai-rules` from any directory.
|
|
77
77
|
|
|
78
|
+
### From GitHub (Development)
|
|
79
|
+
|
|
80
|
+
Install from GitHub to get the latest development code:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uvx --from ai-agent-rules ai-rules setup --github
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This installs from the main branch and auto-detects the GitHub source for future updates.
|
|
87
|
+
|
|
78
88
|
### Local Development
|
|
79
89
|
|
|
80
90
|
For contributing or local development:
|
|
@@ -82,10 +92,11 @@ For contributing or local development:
|
|
|
82
92
|
```bash
|
|
83
93
|
git clone https://github.com/wpfleger96/ai-rules.git
|
|
84
94
|
cd ai-rules
|
|
85
|
-
uv
|
|
86
|
-
ai-rules install
|
|
95
|
+
uv run ai-rules install
|
|
87
96
|
```
|
|
88
97
|
|
|
98
|
+
Use `uv run ai-rules <command>` to test local changes. The global `ai-rules` command continues to run your installed version (PyPI/GitHub).
|
|
99
|
+
|
|
89
100
|
### Updating
|
|
90
101
|
|
|
91
102
|
Check for and install updates:
|
|
@@ -109,6 +120,8 @@ notify_only: false
|
|
|
109
120
|
|
|
110
121
|
```bash
|
|
111
122
|
ai-rules setup # One-time setup: install symlinks + make available system-wide
|
|
123
|
+
ai-rules setup --github # Install from GitHub (pre-release)
|
|
124
|
+
ai-rules setup --profile work # Setup with a specific profile
|
|
112
125
|
ai-rules upgrade # Upgrade to latest version
|
|
113
126
|
ai-rules upgrade --check # Check for updates without installing
|
|
114
127
|
|
|
@@ -118,7 +131,7 @@ ai-rules install --dry-run # Preview changes
|
|
|
118
131
|
ai-rules install --force # Skip confirmations
|
|
119
132
|
ai-rules install --rebuild-cache # Rebuild merged settings cache
|
|
120
133
|
|
|
121
|
-
ai-rules status # Check symlink status + optional tools (✓✗⚠○)
|
|
134
|
+
ai-rules status # Check symlink status + optional tools + active profile (✓✗⚠○)
|
|
122
135
|
ai-rules diff # Show config differences
|
|
123
136
|
ai-rules validate # Verify source files exist
|
|
124
137
|
ai-rules update # Re-sync after adding files
|
|
@@ -145,6 +158,7 @@ ai-rules exclude list # List all exclusions
|
|
|
145
158
|
|
|
146
159
|
# Manage settings overrides (for machine-specific settings)
|
|
147
160
|
ai-rules override set claude.model "claude-sonnet-4-5-20250929" # Set simple override
|
|
161
|
+
ai-rules override set cursor.editor.fontSize 14 # Override Cursor font size
|
|
148
162
|
ai-rules override set claude.hooks.SubagentStop[0].hooks[0].command "script.py" # Array notation
|
|
149
163
|
ai-rules override unset claude.model # Remove override
|
|
150
164
|
ai-rules override list # List all overrides
|
|
@@ -185,12 +199,17 @@ settings_overrides:
|
|
|
185
199
|
claude:
|
|
186
200
|
model: "claude-sonnet-4-5-20250929" # Override model on personal laptop
|
|
187
201
|
# Other settings inherited from base config/claude/settings.json
|
|
202
|
+
cursor:
|
|
203
|
+
editor.fontSize: 14 # Override font size on this machine
|
|
188
204
|
goose:
|
|
189
205
|
provider: "anthropic"
|
|
190
206
|
```
|
|
191
207
|
|
|
192
208
|
**Config File Location:**
|
|
193
209
|
- `~/.ai-rules-config.yaml` - User-specific config (exclusions and overrides)
|
|
210
|
+
- `~/.ai-rules/state.yaml` - Active profile and last install timestamp (auto-managed)
|
|
211
|
+
- `~/.ai-rules/cache/` - Merged settings cache (auto-generated)
|
|
212
|
+
- `~/.ai-rules/update_config.yaml` - Update check configuration
|
|
194
213
|
|
|
195
214
|
### Settings Overrides - Syncing Configs Across Machines
|
|
196
215
|
|
|
@@ -241,6 +260,64 @@ ai-rules override set claude.modle "sonnet"
|
|
|
241
260
|
|
|
242
261
|
Path validation ensures you only set valid overrides that exist in the base settings, preventing typos and configuration errors.
|
|
243
262
|
|
|
263
|
+
### Cursor Settings
|
|
264
|
+
|
|
265
|
+
Cursor settings support the same override mechanism as other agents:
|
|
266
|
+
|
|
267
|
+
```yaml
|
|
268
|
+
# ~/.ai-rules-config.yaml
|
|
269
|
+
settings_overrides:
|
|
270
|
+
cursor:
|
|
271
|
+
editor.fontSize: 14
|
|
272
|
+
terminal.integrated.defaultLocation: "editor"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
> **Note:** `keybindings.json` uses direct symlinks without override merging (array structure).
|
|
276
|
+
|
|
277
|
+
### Profiles - Machine-Specific Configuration
|
|
278
|
+
|
|
279
|
+
Profiles let you group configuration overrides into named presets. Instead of manually maintaining different `~/.ai-rules-config.yaml` files across machines, define profiles once and select them at install time.
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# List available profiles
|
|
283
|
+
ai-rules profile list
|
|
284
|
+
|
|
285
|
+
# View profile details
|
|
286
|
+
ai-rules profile show work
|
|
287
|
+
ai-rules profile show work --resolved # Show with inheritance
|
|
288
|
+
|
|
289
|
+
# Check which profile is active
|
|
290
|
+
ai-rules profile current
|
|
291
|
+
|
|
292
|
+
# Switch to a different profile
|
|
293
|
+
ai-rules profile switch work
|
|
294
|
+
|
|
295
|
+
# Install with a specific profile
|
|
296
|
+
ai-rules install --profile work
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Profiles are stored in `src/ai_rules/config/profiles/` and support inheritance:
|
|
300
|
+
|
|
301
|
+
```yaml
|
|
302
|
+
# profiles/work.yaml
|
|
303
|
+
name: work
|
|
304
|
+
description: Work laptop with extended context model
|
|
305
|
+
extends: null
|
|
306
|
+
settings_overrides:
|
|
307
|
+
claude:
|
|
308
|
+
env:
|
|
309
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "claude-sonnet-4-5-20250929[1m]"
|
|
310
|
+
model: opusplan
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Configuration layers (lowest to highest priority):
|
|
314
|
+
1. Profile overrides
|
|
315
|
+
2. Local `~/.ai-rules-config.yaml` overrides
|
|
316
|
+
|
|
317
|
+
Your local config always wins, so you can use a profile as a base and tweak specific settings per-machine. Profiles are git-tracked and can be shared across your team.
|
|
318
|
+
|
|
319
|
+
The active profile is tracked in `~/.ai-rules/state.yaml` and persists across sessions. Use `profile current` to see which profile is active, or `profile switch` to quickly change profiles without re-running the full install.
|
|
320
|
+
|
|
244
321
|
## Structure
|
|
245
322
|
|
|
246
323
|
```
|
|
@@ -250,10 +327,18 @@ config/
|
|
|
250
327
|
│ ├── settings.json # → ~/.claude/settings.json
|
|
251
328
|
│ ├── agents/*.md # → ~/.claude/agents/*.md (dynamic)
|
|
252
329
|
│ └── commands/*.md # → ~/.claude/commands/*.md (dynamic)
|
|
330
|
+
├── cursor/
|
|
331
|
+
│ ├── settings.json # → ~/Library/Application Support/Cursor/User/ (macOS)
|
|
332
|
+
│ │ # ~/AppData/Roaming/Cursor/User/ (Windows)
|
|
333
|
+
│ │ # ~/.config/Cursor/User/ (Linux)
|
|
334
|
+
│ └── keybindings.json # → (same paths as settings.json)
|
|
253
335
|
└── goose/
|
|
254
336
|
└── config.yaml # → ~/.config/goose/config.yaml
|
|
255
337
|
```
|
|
256
338
|
|
|
339
|
+
> **Note:** The Cursor config files contain the maintainer's personal preferences
|
|
340
|
+
> (e.g., macOS-specific terminal settings). Customize for your environment.
|
|
341
|
+
|
|
257
342
|
## Optional Tools
|
|
258
343
|
|
|
259
344
|
AI Rules automatically installs optional tools that enhance functionality:
|
|
@@ -1,26 +1,32 @@
|
|
|
1
|
-
ai_agent_rules-0.
|
|
1
|
+
ai_agent_rules-0.15.8.dist-info/licenses/LICENSE,sha256=eRdOpQ8Kaod-FPwMA-sD9U2817DCp0QNub7f_UpqMe0,1070
|
|
2
2
|
ai_rules/__init__.py,sha256=h0sNb8H1ED7Dy0IXON1Ww3O8cls08Sr_g1osdcQ-4i0,231
|
|
3
|
-
ai_rules/cli.py,sha256=
|
|
3
|
+
ai_rules/cli.py,sha256=yi4iAjTRY6LzNtsoZLoHwmdy-TKCz78Q27pYYKszvn4,88918
|
|
4
4
|
ai_rules/completions.py,sha256=7Ymgfzd93Owlscfn6sWkopbbAfT_J5PXnY5vl3CN_Xs,5736
|
|
5
|
-
ai_rules/config.py,sha256
|
|
5
|
+
ai_rules/config.py,sha256=-AbRaAI6LsGjqOHaoHYUIN_KxkfLAtnmH-5ChEQXI4E,21041
|
|
6
6
|
ai_rules/display.py,sha256=dltgyoJZSseP1xrj-YBvj8_TOBSTouoiQMjwdC5d2MI,1225
|
|
7
|
-
ai_rules/mcp.py,sha256=
|
|
7
|
+
ai_rules/mcp.py,sha256=BFBD0MWEjbbGOjZmmLwE8atK0KN-ExylA7VeDAZnv-M,11942
|
|
8
|
+
ai_rules/profiles.py,sha256=YI_p39bvwhtUAcgt6vv27ZymWUBSsRNIY4LjDyFOomg,6223
|
|
9
|
+
ai_rules/state.py,sha256=3VHsdDVERMeNlNNK_s-NwjQKTjx5cH9qwu4Oh2_7W-A,1191
|
|
8
10
|
ai_rules/symlinks.py,sha256=gXBVMcDU96pMHXR675ePZSnXKY9d0kEEwXnUazd0xhw,6674
|
|
11
|
+
ai_rules/utils.py,sha256=_mjJzcNbi3-zwQLGVdgqeVUv9eVj0SYI0xebZh3262E,1324
|
|
9
12
|
ai_rules/agents/__init__.py,sha256=VG6BFISMVTETyfs7aAXTa_co6NH_dmvp_5aw09HI35w,32
|
|
10
13
|
ai_rules/agents/base.py,sha256=vVPEkdczTz8oLUS5XdjHrJbARL2kDfaKDwQOVBX6bYI,1981
|
|
11
|
-
ai_rules/agents/claude.py,sha256=
|
|
12
|
-
ai_rules/agents/
|
|
14
|
+
ai_rules/agents/claude.py,sha256=XzlkW-E-4oZIWCZTvUihpzptPTiAKH23ADXlczdKHDM,3789
|
|
15
|
+
ai_rules/agents/cursor.py,sha256=PK7sl64UBVJBySRffAsdvo7Azj7lCEE9G-4NeuSNdKk,2105
|
|
16
|
+
ai_rules/agents/goose.py,sha256=omBEPTc0N8A0KenmDCNIkV5oqJsgeCEespJZVJakigw,1136
|
|
13
17
|
ai_rules/agents/shared.py,sha256=Tj9ll1Z_wZzwM19368r2ku_6aTQYyNCwxzOc_4lXqqg,839
|
|
14
|
-
ai_rules/bootstrap/__init__.py,sha256=
|
|
18
|
+
ai_rules/bootstrap/__init__.py,sha256=zDqyT51J9K_8Ys2vORgXuPLMlS0c4vONpvlFDKoT5bE,1922
|
|
15
19
|
ai_rules/bootstrap/config.py,sha256=Cp133LLC2xnyM-RRtmKsZ73cSMdm8GVzCmwHjFrcK1I,6970
|
|
16
|
-
ai_rules/bootstrap/installer.py,sha256=
|
|
17
|
-
ai_rules/bootstrap/updater.py,sha256=
|
|
20
|
+
ai_rules/bootstrap/installer.py,sha256=OrlVO6Bkky86_QE9f_UPD0Mol4k642sdDflf7DcegS0,9274
|
|
21
|
+
ai_rules/bootstrap/updater.py,sha256=vytyMV3FsJqJbR56vSU_lICVz4zc93pXorPJzK4CutE,10581
|
|
18
22
|
ai_rules/bootstrap/version.py,sha256=WCj4LwzZk-M62ViOf6qg7mQPk-AlGswwHsJWfv9bEko,1236
|
|
19
|
-
ai_rules/config/AGENTS.md,sha256=
|
|
23
|
+
ai_rules/config/AGENTS.md,sha256=zwQCZ8DiK5t0FuXaLGhfDYl5tUro_32U-CrCL3KAY68,11616
|
|
20
24
|
ai_rules/config/chat_agent_hints.md,sha256=lW3nCigFn4iuZ6vsCjntwd-Po7MVGFZynjR9X5__vY0,491
|
|
25
|
+
ai_rules/config/claude/CLAUDE.md,sha256=2BtrA5bS05_z_SQYFreWFVUJ-C9ryln1axNLOS44PD4,13
|
|
21
26
|
ai_rules/config/claude/mcps.json,sha256=yj0WO6sFU4GCciYUBWjzvvfqrBh869doeOC2Pp5EI1Y,3
|
|
22
|
-
ai_rules/config/claude/settings.json,sha256=
|
|
27
|
+
ai_rules/config/claude/settings.json,sha256=MtJgKbyMgjx5fc8P0UVeePySXLJfsKKz9fkVcERp5Ok,2989
|
|
23
28
|
ai_rules/config/claude/agents/code-reviewer.md,sha256=hFyRbuAzliQOmRVTTNAy1unV4Xx5kFb9JlWEwULL7nY,4417
|
|
29
|
+
ai_rules/config/claude/commands/agents-md.md,sha256=9ohpiGf0kC2KkqmkJmx-ZxwrLZwBQYeTZWP3bXily7c,13420
|
|
24
30
|
ai_rules/config/claude/commands/annotate-changelog.md,sha256=a9AL9Os8VLScL0hryHmesrMdusG2z0TTxH6myeu4aVU,5573
|
|
25
31
|
ai_rules/config/claude/commands/comment-cleanup.md,sha256=gNLc36PlplP08By-QK4n44gefgPusSQa0lnLQDGujBA,5296
|
|
26
32
|
ai_rules/config/claude/commands/continue-crash.md,sha256=rRi16p7PyS9IYuanuTUCh6fMmXleSoqbNqZFWzoJR0s,1378
|
|
@@ -34,9 +40,14 @@ ai_rules/config/claude/skills/doc-writer/resources/templates.md,sha256=qGkqvY78N
|
|
|
34
40
|
ai_rules/config/claude/skills/prompt-engineer/SKILL.md,sha256=TA6c1SUvJKy0pQnTwGGj_IbQEXqe4wUrFZJuaBVpNJ4,9664
|
|
35
41
|
ai_rules/config/claude/skills/prompt-engineer/resources/prompt_engineering_guide_2025.md,sha256=h3WlOQTYn1zGymJGSoD6dHCZsOTCe9Tne9a3tuRJFFQ,29592
|
|
36
42
|
ai_rules/config/claude/skills/prompt-engineer/resources/templates.md,sha256=4gIR1i5k-_GxWrZXStFyYAEfqZJvzh1KngZfq1T2pLY,6856
|
|
43
|
+
ai_rules/config/cursor/keybindings.json,sha256=nfCqj_G_WUjUNj6f3hkchMH6M-SHADhh9-GNS95ypEk,291
|
|
44
|
+
ai_rules/config/cursor/settings.json,sha256=uzPbbyVBXJlNl0EKupT-ACDyoMT3-KNhEKKrXtgLIfk,2488
|
|
45
|
+
ai_rules/config/goose/.goosehints,sha256=2BtrA5bS05_z_SQYFreWFVUJ-C9ryln1axNLOS44PD4,13
|
|
37
46
|
ai_rules/config/goose/config.yaml,sha256=N_QzIANL_7WnX5xSrMZN_t94wbrGXXHMleQ6gzzIaC8,1396
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
ai_agent_rules-0.
|
|
41
|
-
ai_agent_rules-0.
|
|
42
|
-
ai_agent_rules-0.
|
|
47
|
+
ai_rules/config/profiles/default.yaml,sha256=B1sjVz0V11gE1t3_XYXD5HpiaOWPPXEdzzotwVH8UmI,140
|
|
48
|
+
ai_rules/config/profiles/work.yaml,sha256=e982yg7kY6Mp0h6wE94m48s-mK_kpm7zAQ5OYyBIpBo,314
|
|
49
|
+
ai_agent_rules-0.15.8.dist-info/METADATA,sha256=doQIzH2GCY8A97ZVAhdlsfNFJLMgo7IbYvlqXBRq6GI,15749
|
|
50
|
+
ai_agent_rules-0.15.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
51
|
+
ai_agent_rules-0.15.8.dist-info/entry_points.txt,sha256=Dnt2pQp6vSWz374RsUNZ5Zh5qmuTy45xR02r5fH_p_E,82
|
|
52
|
+
ai_agent_rules-0.15.8.dist-info/top_level.txt,sha256=1jJObrxql_i5okMXb4dxsiQa_cpjtzwwskVvwedEHow,9
|
|
53
|
+
ai_agent_rules-0.15.8.dist-info/RECORD,,
|
ai_rules/agents/claude.py
CHANGED
|
@@ -35,7 +35,9 @@ class ClaudeAgent(Agent):
|
|
|
35
35
|
"""Cached list of all Claude Code symlinks including dynamic agents/commands."""
|
|
36
36
|
result = []
|
|
37
37
|
|
|
38
|
-
result.append(
|
|
38
|
+
result.append(
|
|
39
|
+
(Path("~/.claude/CLAUDE.md"), self.config_dir / "claude" / "CLAUDE.md")
|
|
40
|
+
)
|
|
39
41
|
|
|
40
42
|
settings_file = self.config_dir / "claude" / "settings.json"
|
|
41
43
|
if settings_file.exists():
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Cursor editor agent implementation."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from functools import cached_property
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ai_rules.agents.base import Agent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_cursor_target_prefix() -> str:
|
|
12
|
+
"""Get platform-specific Cursor config path with ~ prefix.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Path string with ~ prefix for the current platform:
|
|
16
|
+
- macOS: ~/Library/Application Support/Cursor/User
|
|
17
|
+
- Windows: ~/AppData/Roaming/Cursor/User
|
|
18
|
+
- Linux/WSL: ~/.config/Cursor/User
|
|
19
|
+
"""
|
|
20
|
+
if sys.platform == "darwin":
|
|
21
|
+
return "~/Library/Application Support/Cursor/User"
|
|
22
|
+
elif sys.platform == "win32":
|
|
23
|
+
return "~/AppData/Roaming/Cursor/User"
|
|
24
|
+
else: # Linux/WSL
|
|
25
|
+
return "~/.config/Cursor/User"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CursorAgent(Agent):
|
|
29
|
+
"""Agent for Cursor editor configuration."""
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def name(self) -> str:
|
|
33
|
+
return "Cursor"
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def agent_id(self) -> str:
|
|
37
|
+
return "cursor"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def config_file_name(self) -> str:
|
|
41
|
+
return "settings.json"
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def config_file_format(self) -> str:
|
|
45
|
+
return "json"
|
|
46
|
+
|
|
47
|
+
@cached_property
|
|
48
|
+
def symlinks(self) -> list[tuple[Path, Path]]:
|
|
49
|
+
"""Cached list of all Cursor symlinks.
|
|
50
|
+
|
|
51
|
+
Settings file uses cache-based approach with override merging.
|
|
52
|
+
Keybindings file uses direct symlink (array structure, no merging).
|
|
53
|
+
"""
|
|
54
|
+
result = []
|
|
55
|
+
prefix = _get_cursor_target_prefix()
|
|
56
|
+
|
|
57
|
+
# Settings file - use cache if overrides exist
|
|
58
|
+
settings_file = self.config_dir / "cursor" / "settings.json"
|
|
59
|
+
if settings_file.exists():
|
|
60
|
+
target_file = self.config.get_settings_file_for_symlink(
|
|
61
|
+
"cursor", settings_file
|
|
62
|
+
)
|
|
63
|
+
result.append((Path(f"{prefix}/settings.json"), target_file))
|
|
64
|
+
|
|
65
|
+
# Keybindings file - direct symlink (no override merging for arrays)
|
|
66
|
+
keybindings_file = self.config_dir / "cursor" / "keybindings.json"
|
|
67
|
+
if keybindings_file.exists():
|
|
68
|
+
result.append((Path(f"{prefix}/keybindings.json"), keybindings_file))
|
|
69
|
+
|
|
70
|
+
return result
|
ai_rules/agents/goose.py
CHANGED
|
@@ -31,7 +31,10 @@ class GooseAgent(Agent):
|
|
|
31
31
|
result = []
|
|
32
32
|
|
|
33
33
|
result.append(
|
|
34
|
-
(
|
|
34
|
+
(
|
|
35
|
+
Path("~/.config/goose/.goosehints"),
|
|
36
|
+
self.config_dir / "goose" / ".goosehints",
|
|
37
|
+
)
|
|
35
38
|
)
|
|
36
39
|
|
|
37
40
|
config_file = self.config_dir / "goose" / "config.yaml"
|
ai_rules/bootstrap/__init__.py
CHANGED
|
@@ -24,8 +24,10 @@ from .config import (
|
|
|
24
24
|
)
|
|
25
25
|
from .installer import (
|
|
26
26
|
UV_NOT_FOUND_ERROR,
|
|
27
|
+
ToolSource,
|
|
27
28
|
ensure_statusline_installed,
|
|
28
29
|
get_tool_config_dir,
|
|
30
|
+
get_tool_source,
|
|
29
31
|
get_tool_version,
|
|
30
32
|
install_tool,
|
|
31
33
|
is_command_available,
|
|
@@ -35,10 +37,10 @@ from .updater import (
|
|
|
35
37
|
UPDATABLE_TOOLS,
|
|
36
38
|
ToolSpec,
|
|
37
39
|
UpdateInfo,
|
|
38
|
-
|
|
40
|
+
check_index_updates,
|
|
39
41
|
check_tool_updates,
|
|
40
42
|
get_tool_by_id,
|
|
41
|
-
|
|
43
|
+
perform_tool_upgrade,
|
|
42
44
|
)
|
|
43
45
|
from .version import get_package_version, is_newer, parse_version
|
|
44
46
|
|
|
@@ -47,8 +49,10 @@ __all__ = [
|
|
|
47
49
|
"is_newer",
|
|
48
50
|
"parse_version",
|
|
49
51
|
"UV_NOT_FOUND_ERROR",
|
|
52
|
+
"ToolSource",
|
|
50
53
|
"ensure_statusline_installed",
|
|
51
54
|
"get_tool_config_dir",
|
|
55
|
+
"get_tool_source",
|
|
52
56
|
"get_tool_version",
|
|
53
57
|
"install_tool",
|
|
54
58
|
"is_command_available",
|
|
@@ -56,10 +60,10 @@ __all__ = [
|
|
|
56
60
|
"UPDATABLE_TOOLS",
|
|
57
61
|
"ToolSpec",
|
|
58
62
|
"UpdateInfo",
|
|
59
|
-
"
|
|
63
|
+
"check_index_updates",
|
|
60
64
|
"check_tool_updates",
|
|
61
65
|
"get_tool_by_id",
|
|
62
|
-
"
|
|
66
|
+
"perform_tool_upgrade",
|
|
63
67
|
"AutoUpdateConfig",
|
|
64
68
|
"clear_all_pending_updates",
|
|
65
69
|
"clear_pending_update",
|
ai_rules/bootstrap/installer.py
CHANGED
|
@@ -6,6 +6,7 @@ import shutil
|
|
|
6
6
|
import subprocess
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
|
+
from enum import Enum, auto
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
|
|
11
12
|
if sys.version_info >= (3, 11):
|
|
@@ -13,7 +14,30 @@ if sys.version_info >= (3, 11):
|
|
|
13
14
|
else:
|
|
14
15
|
import tomli as tomllib
|
|
15
16
|
|
|
17
|
+
|
|
18
|
+
class ToolSource(Enum):
|
|
19
|
+
"""Source from which a tool was installed."""
|
|
20
|
+
|
|
21
|
+
PYPI = auto()
|
|
22
|
+
GITHUB = auto()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def make_github_install_url(repo: str) -> str:
|
|
26
|
+
"""Construct GitHub install URL for uv tool install.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
repo: GitHub repository in format "owner/repo"
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Full git+ssh URL for uv tool install
|
|
33
|
+
"""
|
|
34
|
+
return f"git+ssh://git@github.com/{repo}.git"
|
|
35
|
+
|
|
36
|
+
|
|
16
37
|
UV_NOT_FOUND_ERROR = "uv not found in PATH. Install from https://docs.astral.sh/uv/"
|
|
38
|
+
PACKAGE_NAME = "ai-agent-rules"
|
|
39
|
+
GITHUB_REPO = "wpfleger96/ai-rules"
|
|
40
|
+
STATUSLINE_GITHUB_REPO = "wpfleger96/claude-code-status-line"
|
|
17
41
|
|
|
18
42
|
|
|
19
43
|
def _validate_package_name(package_name: str) -> bool:
|
|
@@ -50,15 +74,15 @@ def get_tool_config_dir(package_name: str = "ai-agent-rules") -> Path:
|
|
|
50
74
|
)
|
|
51
75
|
|
|
52
76
|
|
|
53
|
-
def get_tool_source(package_name: str) ->
|
|
54
|
-
"""Detect how a uv tool was installed
|
|
77
|
+
def get_tool_source(package_name: str) -> ToolSource | None:
|
|
78
|
+
"""Detect how a uv tool was installed.
|
|
55
79
|
|
|
56
80
|
Args:
|
|
57
81
|
package_name: Name of the uv tool package
|
|
58
82
|
|
|
59
83
|
Returns:
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
ToolSource.PYPI if installed from PyPI
|
|
85
|
+
ToolSource.GITHUB if installed from GitHub
|
|
62
86
|
None if tool not installed or receipt file not found
|
|
63
87
|
"""
|
|
64
88
|
data_home = os.environ.get("XDG_DATA_HOME", str(Path.home() / ".local" / "share"))
|
|
@@ -75,12 +99,12 @@ def get_tool_source(package_name: str) -> str | None:
|
|
|
75
99
|
if not requirements:
|
|
76
100
|
return None
|
|
77
101
|
|
|
78
|
-
# Check first requirement for path key (indicates local install)
|
|
79
102
|
first_req = requirements[0]
|
|
80
|
-
if isinstance(first_req, dict)
|
|
81
|
-
|
|
103
|
+
if isinstance(first_req, dict):
|
|
104
|
+
if "git" in first_req and "github.com" in first_req["git"]:
|
|
105
|
+
return ToolSource.GITHUB
|
|
82
106
|
|
|
83
|
-
return
|
|
107
|
+
return ToolSource.PYPI
|
|
84
108
|
|
|
85
109
|
except (OSError, tomllib.TOMLDecodeError, KeyError, IndexError):
|
|
86
110
|
return None
|
|
@@ -100,28 +124,39 @@ def is_command_available(command: str) -> bool:
|
|
|
100
124
|
|
|
101
125
|
def install_tool(
|
|
102
126
|
package_name: str = "ai-agent-rules",
|
|
127
|
+
from_github: bool = False,
|
|
128
|
+
github_url: str | None = None,
|
|
103
129
|
force: bool = False,
|
|
104
130
|
dry_run: bool = False,
|
|
105
131
|
) -> tuple[bool, str]:
|
|
106
|
-
"""Install package as a uv tool
|
|
132
|
+
"""Install package as a uv tool.
|
|
107
133
|
|
|
108
134
|
Args:
|
|
109
|
-
package_name: Name of package to install
|
|
135
|
+
package_name: Name of package to install (ignored if from_github=True)
|
|
136
|
+
from_github: Install from GitHub instead of PyPI
|
|
137
|
+
github_url: GitHub URL to install from (only used if from_github=True)
|
|
110
138
|
force: Force reinstall if already installed
|
|
111
139
|
dry_run: Show what would be done without executing
|
|
112
140
|
|
|
113
141
|
Returns:
|
|
114
142
|
Tuple of (success, message)
|
|
115
143
|
"""
|
|
116
|
-
if not _validate_package_name(package_name):
|
|
144
|
+
if not from_github and not _validate_package_name(package_name):
|
|
117
145
|
return False, f"Invalid package name: {package_name}"
|
|
118
146
|
|
|
119
147
|
if not is_command_available("uv"):
|
|
120
148
|
return False, UV_NOT_FOUND_ERROR
|
|
121
149
|
|
|
122
|
-
|
|
150
|
+
if from_github:
|
|
151
|
+
source = github_url if github_url else make_github_install_url(GITHUB_REPO)
|
|
152
|
+
else:
|
|
153
|
+
source = package_name
|
|
154
|
+
cmd = ["uv", "tool", "install", source]
|
|
155
|
+
|
|
123
156
|
if force:
|
|
124
157
|
cmd.insert(3, "--force")
|
|
158
|
+
if from_github:
|
|
159
|
+
cmd.insert(4, "--reinstall")
|
|
125
160
|
|
|
126
161
|
if dry_run:
|
|
127
162
|
return True, f"Would run: {' '.join(cmd)}"
|
|
@@ -227,23 +262,60 @@ def get_tool_version(tool_name: str) -> str | None:
|
|
|
227
262
|
return None
|
|
228
263
|
|
|
229
264
|
|
|
230
|
-
def ensure_statusline_installed(
|
|
231
|
-
|
|
265
|
+
def ensure_statusline_installed(
|
|
266
|
+
dry_run: bool = False, from_github: bool = False
|
|
267
|
+
) -> tuple[str, str | None]:
|
|
268
|
+
"""Install or upgrade claude-code-statusline if needed. Fails open.
|
|
232
269
|
|
|
233
270
|
Args:
|
|
234
|
-
dry_run: If True,
|
|
271
|
+
dry_run: If True, show what would be done without executing
|
|
272
|
+
from_github: Install from GitHub instead of PyPI
|
|
235
273
|
|
|
236
274
|
Returns:
|
|
237
|
-
|
|
275
|
+
Tuple of (status, message) where status is:
|
|
276
|
+
"already_installed", "installed", "upgraded", "upgrade_available", "failed", or "skipped"
|
|
277
|
+
Message is only provided in dry_run mode or when upgraded
|
|
238
278
|
"""
|
|
239
279
|
if is_command_available("claude-statusline"):
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
280
|
+
try:
|
|
281
|
+
from ai_rules.bootstrap.updater import (
|
|
282
|
+
check_tool_updates,
|
|
283
|
+
get_tool_by_id,
|
|
284
|
+
perform_tool_upgrade,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
statusline_tool = get_tool_by_id("statusline")
|
|
288
|
+
if statusline_tool:
|
|
289
|
+
update_info = check_tool_updates(statusline_tool, timeout=10)
|
|
290
|
+
if update_info and update_info.has_update:
|
|
291
|
+
if dry_run:
|
|
292
|
+
return (
|
|
293
|
+
"upgrade_available",
|
|
294
|
+
f"Would upgrade statusline {update_info.current_version} → {update_info.latest_version}",
|
|
295
|
+
)
|
|
296
|
+
success, msg, _ = perform_tool_upgrade(statusline_tool)
|
|
297
|
+
if success:
|
|
298
|
+
return (
|
|
299
|
+
"upgraded",
|
|
300
|
+
f"{update_info.current_version} → {update_info.latest_version}",
|
|
301
|
+
)
|
|
302
|
+
except Exception:
|
|
303
|
+
pass
|
|
304
|
+
return "already_installed", None
|
|
244
305
|
|
|
245
306
|
try:
|
|
246
|
-
success,
|
|
247
|
-
|
|
307
|
+
success, message = install_tool(
|
|
308
|
+
"claude-code-statusline",
|
|
309
|
+
from_github=from_github,
|
|
310
|
+
github_url=make_github_install_url(STATUSLINE_GITHUB_REPO)
|
|
311
|
+
if from_github
|
|
312
|
+
else None,
|
|
313
|
+
force=False,
|
|
314
|
+
dry_run=dry_run,
|
|
315
|
+
)
|
|
316
|
+
if success:
|
|
317
|
+
return "installed", message if dry_run else None
|
|
318
|
+
else:
|
|
319
|
+
return "failed", None
|
|
248
320
|
except Exception:
|
|
249
|
-
return "failed"
|
|
321
|
+
return "failed", None
|