agent-confhub-cli 0.1.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.
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-confhub-cli
3
+ Version: 0.1.0
4
+ Summary: AI 에이전트 설정 중앙 관리 CLI 프레임워크
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: gitpython>=3.1.0
7
+ Requires-Dist: jinja2>=3.1.0
8
+ Requires-Dist: pyyaml>=6.0
9
+ Requires-Dist: rich>=13.0.0
10
+ Requires-Dist: typer>=0.9.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pre-commit>=3.0; extra == 'dev'
13
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
14
+ Requires-Dist: pytest>=7.0; extra == 'dev'
15
+ Requires-Dist: ruff>=0.9.0; extra == 'dev'
@@ -0,0 +1,24 @@
1
+ nexus/__init__.py,sha256=4iQaH0qMlBPNJzsRQpypERULNrK4mAMdJ2276uBug8Q,94
2
+ nexus/cli.py,sha256=zeipobflE-yOP8d8QSaJb543IkqmXovpVeyNyYjIWD0,3432
3
+ nexus/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ nexus/commands/agent.py,sha256=VEyYvYCtd3pnRMC87cRDPO-e-Thf4EEoLE0bsUTXGDM,11549
5
+ nexus/commands/app.py,sha256=SbOI55I1RKyb-GACOqRnwA7QEAWx-wh-x2r2G0W6Uuc,7690
6
+ nexus/commands/init.py,sha256=hh5YTOvxaeeUzh1NfUorGTHDxcYW4ij6WZcU-dqjzOY,5368
7
+ nexus/commands/install.py,sha256=IoL-NtQBKeEWJ8IdUsOmBQXF_L12jiNyEEGNAUVS0gU,5177
8
+ nexus/commands/link.py,sha256=g6EiriBflpFqTvjh3l1S3xdqa9OXc4tsT0UEIhrU6Ho,4885
9
+ nexus/commands/resolve.py,sha256=HRbohWpQH4yAJbx2T9Y1ocfpy2V2kjW13hh5-Yc5o7w,4550
10
+ nexus/commands/status.py,sha256=cHw9BIyrmg99rVGRpzRw1xaCi3KheXDb3DaWf6InHCY,4033
11
+ nexus/commands/submodule.py,sha256=g7JvocO1ML_vNsUfVegUq7Wxe4R3mhk-iyYPHiRYWiE,12569
12
+ nexus/commands/sync.py,sha256=5bXBE_OV5_WJ0b5vLNDhuBQuStK6FWqC4Jo2sdgIv6E,4350
13
+ nexus/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ nexus/core/agents.py,sha256=qCiy1Z4UFiWrOXWmrqovnQmijDp4ZCOVUGyNeAzEmkw,3222
15
+ nexus/core/linker.py,sha256=Ep1EjVOJ5g1atUkWrtOPQjX2wc1Fih5CSX2AuEKBQmg,7350
16
+ nexus/core/merger.py,sha256=6hyQ8LEk_U7eMvyzu68FHhbIqG9q_7Zu5UmRpuL0Mcg,7245
17
+ nexus/core/registry.py,sha256=qrWdZO97ed_fOsVduH71Z5eLrmF-rclAecu9tjpuOnk,5191
18
+ nexus/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ nexus/utils/console.py,sha256=YI_MoKmzRrpICVM6Po1tc1yg2g0D9m3vur7_jj1EqSQ,1110
20
+ nexus/utils/git.py,sha256=-Adeyoo3ol2UJuzHySsTtR8Y0BamZNexZ9SHOVcdIEc,3671
21
+ agent_confhub_cli-0.1.0.dist-info/METADATA,sha256=PMXOcA0D1ogYMt1ffgictcL_wcMScRWwyFFiMbnUgWM,495
22
+ agent_confhub_cli-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
23
+ agent_confhub_cli-0.1.0.dist-info/entry_points.txt,sha256=xxn5ixCInjp86tT55MXem9N7gmJ78cSq0BZnVftcc1s,38
24
+ agent_confhub_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nxs = nexus.cli:app
nexus/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Nexus CLI - AI 에이전트 설정 중앙 관리 프레임워크"""
2
+
3
+ __version__ = "0.1.0"
nexus/cli.py ADDED
@@ -0,0 +1,113 @@
1
+ """Nexus CLI - 진입점 모듈"""
2
+
3
+ from pathlib import Path
4
+
5
+ import typer
6
+
7
+ from nexus import __version__
8
+ from nexus.commands import agent as agent_cmd
9
+ from nexus.commands import app as app_cmd
10
+ from nexus.commands.link import link_app
11
+ from nexus.commands.submodule import submodule_app
12
+ from nexus.commands.sync import sync_app
13
+
14
+ app = typer.Typer(
15
+ name="nxs",
16
+ help="Nexus CLI - AI 에이전트 설정 관리",
17
+ add_completion=False,
18
+ )
19
+
20
+ app.add_typer(app_cmd.app, name="app")
21
+ app.add_typer(agent_cmd.app, name="agent")
22
+ app.add_typer(link_app, name="link")
23
+ app.add_typer(submodule_app, name="submodule")
24
+ app.add_typer(sync_app, name="sync")
25
+
26
+
27
+ def version_callback(value: bool):
28
+ """버전 정보 출력 콜백"""
29
+ if value:
30
+ typer.echo(f"nexus-cli version: {__version__}")
31
+ raise typer.Exit()
32
+
33
+
34
+ @app.callback()
35
+ def main(
36
+ version: bool | None = typer.Option(
37
+ None,
38
+ "--version",
39
+ "-v",
40
+ help="현재 버전을 출력합니다.",
41
+ callback=version_callback,
42
+ is_eager=True,
43
+ ),
44
+ ):
45
+ """Nexus CLI - AI 에이전트 설정 중앙 관리 프레임워크"""
46
+
47
+
48
+ @app.command("init")
49
+ def init_command(
50
+ path: Path | None = typer.Option(None, "--path", "-p", help="Registry 경로"),
51
+ from_repo: str | None = typer.Option(None, "--from-repo", help="Git 레포 URL에서 초기화"),
52
+ ):
53
+ """Nexus Registry를 초기화합니다."""
54
+ from nexus.commands.init import do_init
55
+
56
+ do_init(path, from_repo)
57
+
58
+
59
+ @app.command("resolve")
60
+ def resolve_command(
61
+ app_name: str | None = typer.Argument(None, help="앱 이름 (생략시 --all 필요)"),
62
+ all_apps: bool = typer.Option(False, "--all", "-a", help="모든 앱 빌드"),
63
+ dry_run: bool = typer.Option(False, "--dry-run", help="결과 미리보기 (파일 미생성)"),
64
+ ):
65
+ """설정을 병합하여 resolved 디렉토리에 저장합니다."""
66
+ from nexus.commands.resolve import do_resolve
67
+
68
+ do_resolve(app_name, all_apps, dry_run)
69
+
70
+
71
+ @app.command("unlink")
72
+ def unlink_command(
73
+ app_name: str = typer.Argument(..., help="앱 이름"),
74
+ target: Path | None = typer.Option(
75
+ None, "--target", "-t", help="링크 해제할 프로젝트 경로 (기본: 현재 디렉토리)"
76
+ ),
77
+ agent: str | None = typer.Option(
78
+ None, "--agent", help="특정 에이전트만 해제 (쉼표 구분: claude,gemini)"
79
+ ),
80
+ ):
81
+ """프로젝트의 심볼릭 링크를 해제합니다."""
82
+ from nexus.commands.link import do_unlink
83
+
84
+ do_unlink(app_name, target, agent)
85
+
86
+
87
+ @app.command("status")
88
+ def status_command(
89
+ app_name: str | None = typer.Option(None, "--app", help="특정 앱 이름"),
90
+ with_links: bool = typer.Option(False, "--with-links", help="링크 상태 포함"),
91
+ ):
92
+ """Registry 상태를 표시합니다."""
93
+ from nexus.commands.status import do_status
94
+
95
+ do_status(app_name, with_links)
96
+
97
+
98
+ @app.command("install")
99
+ def install_command(
100
+ from_repo: str | None = typer.Option(None, "--from-repo", help="Git 레포 URL에서 설치"),
101
+ verify: bool = typer.Option(False, "--verify", help="설치 상태 확인"),
102
+ apps: str | None = typer.Option(
103
+ None, "--apps", help="특정 앱만 설치 (쉼표 구분: web-frontend,api-server)"
104
+ ),
105
+ ):
106
+ """Git 레포에서 Registry를 설치합니다."""
107
+ from nexus.commands.install import do_install
108
+
109
+ do_install(from_repo, verify, apps)
110
+
111
+
112
+ if __name__ == "__main__":
113
+ app()
File without changes
@@ -0,0 +1,325 @@
1
+ """nxs agent 명령어 - 에이전트 설정 관리"""
2
+
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ import typer
7
+ import yaml
8
+ from rich.panel import Panel
9
+ from rich.syntax import Syntax
10
+
11
+ from nexus.core.agents import get_agent
12
+ from nexus.core.registry import Registry, RegistryNotFoundError
13
+ from nexus.utils.console import (
14
+ console,
15
+ make_table,
16
+ print_error,
17
+ print_info,
18
+ print_success,
19
+ print_warning,
20
+ )
21
+
22
+ app = typer.Typer(help="에이전트 설정 관리")
23
+
24
+
25
+ # ── 에이전트 config.yaml 템플릿 ────────────────────────────────────────────────
26
+
27
+
28
+ def _build_agent_config(agent_id: str, scope: str) -> dict:
29
+ """agent.config.yaml 템플릿 생성"""
30
+ agent = get_agent(agent_id)
31
+ merge_dict = {}
32
+ for filename in agent.default_files:
33
+ base = Path(filename).name
34
+ if base.endswith(".md"):
35
+ merge_dict[base] = "append"
36
+ elif base.endswith(".json"):
37
+ merge_dict[base] = "deep-merge"
38
+ else:
39
+ merge_dict[base] = "overwrite"
40
+
41
+ return {
42
+ "agent": agent_id,
43
+ "version": "1.0.0",
44
+ "scope": scope,
45
+ "merge": merge_dict,
46
+ }
47
+
48
+
49
+ # ── 유틸리티 ───────────────────────────────────────────────────────────────────
50
+
51
+
52
+ def _get_registry() -> Registry:
53
+ """기본 Registry를 반환하고, 초기화 여부를 확인한다."""
54
+ registry = Registry.get_default()
55
+ registry.require_initialized()
56
+ return registry
57
+
58
+
59
+ def _resolve_scope_and_dir(
60
+ registry: Registry,
61
+ agent_id: str,
62
+ app_name: str | None,
63
+ root: bool,
64
+ ) -> tuple[str, Path]:
65
+ """(scope, agent_dir) 튜플 반환. 유효성 검사도 포함."""
66
+ if root and app_name:
67
+ print_error("--root 와 --app 옵션을 동시에 사용할 수 없습니다.")
68
+ raise typer.Exit(1)
69
+ if not root and not app_name:
70
+ print_error("--app <앱이름> 또는 --root 중 하나를 지정해야 합니다.")
71
+ raise typer.Exit(1)
72
+
73
+ if root:
74
+ scope = "root"
75
+ agent_dir = registry.get_root_agent_path(agent_id)
76
+ else:
77
+ assert app_name is not None
78
+ if not registry.app_exists(app_name):
79
+ print_error(f"앱 '{app_name}'을(를) 찾을 수 없습니다. (not found)")
80
+ raise typer.Exit(1)
81
+ scope = "app"
82
+ agent_dir = registry.get_app_agent_path(app_name, agent_id)
83
+
84
+ return scope, agent_dir
85
+
86
+
87
+ # ── 명령어 ─────────────────────────────────────────────────────────────────────
88
+
89
+
90
+ @app.command("add")
91
+ def agent_add(
92
+ agent_id: str = typer.Argument(
93
+ ..., help="에이전트 식별자 (claude, gemini, codex, cursor, copilot)"
94
+ ),
95
+ app_name: str | None = typer.Option(None, "--app", "-a", help="앱 이름"),
96
+ root: bool = typer.Option(False, "--root", "-r", help="루트 레벨에 추가"),
97
+ ):
98
+ """에이전트 설정을 추가합니다."""
99
+ try:
100
+ registry = _get_registry()
101
+ except RegistryNotFoundError as exc:
102
+ print_error(str(exc))
103
+ raise typer.Exit(1)
104
+
105
+ # 에이전트 유효성 검사
106
+ try:
107
+ agent_cfg = get_agent(agent_id)
108
+ except ValueError as exc:
109
+ print_error(str(exc))
110
+ raise typer.Exit(1)
111
+
112
+ # 스코프 결정
113
+ try:
114
+ scope, agent_dir = _resolve_scope_and_dir(registry, agent_id, app_name, root)
115
+ except typer.Exit:
116
+ raise
117
+
118
+ # 이미 존재 여부 확인
119
+ if (agent_dir / "agent.config.yaml").exists():
120
+ print_warning(f"에이전트 '{agent_id}' 설정이 이미 존재합니다. ({agent_dir})")
121
+ raise typer.Exit(1)
122
+
123
+ try:
124
+ # 디렉토리 생성
125
+ agent_dir.mkdir(parents=True, exist_ok=True)
126
+
127
+ # agent.config.yaml 생성
128
+ config = _build_agent_config(agent_id, scope)
129
+ with open(agent_dir / "agent.config.yaml", "w", encoding="utf-8") as f:
130
+ yaml.dump(config, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
131
+
132
+ # 기본 파일들 생성
133
+ for rel_path, content in agent_cfg.default_files.items():
134
+ file_path = agent_dir / rel_path
135
+ file_path.parent.mkdir(parents=True, exist_ok=True)
136
+ file_path.write_text(content, encoding="utf-8")
137
+
138
+ # app scope이면 app.config.yaml의 agents 리스트에 추가
139
+ if scope == "app" and app_name:
140
+ app_config = registry.load_app_config(app_name)
141
+ agents_list = app_config.get("agents", [])
142
+ if agent_id not in agents_list:
143
+ agents_list.append(agent_id)
144
+ app_config["agents"] = agents_list
145
+ registry.save_app_config(app_name, app_config)
146
+
147
+ print_success(f"에이전트 '{agent_id}' ({agent_cfg.display_name}) 추가 완료")
148
+
149
+ except OSError as exc:
150
+ print_error(f"파일 시스템 오류: {exc}")
151
+ raise typer.Exit(1)
152
+
153
+
154
+ @app.command("list")
155
+ def agent_list(
156
+ app_name: str | None = typer.Option(None, "--app", "-a", help="앱 이름"),
157
+ root: bool = typer.Option(False, "--root", "-r", help="루트 레벨 목록"),
158
+ ):
159
+ """에이전트 목록을 표시합니다."""
160
+ try:
161
+ registry = _get_registry()
162
+ except RegistryNotFoundError as exc:
163
+ print_error(str(exc))
164
+ raise typer.Exit(1)
165
+
166
+ if root and app_name:
167
+ print_error("--root 와 --app 옵션을 동시에 사용할 수 없습니다.")
168
+ raise typer.Exit(1)
169
+ if not root and not app_name:
170
+ print_error("--app <앱이름> 또는 --root 중 하나를 지정해야 합니다.")
171
+ raise typer.Exit(1)
172
+
173
+ if root:
174
+ scope_label = "루트"
175
+ agents_base = registry.root_agents_path
176
+ else:
177
+ assert app_name is not None
178
+ if not registry.app_exists(app_name):
179
+ print_error(f"앱 '{app_name}'을(를) 찾을 수 없습니다. (not found)")
180
+ raise typer.Exit(1)
181
+ scope_label = f"앱: {app_name}"
182
+ agents_base = registry.get_app_path(app_name) / "agents"
183
+
184
+ if not agents_base.exists():
185
+ print_info(f"등록된 에이전트가 없습니다. ({scope_label})")
186
+ return
187
+
188
+ found_agents = []
189
+ for agent_dir in sorted(agents_base.iterdir()):
190
+ if agent_dir.is_dir() and (agent_dir / "agent.config.yaml").exists():
191
+ found_agents.append(agent_dir.name)
192
+
193
+ if not found_agents:
194
+ print_info(f"등록된 에이전트가 없습니다. ({scope_label})")
195
+ return
196
+
197
+ table = make_table(f"에이전트 목록 ({scope_label})", ["Agent", "Display Name", "Files"])
198
+
199
+ for agent_id in found_agents:
200
+ try:
201
+ agent_cfg = get_agent(agent_id)
202
+ display_name = agent_cfg.display_name
203
+ except ValueError:
204
+ display_name = "(알 수 없음)"
205
+
206
+ agent_dir = agents_base / agent_id
207
+ actual_files = []
208
+ for item in sorted(agent_dir.rglob("*")):
209
+ if item.is_file() and item.name != "agent.config.yaml":
210
+ actual_files.append(item.relative_to(agent_dir).as_posix())
211
+ files_str = ", ".join(actual_files) if actual_files else "-"
212
+ table.add_row(agent_id, display_name, files_str)
213
+
214
+ console.print(table)
215
+
216
+
217
+ @app.command("show")
218
+ def agent_show(
219
+ agent_id: str = typer.Argument(..., help="에이전트 식별자"),
220
+ app_name: str | None = typer.Option(None, "--app", "-a", help="앱 이름"),
221
+ root: bool = typer.Option(False, "--root", "-r", help="루트 레벨"),
222
+ resolved: bool = typer.Option(False, "--resolved", help="상속 병합 후 최종값 조회"),
223
+ ):
224
+ """에이전트 설정을 표시합니다."""
225
+ try:
226
+ registry = _get_registry()
227
+ except RegistryNotFoundError as exc:
228
+ print_error(str(exc))
229
+ raise typer.Exit(1)
230
+
231
+ if resolved:
232
+ print_info("--resolved 옵션은 Phase 5에서 지원 예정입니다.")
233
+ raise typer.Exit(0)
234
+
235
+ # 에이전트 유효성 검사
236
+ try:
237
+ agent_cfg = get_agent(agent_id)
238
+ except ValueError as exc:
239
+ print_error(str(exc))
240
+ raise typer.Exit(1)
241
+
242
+ try:
243
+ scope, agent_dir = _resolve_scope_and_dir(registry, agent_id, app_name, root)
244
+ except typer.Exit:
245
+ raise
246
+
247
+ if not (agent_dir / "agent.config.yaml").exists():
248
+ print_error(f"에이전트 '{agent_id}' 설정을 찾을 수 없습니다. ({agent_dir})")
249
+ raise typer.Exit(1)
250
+
251
+ # agent.config.yaml 내용 표시
252
+ config_content = (agent_dir / "agent.config.yaml").read_text(encoding="utf-8")
253
+ syntax = Syntax(config_content, "yaml", theme="monokai", line_numbers=True)
254
+ console.print(
255
+ Panel(syntax, title=f"에이전트: {agent_id} ({agent_cfg.display_name})", style="blue")
256
+ )
257
+
258
+ # 기본 파일들도 표시
259
+ for rel_path in agent_cfg.default_files:
260
+ file_path = agent_dir / rel_path
261
+ if file_path.exists():
262
+ content = file_path.read_text(encoding="utf-8")
263
+ ext = Path(rel_path).suffix.lstrip(".") or "text"
264
+ lang_map = {"md": "markdown", "json": "json", "yaml": "yaml", "yml": "yaml"}
265
+ lang = lang_map.get(ext, "text")
266
+ file_syntax = Syntax(content, lang, theme="monokai", line_numbers=True)
267
+ console.print(Panel(file_syntax, title=f"파일: {rel_path}", style="cyan"))
268
+
269
+
270
+ @app.command("remove")
271
+ def agent_remove(
272
+ agent_id: str = typer.Argument(..., help="에이전트 식별자"),
273
+ app_name: str | None = typer.Option(None, "--app", "-a", help="앱 이름"),
274
+ root: bool = typer.Option(False, "--root", "-r", help="루트 레벨"),
275
+ force: bool = typer.Option(False, "--force", "-f", help="확인 없이 삭제"),
276
+ ):
277
+ """에이전트 설정을 삭제합니다."""
278
+ try:
279
+ registry = _get_registry()
280
+ except RegistryNotFoundError as exc:
281
+ print_error(str(exc))
282
+ raise typer.Exit(1)
283
+
284
+ # 에이전트 유효성 검사
285
+ try:
286
+ get_agent(agent_id)
287
+ except ValueError as exc:
288
+ print_error(str(exc))
289
+ raise typer.Exit(1)
290
+
291
+ try:
292
+ scope, agent_dir = _resolve_scope_and_dir(registry, agent_id, app_name, root)
293
+ except typer.Exit:
294
+ raise
295
+
296
+ if not agent_dir.exists():
297
+ print_error(f"에이전트 '{agent_id}' 설정을 찾을 수 없습니다. ({agent_dir})")
298
+ raise typer.Exit(1)
299
+
300
+ if not force:
301
+ confirmed = typer.confirm(f"에이전트 '{agent_id}' 설정을 삭제하시겠습니까?")
302
+ if not confirmed:
303
+ print_info("삭제가 취소되었습니다.")
304
+ raise typer.Exit(0)
305
+
306
+ try:
307
+ shutil.rmtree(agent_dir)
308
+
309
+ # app scope이면 app.config.yaml의 agents 리스트에서 제거
310
+ if scope == "app" and app_name:
311
+ try:
312
+ app_config = registry.load_app_config(app_name)
313
+ agents_list = app_config.get("agents", [])
314
+ if agent_id in agents_list:
315
+ agents_list.remove(agent_id)
316
+ app_config["agents"] = agents_list
317
+ registry.save_app_config(app_name, app_config)
318
+ except FileNotFoundError:
319
+ pass
320
+
321
+ print_success(f"에이전트 '{agent_id}' 설정 삭제 완료")
322
+
323
+ except OSError as exc:
324
+ print_error(f"파일 시스템 오류: {exc}")
325
+ raise typer.Exit(1)