agentix-cli 0.1.1__tar.gz → 0.2.0__tar.gz

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 (48) hide show
  1. agentix_cli-0.2.0/.github/workflows/ci.yml +56 -0
  2. {agentix_cli-0.1.1 → agentix_cli-0.2.0}/PKG-INFO +12 -2
  3. {agentix_cli-0.1.1 → agentix_cli-0.2.0}/pyproject.toml +23 -4
  4. {agentix_cli-0.1.1 → agentix_cli-0.2.0}/src/agentix/__init__.py +1 -1
  5. agentix_cli-0.2.0/src/agentix/cli.py +76 -0
  6. agentix_cli-0.2.0/src/agentix/config/__init__.py +1 -0
  7. agentix_cli-0.2.0/src/agentix/config/commands.py +180 -0
  8. agentix_cli-0.2.0/src/agentix/config/manager.py +109 -0
  9. agentix_cli-0.2.0/src/agentix/config/models.py +105 -0
  10. agentix_cli-0.2.0/src/agentix/confluence/__init__.py +1 -0
  11. agentix_cli-0.2.0/src/agentix/confluence/client.py +224 -0
  12. agentix_cli-0.2.0/src/agentix/confluence/commands.py +260 -0
  13. agentix_cli-0.2.0/src/agentix/confluence/models.py +78 -0
  14. agentix_cli-0.2.0/src/agentix/core/__init__.py +1 -0
  15. agentix_cli-0.2.0/src/agentix/core/auth.py +86 -0
  16. agentix_cli-0.2.0/src/agentix/core/exceptions.py +73 -0
  17. agentix_cli-0.2.0/src/agentix/core/http.py +223 -0
  18. agentix_cli-0.2.0/src/agentix/core/output.py +63 -0
  19. agentix_cli-0.2.0/src/agentix/jenkins/__init__.py +1 -0
  20. agentix_cli-0.2.0/src/agentix/jenkins/client.py +248 -0
  21. agentix_cli-0.2.0/src/agentix/jenkins/commands.py +294 -0
  22. agentix_cli-0.2.0/src/agentix/jenkins/models.py +100 -0
  23. agentix_cli-0.2.0/src/agentix/jira/__init__.py +1 -0
  24. agentix_cli-0.2.0/src/agentix/jira/client.py +269 -0
  25. agentix_cli-0.2.0/src/agentix/jira/commands.py +475 -0
  26. agentix_cli-0.2.0/src/agentix/jira/models.py +133 -0
  27. agentix_cli-0.2.0/tests/conftest.py +51 -0
  28. agentix_cli-0.2.0/tests/test_config.py +97 -0
  29. agentix_cli-0.2.0/tests/test_confluence/__init__.py +0 -0
  30. agentix_cli-0.2.0/tests/test_core/__init__.py +0 -0
  31. agentix_cli-0.2.0/tests/test_core/test_auth.py +57 -0
  32. agentix_cli-0.2.0/tests/test_core/test_exceptions.py +47 -0
  33. agentix_cli-0.2.0/tests/test_core/test_http.py +173 -0
  34. agentix_cli-0.2.0/tests/test_core/test_output.py +74 -0
  35. agentix_cli-0.2.0/tests/test_jenkins/__init__.py +0 -0
  36. agentix_cli-0.2.0/tests/test_jira/__init__.py +0 -0
  37. agentix_cli-0.2.0/tests/test_jira/test_client.py +151 -0
  38. agentix_cli-0.2.0/tests/test_jira/test_commands.py +152 -0
  39. agentix_cli-0.2.0/uv.lock +832 -0
  40. agentix_cli-0.1.1/src/agentix/cli.py +0 -12
  41. {agentix_cli-0.1.1 → agentix_cli-0.2.0}/.github/workflows/publish.yml +0 -0
  42. {agentix_cli-0.1.1 → agentix_cli-0.2.0}/.gitignore +0 -0
  43. {agentix_cli-0.1.1 → agentix_cli-0.2.0}/.python-version +0 -0
  44. {agentix_cli-0.1.1 → agentix_cli-0.2.0}/CLAUDE.md +0 -0
  45. {agentix_cli-0.1.1 → agentix_cli-0.2.0}/LICENSE +0 -0
  46. {agentix_cli-0.1.1 → agentix_cli-0.2.0}/README.md +0 -0
  47. {agentix_cli-0.1.1 → agentix_cli-0.2.0}/src/agentix/__main__.py +0 -0
  48. {agentix_cli-0.1.1 → agentix_cli-0.2.0}/tests/__init__.py +0 -0
