universal-agent-context 0.2.0__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 (47) hide show
  1. uacs/__init__.py +12 -0
  2. uacs/adapters/__init__.py +19 -0
  3. uacs/adapters/agent_skill_adapter.py +202 -0
  4. uacs/adapters/agents_md_adapter.py +330 -0
  5. uacs/adapters/base.py +261 -0
  6. uacs/adapters/clinerules_adapter.py +39 -0
  7. uacs/adapters/cursorrules_adapter.py +39 -0
  8. uacs/api.py +262 -0
  9. uacs/cli/__init__.py +6 -0
  10. uacs/cli/context.py +349 -0
  11. uacs/cli/main.py +195 -0
  12. uacs/cli/mcp.py +115 -0
  13. uacs/cli/memory.py +142 -0
  14. uacs/cli/packages.py +309 -0
  15. uacs/cli/skills.py +144 -0
  16. uacs/cli/utils.py +24 -0
  17. uacs/config/repositories.yaml +26 -0
  18. uacs/context/__init__.py +0 -0
  19. uacs/context/agent_context.py +406 -0
  20. uacs/context/shared_context.py +661 -0
  21. uacs/context/unified_context.py +332 -0
  22. uacs/mcp_server_entry.py +80 -0
  23. uacs/memory/__init__.py +5 -0
  24. uacs/memory/simple_memory.py +255 -0
  25. uacs/packages/__init__.py +26 -0
  26. uacs/packages/manager.py +413 -0
  27. uacs/packages/models.py +60 -0
  28. uacs/packages/sources.py +270 -0
  29. uacs/protocols/__init__.py +5 -0
  30. uacs/protocols/mcp/__init__.py +8 -0
  31. uacs/protocols/mcp/manager.py +77 -0
  32. uacs/protocols/mcp/skills_server.py +700 -0
  33. uacs/skills_validator.py +367 -0
  34. uacs/utils/__init__.py +5 -0
  35. uacs/utils/paths.py +24 -0
  36. uacs/visualization/README.md +132 -0
  37. uacs/visualization/__init__.py +36 -0
  38. uacs/visualization/models.py +195 -0
  39. uacs/visualization/static/index.html +857 -0
  40. uacs/visualization/storage.py +402 -0
  41. uacs/visualization/visualization.py +328 -0
  42. uacs/visualization/web_server.py +364 -0
  43. universal_agent_context-0.2.0.dist-info/METADATA +873 -0
  44. universal_agent_context-0.2.0.dist-info/RECORD +47 -0
  45. universal_agent_context-0.2.0.dist-info/WHEEL +4 -0
  46. universal_agent_context-0.2.0.dist-info/entry_points.txt +2 -0
  47. universal_agent_context-0.2.0.dist-info/licenses/LICENSE +21 -0
