agent-notes 2.0.4__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.
- agent_notes/VERSION +1 -0
- agent_notes/__init__.py +1 -0
- agent_notes/__main__.py +4 -0
- agent_notes/cli.py +348 -0
- agent_notes/commands/__init__.py +27 -0
- agent_notes/commands/_install_helpers.py +262 -0
- agent_notes/commands/build.py +170 -0
- agent_notes/commands/doctor.py +112 -0
- agent_notes/commands/info.py +95 -0
- agent_notes/commands/install.py +99 -0
- agent_notes/commands/list.py +169 -0
- agent_notes/commands/memory.py +430 -0
- agent_notes/commands/regenerate.py +152 -0
- agent_notes/commands/set_role.py +143 -0
- agent_notes/commands/uninstall.py +26 -0
- agent_notes/commands/update.py +169 -0
- agent_notes/commands/validate.py +199 -0
- agent_notes/commands/wizard.py +720 -0
- agent_notes/config.py +154 -0
- agent_notes/data/agents/agents.yaml +352 -0
- agent_notes/data/agents/analyst.md +45 -0
- agent_notes/data/agents/api-reviewer.md +47 -0
- agent_notes/data/agents/architect.md +46 -0
- agent_notes/data/agents/coder.md +28 -0
- agent_notes/data/agents/database-specialist.md +45 -0
- agent_notes/data/agents/debugger.md +47 -0
- agent_notes/data/agents/devil.md +47 -0
- agent_notes/data/agents/devops.md +38 -0
- agent_notes/data/agents/explorer.md +23 -0
- agent_notes/data/agents/integrations.md +44 -0
- agent_notes/data/agents/lead.md +216 -0
- agent_notes/data/agents/performance-profiler.md +44 -0
- agent_notes/data/agents/refactorer.md +48 -0
- agent_notes/data/agents/reviewer.md +44 -0
- agent_notes/data/agents/security-auditor.md +44 -0
- agent_notes/data/agents/system-auditor.md +38 -0
- agent_notes/data/agents/tech-writer.md +32 -0
- agent_notes/data/agents/test-runner.md +36 -0
- agent_notes/data/agents/test-writer.md +39 -0
- agent_notes/data/cli/claude.yaml +25 -0
- agent_notes/data/cli/copilot.yaml +18 -0
- agent_notes/data/cli/opencode.yaml +22 -0
- agent_notes/data/commands/brainstorm.md +8 -0
- agent_notes/data/commands/debug.md +9 -0
- agent_notes/data/commands/review.md +10 -0
- agent_notes/data/global-claude.md +290 -0
- agent_notes/data/global-copilot.md +27 -0
- agent_notes/data/global-opencode.md +40 -0
- agent_notes/data/hooks/session-context.md.tpl +19 -0
- agent_notes/data/models/claude-haiku-4-5.yaml +15 -0
- agent_notes/data/models/claude-opus-4-1.yaml +16 -0
- agent_notes/data/models/claude-opus-4-5.yaml +16 -0
- agent_notes/data/models/claude-opus-4-6.yaml +16 -0
- agent_notes/data/models/claude-opus-4-7.yaml +15 -0
- agent_notes/data/models/claude-sonnet-4-5.yaml +16 -0
- agent_notes/data/models/claude-sonnet-4-6.yaml +15 -0
- agent_notes/data/models/claude-sonnet-4.yaml +16 -0
- agent_notes/data/pricing.yaml +33 -0
- agent_notes/data/roles/orchestrator.yaml +5 -0
- agent_notes/data/roles/reasoner.yaml +5 -0
- agent_notes/data/roles/scout.yaml +5 -0
- agent_notes/data/roles/worker.yaml +5 -0
- agent_notes/data/rules/code-quality.md +9 -0
- agent_notes/data/rules/safety.md +10 -0
- agent_notes/data/scripts/cost-report +211 -0
- agent_notes/data/skills/brainstorming/SKILL.md +57 -0
- agent_notes/data/skills/code-review/SKILL.md +64 -0
- agent_notes/data/skills/debugging-protocol/SKILL.md +51 -0
- agent_notes/data/skills/docker-compose/SKILL.md +318 -0
- agent_notes/data/skills/docker-compose-advanced/SKILL.md +575 -0
- agent_notes/data/skills/docker-dockerfile/SKILL.md +385 -0
- agent_notes/data/skills/docker-dockerfile-languages/SKILL.md +293 -0
- agent_notes/data/skills/git/SKILL.md +87 -0
- agent_notes/data/skills/rails-active-storage/SKILL.md +321 -0
- agent_notes/data/skills/rails-broadcasting/SKILL.md +374 -0
- agent_notes/data/skills/rails-concerns/SKILL.md +806 -0
- agent_notes/data/skills/rails-controllers/SKILL.md +510 -0
- agent_notes/data/skills/rails-controllers-advanced/SKILL.md +441 -0
- agent_notes/data/skills/rails-helpers/SKILL.md +677 -0
- agent_notes/data/skills/rails-initializers/SKILL.md +79 -0
- agent_notes/data/skills/rails-javascript/SKILL.md +567 -0
- agent_notes/data/skills/rails-jobs/SKILL.md +700 -0
- agent_notes/data/skills/rails-kamal/SKILL.md +483 -0
- agent_notes/data/skills/rails-lib/SKILL.md +101 -0
- agent_notes/data/skills/rails-mailers/SKILL.md +321 -0
- agent_notes/data/skills/rails-migrations/SKILL.md +268 -0
- agent_notes/data/skills/rails-models/SKILL.md +459 -0
- agent_notes/data/skills/rails-models-advanced/SKILL.md +398 -0
- agent_notes/data/skills/rails-routes/SKILL.md +804 -0
- agent_notes/data/skills/rails-style/SKILL.md +538 -0
- agent_notes/data/skills/rails-testing-controllers/SKILL.md +343 -0
- agent_notes/data/skills/rails-testing-models/SKILL.md +296 -0
- agent_notes/data/skills/rails-testing-system/SKILL.md +375 -0
- agent_notes/data/skills/rails-validations/SKILL.md +108 -0
- agent_notes/data/skills/rails-view-components/SKILL.md +511 -0
- agent_notes/data/skills/rails-view-components-advanced/SKILL.md +376 -0
- agent_notes/data/skills/rails-views/SKILL.md +413 -0
- agent_notes/data/skills/rails-views-advanced/SKILL.md +450 -0
- agent_notes/data/skills/refactoring-protocol/SKILL.md +64 -0
- agent_notes/data/skills/tdd/SKILL.md +57 -0
- agent_notes/data/templates/__init__.py +1 -0
- agent_notes/data/templates/__pycache__/__init__.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__init__.py +1 -0
- agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/cursor.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/claude.py +44 -0
- agent_notes/data/templates/frontmatter/opencode.py +104 -0
- agent_notes/doctor_checks.py +189 -0
- agent_notes/domain/__init__.py +17 -0
- agent_notes/domain/agent.py +34 -0
- agent_notes/domain/cli_backend.py +40 -0
- agent_notes/domain/diagnostics.py +29 -0
- agent_notes/domain/diff.py +44 -0
- agent_notes/domain/model.py +27 -0
- agent_notes/domain/role.py +13 -0
- agent_notes/domain/rule.py +13 -0
- agent_notes/domain/skill.py +15 -0
- agent_notes/domain/state.py +46 -0
- agent_notes/install_state.py +11 -0
- agent_notes/registries/__init__.py +16 -0
- agent_notes/registries/_base.py +46 -0
- agent_notes/registries/agent_registry.py +107 -0
- agent_notes/registries/cli_registry.py +89 -0
- agent_notes/registries/model_registry.py +85 -0
- agent_notes/registries/role_registry.py +64 -0
- agent_notes/registries/rule_registry.py +80 -0
- agent_notes/registries/skill_registry.py +141 -0
- agent_notes/services/__init__.py +8 -0
- agent_notes/services/diagnostics/__init__.py +47 -0
- agent_notes/services/diagnostics/_checks.py +272 -0
- agent_notes/services/diagnostics/_display.py +346 -0
- agent_notes/services/diagnostics/_fix.py +169 -0
- agent_notes/services/diff.py +349 -0
- agent_notes/services/fs.py +195 -0
- agent_notes/services/install_state_builder.py +210 -0
- agent_notes/services/installer.py +293 -0
- agent_notes/services/memory_backend.py +155 -0
- agent_notes/services/rendering.py +329 -0
- agent_notes/services/session_context.py +23 -0
- agent_notes/services/settings_writer.py +79 -0
- agent_notes/services/state_store.py +249 -0
- agent_notes/services/ui.py +419 -0
- agent_notes/services/user_config.py +62 -0
- agent_notes/services/validation.py +67 -0
- agent_notes/state.py +21 -0
- agent_notes-2.0.4.dist-info/METADATA +14 -0
- agent_notes-2.0.4.dist-info/RECORD +162 -0
- agent_notes-2.0.4.dist-info/WHEEL +5 -0
- agent_notes-2.0.4.dist-info/entry_points.txt +2 -0
- agent_notes-2.0.4.dist-info/licenses/LICENSE +21 -0
- agent_notes-2.0.4.dist-info/top_level.txt +2 -0
- tests/conftest.py +20 -0
- tests/functional/__init__.py +0 -0
- tests/functional/test_build_commands.py +88 -0
- tests/functional/test_registries.py +128 -0
- tests/integration/__init__.py +0 -0
- tests/integration/test_build_output.py +129 -0
- tests/plugins/__init__.py +0 -0
- tests/plugins/test_agents.py +93 -0
- tests/plugins/test_skills.py +77 -0
agent_notes/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.0.4
|
agent_notes/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""agent-notes — AI agent configuration manager."""
|
agent_notes/__main__.py
ADDED
agent_notes/cli.py
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""CLI entry point with argument parsing."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from .config import Color
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
DESCRIPTION = "AgentNotes is a hub for installing AI best-practices (agents, skills, rules) across AI CLIs."
|
|
10
|
+
|
|
11
|
+
# Pre-colored usage line: "agent-notes <command> [options]"
|
|
12
|
+
# - "agent-notes" magenta (violet)
|
|
13
|
+
# - "<command>" cyan (matches the command-name color in the Commands section)
|
|
14
|
+
# - "[options]" green (matches the flag color)
|
|
15
|
+
USAGE = (
|
|
16
|
+
f"{Color.MAGENTA}agent-notes{Color.NC} "
|
|
17
|
+
f"{Color.CYAN}<command>{Color.NC} "
|
|
18
|
+
f"{Color.GREEN}[options]{Color.NC}"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Per-command default-behavior hints shown inline next to the description.
|
|
22
|
+
# These describe what happens when the command is run with no flags.
|
|
23
|
+
COMMAND_DEFAULTS = {
|
|
24
|
+
"install": "default: interactive wizard",
|
|
25
|
+
"uninstall": "default: global",
|
|
26
|
+
"update": "default: pull, show diff, ask to apply",
|
|
27
|
+
"doctor": "default: global scope, read-only",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# (command, explanation) pairs — rendered with color in _build_epilog()
|
|
31
|
+
EXAMPLES = [
|
|
32
|
+
("agent-notes install", "Interactive wizard (recommended)"),
|
|
33
|
+
("agent-notes install --local", "Install into current project (Claude + OpenCode, symlinks)"),
|
|
34
|
+
("agent-notes install --local --copy", "Same, but copy files (allows local edits)"),
|
|
35
|
+
("agent-notes update --dry-run", "Show what would change, don't apply"),
|
|
36
|
+
("agent-notes update --only agents --yes", "Apply only agent changes, no prompt"),
|
|
37
|
+
("agent-notes doctor --fix", "Check and repair installation"),
|
|
38
|
+
("agent-notes list agents", "List all configured agents"),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _heading(label: str) -> str:
|
|
43
|
+
"""Section heading — no color (terminal default)."""
|
|
44
|
+
return label
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _colorize_command(cmd: str) -> str:
|
|
48
|
+
"""Color a command line with the same scheme as the Usage: line.
|
|
49
|
+
|
|
50
|
+
- "agent-notes" → magenta
|
|
51
|
+
- tokens starting with "-" → green (flags, including their values when joined)
|
|
52
|
+
- other tokens → cyan (subcommand names, positional values)
|
|
53
|
+
"""
|
|
54
|
+
parts = cmd.split(" ")
|
|
55
|
+
out = []
|
|
56
|
+
for tok in parts:
|
|
57
|
+
if tok == "agent-notes":
|
|
58
|
+
out.append(f"{Color.MAGENTA}{tok}{Color.NC}")
|
|
59
|
+
elif tok.startswith("-"):
|
|
60
|
+
out.append(f"{Color.GREEN}{tok}{Color.NC}")
|
|
61
|
+
else:
|
|
62
|
+
out.append(f"{Color.CYAN}{tok}{Color.NC}")
|
|
63
|
+
return " ".join(out)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _build_epilog() -> str:
|
|
67
|
+
"""Render the Examples section with ANSI colors (auto-stripped on non-TTY)."""
|
|
68
|
+
# Width is based on the visible (uncolored) length so columns align.
|
|
69
|
+
width = max(len(cmd) for cmd, _ in EXAMPLES)
|
|
70
|
+
lines = [_heading("Examples:")]
|
|
71
|
+
for cmd, note in EXAMPLES:
|
|
72
|
+
padding = " " * (width - len(cmd))
|
|
73
|
+
colored = _colorize_command(cmd)
|
|
74
|
+
lines.append(
|
|
75
|
+
f" {colored}{padding} {Color.DIM}{note}{Color.NC}"
|
|
76
|
+
)
|
|
77
|
+
return "\n".join(lines)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _collect_flags(sub_parser: argparse.ArgumentParser) -> list[tuple[str, str, str]]:
|
|
81
|
+
"""Extract (flag_string, help_text, default_repr) tuples from a subparser.
|
|
82
|
+
|
|
83
|
+
- Skips -h/--help.
|
|
84
|
+
- Joins short/long forms: `-y, --yes`.
|
|
85
|
+
- Positionals become `<name>` or `[{choice1|choice2}]`.
|
|
86
|
+
- default_repr is `(default: X)` for positionals with a non-None default, else "".
|
|
87
|
+
"""
|
|
88
|
+
items: list[tuple[str, str, str]] = []
|
|
89
|
+
for action in sub_parser._actions:
|
|
90
|
+
if isinstance(action, argparse._HelpAction):
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
default_repr = ""
|
|
94
|
+
if action.default is not None and action.default is not False and not isinstance(action, argparse._StoreTrueAction):
|
|
95
|
+
# Show default only if it is a real value (not False from store_true).
|
|
96
|
+
default_repr = f"(default: {action.default})"
|
|
97
|
+
|
|
98
|
+
if action.option_strings:
|
|
99
|
+
flag = ", ".join(action.option_strings)
|
|
100
|
+
# Show what value the flag accepts (choices or free-form metavar)
|
|
101
|
+
if action.choices:
|
|
102
|
+
flag = f"{flag} {{{','.join(map(str, action.choices))}}}"
|
|
103
|
+
elif action.nargs != 0 and not isinstance(action, argparse._StoreTrueAction) \
|
|
104
|
+
and not isinstance(action, argparse._StoreFalseAction) \
|
|
105
|
+
and not isinstance(action, argparse._StoreConstAction):
|
|
106
|
+
metavar = action.metavar or f"<{action.dest}>"
|
|
107
|
+
flag = f"{flag} {metavar}"
|
|
108
|
+
items.append((flag, action.help or "", default_repr))
|
|
109
|
+
else:
|
|
110
|
+
if action.choices:
|
|
111
|
+
name = "{" + "|".join(map(str, action.choices)) + "}"
|
|
112
|
+
else:
|
|
113
|
+
name = f"<{action.dest}>"
|
|
114
|
+
if action.nargs == "?":
|
|
115
|
+
name = f"[{name}]"
|
|
116
|
+
items.append((name, action.help or "", default_repr))
|
|
117
|
+
return items
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _render_commands_section(subparsers_action: argparse._SubParsersAction) -> str:
|
|
121
|
+
"""Build the Commands section manually, with per-command flags inlined."""
|
|
122
|
+
lines = [_heading("Commands:")]
|
|
123
|
+
for name, sub in subparsers_action.choices.items():
|
|
124
|
+
help_text = ""
|
|
125
|
+
for action in subparsers_action._choices_actions:
|
|
126
|
+
if action.dest == name:
|
|
127
|
+
help_text = action.help or ""
|
|
128
|
+
break
|
|
129
|
+
default_hint = COMMAND_DEFAULTS.get(name, "")
|
|
130
|
+
suffix = f" {Color.DIM}({default_hint}){Color.NC}" if default_hint else ""
|
|
131
|
+
lines.append(
|
|
132
|
+
f" {Color.CYAN}{name:<11}{Color.NC} {help_text}{suffix}"
|
|
133
|
+
)
|
|
134
|
+
flags = _collect_flags(sub)
|
|
135
|
+
# Compute per-command flag column width (min 20 for visual rhythm).
|
|
136
|
+
flag_col = max([20, *(len(flag) for flag, _, _ in flags)])
|
|
137
|
+
for flag, flag_help, default_repr in flags:
|
|
138
|
+
default_tail = f" {Color.DIM}{default_repr}{Color.NC}" if default_repr else ""
|
|
139
|
+
padding = " " * (flag_col - len(flag))
|
|
140
|
+
lines.append(
|
|
141
|
+
f" {Color.GREEN}{flag}{Color.NC}{padding} "
|
|
142
|
+
f"{Color.DIM}{flag_help}{Color.NC}{default_tail}"
|
|
143
|
+
)
|
|
144
|
+
lines.append("")
|
|
145
|
+
while lines and lines[-1] == "":
|
|
146
|
+
lines.pop()
|
|
147
|
+
return "\n".join(lines)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class _AgentNotesHelp(argparse.RawDescriptionHelpFormatter):
|
|
151
|
+
"""Help formatter: plain 'Usage:' prefix and colored global options."""
|
|
152
|
+
|
|
153
|
+
def add_usage(self, usage, actions, groups, prefix=None):
|
|
154
|
+
if prefix is None:
|
|
155
|
+
prefix = "Usage: "
|
|
156
|
+
return super().add_usage(usage, actions, groups, prefix)
|
|
157
|
+
|
|
158
|
+
def _format_action_invocation(self, action):
|
|
159
|
+
"""Colorize -h/--help and -v/--version in green."""
|
|
160
|
+
text = super()._format_action_invocation(action)
|
|
161
|
+
if action.option_strings and any(
|
|
162
|
+
opt in ("-h", "--help", "-v", "--version") for opt in action.option_strings
|
|
163
|
+
):
|
|
164
|
+
text = f"{Color.GREEN}{text}{Color.NC}"
|
|
165
|
+
return text
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class _AgentNotesParser(argparse.ArgumentParser):
|
|
169
|
+
"""Parser with custom help layout: description → usage → commands → options → examples."""
|
|
170
|
+
|
|
171
|
+
def format_help(self) -> str:
|
|
172
|
+
formatter = self._get_formatter()
|
|
173
|
+
|
|
174
|
+
# 1. Description (blue)
|
|
175
|
+
if self.description:
|
|
176
|
+
formatter.add_text(f"{Color.BLUE}{self.description}{Color.NC}")
|
|
177
|
+
|
|
178
|
+
# 2. Usage (plain "Usage:" prefix; usage line has its parts colored)
|
|
179
|
+
formatter.add_usage(
|
|
180
|
+
self.usage, self._actions, self._mutually_exclusive_groups,
|
|
181
|
+
prefix="Usage: ",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# 3. Commands — hand-rolled to show per-command flags
|
|
185
|
+
subparsers_action = next(
|
|
186
|
+
(a for a in self._actions if isinstance(a, argparse._SubParsersAction)),
|
|
187
|
+
None,
|
|
188
|
+
)
|
|
189
|
+
if subparsers_action is not None:
|
|
190
|
+
formatter.add_text(_render_commands_section(subparsers_action))
|
|
191
|
+
|
|
192
|
+
# 4. Options (global) — custom heading color
|
|
193
|
+
optional_group = next(
|
|
194
|
+
(g for g in self._action_groups
|
|
195
|
+
if g.title in ("options", "optional arguments") and g._group_actions),
|
|
196
|
+
None,
|
|
197
|
+
)
|
|
198
|
+
if optional_group is not None:
|
|
199
|
+
formatter.start_section(_heading("Options"))
|
|
200
|
+
formatter.add_text(optional_group.description)
|
|
201
|
+
formatter.add_arguments(optional_group._group_actions)
|
|
202
|
+
formatter.end_section()
|
|
203
|
+
|
|
204
|
+
# 5. Examples
|
|
205
|
+
formatter.add_text(_build_epilog())
|
|
206
|
+
|
|
207
|
+
return formatter.format_help()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def main():
|
|
211
|
+
parser = _AgentNotesParser(
|
|
212
|
+
prog="agent-notes",
|
|
213
|
+
description=DESCRIPTION,
|
|
214
|
+
usage=USAGE,
|
|
215
|
+
formatter_class=_AgentNotesHelp,
|
|
216
|
+
)
|
|
217
|
+
parser.add_argument("-v", "--version", action="store_true", help="Show version")
|
|
218
|
+
|
|
219
|
+
subparsers = parser.add_subparsers(
|
|
220
|
+
dest="command",
|
|
221
|
+
title="Commands",
|
|
222
|
+
metavar="",
|
|
223
|
+
parser_class=argparse.ArgumentParser, # subparsers use default formatting
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# install
|
|
227
|
+
p_install = subparsers.add_parser("install", help="Build and install components")
|
|
228
|
+
p_install.add_argument("--local", action="store_true", help="Install to current project")
|
|
229
|
+
p_install.add_argument("--copy", action="store_true", help="Copy instead of symlink (with --local)")
|
|
230
|
+
p_install.add_argument("--reconfigure", action="store_true",
|
|
231
|
+
help="Clear existing state for this scope and re-run the wizard")
|
|
232
|
+
|
|
233
|
+
# build
|
|
234
|
+
subparsers.add_parser("build", help="Build agent configuration files from source")
|
|
235
|
+
|
|
236
|
+
# uninstall
|
|
237
|
+
p_uninstall = subparsers.add_parser("uninstall", help="Remove installed components")
|
|
238
|
+
p_uninstall.add_argument("--local", action="store_true", help="Remove from current project")
|
|
239
|
+
|
|
240
|
+
# update
|
|
241
|
+
p_update = subparsers.add_parser("update", help="Pull latest, show diff, reinstall")
|
|
242
|
+
p_update.add_argument("--dry-run", action="store_true", help="Show diff only, do not reinstall")
|
|
243
|
+
p_update.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompt")
|
|
244
|
+
p_update.add_argument("--only", action="append", choices=["agents","skills","rules","commands","config","settings"],
|
|
245
|
+
help="Filter diff to these component types (repeatable)")
|
|
246
|
+
p_update.add_argument("--since", help="Override 'before' commit label (cosmetic only for now)")
|
|
247
|
+
p_update.add_argument("--skip-pull", action="store_true", help="Skip git pull")
|
|
248
|
+
|
|
249
|
+
# doctor
|
|
250
|
+
p_doctor = subparsers.add_parser("doctor", help="Check installation health")
|
|
251
|
+
p_doctor.add_argument("--local", action="store_true", help="Check local installation")
|
|
252
|
+
p_doctor.add_argument("--fix", action="store_true", help="Fix found issues")
|
|
253
|
+
|
|
254
|
+
# info
|
|
255
|
+
subparsers.add_parser("info", help="Show status and component counts")
|
|
256
|
+
|
|
257
|
+
# list
|
|
258
|
+
p_list = subparsers.add_parser("list", help="List installed components")
|
|
259
|
+
p_list.add_argument("filter", nargs="?", default="all",
|
|
260
|
+
choices=["agents", "skills", "rules", "clis", "models", "roles", "all"],
|
|
261
|
+
help="Which components to list")
|
|
262
|
+
|
|
263
|
+
# validate
|
|
264
|
+
subparsers.add_parser("validate", help="Lint source configuration files")
|
|
265
|
+
|
|
266
|
+
# set
|
|
267
|
+
p_set = subparsers.add_parser("set", help="Configure installation")
|
|
268
|
+
p_set_subparsers = p_set.add_subparsers(dest="entity", help="What to configure")
|
|
269
|
+
p_set_role = p_set_subparsers.add_parser("role", help="Set role→model assignment")
|
|
270
|
+
p_set_role.add_argument("role_name", help="Role name")
|
|
271
|
+
p_set_role.add_argument("model_id", help="Model ID")
|
|
272
|
+
p_set_role.add_argument("--cli", help="Target CLI (auto-detect if omitted)")
|
|
273
|
+
p_set_role.add_argument("--scope", choices=["global", "local"], help="Install scope")
|
|
274
|
+
p_set_role.add_argument("--local", action="store_true", help="Use local scope")
|
|
275
|
+
|
|
276
|
+
# regenerate
|
|
277
|
+
p_regen = subparsers.add_parser("regenerate", help="Rebuild files from state")
|
|
278
|
+
p_regen.add_argument("--scope", choices=["global", "local"], help="Install scope")
|
|
279
|
+
p_regen.add_argument("--cli", help="Regenerate specific CLI only")
|
|
280
|
+
p_regen.add_argument("--local", action="store_true", help="Use local scope")
|
|
281
|
+
|
|
282
|
+
# memory
|
|
283
|
+
p_memory = subparsers.add_parser("memory", help="Manage agent memory")
|
|
284
|
+
p_memory.add_argument("action", nargs="?", default="list",
|
|
285
|
+
choices=["init", "list", "vault", "index", "add", "size", "show", "reset", "export", "import"],
|
|
286
|
+
help="Memory action")
|
|
287
|
+
p_memory.add_argument("name", nargs="?", help="Agent name / note title (for show/reset/add)")
|
|
288
|
+
p_memory.add_argument("extra", nargs="*", help="Additional args (for add: body [type] [agent] [project])")
|
|
289
|
+
|
|
290
|
+
args = parser.parse_args()
|
|
291
|
+
|
|
292
|
+
if args.version:
|
|
293
|
+
from .config import get_version
|
|
294
|
+
print(f"agent-notes {get_version()}")
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
if not args.command:
|
|
298
|
+
parser.print_help()
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
# Route to modules
|
|
302
|
+
if args.command == "build":
|
|
303
|
+
from .commands.build import build
|
|
304
|
+
build()
|
|
305
|
+
elif args.command == "install":
|
|
306
|
+
if args.local or args.copy:
|
|
307
|
+
from .commands.install import install
|
|
308
|
+
install(local=args.local, copy=args.copy, reconfigure=args.reconfigure)
|
|
309
|
+
else:
|
|
310
|
+
from .commands.wizard import interactive_install
|
|
311
|
+
interactive_install()
|
|
312
|
+
elif args.command == "uninstall":
|
|
313
|
+
from .commands.install import uninstall
|
|
314
|
+
uninstall(local=args.local)
|
|
315
|
+
elif args.command == "update":
|
|
316
|
+
from .commands.update import update
|
|
317
|
+
update(
|
|
318
|
+
dry_run=args.dry_run,
|
|
319
|
+
yes=args.yes,
|
|
320
|
+
only=args.only,
|
|
321
|
+
since=args.since,
|
|
322
|
+
skip_pull=args.skip_pull,
|
|
323
|
+
)
|
|
324
|
+
elif args.command == "doctor":
|
|
325
|
+
from .commands.doctor import doctor
|
|
326
|
+
doctor(local=args.local, fix=args.fix)
|
|
327
|
+
elif args.command == "info":
|
|
328
|
+
from .commands.info import show_info
|
|
329
|
+
show_info()
|
|
330
|
+
elif args.command == "list":
|
|
331
|
+
from .commands.list import list_components
|
|
332
|
+
list_components(args.filter)
|
|
333
|
+
elif args.command == "validate":
|
|
334
|
+
from .commands.validate import validate
|
|
335
|
+
validate()
|
|
336
|
+
elif args.command == "set":
|
|
337
|
+
if args.entity == "role":
|
|
338
|
+
from .commands.set_role import set_role
|
|
339
|
+
set_role(args.role_name, args.model_id, cli=args.cli, scope=args.scope, local=args.local)
|
|
340
|
+
elif args.command == "regenerate":
|
|
341
|
+
from .commands.regenerate import regenerate
|
|
342
|
+
regenerate(scope=args.scope, cli=args.cli, local=args.local)
|
|
343
|
+
elif args.command == "memory":
|
|
344
|
+
from .commands.memory import memory
|
|
345
|
+
memory(args.action, args.name, getattr(args, "extra", None))
|
|
346
|
+
|
|
347
|
+
if __name__ == "__main__":
|
|
348
|
+
main()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""User-facing commands. Each module is a thin orchestrator.
|
|
2
|
+
|
|
3
|
+
Commands may import from: services/, registries/, domain/, config.
|
|
4
|
+
Commands MUST NOT import other commands (use services to share logic).
|
|
5
|
+
|
|
6
|
+
Exception: install/uninstall/info share helpers via _install_helpers.py since
|
|
7
|
+
they are sibling members of one logical command group.
|
|
8
|
+
"""
|
|
9
|
+
from .install import install
|
|
10
|
+
from .uninstall import uninstall
|
|
11
|
+
from .info import show_info
|
|
12
|
+
from .build import build
|
|
13
|
+
from .doctor import doctor
|
|
14
|
+
from .validate import validate
|
|
15
|
+
from .update import update
|
|
16
|
+
from .regenerate import regenerate
|
|
17
|
+
from .set_role import set_role
|
|
18
|
+
from .wizard import interactive_install
|
|
19
|
+
from . import list as list_cmd
|
|
20
|
+
from . import memory as memory_cmd
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"install", "uninstall", "show_info",
|
|
24
|
+
"build", "doctor", "validate", "update",
|
|
25
|
+
"regenerate", "set_role", "interactive_install",
|
|
26
|
+
"list_cmd", "memory_cmd",
|
|
27
|
+
]
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""Shared installation helpers for install/uninstall/info commands."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from ..config import (
|
|
8
|
+
ROOT, DIST_CLAUDE_DIR, DIST_OPENCODE_DIR, DIST_GITHUB_DIR, DIST_RULES_DIR, DIST_SKILLS_DIR, DIST_SCRIPTS_DIR,
|
|
9
|
+
CLAUDE_HOME, OPENCODE_HOME, GITHUB_HOME, AGENTS_HOME, BIN_HOME,
|
|
10
|
+
linked, removed, skipped, info, get_version, Color, PKG_DIR
|
|
11
|
+
)
|
|
12
|
+
from ..services.fs import (
|
|
13
|
+
files_identical as _files_identical,
|
|
14
|
+
handle_existing as _handle_existing,
|
|
15
|
+
place_file, place_dir_contents, remove_symlink,
|
|
16
|
+
remove_all_symlinks_in_dir, remove_dir_if_empty
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def install_scripts_global() -> None:
|
|
21
|
+
"""Install scripts to ~/.local/bin/."""
|
|
22
|
+
from ..services.installer import install_scripts_global as _service_impl
|
|
23
|
+
_service_impl()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _install_skills_to(targets: List[Path], dist_skills_dir: Path, copy_mode: bool) -> None:
|
|
27
|
+
"""Install skills from dist_skills_dir to each directory in targets."""
|
|
28
|
+
if not dist_skills_dir.exists():
|
|
29
|
+
return
|
|
30
|
+
for target_dir in targets:
|
|
31
|
+
print(f"Installing skills to {target_dir} ...")
|
|
32
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
for skill_dir in sorted(dist_skills_dir.iterdir()):
|
|
34
|
+
if skill_dir.is_dir():
|
|
35
|
+
place_file(skill_dir, target_dir / skill_dir.name, copy_mode)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def install_skills_global(copy_mode: bool = False) -> None:
|
|
39
|
+
"""Install skills globally."""
|
|
40
|
+
targets = [CLAUDE_HOME / "skills", OPENCODE_HOME / "skills", AGENTS_HOME / "skills"]
|
|
41
|
+
_install_skills_to(targets, DIST_SKILLS_DIR, copy_mode)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def install_skills_local(copy_mode: bool = False) -> None:
|
|
45
|
+
"""Install skills locally."""
|
|
46
|
+
targets = [Path(".claude/skills"), Path(".opencode/skills")]
|
|
47
|
+
_install_skills_to(targets, DIST_SKILLS_DIR, copy_mode)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def install_agents_global(copy_mode: bool = False) -> None:
|
|
51
|
+
"""Install agents globally."""
|
|
52
|
+
print("Installing Claude Code agents to ~/.claude/agents/ ...")
|
|
53
|
+
place_dir_contents(DIST_CLAUDE_DIR / "agents", CLAUDE_HOME / "agents", "*.md", copy_mode)
|
|
54
|
+
|
|
55
|
+
print("Installing OpenCode agents to ~/.config/opencode/agents/ ...")
|
|
56
|
+
place_dir_contents(DIST_OPENCODE_DIR / "agents", OPENCODE_HOME / "agents", "*.md", copy_mode)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def install_agents_local(copy_mode: bool = False) -> None:
|
|
60
|
+
"""Install agents locally."""
|
|
61
|
+
print("Installing Claude Code agents to .claude/agents/ ...")
|
|
62
|
+
place_dir_contents(DIST_CLAUDE_DIR / "agents", Path(".claude/agents"), "*.md", copy_mode)
|
|
63
|
+
|
|
64
|
+
print("Installing OpenCode agents to .opencode/agents/ ...")
|
|
65
|
+
place_dir_contents(DIST_OPENCODE_DIR / "agents", Path(".opencode/agents"), "*.md", copy_mode)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def install_rules_global(copy_mode: bool = False) -> None:
|
|
69
|
+
"""Install global config and rules."""
|
|
70
|
+
print("Installing global config ...")
|
|
71
|
+
|
|
72
|
+
# CLAUDE.md → ~/.claude/CLAUDE.md
|
|
73
|
+
claude_global = DIST_CLAUDE_DIR / "CLAUDE.md"
|
|
74
|
+
if claude_global.exists():
|
|
75
|
+
place_file(claude_global, CLAUDE_HOME / "CLAUDE.md", copy_mode)
|
|
76
|
+
|
|
77
|
+
# AGENTS.md → ~/.config/opencode/AGENTS.md
|
|
78
|
+
agents_global = DIST_OPENCODE_DIR / "AGENTS.md"
|
|
79
|
+
if agents_global.exists():
|
|
80
|
+
place_file(agents_global, OPENCODE_HOME / "AGENTS.md", copy_mode)
|
|
81
|
+
|
|
82
|
+
# Rules → ~/.claude/rules/
|
|
83
|
+
if DIST_RULES_DIR.exists():
|
|
84
|
+
place_dir_contents(DIST_RULES_DIR, CLAUDE_HOME / "rules", "*.md", copy_mode)
|
|
85
|
+
|
|
86
|
+
# Copilot → ~/.github/copilot-instructions.md
|
|
87
|
+
copilot_global = DIST_GITHUB_DIR / "copilot-instructions.md"
|
|
88
|
+
if copilot_global.exists():
|
|
89
|
+
place_file(copilot_global, GITHUB_HOME / "copilot-instructions.md", copy_mode)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def install_rules_local(copy_mode: bool = False) -> None:
|
|
93
|
+
"""Install local config and rules."""
|
|
94
|
+
print("Installing project rules ...")
|
|
95
|
+
|
|
96
|
+
# CLAUDE.md → ./CLAUDE.md
|
|
97
|
+
claude_global = DIST_CLAUDE_DIR / "CLAUDE.md"
|
|
98
|
+
if claude_global.exists():
|
|
99
|
+
place_file(claude_global, Path("./CLAUDE.md"), copy_mode)
|
|
100
|
+
|
|
101
|
+
# AGENTS.md → ./AGENTS.md
|
|
102
|
+
agents_global = DIST_OPENCODE_DIR / "AGENTS.md"
|
|
103
|
+
if agents_global.exists():
|
|
104
|
+
place_file(agents_global, Path("./AGENTS.md"), copy_mode)
|
|
105
|
+
|
|
106
|
+
# Rules → .claude/rules/
|
|
107
|
+
if DIST_RULES_DIR.exists():
|
|
108
|
+
place_dir_contents(DIST_RULES_DIR, Path(".claude/rules"), "*.md", copy_mode)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def uninstall_scripts_global() -> None:
|
|
112
|
+
"""Uninstall scripts from ~/.local/bin/."""
|
|
113
|
+
from ..services.installer import uninstall_scripts_global as _service_impl
|
|
114
|
+
_service_impl()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _uninstall_skills_from(targets: List[Path]) -> None:
|
|
118
|
+
"""Remove skills from each directory in targets."""
|
|
119
|
+
for target_dir in targets:
|
|
120
|
+
if target_dir.exists():
|
|
121
|
+
print(f"Removing skills from {target_dir} ...")
|
|
122
|
+
remove_all_symlinks_in_dir(target_dir)
|
|
123
|
+
remove_dir_if_empty(target_dir)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def uninstall_skills_global() -> None:
|
|
127
|
+
"""Uninstall skills globally."""
|
|
128
|
+
from .. import config
|
|
129
|
+
targets = [config.CLAUDE_HOME / "skills", config.OPENCODE_HOME / "skills", config.AGENTS_HOME / "skills"]
|
|
130
|
+
_uninstall_skills_from(targets)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def uninstall_skills_local() -> None:
|
|
134
|
+
"""Uninstall skills locally."""
|
|
135
|
+
_uninstall_skills_from([Path(".claude/skills"), Path(".opencode/skills")])
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _uninstall_agents_from(dirs: List[Path]) -> None:
|
|
139
|
+
"""Remove agent symlinks from each directory in dirs."""
|
|
140
|
+
for agents_dir in dirs:
|
|
141
|
+
print(f"Removing agents from {agents_dir} ...")
|
|
142
|
+
remove_all_symlinks_in_dir(agents_dir)
|
|
143
|
+
remove_dir_if_empty(agents_dir)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def uninstall_agents_global() -> None:
|
|
147
|
+
"""Uninstall agents globally."""
|
|
148
|
+
from .. import config
|
|
149
|
+
_uninstall_agents_from([config.CLAUDE_HOME / "agents", config.OPENCODE_HOME / "agents"])
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def uninstall_agents_local() -> None:
|
|
153
|
+
"""Uninstall agents locally."""
|
|
154
|
+
_uninstall_agents_from([Path(".claude/agents"), Path(".opencode/agents")])
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _uninstall_rules_from(config_symlinks: List[Path], rules_dir: Path, label: str) -> None:
|
|
158
|
+
"""Remove config symlinks and rules directory."""
|
|
159
|
+
print(label)
|
|
160
|
+
for symlink in config_symlinks:
|
|
161
|
+
remove_symlink(symlink)
|
|
162
|
+
if rules_dir.exists():
|
|
163
|
+
remove_all_symlinks_in_dir(rules_dir)
|
|
164
|
+
remove_dir_if_empty(rules_dir)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def uninstall_rules_global() -> None:
|
|
168
|
+
"""Uninstall global config and rules."""
|
|
169
|
+
from .. import config
|
|
170
|
+
_uninstall_rules_from(
|
|
171
|
+
[config.CLAUDE_HOME / "CLAUDE.md", config.OPENCODE_HOME / "AGENTS.md", config.GITHUB_HOME / "copilot-instructions.md"],
|
|
172
|
+
config.CLAUDE_HOME / "rules",
|
|
173
|
+
"Removing global config ...",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def uninstall_rules_local() -> None:
|
|
178
|
+
"""Uninstall local config and rules."""
|
|
179
|
+
_uninstall_rules_from(
|
|
180
|
+
[Path("./CLAUDE.md"), Path("./AGENTS.md")],
|
|
181
|
+
Path(".claude/rules"),
|
|
182
|
+
"Removing project rules ...",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def count_scripts() -> int:
|
|
187
|
+
"""Count script files."""
|
|
188
|
+
return len([f for f in DIST_SCRIPTS_DIR.iterdir() if f.is_file()]) if DIST_SCRIPTS_DIR.exists() else 0
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def count_skills() -> int:
|
|
192
|
+
"""Count skill directories."""
|
|
193
|
+
if not DIST_SKILLS_DIR.exists():
|
|
194
|
+
return 0
|
|
195
|
+
return len([d for d in DIST_SKILLS_DIR.iterdir() if d.is_dir()])
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def count_agents(backend) -> int:
|
|
199
|
+
"""Count agent *.md files in backend's dist directory. Returns 0 if backend
|
|
200
|
+
doesn't support agents."""
|
|
201
|
+
from ..services import installer
|
|
202
|
+
from ..domain.cli_backend import CLIBackend
|
|
203
|
+
if not backend.supports("agents"):
|
|
204
|
+
return 0
|
|
205
|
+
src = installer.dist_source_for(backend, "agents")
|
|
206
|
+
if src is None or not src.exists():
|
|
207
|
+
return 0
|
|
208
|
+
return len(list(src.glob("*.md")))
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def count_global() -> int:
|
|
212
|
+
"""Count global config files."""
|
|
213
|
+
count = 0
|
|
214
|
+
|
|
215
|
+
# Check each potential global config file (maintaining backward compatibility)
|
|
216
|
+
if (DIST_CLAUDE_DIR / "CLAUDE.md").exists():
|
|
217
|
+
count += 1
|
|
218
|
+
if (DIST_OPENCODE_DIR / "AGENTS.md").exists():
|
|
219
|
+
count += 1
|
|
220
|
+
if (DIST_GITHUB_DIR / "copilot-instructions.md").exists():
|
|
221
|
+
count += 1
|
|
222
|
+
|
|
223
|
+
# Count rules files
|
|
224
|
+
if DIST_RULES_DIR.exists():
|
|
225
|
+
count += len(list(DIST_RULES_DIR.glob("*.md")))
|
|
226
|
+
|
|
227
|
+
return count
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _verify_install(scope_state, scope, project_path, registry) -> list[str]:
|
|
231
|
+
"""Check each file recorded in scope_state.installed exists. Return list of missing issues."""
|
|
232
|
+
from pathlib import Path
|
|
233
|
+
issues = []
|
|
234
|
+
for cli_name, backend_state in scope_state.clis.items():
|
|
235
|
+
try:
|
|
236
|
+
backend = registry.get(cli_name)
|
|
237
|
+
except KeyError:
|
|
238
|
+
issues.append(f"CLI '{cli_name}' no longer in registry")
|
|
239
|
+
continue
|
|
240
|
+
# Print per-component counts
|
|
241
|
+
for component_type, items in backend_state.installed.items():
|
|
242
|
+
present = 0
|
|
243
|
+
missing_names = []
|
|
244
|
+
for name, item in items.items():
|
|
245
|
+
if Path(item.target).exists() or Path(item.target).is_symlink():
|
|
246
|
+
present += 1
|
|
247
|
+
else:
|
|
248
|
+
missing_names.append(name)
|
|
249
|
+
total = len(items)
|
|
250
|
+
if missing_names:
|
|
251
|
+
comp_label = f"{backend.label} {component_type}"
|
|
252
|
+
print(f" ✗ {comp_label}: {len(missing_names)} missing ({', '.join(missing_names[:3])}{'...' if len(missing_names) > 3 else ''})")
|
|
253
|
+
for m in missing_names:
|
|
254
|
+
issues.append(f"{comp_label}: {m} missing")
|
|
255
|
+
else:
|
|
256
|
+
if total > 0:
|
|
257
|
+
if component_type == "config":
|
|
258
|
+
comp_label = f"{backend.label} config"
|
|
259
|
+
else:
|
|
260
|
+
comp_label = f"{backend.label} {component_type}"
|
|
261
|
+
print(f" ✓ {total} {comp_label} present")
|
|
262
|
+
return issues
|