@@ -0,0 +1,56 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ name: Test (Python ${{ matrix.python-version }}, ${{ matrix.os }})
12
+ runs-on: ${{ matrix.os }}
13
+ strategy:
14
+ matrix:
15
+ os: [ubuntu-latest, macos-latest, windows-latest]
16
+ python-version: ["3.9", "3.12", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v5
22
+ with:
23
+ enable-cache: true
24
+
25
+ - name: Set up Python ${{ matrix.python-version }}
26
+ run: uv python install ${{ matrix.python-version }}
27
+
28
+ - name: Install dependencies
29
+ run: uv pip install -e ".[dev]"
30
+
31
+ - name: Lint
32
+ run: uv run ruff check src/ tests/
33
+
34
+ - name: Test
35
+ run: uv run pytest tests/ -v --tb=short
36
+
37
+ build:
38
+ name: Build package
39
+ runs-on: ubuntu-latest
40
+ needs: [test]
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+
44
+ - name: Install uv
45
+ uses: astral-sh/setup-uv@v5
46
+ with:
47
+ enable-cache: true
48
+
49
+ - name: Set up Python
50
+ run: uv python install
51
+
52
+ - name: Build
53
+ run: uv build
54
+
55
+ - name: Verify package
56
+ run: ls -la dist/
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentix-cli
3
- Version: 0.1.1
4
- Summary: A CLI tool for [description TBD]
3
+ Version: 0.2.0
4
+ Summary: Unified CLI and SDK for Jira, Confluence, and Jenkins
5
5
  Project-URL: Homepage, https://github.com/saggl/agentix
6
6
  Project-URL: Repository, https://github.com/saggl/agentix.git
7
7
  Project-URL: Issues, https://github.com/saggl/agentix/issues
@@ -18,6 +18,16 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
20
  Requires-Python: >=3.9
21
+ Requires-Dist: click>=8.0
22
+ Requires-Dist: requests>=2.28
23
+ Requires-Dist: tabulate>=0.9
24
+ Requires-Dist: tomli-w>=1.0
25
+ Requires-Dist: tomli>=1.0; python_version < '3.11'
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
28
+ Requires-Dist: pytest>=7.0; extra == 'dev'
29
+ Requires-Dist: responses>=0.23; extra == 'dev'
30
+ Requires-Dist: ruff>=0.4; extra == 'dev'
21
31
  Description-Content-Type: text/markdown
22
32
 
23
33
  # agentix
@@ -3,12 +3,17 @@ requires = ["hatchling"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [dependency-groups]
6
- dev = []
6
+ dev = [
7
+ "pytest>=7.0",
8
+ "pytest-cov>=4.0",
9
+ "responses>=0.23",
10
+ "ruff>=0.4",
11
+ ]
7
12
 
8
13
  [project]
9
14
  name = "agentix-cli"
10
- version = "0.1.1"
11
- description = "A CLI tool for [description TBD]"
15
+ version = "0.2.0"
16
+ description = "Unified CLI and SDK for Jira, Confluence, and Jenkins"
12
17
  readme = "README.md"
13
18
  requires-python = ">=3.9"
14
19
  license = {text = "MIT"}
@@ -26,7 +31,21 @@ classifiers = [
26
31
  "Programming Language :: Python :: 3.12",
27
32
  "Programming Language :: Python :: 3.13",
28
33
  ]
29
- dependencies = []
34
+ dependencies = [
35
+ "click>=8.0",
36
+ "requests>=2.28",
37
+ "tomli>=1.0; python_version < '3.11'",
38
+ "tomli-w>=1.0",
39
+ "tabulate>=0.9",
40
+ ]
41
+
42
+ [project.optional-dependencies]
43
+ dev = [
44
+ "pytest>=7.0",
45
+ "pytest-cov>=4.0",
46
+ "responses>=0.23",
47
+ "ruff>=0.4",
48
+ ]
30
49
 
31
50
  [project.scripts]
32
51
  agentix = "agentix.__main__:main"
@@ -1,3 +1,3 @@
1
1
  """agentix - A CLI tool"""
2
2
 
3
- __version__ = "0.1.1"
3
+ __version__ = "0.2.0"
@@ -0,0 +1,76 @@
1
+ """Root CLI for agentix."""
2
+
3
+ import logging
4
+ import sys
5
+
6
+ import click
7
+
8
+ from agentix import __version__
9
+ from agentix.config.commands import config_group
10
+ from agentix.config.manager import ConfigManager
11
+ from agentix.core.exceptions import AgentixError
12
+ from agentix.core.output import OutputFormatter
13
+ from agentix.confluence.commands import confluence_group
14
+ from agentix.jenkins.commands import jenkins_group
15
+ from agentix.jira.commands import jira_group
16
+
17
+
18
+ @click.group()
19
+ @click.option(
20
+ "--profile",
21
+ default=None,
22
+ envvar="AGENTIX_DEFAULT_PROFILE",
23
+ help="Config profile to use.",
24
+ )
25
+ @click.option(
26
+ "--format",
27
+ "output_format",
28
+ type=click.Choice(["json", "table"]),
29
+ default=None,
30
+ help="Output format (default: json).",
31
+ )
32
+ @click.option("--verbose", is_flag=True, help="Enable verbose logging.")
33
+ @click.version_option(version=__version__, prog_name="agentix")
34
+ @click.pass_context
35
+ def cli(ctx, profile, output_format, verbose):
36
+ """agentix — Unified CLI for Jira, Confluence, and Jenkins."""
37
+ ctx.ensure_object(dict)
38
+
39
+ if verbose:
40
+ logging.basicConfig(level=logging.DEBUG, stream=sys.stderr)
41
+
42
+ config_manager = ConfigManager()
43
+ resolved_format = (
44
+ output_format
45
+ or config_manager.config.defaults.format
46
+ or "json"
47
+ )
48
+
49
+ ctx.obj["profile"] = profile
50
+ ctx.obj["config_manager"] = config_manager
51
+ ctx.obj["formatter"] = OutputFormatter(resolved_format)
52
+
53
+
54
+ # Register subgroups
55
+ cli.add_command(config_group)
56
+ cli.add_command(jira_group)
57
+ cli.add_command(confluence_group)
58
+ cli.add_command(jenkins_group)
59
+
60
+
61
+ def main():
62
+ """Entry point for the agentix CLI."""
63
+ try:
64
+ cli(standalone_mode=False)
65
+ except AgentixError as e:
66
+ formatter = OutputFormatter("json")
67
+ formatter.error(e)
68
+ sys.exit(e.exit_code)
69
+ except click.exceptions.Abort:
70
+ sys.exit(130)
71
+ except click.exceptions.Exit as e:
72
+ sys.exit(e.exit_code)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()
@@ -0,0 +1 @@
1
+ """Configuration management for agentix."""
@@ -0,0 +1,180 @@
1
+ """CLI commands for agentix config management."""
2
+
3
+ import json
4
+
5
+ import click
6
+ import requests
7
+ from requests.auth import HTTPBasicAuth
8
+
9
+ from agentix.config.models import (
10
+ ConfluenceConfig,
11
+ JenkinsConfig,
12
+ JiraConfig,
13
+ )
14
+
15
+
16
+ @click.group("config")
17
+ def config_group():
18
+ """Manage agentix configuration."""
19
+ pass
20
+
21
+
22
+ @config_group.command()
23
+ @click.pass_context
24
+ def init(ctx):
25
+ """Interactive setup wizard."""
26
+ cm = ctx.obj["config_manager"]
27
+ config = cm.config
28
+
29
+ profile_name = click.prompt("Profile name", default="default")
30
+ profile = config.get_profile(profile_name)
31
+
32
+ # Jira
33
+ if click.confirm("Configure Jira?", default=True):
34
+ profile.jira = _setup_jira()
35
+
36
+ # Confluence
37
+ if click.confirm("Configure Confluence?", default=True):
38
+ profile.confluence = _setup_confluence(profile.jira)
39
+
40
+ # Jenkins
41
+ if click.confirm("Configure Jenkins?", default=False):
42
+ profile.jenkins = _setup_jenkins()
43
+
44
+ config.default_profile = profile_name
45
+ config.profiles[profile_name] = profile
46
+ cm.save(config)
47
+
48
+ formatter = ctx.obj["formatter"]
49
+ formatter.success(f"Configuration saved to {cm.config_path}")
50
+
51
+
52
+ def _setup_jira() -> JiraConfig:
53
+ base_url = click.prompt("Jira base URL (e.g., https://company.atlassian.net)")
54
+ email = click.prompt("Jira email")
55
+ api_token = click.prompt("Jira API token", hide_input=True)
56
+
57
+ # Validate
58
+ click.echo("Validating credentials... ", nl=False)
59
+ try:
60
+ resp = requests.get(
61
+ f"{base_url.rstrip('/')}/rest/api/3/myself",
62
+ auth=HTTPBasicAuth(email, api_token),
63
+ timeout=10,
64
+ )
65
+ if resp.ok:
66
+ user = resp.json()
67
+ click.echo(f"OK (authenticated as {user.get('displayName', email)})")
68
+ else:
69
+ click.echo(f"Warning: got HTTP {resp.status_code} — credentials may be invalid")
70
+ except requests.RequestException as e:
71
+ click.echo(f"Warning: could not validate — {e}")
72
+
73
+ return JiraConfig(base_url=base_url.rstrip("/"), email=email, api_token=api_token)
74
+
75
+
76
+ def _setup_confluence(jira_config: JiraConfig) -> ConfluenceConfig:
77
+ default_url = ""
78
+ default_email = ""
79
+ default_token = ""
80
+
81
+ if jira_config.base_url:
82
+ default_url = jira_config.base_url.rstrip("/") + "/wiki"
83
+ default_email = jira_config.email
84
+ default_token = jira_config.api_token
85
+
86
+ base_url = click.prompt(
87
+ "Confluence base URL", default=default_url or None
88
+ )
89
+ email = click.prompt("Confluence email", default=default_email or None)
90
+ api_token = click.prompt(
91
+ "Confluence API token (same as Jira for Atlassian Cloud)",
92
+ default=default_token or None,
93
+ hide_input=True,
94
+ )
95
+
96
+ return ConfluenceConfig(
97
+ base_url=base_url.rstrip("/"), email=email, api_token=api_token
98
+ )
99
+
100
+
101
+ def _setup_jenkins() -> JenkinsConfig:
102
+ base_url = click.prompt("Jenkins base URL (e.g., https://jenkins.company.com)")
103
+ username = click.prompt("Jenkins username")
104
+ api_token = click.prompt("Jenkins API token", hide_input=True)
105
+
106
+ # Validate
107
+ click.echo("Validating credentials... ", nl=False)
108
+ try:
109
+ resp = requests.get(
110
+ f"{base_url.rstrip('/')}/api/json",
111
+ auth=HTTPBasicAuth(username, api_token),
112
+ timeout=10,
113
+ )
114
+ if resp.ok:
115
+ click.echo("OK")
116
+ else:
117
+ click.echo(f"Warning: got HTTP {resp.status_code}")
118
+ except requests.RequestException as e:
119
+ click.echo(f"Warning: could not validate — {e}")
120
+
121
+ return JenkinsConfig(
122
+ base_url=base_url.rstrip("/"), username=username, api_token=api_token
123
+ )
124
+
125
+
126
+ @config_group.command()
127
+ @click.argument("key")
128
+ @click.argument("value")
129
+ @click.pass_context
130
+ def set(ctx, key, value):
131
+ """Set a config value (e.g., agentix config set profiles.work.jira.base_url https://...)."""
132
+ cm = ctx.obj["config_manager"]
133
+ cm.set_value(key, value)
134
+ ctx.obj["formatter"].success(f"Set {key} = {value}")
135
+
136
+
137
+ @config_group.command()
138
+ @click.argument("key")
139
+ @click.pass_context
140
+ def get(ctx, key):
141
+ """Get a config value."""
142
+ cm = ctx.obj["config_manager"]
143
+ try:
144
+ value = cm.get_value(key)
145
+ except Exception as e:
146
+ ctx.obj["formatter"].error(e)
147
+ ctx.exit(2)
148
+ return
149
+ formatter = ctx.obj["formatter"]
150
+ if isinstance(value, dict):
151
+ formatter.output(value)
152
+ else:
153
+ if formatter.fmt == "json":
154
+ print(json.dumps({"key": key, "value": value}))
155
+ else:
156
+ print(value)
157
+
158
+
159
+ @config_group.command()
160
+ @click.pass_context
161
+ def show(ctx):
162
+ """Show full config with tokens masked."""
163
+ cm = ctx.obj["config_manager"]
164
+ if not cm.exists():
165
+ click.echo("No configuration found. Run 'agentix config init' to create one.")
166
+ ctx.exit(2)
167
+ return
168
+ ctx.obj["formatter"].output(cm.mask_tokens())
169
+
170
+
171
+ @config_group.command()
172
+ @click.pass_context
173
+ def path(ctx):
174
+ """Print config file path."""
175
+ cm = ctx.obj["config_manager"]
176
+ formatter = ctx.obj["formatter"]
177
+ if formatter.fmt == "json":
178
+ print(json.dumps({"path": str(cm.config_path), "exists": cm.exists()}))
179
+ else:
180
+ print(cm.config_path)
@@ -0,0 +1,109 @@
1
+ """Configuration manager for agentix."""
2
+
3
+ import os
4
+ import stat
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Any, Optional
8
+
9
+ try:
10
+ import tomllib
11
+ except ModuleNotFoundError:
12
+ import tomli as tomllib
13
+
14
+ import tomli_w
15
+
16
+ from agentix.core.exceptions import ConfigError
17
+
18
+ from .models import AgentixConfig, get_config_path
19
+
20
+
21
+ class ConfigManager:
22
+ """Load, save, and query agentix configuration."""
23
+
24
+ def __init__(self, config_path: Optional[Path] = None):
25
+ self.config_path = config_path or get_config_path()
26
+ self._config: Optional[AgentixConfig] = None
27
+
28
+ @property
29
+ def config(self) -> AgentixConfig:
30
+ if self._config is None:
31
+ self._config = self.load()
32
+ return self._config
33
+
34
+ def exists(self) -> bool:
35
+ return self.config_path.exists()
36
+
37
+ def load(self) -> AgentixConfig:
38
+ if not self.config_path.exists():
39
+ return AgentixConfig()
40
+ try:
41
+ data = tomllib.loads(self.config_path.read_text(encoding="utf-8"))
42
+ return AgentixConfig.from_dict(data)
43
+ except Exception as e:
44
+ raise ConfigError(
45
+ f"Failed to load config from {self.config_path}: {e}"
46
+ ) from e
47
+
48
+ def save(self, config: Optional[AgentixConfig] = None) -> None:
49
+ cfg = config or self.config
50
+ config_dir = self.config_path.parent
51
+ config_dir.mkdir(parents=True, exist_ok=True)
52
+
53
+ # Set restrictive permissions on directory
54
+ if sys.platform != "win32":
55
+ os.chmod(config_dir, stat.S_IRWXU)
56
+
57
+ self.config_path.write_text(
58
+ tomli_w.dumps(cfg.to_dict()), encoding="utf-8"
59
+ )
60
+
61
+ # Set restrictive permissions on file
62
+ if sys.platform != "win32":
63
+ os.chmod(self.config_path, stat.S_IRUSR | stat.S_IWUSR)
64
+
65
+ self._config = cfg
66
+
67
+ def get_value(self, key: str) -> Any:
68
+ """Get a config value by dotted key path (e.g., 'profiles.work.jira.base_url')."""
69
+ parts = key.split(".")
70
+ data = self.config.to_dict()
71
+ for part in parts:
72
+ if isinstance(data, dict) and part in data:
73
+ data = data[part]
74
+ else:
75
+ raise ConfigError(f"Config key not found: {key}")
76
+ return data
77
+
78
+ def set_value(self, key: str, value: str) -> None:
79
+ """Set a config value by dotted key path."""
80
+ parts = key.split(".")
81
+ data = self.config.to_dict()
82
+
83
+ # Navigate to parent
84
+ current = data
85
+ for part in parts[:-1]:
86
+ if part not in current:
87
+ current[part] = {}
88
+ current = current[part]
89
+ current[parts[-1]] = value
90
+
91
+ self._config = AgentixConfig.from_dict(data)
92
+ self.save()
93
+
94
+ def mask_tokens(self) -> dict:
95
+ """Return config dict with tokens/secrets masked."""
96
+ data = self.config.to_dict()
97
+ secret_keys = {"api_token", "token", "password", "secret"}
98
+
99
+ def _mask(d: Any) -> Any:
100
+ if isinstance(d, dict):
101
+ return {
102
+ k: ("***" if k in secret_keys and v else _mask(v))
103
+ for k, v in d.items()
104
+ }
105
+ if isinstance(d, list):
106
+ return [_mask(i) for i in d]
107
+ return d
108
+
109
+ return _mask(data)
@@ -0,0 +1,105 @@
1
+ """Configuration data models for agentix."""
2
+
3
+ import os
4
+ import sys
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any, Dict, Optional
8
+
9
+
10
+ @dataclass
11
+ class JiraConfig:
12
+ base_url: str = ""
13
+ email: str = ""
14
+ api_token: str = ""
15
+
16
+
17
+ @dataclass
18
+ class ConfluenceConfig:
19
+ base_url: str = ""
20
+ email: str = ""
21
+ api_token: str = ""
22
+
23
+
24
+ @dataclass
25
+ class JenkinsConfig:
26
+ base_url: str = ""
27
+ username: str = ""
28
+ api_token: str = ""
29
+
30
+
31
+ @dataclass
32
+ class Profile:
33
+ jira: JiraConfig = field(default_factory=JiraConfig)
34
+ confluence: ConfluenceConfig = field(default_factory=ConfluenceConfig)
35
+ jenkins: JenkinsConfig = field(default_factory=JenkinsConfig)
36
+
37
+ @classmethod
38
+ def from_dict(cls, data: Dict[str, Any]) -> "Profile":
39
+ return cls(
40
+ jira=JiraConfig(**data.get("jira", {})),
41
+ confluence=ConfluenceConfig(**data.get("confluence", {})),
42
+ jenkins=JenkinsConfig(**data.get("jenkins", {})),
43
+ )
44
+
45
+ def to_dict(self) -> Dict[str, Any]:
46
+ result = {}
47
+ for service_name in ("jira", "confluence", "jenkins"):
48
+ cfg = getattr(self, service_name)
49
+ d = {k: v for k, v in cfg.__dict__.items() if v}
50
+ if d:
51
+ result[service_name] = d
52
+ return result
53
+
54
+
55
+ @dataclass
56
+ class Defaults:
57
+ format: str = "json"
58
+
59
+
60
+ @dataclass
61
+ class AgentixConfig:
62
+ default_profile: str = "default"
63
+ defaults: Defaults = field(default_factory=Defaults)
64
+ profiles: Dict[str, Profile] = field(default_factory=dict)
65
+
66
+ @classmethod
67
+ def from_dict(cls, data: Dict[str, Any]) -> "AgentixConfig":
68
+ profiles = {}
69
+ for name, pdata in data.get("profiles", {}).items():
70
+ profiles[name] = Profile.from_dict(pdata)
71
+ return cls(
72
+ default_profile=data.get("default_profile", "default"),
73
+ defaults=Defaults(**data.get("defaults", {})),
74
+ profiles=profiles,
75
+ )
76
+
77
+ def to_dict(self) -> Dict[str, Any]:
78
+ result: Dict[str, Any] = {"default_profile": self.default_profile}
79
+ defaults_dict = {k: v for k, v in self.defaults.__dict__.items() if v}
80
+ if defaults_dict:
81
+ result["defaults"] = defaults_dict
82
+ if self.profiles:
83
+ result["profiles"] = {
84
+ name: p.to_dict() for name, p in self.profiles.items()
85
+ }
86
+ return result
87
+
88
+ def get_profile(self, name: Optional[str] = None) -> Profile:
89
+ profile_name = name or self.default_profile
90
+ if profile_name not in self.profiles:
91
+ self.profiles[profile_name] = Profile()
92
+ return self.profiles[profile_name]
93
+
94
+
95
+ def get_config_dir() -> Path:
96
+ """Return the platform-appropriate config directory."""
97
+ if sys.platform == "win32":
98
+ base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
99
+ else:
100
+ base = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))
101
+ return base / "agentix"
102
+
103
+
104
+ def get_config_path() -> Path:
105
+ return get_config_dir() / "config.toml"
@@ -0,0 +1 @@
1
+ """Confluence integration for agentix."""