uacs/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ """Universal Agent Context System (UACS)
2
+
3
+ Provides unified context management for AI agents:
4
+ - Marketplace discovery (Skills + MCP)
5
+ - Format adapters (Agent Skills, AGENTS.md, etc.)
6
+ - Context management (shared memory + compression)
7
+ """
8
+
9
+ from uacs.api import UACS
10
+
11
+ __version__ = "0.2.0"
12
+ __all__ = ["UACS"]
@@ -0,0 +1,19 @@
1
+ """Format adapters for various instruction formats."""
2
+
3
+ from .agent_skill_adapter import AgentSkillAdapter
4
+ from .agents_md_adapter import AgentsMDAdapter, AgentsMDConfig, AgentsMDSection
5
+ from .base import BaseFormatAdapter, FormatAdapterRegistry, ParsedContent
6
+ from .clinerules_adapter import ClineRulesAdapter
7
+ from .cursorrules_adapter import CursorRulesAdapter
8
+
9
+ __all__ = [
10
+ "AgentSkillAdapter",
11
+ "AgentsMDAdapter",
12
+ "AgentsMDConfig",
13
+ "AgentsMDSection",
14
+ "BaseFormatAdapter",
15
+ "ClineRulesAdapter",
16
+ "CursorRulesAdapter",
17
+ "FormatAdapterRegistry",
18
+ "ParsedContent",
19
+ ]
@@ -0,0 +1,202 @@
1
+ """Adapter for Agent Skills SKILL.md format (individual skill files).
2
+
3
+ Supports both .agent/ (recommended) and .claude/ (for importing external repos) directories.
4
+ See https://agentskills.io for the vendor-neutral format specification.
5
+ """
6
+
7
+ import re
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from .base import BaseFormatAdapter, FormatAdapterRegistry, ParsedContent
12
+
13
+
14
+ @FormatAdapterRegistry.register
15
+ class AgentSkillAdapter(BaseFormatAdapter):
16
+ """Agent Skills SKILL.md format adapter.
17
+
18
+ Supports the vendor-neutral Agent Skills format with YAML frontmatter:
19
+ ```
20
+ ---
21
+ name: skill-name
22
+ description: Brief description
23
+ ---
24
+ # skill-name
25
+
26
+ ## Instructions
27
+ ...
28
+ ```
29
+
30
+ See https://agentskills.io for full specification.
31
+ """
32
+
33
+ FORMAT_NAME = "agent_skill"
34
+ SUPPORTED_FILES = ["SKILL.md"]
35
+
36
+ source_directory: str | None = None
37
+
38
+ def parse(self, content: str) -> ParsedContent:
39
+ """Parse Claude Code SKILL.md format.
40
+
41
+ Args:
42
+ content: Raw SKILL.md content
43
+
44
+ Returns:
45
+ ParsedContent with skill metadata and instructions
46
+ """
47
+ # Parse YAML frontmatter
48
+ metadata = {}
49
+ instructions = content
50
+
51
+ # Check for YAML frontmatter (--- ... ---)
52
+ yaml_pattern = r"^---\s*\n(.*?)\n---\s*\n(.*)$"
53
+ match = re.match(yaml_pattern, content, re.DOTALL)
54
+
55
+ if match:
56
+ yaml_content = match.group(1)
57
+ instructions = match.group(2)
58
+
59
+ # Parse simple YAML (key: value pairs)
60
+ for line in yaml_content.split("\n"):
61
+ line = line.strip()
62
+ if ":" in line:
63
+ key, value = line.split(":", 1)
64
+ metadata[key.strip()] = value.strip()
65
+
66
+ return ParsedContent(
67
+ name=metadata.get("name", "unnamed-skill"),
68
+ description=metadata.get("description", ""),
69
+ instructions=instructions.strip(),
70
+ metadata=metadata,
71
+ triggers=self._extract_triggers(instructions),
72
+ )
73
+
74
+ def _extract_triggers(self, content: str) -> list[str]:
75
+ """Extract triggers from markdown content."""
76
+ triggers = []
77
+
78
+ # Look for ## Triggers section
79
+ trigger_section = re.search(
80
+ r"##\s+Triggers\s*\n(.*?)(?:\n##|$)", content, re.DOTALL | re.IGNORECASE
81
+ )
82
+ if trigger_section:
83
+ section_content = trigger_section.group(1)
84
+ # Extract list items
85
+ for line in section_content.split("\n"):
86
+ line = line.strip()
87
+ if line.startswith("- "):
88
+ triggers.append(line[2:].strip())
89
+
90
+ return triggers
91
+
92
+ def to_system_prompt(self) -> str:
93
+ """Convert to system prompt.
94
+
95
+ Returns:
96
+ Formatted system prompt
97
+ """
98
+ if not self.parsed:
99
+ return ""
100
+
101
+ prompt_parts = [
102
+ f"# Skill: {self.parsed.name}",
103
+ "",
104
+ f"**Description**: {self.parsed.description}",
105
+ "",
106
+ "## Instructions",
107
+ self.parsed.instructions,
108
+ ]
109
+
110
+ return "\n".join(prompt_parts)
111
+
112
+ def to_adk_capabilities(self) -> dict[str, Any]:
113
+ """Convert to ADK capability format.
114
+
115
+ Returns:
116
+ ADK capability dictionary
117
+ """
118
+ if not self.parsed:
119
+ return {}
120
+
121
+ return {
122
+ "name": self.parsed.name,
123
+ "description": self.parsed.description,
124
+ "instructions": self.parsed.instructions,
125
+ "metadata": self.parsed.metadata,
126
+ }
127
+
128
+ @classmethod
129
+ def discover_skills(cls, project_path: Path) -> list["AgentSkillAdapter"]:
130
+ """Discover all SKILL.md files in project and personal directories.
131
+
132
+ Searches multiple directories with precedence:
133
+ 1. project_path/.agent/skills/*/SKILL.md (highest priority)
134
+ 2. ~/.agent/skills/*/SKILL.md
135
+ 3. project_path/.claude/skills/*/SKILL.md
136
+ 4. ~/.claude/skills/*/SKILL.md (lowest priority)
137
+
138
+ When duplicate skill names exist, .agent/ versions take precedence.
139
+ The .claude/ directory is supported for importing external skill repos.
140
+
141
+ Returns:
142
+ List of skill adapters, deduplicated by skill name
143
+ """
144
+ skills_by_name = {} # Use dict to handle deduplication
145
+
146
+ # Search in precedence order (higher priority first)
147
+ search_paths = [
148
+ project_path / ".agent" / "skills",
149
+ Path.home() / ".agent" / "skills",
150
+ project_path / ".claude" / "skills",
151
+ Path.home() / ".claude" / "skills",
152
+ ]
153
+
154
+ for search_path in search_paths:
155
+ if not search_path.exists():
156
+ continue
157
+
158
+ # Find all SKILL.md files in subdirectories
159
+ for skill_dir in search_path.iterdir():
160
+ if not skill_dir.is_dir():
161
+ continue
162
+
163
+ skill_file = skill_dir / "SKILL.md"
164
+ if skill_file.exists():
165
+ try:
166
+ adapter = cls(skill_file)
167
+ skill_name = (
168
+ adapter.parsed.name if adapter.parsed else skill_dir.name
169
+ )
170
+
171
+ # Only add if not already found (precedence: earlier paths win)
172
+ if skill_name not in skills_by_name:
173
+ # Track source directory for metadata
174
+ adapter.source_directory = str(search_path)
175
+ skills_by_name[skill_name] = adapter
176
+ except Exception as e:
177
+ # Log warning but continue discovering other skills
178
+ print(f"Warning: Failed to load {skill_file}: {e}")
179
+
180
+ return list(skills_by_name.values())
181
+
182
+ def find_skill_by_trigger(self, query: str) -> bool:
183
+ """Check if this skill matches a query trigger.
184
+
185
+ Args:
186
+ query: Query string to match against triggers
187
+
188
+ Returns:
189
+ True if any trigger matches the query
190
+ """
191
+ if not self.parsed or not hasattr(self.parsed, "triggers"):
192
+ return False
193
+
194
+ query_lower = query.lower()
195
+ for trigger in self.parsed.triggers:
196
+ if trigger.lower() in query_lower or query_lower in trigger.lower():
197
+ return True
198
+
199
+ return False
200
+
201
+
202
+ __all__ = ["AgentSkillAdapter"]
@@ -0,0 +1,330 @@
1
+ """Adapter for AGENTS.md specification support.
2
+
3
+ This module parses AGENTS.md files (the open standard for AI agent instructions)
4
+ and converts them to A2A/ADK-compatible agent capabilities. The adapter follows
5
+ the AGENTS.md specification and integrates with the Agent Development Kit (ADK)
6
+ and Agent-to-Agent (A2A) protocol.
7
+
8
+ Spec: https://agents.md/
9
+ """
10
+
11
+ import re
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+
15
+ from .base import BaseFormatAdapter, FormatAdapterRegistry, ParsedContent
16
+
17
+
18
+ @dataclass
19
+ class AgentsMDSection:
20
+ """Represents a parsed section from AGENTS.md.
21
+
22
+ Attributes:
23
+ title: Section title/heading text.
24
+ content: Section body content as markdown.
25
+ level: Heading level (1 for #, 2 for ##, etc.).
26
+ subsections: Nested child sections.
27
+ """
28
+
29
+ title: str
30
+ content: str
31
+ level: int
32
+ subsections: list["AgentsMDSection"]
33
+
34
+
35
+ @dataclass
36
+ class AgentsMDConfig:
37
+ """Parsed AGENTS.md configuration.
38
+
39
+ Attributes:
40
+ project_overview: High-level project description and context.
41
+ setup_commands: Commands for project setup/installation.
42
+ dev_environment_tips: Development environment recommendations.
43
+ code_style: Code style guidelines and conventions.
44
+ build_commands: Commands for building the project.
45
+ testing_instructions: How to run tests and validation.
46
+ security_considerations: Security guidelines and best practices.
47
+ pr_instructions: Pull request guidelines and workflow.
48
+ custom_sections: Additional custom sections not in the standard spec.
49
+ """
50
+
51
+ project_overview: str
52
+ setup_commands: list[str]
53
+ dev_environment_tips: list[str]
54
+ code_style: list[str]
55
+ build_commands: list[str]
56
+ testing_instructions: list[str]
57
+ security_considerations: list[str]
58
+ pr_instructions: list[str]
59
+ custom_sections: dict[str, str]
60
+
61
+
62
+ @FormatAdapterRegistry.register
63
+ class AgentsMDAdapter(BaseFormatAdapter):
64
+ """Adapter for parsing and using AGENTS.md files.
65
+
66
+ This adapter implements support for the AGENTS.md specification, which provides
67
+ a standardized way to document project context and guidelines for AI agents.
68
+ It parses AGENTS.md files and converts them to A2A/ADK-compatible capabilities.
69
+ """
70
+
71
+ FORMAT_NAME = "agents_md"
72
+ SUPPORTED_FILES = ["AGENTS.md"]
73
+
74
+ def __init__(self, file_path: Path | None):
75
+ """Initialize AGENTS.md adapter.
76
+
77
+ Args:
78
+ file_path: Path to AGENTS.md file
79
+ """
80
+ # Call parent init which will parse the content
81
+ super().__init__(file_path)
82
+
83
+ # Store parsed config for backward compatibility
84
+ self.config: AgentsMDConfig | None = None
85
+ if self.parsed:
86
+ self.config = self.parsed.to_dict().get("config")
87
+
88
+ # For backward compatibility
89
+ self.project_root = file_path.parent if file_path else None
90
+ self.agents_md_path = file_path
91
+
92
+ def parse(self, content: str) -> ParsedContent:
93
+ """Parse AGENTS.md content (required by base class).
94
+
95
+ Args:
96
+ content: Raw AGENTS.md content
97
+
98
+ Returns:
99
+ ParsedContent with parsed configuration
100
+ """
101
+ config = self._parse_agents_md_content(content)
102
+ return ParsedContent(config=config)
103
+
104
+ def _parse_agents_md_content(self, content: str) -> AgentsMDConfig:
105
+ """Parse AGENTS.md file into structured config.
106
+
107
+ Extracts standard sections (overview, setup, code style, etc.) and
108
+ custom sections from the AGENTS.md file into a structured configuration.
109
+
110
+ Args:
111
+ content: Raw AGENTS.md content
112
+
113
+ Returns:
114
+ Parsed AgentsMDConfig
115
+ """
116
+
117
+ # Initialize config with defaults
118
+ config = AgentsMDConfig(
119
+ project_overview="",
120
+ setup_commands=[],
121
+ dev_environment_tips=[],
122
+ code_style=[],
123
+ build_commands=[],
124
+ testing_instructions=[],
125
+ security_considerations=[],
126
+ pr_instructions=[],
127
+ custom_sections={},
128
+ )
129
+
130
+ # Parse sections
131
+ sections = self._parse_sections(content)
132
+
133
+ for section in sections:
134
+ title_lower = section.title.lower()
135
+
136
+ if "overview" in title_lower or "project" in title_lower:
137
+ config.project_overview = section.content.strip()
138
+ elif "setup" in title_lower:
139
+ config.setup_commands = self._extract_commands(section.content)
140
+ elif "dev" in title_lower or "environment" in title_lower:
141
+ config.dev_environment_tips = self._extract_bullets(section.content)
142
+ elif "style" in title_lower or "code" in title_lower:
143
+ config.code_style = self._extract_bullets(section.content)
144
+ elif "build" in title_lower:
145
+ config.build_commands = self._extract_commands(section.content)
146
+ elif "test" in title_lower:
147
+ config.testing_instructions = self._extract_bullets(section.content)
148
+ elif "security" in title_lower:
149
+ config.security_considerations = self._extract_bullets(section.content)
150
+ elif "pr" in title_lower or "pull request" in title_lower:
151
+ config.pr_instructions = self._extract_bullets(section.content)
152
+ else:
153
+ # Custom section
154
+ config.custom_sections[section.title] = section.content.strip()
155
+
156
+ return config
157
+
158
+ def _parse_sections(self, content: str) -> list[AgentsMDSection]:
159
+ """Parse markdown sections.
160
+
161
+ Identifies markdown headers (# ## ###) and groups content into
162
+ hierarchical sections with their heading levels preserved.
163
+
164
+ Args:
165
+ content: Markdown content
166
+
167
+ Returns:
168
+ List of parsed sections
169
+ """
170
+ sections = []
171
+ lines = content.split("\n")
172
+ current_section = None
173
+ current_content = []
174
+
175
+ for line in lines:
176
+ # Check for section header
177
+ if line.startswith("#"):
178
+ # Save previous section
179
+ if current_section:
180
+ current_section.content = "\n".join(current_content)
181
+ sections.append(current_section)
182
+
183
+ # Parse new section
184
+ level = len(re.match(r"^#+", line).group())
185
+ title = line.lstrip("#").strip()
186
+
187
+ current_section = AgentsMDSection(
188
+ title=title, content="", level=level, subsections=[]
189
+ )
190
+ current_content = []
191
+ elif current_section:
192
+ current_content.append(line)
193
+
194
+ # Save last section
195
+ if current_section:
196
+ current_section.content = "\n".join(current_content)
197
+ sections.append(current_section)
198
+
199
+ return sections
200
+
201
+ def _extract_commands(self, content: str) -> list[str]:
202
+ """Extract command strings from content.
203
+
204
+ Finds commands in inline code (`command`) and code blocks,
205
+ typically used for setup, build, and test commands.
206
+
207
+ Args:
208
+ content: Section content
209
+
210
+ Returns:
211
+ List of command strings
212
+ """
213
+ commands = []
214
+
215
+ # Extract from code blocks
216
+ code_blocks = re.findall(r"`([^`]+)`", content)
217
+ commands.extend(code_blocks)
218
+
219
+ # Extract from bullets with code
220
+ bullets = re.findall(r"^\s*[-*]\s+.*?`([^`]+)`", content, re.MULTILINE)
221
+ commands.extend(bullets)
222
+
223
+ return commands
224
+
225
+ def _extract_bullets(self, content: str) -> list[str]:
226
+ """Extract bullet points from content.
227
+
228
+ Parses markdown list items (- or *) from the content,
229
+ commonly used for guidelines and recommendations.
230
+
231
+ Args:
232
+ content: Section content
233
+
234
+ Returns:
235
+ List of bullet point strings
236
+ """
237
+ bullets = []
238
+
239
+ for line in content.split("\n"):
240
+ line = line.strip()
241
+ if line.startswith("-") or line.startswith("*"):
242
+ bullets.append(line[1:].strip())
243
+
244
+ return bullets
245
+
246
+ def to_system_prompt(self) -> str:
247
+ """Convert AGENTS.md to system prompt for agents.
248
+
249
+ Returns:
250
+ Formatted system prompt string
251
+ """
252
+ if not self.config:
253
+ return ""
254
+
255
+ prompt_parts = []
256
+
257
+ if self.config.project_overview:
258
+ prompt_parts.append(f"# Project Context\n\n{self.config.project_overview}")
259
+
260
+ if self.config.setup_commands:
261
+ prompt_parts.append("\n## Setup Commands\n")
262
+ for cmd in self.config.setup_commands:
263
+ prompt_parts.append(f"- `{cmd}`")
264
+
265
+ if self.config.code_style:
266
+ prompt_parts.append("\n## Code Style Rules\n")
267
+ for rule in self.config.code_style:
268
+ prompt_parts.append(f"- {rule}")
269
+
270
+ if self.config.build_commands:
271
+ prompt_parts.append("\n## Build Commands\n")
272
+ for cmd in self.config.build_commands:
273
+ prompt_parts.append(f"- `{cmd}`")
274
+
275
+ if self.config.testing_instructions:
276
+ prompt_parts.append("\n## Testing Instructions\n")
277
+ for instruction in self.config.testing_instructions:
278
+ prompt_parts.append(f"- {instruction}")
279
+
280
+ if self.config.pr_instructions:
281
+ prompt_parts.append("\n## PR Guidelines\n")
282
+ for instruction in self.config.pr_instructions:
283
+ prompt_parts.append(f"- {instruction}")
284
+
285
+ return "\n".join(prompt_parts)
286
+
287
+ def to_adk_capabilities(self) -> dict:
288
+ """Convert AGENTS.md to ADK capabilities format.
289
+
290
+ Transforms the parsed AGENTS.md configuration into the capability
291
+ format expected by the Agent Development Kit (ADK) and A2A protocol.
292
+
293
+ Returns:
294
+ ADK capabilities dictionary with project context and guidelines.
295
+ """
296
+ if not self.config:
297
+ return {}
298
+
299
+ return {
300
+ "project_context": self.config.project_overview,
301
+ "setup": self.config.setup_commands,
302
+ "development": self.config.dev_environment_tips,
303
+ "code_style": self.config.code_style,
304
+ "build": self.config.build_commands,
305
+ "testing": self.config.testing_instructions,
306
+ "security": self.config.security_considerations,
307
+ "pr_guidelines": self.config.pr_instructions,
308
+ "custom": self.config.custom_sections,
309
+ }
310
+
311
+ def merge_with_skills(self, skills_prompt: str) -> str:
312
+ """Merge AGENTS.md context with agent skills prompt.
313
+
314
+ Args:
315
+ skills_prompt: Existing skills-based prompt
316
+
317
+ Returns:
318
+ Combined prompt with both contexts
319
+ """
320
+ agents_prompt = self.to_system_prompt()
321
+
322
+ if not agents_prompt:
323
+ return skills_prompt
324
+
325
+ return f"""{agents_prompt}
326
+
327
+ ---
328
+
329
+ {skills_prompt}
330
+ """