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.
Files changed (162) hide show
  1. agent_notes/VERSION +1 -0
  2. agent_notes/__init__.py +1 -0
  3. agent_notes/__main__.py +4 -0
  4. agent_notes/cli.py +348 -0
  5. agent_notes/commands/__init__.py +27 -0
  6. agent_notes/commands/_install_helpers.py +262 -0
  7. agent_notes/commands/build.py +170 -0
  8. agent_notes/commands/doctor.py +112 -0
  9. agent_notes/commands/info.py +95 -0
  10. agent_notes/commands/install.py +99 -0
  11. agent_notes/commands/list.py +169 -0
  12. agent_notes/commands/memory.py +430 -0
  13. agent_notes/commands/regenerate.py +152 -0
  14. agent_notes/commands/set_role.py +143 -0
  15. agent_notes/commands/uninstall.py +26 -0
  16. agent_notes/commands/update.py +169 -0
  17. agent_notes/commands/validate.py +199 -0
  18. agent_notes/commands/wizard.py +720 -0
  19. agent_notes/config.py +154 -0
  20. agent_notes/data/agents/agents.yaml +352 -0
  21. agent_notes/data/agents/analyst.md +45 -0
  22. agent_notes/data/agents/api-reviewer.md +47 -0
  23. agent_notes/data/agents/architect.md +46 -0
  24. agent_notes/data/agents/coder.md +28 -0
  25. agent_notes/data/agents/database-specialist.md +45 -0
  26. agent_notes/data/agents/debugger.md +47 -0
  27. agent_notes/data/agents/devil.md +47 -0
  28. agent_notes/data/agents/devops.md +38 -0
  29. agent_notes/data/agents/explorer.md +23 -0
  30. agent_notes/data/agents/integrations.md +44 -0
  31. agent_notes/data/agents/lead.md +216 -0
  32. agent_notes/data/agents/performance-profiler.md +44 -0
  33. agent_notes/data/agents/refactorer.md +48 -0
  34. agent_notes/data/agents/reviewer.md +44 -0
  35. agent_notes/data/agents/security-auditor.md +44 -0
  36. agent_notes/data/agents/system-auditor.md +38 -0
  37. agent_notes/data/agents/tech-writer.md +32 -0
  38. agent_notes/data/agents/test-runner.md +36 -0
  39. agent_notes/data/agents/test-writer.md +39 -0
  40. agent_notes/data/cli/claude.yaml +25 -0
  41. agent_notes/data/cli/copilot.yaml +18 -0
  42. agent_notes/data/cli/opencode.yaml +22 -0
  43. agent_notes/data/commands/brainstorm.md +8 -0
  44. agent_notes/data/commands/debug.md +9 -0
  45. agent_notes/data/commands/review.md +10 -0
  46. agent_notes/data/global-claude.md +290 -0
  47. agent_notes/data/global-copilot.md +27 -0
  48. agent_notes/data/global-opencode.md +40 -0
  49. agent_notes/data/hooks/session-context.md.tpl +19 -0
  50. agent_notes/data/models/claude-haiku-4-5.yaml +15 -0
  51. agent_notes/data/models/claude-opus-4-1.yaml +16 -0
  52. agent_notes/data/models/claude-opus-4-5.yaml +16 -0
  53. agent_notes/data/models/claude-opus-4-6.yaml +16 -0
  54. agent_notes/data/models/claude-opus-4-7.yaml +15 -0
  55. agent_notes/data/models/claude-sonnet-4-5.yaml +16 -0
  56. agent_notes/data/models/claude-sonnet-4-6.yaml +15 -0
  57. agent_notes/data/models/claude-sonnet-4.yaml +16 -0
  58. agent_notes/data/pricing.yaml +33 -0
  59. agent_notes/data/roles/orchestrator.yaml +5 -0
  60. agent_notes/data/roles/reasoner.yaml +5 -0
  61. agent_notes/data/roles/scout.yaml +5 -0
  62. agent_notes/data/roles/worker.yaml +5 -0
  63. agent_notes/data/rules/code-quality.md +9 -0
  64. agent_notes/data/rules/safety.md +10 -0
  65. agent_notes/data/scripts/cost-report +211 -0
  66. agent_notes/data/skills/brainstorming/SKILL.md +57 -0
  67. agent_notes/data/skills/code-review/SKILL.md +64 -0
  68. agent_notes/data/skills/debugging-protocol/SKILL.md +51 -0
  69. agent_notes/data/skills/docker-compose/SKILL.md +318 -0
  70. agent_notes/data/skills/docker-compose-advanced/SKILL.md +575 -0
  71. agent_notes/data/skills/docker-dockerfile/SKILL.md +385 -0
  72. agent_notes/data/skills/docker-dockerfile-languages/SKILL.md +293 -0
  73. agent_notes/data/skills/git/SKILL.md +87 -0
  74. agent_notes/data/skills/rails-active-storage/SKILL.md +321 -0
  75. agent_notes/data/skills/rails-broadcasting/SKILL.md +374 -0
  76. agent_notes/data/skills/rails-concerns/SKILL.md +806 -0
  77. agent_notes/data/skills/rails-controllers/SKILL.md +510 -0
  78. agent_notes/data/skills/rails-controllers-advanced/SKILL.md +441 -0
  79. agent_notes/data/skills/rails-helpers/SKILL.md +677 -0
  80. agent_notes/data/skills/rails-initializers/SKILL.md +79 -0
  81. agent_notes/data/skills/rails-javascript/SKILL.md +567 -0
  82. agent_notes/data/skills/rails-jobs/SKILL.md +700 -0
  83. agent_notes/data/skills/rails-kamal/SKILL.md +483 -0
  84. agent_notes/data/skills/rails-lib/SKILL.md +101 -0
  85. agent_notes/data/skills/rails-mailers/SKILL.md +321 -0
  86. agent_notes/data/skills/rails-migrations/SKILL.md +268 -0
  87. agent_notes/data/skills/rails-models/SKILL.md +459 -0
  88. agent_notes/data/skills/rails-models-advanced/SKILL.md +398 -0
  89. agent_notes/data/skills/rails-routes/SKILL.md +804 -0
  90. agent_notes/data/skills/rails-style/SKILL.md +538 -0
  91. agent_notes/data/skills/rails-testing-controllers/SKILL.md +343 -0
  92. agent_notes/data/skills/rails-testing-models/SKILL.md +296 -0
  93. agent_notes/data/skills/rails-testing-system/SKILL.md +375 -0
  94. agent_notes/data/skills/rails-validations/SKILL.md +108 -0
  95. agent_notes/data/skills/rails-view-components/SKILL.md +511 -0
  96. agent_notes/data/skills/rails-view-components-advanced/SKILL.md +376 -0
  97. agent_notes/data/skills/rails-views/SKILL.md +413 -0
  98. agent_notes/data/skills/rails-views-advanced/SKILL.md +450 -0
  99. agent_notes/data/skills/refactoring-protocol/SKILL.md +64 -0
  100. agent_notes/data/skills/tdd/SKILL.md +57 -0
  101. agent_notes/data/templates/__init__.py +1 -0
  102. agent_notes/data/templates/__pycache__/__init__.cpython-314.pyc +0 -0
  103. agent_notes/data/templates/frontmatter/__init__.py +1 -0
  104. agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-314.pyc +0 -0
  105. agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-314.pyc +0 -0
  106. agent_notes/data/templates/frontmatter/__pycache__/cursor.cpython-314.pyc +0 -0
  107. agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
  108. agent_notes/data/templates/frontmatter/claude.py +44 -0
  109. agent_notes/data/templates/frontmatter/opencode.py +104 -0
  110. agent_notes/doctor_checks.py +189 -0
  111. agent_notes/domain/__init__.py +17 -0
  112. agent_notes/domain/agent.py +34 -0
  113. agent_notes/domain/cli_backend.py +40 -0
  114. agent_notes/domain/diagnostics.py +29 -0
  115. agent_notes/domain/diff.py +44 -0
  116. agent_notes/domain/model.py +27 -0
  117. agent_notes/domain/role.py +13 -0
  118. agent_notes/domain/rule.py +13 -0
  119. agent_notes/domain/skill.py +15 -0
  120. agent_notes/domain/state.py +46 -0
  121. agent_notes/install_state.py +11 -0
  122. agent_notes/registries/__init__.py +16 -0
  123. agent_notes/registries/_base.py +46 -0
  124. agent_notes/registries/agent_registry.py +107 -0
  125. agent_notes/registries/cli_registry.py +89 -0
  126. agent_notes/registries/model_registry.py +85 -0
  127. agent_notes/registries/role_registry.py +64 -0
  128. agent_notes/registries/rule_registry.py +80 -0
  129. agent_notes/registries/skill_registry.py +141 -0
  130. agent_notes/services/__init__.py +8 -0
  131. agent_notes/services/diagnostics/__init__.py +47 -0
  132. agent_notes/services/diagnostics/_checks.py +272 -0
  133. agent_notes/services/diagnostics/_display.py +346 -0
  134. agent_notes/services/diagnostics/_fix.py +169 -0
  135. agent_notes/services/diff.py +349 -0
  136. agent_notes/services/fs.py +195 -0
  137. agent_notes/services/install_state_builder.py +210 -0
  138. agent_notes/services/installer.py +293 -0
  139. agent_notes/services/memory_backend.py +155 -0
  140. agent_notes/services/rendering.py +329 -0
  141. agent_notes/services/session_context.py +23 -0
  142. agent_notes/services/settings_writer.py +79 -0
  143. agent_notes/services/state_store.py +249 -0
  144. agent_notes/services/ui.py +419 -0
  145. agent_notes/services/user_config.py +62 -0
  146. agent_notes/services/validation.py +67 -0
  147. agent_notes/state.py +21 -0
  148. agent_notes-2.0.4.dist-info/METADATA +14 -0
  149. agent_notes-2.0.4.dist-info/RECORD +162 -0
  150. agent_notes-2.0.4.dist-info/WHEEL +5 -0
  151. agent_notes-2.0.4.dist-info/entry_points.txt +2 -0
  152. agent_notes-2.0.4.dist-info/licenses/LICENSE +21 -0
  153. agent_notes-2.0.4.dist-info/top_level.txt +2 -0
  154. tests/conftest.py +20 -0
  155. tests/functional/__init__.py +0 -0
  156. tests/functional/test_build_commands.py +88 -0
  157. tests/functional/test_registries.py +128 -0
  158. tests/integration/__init__.py +0 -0
  159. tests/integration/test_build_output.py +129 -0
  160. tests/plugins/__init__.py +0 -0
  161. tests/plugins/test_agents.py +93 -0
  162. tests/plugins/test_skills.py +77 -0
@@ -0,0 +1,170 @@
1
+ """Build agent configuration files from source."""
2
+
3
+ import yaml
4
+ import shutil
5
+ from pathlib import Path
6
+
7
+ from ..config import (
8
+ AGENTS_YAML,
9
+ DIST_DIR,
10
+ info
11
+ )
12
+ from ..services.rendering import generate_agent_files, render_globals, load_agents_config
13
+
14
+
15
+ # Re-export for backward compatibility
16
+ def _load_frontmatter_template(template_name):
17
+ """DEPRECATED: Use services.rendering._load_frontmatter_template instead."""
18
+ from ..services.rendering import _load_frontmatter_template
19
+ return _load_frontmatter_template(template_name)
20
+ def copy_global_files() -> list[Path]:
21
+ """Copy global files and rules to destination."""
22
+ from ..config import RULES_DIR, DIST_RULES_DIR
23
+
24
+ copied_files = []
25
+
26
+ # Use the rendering service for global files
27
+ copied_files.extend(render_globals())
28
+
29
+ # Copy all rules files
30
+ if RULES_DIR.exists():
31
+ # Build rule files
32
+ DIST_RULES_DIR.mkdir(parents=True, exist_ok=True)
33
+ for rule_file in RULES_DIR.glob('*.md'):
34
+ dest_file = DIST_RULES_DIR / rule_file.name
35
+ shutil.copy2(rule_file, dest_file)
36
+ copied_files.append(dest_file)
37
+
38
+ return copied_files
39
+
40
+
41
+ def copy_skills() -> list[Path]:
42
+ """Copy skill directories to dist/skills/."""
43
+ from ..config import find_skill_dirs
44
+
45
+ dist_skills = DIST_DIR / "skills"
46
+ # Clean and recreate
47
+ if dist_skills.exists():
48
+ shutil.rmtree(dist_skills)
49
+ dist_skills.mkdir(parents=True, exist_ok=True)
50
+
51
+ copied = []
52
+ for skill_dir in find_skill_dirs():
53
+ dest = dist_skills / skill_dir.name
54
+ shutil.copytree(skill_dir, dest)
55
+ copied.append(dest)
56
+ return copied
57
+
58
+
59
+ def copy_scripts() -> list[Path]:
60
+ """Copy script files to dist/scripts/, embedding pricing data from pricing.yaml."""
61
+ import json
62
+ from ..config import SCRIPTS_DIR, DIST_SCRIPTS_DIR, DATA_DIR
63
+
64
+ if not SCRIPTS_DIR.exists():
65
+ return []
66
+ if DIST_SCRIPTS_DIR.exists():
67
+ shutil.rmtree(DIST_SCRIPTS_DIR)
68
+ DIST_SCRIPTS_DIR.mkdir(parents=True, exist_ok=True)
69
+
70
+ pricing_path = DATA_DIR / "pricing.yaml"
71
+ pricing_json = "null"
72
+ if pricing_path.exists():
73
+ pricing_json = json.dumps(yaml.safe_load(pricing_path.read_text()), indent=2)
74
+
75
+ copied = []
76
+ for script in SCRIPTS_DIR.iterdir():
77
+ if script.is_file():
78
+ content = script.read_text()
79
+ if "{{PRICING}}" in content:
80
+ content = content.replace("{{PRICING}}", pricing_json)
81
+ dest = DIST_SCRIPTS_DIR / script.name
82
+ dest.write_text(content)
83
+ dest.chmod(0o755)
84
+ copied.append(dest)
85
+ return copied
86
+
87
+
88
+ def copy_commands() -> list[Path]:
89
+ """Copy command files from data/commands/ to dist/claude/commands/."""
90
+ from ..config import DATA_DIR, DIST_DIR
91
+ src = DATA_DIR / "commands"
92
+ if not src.exists():
93
+ return []
94
+ dest = DIST_DIR / "claude" / "commands"
95
+ if dest.exists():
96
+ shutil.rmtree(dest)
97
+ dest.mkdir(parents=True, exist_ok=True)
98
+ copied = []
99
+ for f in src.glob("*.md"):
100
+ out = dest / f.name
101
+ shutil.copy2(f, out)
102
+ copied.append(out)
103
+ return copied
104
+
105
+
106
+ def count_lines(file_path: Path) -> int:
107
+ """Count lines in a file or all files within a directory."""
108
+ try:
109
+ if file_path.is_dir():
110
+ return sum(
111
+ len(f.read_text().splitlines())
112
+ for f in file_path.rglob("*")
113
+ if f.is_file()
114
+ )
115
+ return len(file_path.read_text().splitlines())
116
+ except Exception:
117
+ return 0
118
+
119
+
120
+ def build() -> None:
121
+ """Build agent configuration files from source."""
122
+ from .. import state as state_module
123
+ from ..config import ROOT
124
+
125
+ # Read configuration
126
+ try:
127
+ agents_config, tiers = load_agents_config()
128
+ except FileNotFoundError as e:
129
+ print(f"Error: {e}")
130
+ return
131
+
132
+ # Load state if present (no error if missing)
133
+ state = state_module.load()
134
+
135
+ # Generate agent files (state=None is backward compatible)
136
+ print("Generating agent files...")
137
+ agent_files = generate_agent_files(agents_config, tiers, state=state)
138
+
139
+ # Copy global files
140
+ print("Copying global files...")
141
+ global_files = copy_global_files()
142
+
143
+ # Copy skills
144
+ print("Copying skills...")
145
+ skill_files = copy_skills()
146
+
147
+ # Copy scripts
148
+ print("Copying scripts...")
149
+ script_files = copy_scripts()
150
+
151
+ # Copy commands
152
+ print("Copying commands...")
153
+ command_files = copy_commands()
154
+
155
+ # Report results
156
+ all_files = agent_files + global_files + skill_files + script_files + command_files
157
+ print(f"\nGenerated {len(all_files)} files:")
158
+
159
+ total_lines = 0
160
+ for file_path in sorted(all_files):
161
+ rel_path = file_path.relative_to(ROOT)
162
+ lines = count_lines(file_path)
163
+ total_lines += lines
164
+ print(f" {rel_path} ({lines} lines)")
165
+
166
+ print(f"\nTotal: {total_lines} lines across {len(all_files)} files")
167
+
168
+
169
+ if __name__ == '__main__':
170
+ build()
@@ -0,0 +1,112 @@
1
+ """Health check for agent-notes installation."""
2
+
3
+ # Re-export for backward compatibility. New code should import from agent_notes.domain.
4
+ from ..domain.diagnostics import Issue, FixAction # noqa: F401
5
+
6
+ # Re-export config constants that tests mock
7
+ from ..config import (
8
+ ROOT, DIST_CLAUDE_DIR, DIST_OPENCODE_DIR, DIST_GITHUB_DIR, DIST_RULES_DIR,
9
+ DIST_SKILLS_DIR, DIST_SCRIPTS_DIR, SCRIPTS_DIR, BIN_HOME,
10
+ CLAUDE_HOME, OPENCODE_HOME, GITHUB_HOME, AGENTS_HOME,
11
+ Color, info, issue, ok, warn, fail
12
+ )
13
+
14
+ # Re-export all diagnostic functions from services
15
+ from ..services.diagnostics import (
16
+ check_stale_files,
17
+ check_broken_symlinks,
18
+ check_shadowed_files,
19
+ check_missing_files,
20
+ check_content_drift,
21
+ check_build_freshness,
22
+ count_stale,
23
+ print_summary,
24
+ print_issues,
25
+ do_fix,
26
+ _check_role_models,
27
+ _find_dist_source,
28
+ _count_agents,
29
+ _count_skills,
30
+ _count_scripts,
31
+ _count_rules,
32
+ _check_config,
33
+ _cli_base_dir,
34
+ _print_status
35
+ )
36
+
37
+ # Re-export filesystem utilities from services/fs for backward compatibility
38
+ from ..services.fs import (
39
+ resolve_symlink,
40
+ symlink_target_exists,
41
+ files_differ
42
+ )
43
+
44
+ def _check_session_hook(scope: str, issues: list) -> None:
45
+ """Check that the Claude Code SessionStart hook is registered in settings.json."""
46
+ from ..services.settings_writer import has_hook
47
+ from ..services.installer import _session_hook_paths
48
+ from ..domain.diagnostics import Issue
49
+
50
+ try:
51
+ from ..registries.cli_registry import load_registry
52
+ registry = load_registry()
53
+ claude_backend = registry.get("claude")
54
+ except KeyError:
55
+ return
56
+
57
+ settings_path, _context_file, hook_command = _session_hook_paths(claude_backend, scope)
58
+ if not has_hook(settings_path, "SessionStart", hook_command):
59
+ issues.append(Issue(
60
+ "missing_hook",
61
+ str(settings_path),
62
+ "SessionStart hook not found — run: agent-notes install to re-add the hook",
63
+ ))
64
+
65
+
66
+ def diagnose(scope: str, fix: bool = False) -> bool:
67
+ """Run all diagnostic checks and optionally apply fixes."""
68
+ from .. import install_state
69
+
70
+ print_summary(scope)
71
+
72
+ issues = []
73
+ fix_actions = []
74
+
75
+ # Run checks
76
+ check_stale_files(scope, issues, fix_actions)
77
+ check_broken_symlinks(scope, issues, fix_actions)
78
+ check_shadowed_files(scope, issues, fix_actions)
79
+ check_missing_files(scope, issues, fix_actions)
80
+ check_content_drift(scope, issues, fix_actions)
81
+
82
+ # Build freshness check (scope-independent)
83
+ check_build_freshness(issues, fix_actions)
84
+
85
+ # SessionStart hook check (Claude Code only)
86
+ _check_session_hook(scope, issues)
87
+
88
+ # Print role→model assignments
89
+ state = install_state.load_current_state()
90
+ if state is not None:
91
+ _check_role_models(state)
92
+
93
+ # Print issues and optionally fix
94
+ if print_issues(issues):
95
+ return True # No issues
96
+
97
+ if fix:
98
+ result = do_fix(issues, fix_actions)
99
+ # Services layer flagged that an install is needed — invoke it here
100
+ # (commands layer), avoiding a services→commands dependency.
101
+ if any(a.action == "_TRIGGER_INSTALL" for a in fix_actions):
102
+ from ..commands.install import install
103
+ install()
104
+ return result
105
+ else:
106
+ return False # Issues found but not fixed
107
+
108
+ # Alias for backward compatibility with CLI
109
+ def doctor(local: bool = False, fix: bool = False):
110
+ """Main doctor function called by CLI."""
111
+ scope = "local" if local else "global"
112
+ return diagnose(scope, fix)
@@ -0,0 +1,95 @@
1
+ """Info command - show installation status and component counts."""
2
+
3
+ from pathlib import Path
4
+
5
+ from .. import install_state
6
+ from ..config import get_version, CLAUDE_HOME, Color
7
+ from ._install_helpers import count_scripts, count_skills, count_agents, count_global
8
+
9
+
10
+ def show_info() -> None:
11
+ """Show installation status and component counts."""
12
+ from ..registries.cli_registry import load_registry
13
+
14
+ version = get_version()
15
+ print(f"agent-notes {version}")
16
+ print("")
17
+ print("Components:")
18
+ print(f" Scripts: {count_scripts()}")
19
+ print(f" Skills: {count_skills()}")
20
+
21
+ # Show agent counts per backend
22
+ registry = load_registry()
23
+ for backend in registry.all():
24
+ if backend.supports("agents"):
25
+ agent_count = count_agents(backend)
26
+ print(f" Agents ({backend.label}): {agent_count}")
27
+
28
+ print(f" Global config: {count_global()} files")
29
+ print("")
30
+ print("Install targets:")
31
+ print(" Scripts: ~/.local/bin/")
32
+
33
+ # Show CLI install targets
34
+ for backend in registry.all():
35
+ print(f" {backend.label}: {backend.global_home}")
36
+
37
+ print(" Universal: ~/.agents/")
38
+ print("")
39
+
40
+ # Check install status
41
+ global_ok = (CLAUDE_HOME / "agents").exists() and any((CLAUDE_HOME / "agents").iterdir())
42
+ local_ok = Path(".claude/agents").exists() and any(Path(".claude/agents").iterdir())
43
+
44
+ print("Status:")
45
+ if global_ok:
46
+ print(f" Global: {Color.GREEN}installed{Color.NC} (use doctor for details)")
47
+ else:
48
+ print(f" Global: {Color.YELLOW}not installed{Color.NC}")
49
+ if local_ok:
50
+ print(f" Local: {Color.GREEN}detected{Color.NC}")
51
+ else:
52
+ print(f" Local: {Color.CYAN}not detected{Color.NC}")
53
+
54
+ # State info
55
+ st = install_state.load_current_state()
56
+ if st is not None:
57
+ print("")
58
+ print("Last install:")
59
+
60
+ # Global install
61
+ if st.global_install:
62
+ gs = st.global_install
63
+ print(f" Global: installed {gs.installed_at}, {gs.mode}")
64
+ # Count backends and items
65
+ if gs.clis:
66
+ backend_summaries = []
67
+ for backend_name, bs in sorted(gs.clis.items()):
68
+ counts = []
69
+ for component_type, items in bs.installed.items():
70
+ if items:
71
+ counts.append(f"{len(items)} {component_type}")
72
+ if counts:
73
+ backend_summaries.append(f"{backend_name} ({', '.join(counts)})")
74
+ if backend_summaries:
75
+ print(f" Backends: {', '.join(backend_summaries)}")
76
+ else:
77
+ print(" Global: none")
78
+
79
+ # Local installs
80
+ if st.local_installs:
81
+ for project_path, ls in sorted(st.local_installs.items()):
82
+ print(f" Local: {project_path} (installed {ls.installed_at}, {ls.mode})")
83
+ if ls.clis:
84
+ backend_summaries = []
85
+ for backend_name, bs in sorted(ls.clis.items()):
86
+ counts = []
87
+ for component_type, items in bs.installed.items():
88
+ if items:
89
+ counts.append(f"{len(items)} {component_type}")
90
+ if counts:
91
+ backend_summaries.append(f"{backend_name} ({', '.join(counts)})")
92
+ if backend_summaries:
93
+ print(f" Backends: {', '.join(backend_summaries)}")
94
+ else:
95
+ print(" Local: none")
@@ -0,0 +1,99 @@
1
+ """Install command."""
2
+
3
+ from pathlib import Path
4
+
5
+ from ..config import Color, PKG_DIR
6
+ from .. import install_state
7
+ from ._install_helpers import _verify_install
8
+
9
+
10
+ def install(local: bool = False, copy: bool = False, reconfigure: bool = False) -> None:
11
+ """Build from source and install to targets."""
12
+ from ..state import get_scope, state_file
13
+ from pathlib import Path
14
+
15
+ scope = "local" if local else "global"
16
+ project_path = Path.cwd().resolve() if local else None
17
+
18
+ state = install_state.load_current_state()
19
+ existing = get_scope(state, scope, project_path) if state else None
20
+
21
+ if existing and not reconfigure:
22
+ # Print existing-install summary
23
+ print(f"Found existing {scope} installation at {state_file()}")
24
+ print(f" Installed: {existing.installed_at}")
25
+ cli_labels = []
26
+ from ..registries.cli_registry import load_registry
27
+ registry = load_registry()
28
+ for cli_name in existing.clis.keys():
29
+ try:
30
+ cli_labels.append(registry.get(cli_name).label)
31
+ except KeyError:
32
+ cli_labels.append(cli_name)
33
+ print(f" CLIs: {', '.join(cli_labels)}")
34
+ print(f" Mode: {existing.mode}")
35
+ print()
36
+ print("Verifying ...")
37
+ # Run verification. Use doctor_checks or a new helper.
38
+ issues = _verify_install(existing, scope, project_path, registry)
39
+ if not issues:
40
+ print()
41
+ print("Installation is healthy.")
42
+ print()
43
+ print("Tip: To reinstall with different choices, run:")
44
+ print(" agent-notes uninstall")
45
+ print(" agent-notes install")
46
+ print()
47
+ print(" Or to re-run the wizard and overwrite in place:")
48
+ print(" agent-notes install --reconfigure")
49
+ else:
50
+ print()
51
+ print(f"Installation has {len(issues)} issue(s).")
52
+ print()
53
+ print("Tip: Run `agent-notes doctor --fix` to repair, or `agent-notes install --reconfigure` to rewizard.")
54
+ return
55
+
56
+ if existing and reconfigure:
57
+ print(f"Clearing existing {scope} state (--reconfigure) ...")
58
+ install_state.remove_install_state(scope, project_path)
59
+ # Fall through to normal install flow
60
+
61
+ # Validate args
62
+ if copy and not local:
63
+ print("Error: --copy is only valid with --local installs.")
64
+ print("Global installs always use symlinks.")
65
+ return
66
+
67
+ # Build first
68
+ print("Building from source...")
69
+ try:
70
+ from ..commands.build import build
71
+ build()
72
+ except Exception as e:
73
+ print(f"{Color.RED}Build failed: {e}{Color.NC}")
74
+ return
75
+
76
+ # Execute
77
+ print(f"Installing ({'local' if local else 'global'}, {'copy' if copy else 'symlink'}) ...")
78
+ print("")
79
+
80
+ from ..services import installer
81
+ scope = "local" if local else "global"
82
+ copy_mode = copy
83
+ installer.install_all(scope, copy_mode)
84
+
85
+ print("")
86
+ print(f"{Color.GREEN}Done.{Color.NC} Restart Claude Code / OpenCode to pick up changes.")
87
+
88
+ # Record state
89
+ try:
90
+ project_path = Path.cwd() if local else None
91
+ st = install_state.build_install_state(
92
+ mode="copy" if copy else "symlink",
93
+ scope="local" if local else "global",
94
+ repo_root=PKG_DIR.parent, # repo root (parent of agent_notes pkg)
95
+ project_path=project_path,
96
+ )
97
+ install_state.record_install_state(st)
98
+ except Exception as e:
99
+ print(f"{Color.YELLOW}Warning: failed to write state.json: {e}{Color.NC}")
@@ -0,0 +1,169 @@
1
+ """List installed components."""
2
+
3
+ import yaml
4
+ from pathlib import Path
5
+ from typing import Dict, Any, Optional
6
+
7
+ from ..registries import default_skill_registry, default_rule_registry
8
+ from ..config import Color, DATA_DIR, find_skill_dirs
9
+
10
+
11
+ def list_clis() -> None:
12
+ """Print all registered CLIs."""
13
+ from ..registries.cli_registry import load_registry
14
+ registry = load_registry()
15
+ backends = sorted(registry.all(), key=lambda b: b.name)
16
+ print(f"CLIs ({len(backends)}):")
17
+ for b in backends:
18
+ # Pad name to ~10 chars, label to ~20 chars
19
+ print(f" {b.name:<10} {b.label:<25} → {b.global_home}")
20
+
21
+
22
+ def list_models() -> None:
23
+ """Print all available models with CLI compatibility."""
24
+ from ..registries.model_registry import load_model_registry
25
+ from ..registries.cli_registry import load_registry
26
+ models = load_model_registry().all()
27
+ registry = load_registry()
28
+ print(f"Models ({len(models)}):")
29
+ for m in sorted(models, key=lambda m: m.id):
30
+ compat = [b.name for b in registry.all() if b.first_alias_for(m.aliases) is not None]
31
+ label = f" {m.label}" if hasattr(m, 'label') and m.label else ""
32
+ model_class = f" [{m.model_class}]" if hasattr(m, 'model_class') and m.model_class else ""
33
+ if compat:
34
+ compat_str = ", ".join(compat)
35
+ print(f" {m.id:<22}{label:<22}{model_class:<10} compatible: {compat_str}")
36
+ else:
37
+ print(f" {m.id:<22}{label:<22}{model_class:<10} compatible: (none)")
38
+
39
+
40
+ def list_roles() -> None:
41
+ """Print all roles."""
42
+ from ..registries.role_registry import load_role_registry
43
+ roles = load_role_registry().all()
44
+ print(f"Roles ({len(roles)}):")
45
+ for r in sorted(roles, key=lambda r: r.name):
46
+ typical = f" (typical: {r.typical_class})" if hasattr(r, 'typical_class') and r.typical_class else ""
47
+ print(f" {r.name:<15} {r.description}{typical}")
48
+
49
+
50
+ def list_agents() -> None:
51
+ """List all agents with metadata from YAML."""
52
+ print(f"{Color.CYAN}Agents:{Color.NC}")
53
+ source_agents_dir = DATA_DIR / "agents"
54
+ agents_yaml = DATA_DIR / "agents" / "agents.yaml"
55
+
56
+ # Load agents metadata from YAML if available
57
+ agents_metadata: Dict[str, Dict[str, Any]] = {}
58
+ if agents_yaml.exists():
59
+ try:
60
+ with open(agents_yaml, 'r') as f:
61
+ yaml_data = yaml.safe_load(f)
62
+ if yaml_data and 'agents' in yaml_data:
63
+ agents_metadata = yaml_data['agents']
64
+ except (yaml.YAMLError, FileNotFoundError):
65
+ pass
66
+
67
+ if source_agents_dir.exists():
68
+ for f in sorted(source_agents_dir.glob("*.md")):
69
+ name = f.stem
70
+
71
+ if name in agents_metadata:
72
+ role = agents_metadata[name].get('role', agents_metadata[name].get('tier', '')) # role or fallback to tier
73
+ description = agents_metadata[name].get('description', '')
74
+ print(f" {name:<22} {Color.DIM}({role:<8}){Color.NC} {description}")
75
+ else:
76
+ print(f" {name}")
77
+
78
+ print("")
79
+
80
+ def list_skills() -> None:
81
+ """List all skills."""
82
+ print(f"{Color.CYAN}Skills:{Color.NC}")
83
+
84
+ skill_dirs = find_skill_dirs()
85
+ if skill_dirs:
86
+ for skill_path in sorted(skill_dirs):
87
+ print(f" {skill_path.name}")
88
+ else:
89
+ # If no skills found via find_skill_dirs, try the registry
90
+ try:
91
+ registry = default_skill_registry()
92
+ skills = registry.all()
93
+ if skills:
94
+ for skill in sorted(skills, key=lambda s: s.name):
95
+ print(f" {skill.name}")
96
+ except Exception:
97
+ pass # Ignore registry errors
98
+
99
+ print("")
100
+
101
+
102
+ def list_rules() -> None:
103
+ """List all rules and global configs."""
104
+ print(f"{Color.CYAN}Rules:{Color.NC}")
105
+
106
+ # Try registry first, but fall back to filesystem if needed for testing
107
+ try:
108
+ registry = default_rule_registry()
109
+ rules = registry.all()
110
+
111
+ # If we get an empty registry but there are files in DATA_DIR/rules, use fallback
112
+ if not rules:
113
+ source_rules_dir = DATA_DIR / "rules"
114
+ if source_rules_dir.exists() and list(source_rules_dir.glob("*.md")):
115
+ # Use fallback
116
+ for f in sorted(source_rules_dir.glob("*.md")):
117
+ print(f" {f.stem}")
118
+ # else: truly no rules
119
+ else:
120
+ for rule in sorted(rules, key=lambda r: r.name):
121
+ print(f" {rule.name}")
122
+ except Exception:
123
+ # Fallback to old behavior if registry fails
124
+ source_rules_dir = DATA_DIR / "rules"
125
+ if source_rules_dir.exists():
126
+ for f in sorted(source_rules_dir.glob("*.md")):
127
+ print(f" {f.stem}")
128
+
129
+ print("")
130
+
131
+ print(f"{Color.CYAN}Global configs:{Color.NC}")
132
+ from ..registries.cli_registry import load_registry
133
+ registry = load_registry()
134
+ for backend in registry.all():
135
+ if backend.global_template:
136
+ print(f" {backend.global_template}")
137
+ print("")
138
+
139
+ def list_all() -> None:
140
+ """Show grouped summary of everything."""
141
+ list_clis()
142
+ print()
143
+ list_models()
144
+ print()
145
+ list_roles()
146
+ print()
147
+ list_agents()
148
+ list_skills()
149
+ list_rules()
150
+
151
+
152
+ def list_components(filter_type: str = "all") -> None:
153
+ """List installed components."""
154
+ dispatch = {
155
+ "agents": list_agents,
156
+ "skills": list_skills,
157
+ "rules": list_rules,
158
+ "clis": list_clis,
159
+ "models": list_models,
160
+ "roles": list_roles,
161
+ "all": list_all,
162
+ }
163
+
164
+ if filter_type in dispatch:
165
+ dispatch[filter_type]()
166
+ else:
167
+ print(f"Unknown filter: {filter_type}")
168
+ print("Usage: agent-notes list [agents|skills|rules|clis|models|roles|all]")
169
+ exit(1)