karajan-cli 0.1.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 (44) hide show
  1. karajan_cli-0.1.0/PKG-INFO +229 -0
  2. karajan_cli-0.1.0/README.md +208 -0
  3. karajan_cli-0.1.0/karajan/__init__.py +4 -0
  4. karajan_cli-0.1.0/karajan/agents/architect.md +23 -0
  5. karajan_cli-0.1.0/karajan/agents/qa.md +13 -0
  6. karajan_cli-0.1.0/karajan/agents/researcher.md +13 -0
  7. karajan_cli-0.1.0/karajan/agents/reviewer.md +13 -0
  8. karajan_cli-0.1.0/karajan/cli.py +102 -0
  9. karajan_cli-0.1.0/karajan/commands/__init__.py +2 -0
  10. karajan_cli-0.1.0/karajan/commands/config.py +89 -0
  11. karajan_cli-0.1.0/karajan/commands/init.py +247 -0
  12. karajan_cli-0.1.0/karajan/commands/logs.py +30 -0
  13. karajan_cli-0.1.0/karajan/commands/models.py +49 -0
  14. karajan_cli-0.1.0/karajan/commands/profiles.py +23 -0
  15. karajan_cli-0.1.0/karajan/commands/status.py +50 -0
  16. karajan_cli-0.1.0/karajan/commands/task.py +57 -0
  17. karajan_cli-0.1.0/karajan/config.py +241 -0
  18. karajan_cli-0.1.0/karajan/graph.py +148 -0
  19. karajan_cli-0.1.0/karajan/nodes/__init__.py +2 -0
  20. karajan_cli-0.1.0/karajan/nodes/architect.py +25 -0
  21. karajan_cli-0.1.0/karajan/nodes/dev.py +66 -0
  22. karajan_cli-0.1.0/karajan/nodes/hitl.py +31 -0
  23. karajan_cli-0.1.0/karajan/nodes/prd.py +26 -0
  24. karajan_cli-0.1.0/karajan/nodes/qa.py +20 -0
  25. karajan_cli-0.1.0/karajan/nodes/research.py +26 -0
  26. karajan_cli-0.1.0/karajan/nodes/reviewer.py +20 -0
  27. karajan_cli-0.1.0/karajan/profiles/solo.toml +27 -0
  28. karajan_cli-0.1.0/karajan/profiles/team.toml +28 -0
  29. karajan_cli-0.1.0/karajan/scripts/__init__.py +2 -0
  30. karajan_cli-0.1.0/karajan/scripts/github.py +8 -0
  31. karajan_cli-0.1.0/karajan/scripts/logger.py +150 -0
  32. karajan_cli-0.1.0/karajan/scripts/tracker.py +145 -0
  33. karajan_cli-0.1.0/karajan_cli.egg-info/PKG-INFO +229 -0
  34. karajan_cli-0.1.0/karajan_cli.egg-info/SOURCES.txt +42 -0
  35. karajan_cli-0.1.0/karajan_cli.egg-info/dependency_links.txt +1 -0
  36. karajan_cli-0.1.0/karajan_cli.egg-info/entry_points.txt +2 -0
  37. karajan_cli-0.1.0/karajan_cli.egg-info/requires.txt +13 -0
  38. karajan_cli-0.1.0/karajan_cli.egg-info/top_level.txt +1 -0
  39. karajan_cli-0.1.0/pyproject.toml +46 -0
  40. karajan_cli-0.1.0/setup.cfg +4 -0
  41. karajan_cli-0.1.0/tests/test_config.py +54 -0
  42. karajan_cli-0.1.0/tests/test_init.py +76 -0
  43. karajan_cli-0.1.0/tests/test_integration.py +102 -0
  44. karajan_cli-0.1.0/tests/test_workflow_dry_run.py +64 -0
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.4
2
+ Name: karajan-cli
3
+ Version: 0.1.0
4
+ Summary: An AI agent harness for software engineers. Built on opencode + LangGraph.
5
+ License-Expression: MIT
6
+ Keywords: ai,agents,langgraph,opencode,workflow,engineering
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: langgraph>=0.2.0
10
+ Requires-Dist: litellm>=1.40.0
11
+ Requires-Dist: python-dotenv>=1.0.0
12
+ Requires-Dist: requests>=2.31.0
13
+ Requires-Dist: typer>=0.12.0
14
+ Requires-Dist: rich>=13.0.0
15
+ Requires-Dist: pydantic>=2.0.0
16
+ Requires-Dist: tomli-w>=1.0.0
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
19
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
20
+ Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
21
+
22
+ # Karajan
23
+
24
+ **An AI agent harness for software engineers.**
25
+ Built on [opencode](https://opencode.ai) + [LangGraph](https://langchain-ai.github.io/langgraph/).
26
+
27
+ > Named after Herbert von Karajan — the conductor who coordinates every musician
28
+ > without playing a single instrument himself.
29
+
30
+ ```
31
+ Research → Architect → [YOU] → PRD → [YOU] → Dev → Reviewer → QA ⟲ → [YOU] → Done
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install karajan
40
+ ```
41
+
42
+ Or from source (development):
43
+
44
+ ```bash
45
+ git clone https://github.com/TU_USER/karajan
46
+ cd karajan
47
+ pip install -e .
48
+ ```
49
+
50
+ ## Quick start
51
+
52
+ ```bash
53
+ # 1. Go to your project
54
+ cd ~/projects/my-app
55
+
56
+ # 2. Initialize karajan
57
+ karajan init --profile solo # or: --profile team
58
+
59
+ # 3. Fill in .agent/CONTEXT.md with your project details
60
+ # 4. Copy .env.karajan.example to .env and add your API keys
61
+
62
+ # 5. Run a workflow
63
+ karajan task QR-42
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Commands
69
+
70
+ ```bash
71
+ karajan init [--profile solo|team] # Initialize in current project
72
+ karajan task <ticket> # Run full workflow
73
+ karajan status [ticket] # Show active workflows
74
+ karajan models [--provider X] # List available models
75
+ karajan profiles # List profiles and their defaults
76
+ karajan config --show # Show current project config
77
+ karajan version # Show version
78
+ ```
79
+
80
+ ## Model switching (per run)
81
+
82
+ ```bash
83
+ # Use profile defaults
84
+ karajan task QR-42
85
+
86
+ # Override reasoning model (Research, Architect, PRD, Reviewer)
87
+ karajan task QR-42 --model anthropic/claude-sonnet-4-20250514
88
+
89
+ # Override dev model (what opencode uses internally)
90
+ karajan task QR-42 --dev-model ollama/qwen2.5-coder:14b
91
+
92
+ # Override all three independently
93
+ karajan task QR-42 \
94
+ --model google/gemini-2.0-flash \
95
+ --dev-model ollama/qwen2.5-coder:14b \
96
+ --qa-model ollama/qwen2.5:7b
97
+
98
+ # Resume interrupted workflow
99
+ karajan task QR-42 --from-stage dev
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Profiles
105
+
106
+ Profiles live in `karajan/profiles/` and ship with the package.
107
+ No separate repos needed — everything in one place.
108
+
109
+ ```bash
110
+ karajan profiles # see all profiles and their defaults
111
+ ```
112
+
113
+ | | `solo` | `team` |
114
+ |--|--|--|
115
+ | Use case | Personal side-projects | Company engineering team |
116
+ | Tracker | GitHub Issues + Projects | Jira |
117
+ | Dev CLI | opencode | Claude Code |
118
+ | Reasoning model | Gemini Flash | Gemini Flash |
119
+ | Dev model | Qwen2.5-Coder 14B (local) | Claude Sonnet |
120
+ | QA model | Qwen2.5 7B (local) | Claude Haiku |
121
+ | Observability | Local logs | Langfuse |
122
+
123
+ ---
124
+
125
+ ## Workflow
126
+
127
+ ```
128
+ karajan task QR-42
129
+ → Fetches ticket (GitHub Issues or Jira)
130
+ → Research agent — understands context and risks
131
+ → Architect agent — designs technical plan
132
+ → [YOU review plan] ← HITL #1
133
+ → PRD agent — documents the feature
134
+ → [YOU review PRD] ← HITL #2
135
+ → Dev agent — opencode/Claude Code implements
136
+ → Reviewer agent — code review
137
+ → QA agent — validates, auto-retries Dev if fails (max 2x)
138
+ → PR opened in GitHub
139
+ → [YOU review PR] ← HITL #3
140
+ → Done
141
+ ```
142
+
143
+ ---
144
+
145
+ ## What karajan init creates
146
+
147
+ ```
148
+ your-project/
149
+ .agent/
150
+ CONTEXT.md ← what this project is
151
+ STACK.md ← technologies and versions
152
+ DECISIONS.md ← architecture decisions (ADRs)
153
+ CONSTRAINTS.md ← what agents can't touch
154
+ TASKS.md ← current work state
155
+ AGENTS.md ← agent instructions
156
+ .agents/
157
+ architect.md ← opencode architect agent definition
158
+ researcher.md ← opencode research agent definition
159
+ reviewer.md ← opencode reviewer agent definition
160
+ qa.md ← opencode QA agent definition
161
+ .karajan/
162
+ config.toml ← project-level config (profile, model overrides)
163
+ .env.karajan.example
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Structure
169
+
170
+ ```
171
+ karajan/
172
+ karajan/
173
+ cli.py ← entry point (karajan command)
174
+ config.py ← config loader with full hierarchy
175
+ graph.py ← LangGraph orchestrator
176
+ profiles/
177
+ solo.toml ← solo profile defaults
178
+ team.toml ← team profile defaults
179
+ nodes/
180
+ research.py ← Research node
181
+ architect.py ← Architect node
182
+ prd.py ← PRD node
183
+ dev.py ← Dev node (opencode / Claude Code)
184
+ reviewer.py ← Reviewer node
185
+ qa.py ← QA node
186
+ hitl.py ← Human-in-the-loop node
187
+ agents/
188
+ architect.md ← opencode agent definition
189
+ researcher.md ← opencode agent definition
190
+ reviewer.md ← opencode agent definition
191
+ qa.md ← opencode agent definition
192
+ commands/
193
+ init.py ← karajan init
194
+ task.py ← karajan task
195
+ status.py ← karajan status
196
+ models.py ← karajan models
197
+ config.py ← karajan config
198
+ scripts/
199
+ tracker.py ← GitHub Issues + Jira unified
200
+ github.py ← PR creation
201
+ pyproject.toml ← pip install config
202
+ README.md
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Config hierarchy
208
+
209
+ ```
210
+ CLI flags (--model, --dev-model, --profile)
211
+ > environment variables (KARAJAN_MODEL_*, GEMINI_API_KEY, etc.)
212
+ > .karajan/config.toml (per project)
213
+ > karajan/profiles/<profile>.toml (package defaults)
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Requirements
219
+
220
+ - Python 3.11+
221
+ - [opencode](https://opencode.ai) (`npm install -g opencode`) — solo profile
222
+ - [Claude Code](https://claude.ai/code) — team profile
223
+ - Ollama (optional, for local models): https://ollama.com
224
+
225
+ ---
226
+
227
+ ## License
228
+
229
+ MIT
@@ -0,0 +1,208 @@
1
+ # Karajan
2
+
3
+ **An AI agent harness for software engineers.**
4
+ Built on [opencode](https://opencode.ai) + [LangGraph](https://langchain-ai.github.io/langgraph/).
5
+
6
+ > Named after Herbert von Karajan — the conductor who coordinates every musician
7
+ > without playing a single instrument himself.
8
+
9
+ ```
10
+ Research → Architect → [YOU] → PRD → [YOU] → Dev → Reviewer → QA ⟲ → [YOU] → Done
11
+ ```
12
+
13
+ ---
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pip install karajan
19
+ ```
20
+
21
+ Or from source (development):
22
+
23
+ ```bash
24
+ git clone https://github.com/TU_USER/karajan
25
+ cd karajan
26
+ pip install -e .
27
+ ```
28
+
29
+ ## Quick start
30
+
31
+ ```bash
32
+ # 1. Go to your project
33
+ cd ~/projects/my-app
34
+
35
+ # 2. Initialize karajan
36
+ karajan init --profile solo # or: --profile team
37
+
38
+ # 3. Fill in .agent/CONTEXT.md with your project details
39
+ # 4. Copy .env.karajan.example to .env and add your API keys
40
+
41
+ # 5. Run a workflow
42
+ karajan task QR-42
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Commands
48
+
49
+ ```bash
50
+ karajan init [--profile solo|team] # Initialize in current project
51
+ karajan task <ticket> # Run full workflow
52
+ karajan status [ticket] # Show active workflows
53
+ karajan models [--provider X] # List available models
54
+ karajan profiles # List profiles and their defaults
55
+ karajan config --show # Show current project config
56
+ karajan version # Show version
57
+ ```
58
+
59
+ ## Model switching (per run)
60
+
61
+ ```bash
62
+ # Use profile defaults
63
+ karajan task QR-42
64
+
65
+ # Override reasoning model (Research, Architect, PRD, Reviewer)
66
+ karajan task QR-42 --model anthropic/claude-sonnet-4-20250514
67
+
68
+ # Override dev model (what opencode uses internally)
69
+ karajan task QR-42 --dev-model ollama/qwen2.5-coder:14b
70
+
71
+ # Override all three independently
72
+ karajan task QR-42 \
73
+ --model google/gemini-2.0-flash \
74
+ --dev-model ollama/qwen2.5-coder:14b \
75
+ --qa-model ollama/qwen2.5:7b
76
+
77
+ # Resume interrupted workflow
78
+ karajan task QR-42 --from-stage dev
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Profiles
84
+
85
+ Profiles live in `karajan/profiles/` and ship with the package.
86
+ No separate repos needed — everything in one place.
87
+
88
+ ```bash
89
+ karajan profiles # see all profiles and their defaults
90
+ ```
91
+
92
+ | | `solo` | `team` |
93
+ |--|--|--|
94
+ | Use case | Personal side-projects | Company engineering team |
95
+ | Tracker | GitHub Issues + Projects | Jira |
96
+ | Dev CLI | opencode | Claude Code |
97
+ | Reasoning model | Gemini Flash | Gemini Flash |
98
+ | Dev model | Qwen2.5-Coder 14B (local) | Claude Sonnet |
99
+ | QA model | Qwen2.5 7B (local) | Claude Haiku |
100
+ | Observability | Local logs | Langfuse |
101
+
102
+ ---
103
+
104
+ ## Workflow
105
+
106
+ ```
107
+ karajan task QR-42
108
+ → Fetches ticket (GitHub Issues or Jira)
109
+ → Research agent — understands context and risks
110
+ → Architect agent — designs technical plan
111
+ → [YOU review plan] ← HITL #1
112
+ → PRD agent — documents the feature
113
+ → [YOU review PRD] ← HITL #2
114
+ → Dev agent — opencode/Claude Code implements
115
+ → Reviewer agent — code review
116
+ → QA agent — validates, auto-retries Dev if fails (max 2x)
117
+ → PR opened in GitHub
118
+ → [YOU review PR] ← HITL #3
119
+ → Done
120
+ ```
121
+
122
+ ---
123
+
124
+ ## What karajan init creates
125
+
126
+ ```
127
+ your-project/
128
+ .agent/
129
+ CONTEXT.md ← what this project is
130
+ STACK.md ← technologies and versions
131
+ DECISIONS.md ← architecture decisions (ADRs)
132
+ CONSTRAINTS.md ← what agents can't touch
133
+ TASKS.md ← current work state
134
+ AGENTS.md ← agent instructions
135
+ .agents/
136
+ architect.md ← opencode architect agent definition
137
+ researcher.md ← opencode research agent definition
138
+ reviewer.md ← opencode reviewer agent definition
139
+ qa.md ← opencode QA agent definition
140
+ .karajan/
141
+ config.toml ← project-level config (profile, model overrides)
142
+ .env.karajan.example
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Structure
148
+
149
+ ```
150
+ karajan/
151
+ karajan/
152
+ cli.py ← entry point (karajan command)
153
+ config.py ← config loader with full hierarchy
154
+ graph.py ← LangGraph orchestrator
155
+ profiles/
156
+ solo.toml ← solo profile defaults
157
+ team.toml ← team profile defaults
158
+ nodes/
159
+ research.py ← Research node
160
+ architect.py ← Architect node
161
+ prd.py ← PRD node
162
+ dev.py ← Dev node (opencode / Claude Code)
163
+ reviewer.py ← Reviewer node
164
+ qa.py ← QA node
165
+ hitl.py ← Human-in-the-loop node
166
+ agents/
167
+ architect.md ← opencode agent definition
168
+ researcher.md ← opencode agent definition
169
+ reviewer.md ← opencode agent definition
170
+ qa.md ← opencode agent definition
171
+ commands/
172
+ init.py ← karajan init
173
+ task.py ← karajan task
174
+ status.py ← karajan status
175
+ models.py ← karajan models
176
+ config.py ← karajan config
177
+ scripts/
178
+ tracker.py ← GitHub Issues + Jira unified
179
+ github.py ← PR creation
180
+ pyproject.toml ← pip install config
181
+ README.md
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Config hierarchy
187
+
188
+ ```
189
+ CLI flags (--model, --dev-model, --profile)
190
+ > environment variables (KARAJAN_MODEL_*, GEMINI_API_KEY, etc.)
191
+ > .karajan/config.toml (per project)
192
+ > karajan/profiles/<profile>.toml (package defaults)
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Requirements
198
+
199
+ - Python 3.11+
200
+ - [opencode](https://opencode.ai) (`npm install -g opencode`) — solo profile
201
+ - [Claude Code](https://claude.ai/code) — team profile
202
+ - Ollama (optional, for local models): https://ollama.com
203
+
204
+ ---
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,4 @@
1
+ """Karajan package."""
2
+
3
+ __version__ = "0.1.0"
4
+
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: architect
3
+ description: Technical planning agent. Designs solutions, writes ADRs, and produces implementation specs. Use before any coding task.
4
+ model: google/gemini-2.0-flash
5
+ temperature: 0.3
6
+ mode: primary
7
+ tools:
8
+ - read
9
+ - write
10
+ permissions:
11
+ allow:
12
+ - read: "**"
13
+ - write: ".agent/DECISIONS.md"
14
+ - write: ".workflow-state/**"
15
+ deny:
16
+ - write: "src/**"
17
+ - write: "*.ts"
18
+ - write: "*.py"
19
+ - bash: "*"
20
+ ---
21
+
22
+ You are a Software Architect with deep experience in TypeScript, Node.js, event-driven systems, and complex integrations.
23
+
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: qa
3
+ description: QA validation agent. Runs tests, verifies acceptance criteria, surfaces edge cases.
4
+ model: ollama/qwen2.5:7b
5
+ temperature: 0.1
6
+ mode: primary
7
+ tools:
8
+ - read
9
+ - bash
10
+ ---
11
+
12
+ You are a QA Engineer.
13
+
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: researcher
3
+ description: Technical research agent. Gathers context, finds patterns, identifies risks before design begins. Always runs before architect.
4
+ model: google/gemini-2.0-flash
5
+ temperature: 0.2
6
+ mode: primary
7
+ tools:
8
+ - read
9
+ - web_search
10
+ ---
11
+
12
+ You are a Technical Research Specialist.
13
+
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: reviewer
3
+ description: Code review agent. Reviews diffs for correctness, conventions, and quality.
4
+ model: google/gemini-2.0-flash
5
+ temperature: 0.1
6
+ mode: primary
7
+ tools:
8
+ - read
9
+ - bash
10
+ ---
11
+
12
+ You are a Tech Lead doing code review.
13
+
@@ -0,0 +1,102 @@
1
+ """Karajan CLI entry point."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from typing import Sequence
7
+
8
+ from . import __version__
9
+
10
+
11
+ def _build_parser() -> argparse.ArgumentParser:
12
+ parser = argparse.ArgumentParser(
13
+ prog="karajan",
14
+ description="An AI agent harness for software engineers. Built on opencode + LangGraph.",
15
+ )
16
+ subparsers = parser.add_subparsers(dest="command")
17
+
18
+ init_parser = subparsers.add_parser("init", help="Initialize in current project")
19
+ init_parser.add_argument("--profile", "-p", choices=["solo", "team"], default=None)
20
+ init_parser.add_argument("--force", "-f", action="store_true")
21
+
22
+ task_parser = subparsers.add_parser("task", help="Run full workflow")
23
+ task_parser.add_argument("ticket")
24
+ task_parser.add_argument("--model", "-m", default=None)
25
+ task_parser.add_argument("--dev-model", default=None)
26
+ task_parser.add_argument("--qa-model", default=None)
27
+ task_parser.add_argument("--from-stage", default=None)
28
+ task_parser.add_argument("--dry-run", action="store_true")
29
+
30
+ status_parser = subparsers.add_parser("status", help="Show active workflows")
31
+ status_parser.add_argument("ticket", nargs="?", default=None)
32
+
33
+ models_parser = subparsers.add_parser("models", help="List available models")
34
+ models_parser.add_argument("--provider", default=None)
35
+
36
+ config_parser = subparsers.add_parser("config", help="Show current project config")
37
+ config_parser.add_argument("--show", action="store_true")
38
+ config_parser.add_argument("--set", dest="set_key", default=None)
39
+
40
+ subparsers.add_parser("version", help="Show version")
41
+ subparsers.add_parser("profiles", help="List profiles and defaults")
42
+
43
+ logs_parser = subparsers.add_parser("logs", help="Show run logs and cost summary")
44
+ logs_parser.add_argument("ticket", nargs="?", default=None)
45
+ logs_parser.add_argument("--tail", "-n", type=int, default=20)
46
+ logs_parser.add_argument("--summary", "-s", action="store_true")
47
+
48
+ return parser
49
+
50
+
51
+ def app(argv: Sequence[str] | None = None) -> None:
52
+ parser = _build_parser()
53
+ args = parser.parse_args(argv)
54
+
55
+ if args.command is None:
56
+ parser.print_help()
57
+ raise SystemExit(0)
58
+
59
+ if args.command == "init":
60
+ from .commands.init import run_init
61
+
62
+ run_init(profile=args.profile, force=args.force)
63
+ elif args.command == "task":
64
+ from .commands.task import run_task
65
+
66
+ run_task(
67
+ ticket=args.ticket,
68
+ model=args.model,
69
+ dev_model=args.dev_model,
70
+ qa_model=args.qa_model,
71
+ from_stage=args.from_stage,
72
+ dry_run=args.dry_run,
73
+ )
74
+ elif args.command == "status":
75
+ from .commands.status import run_status
76
+
77
+ run_status(ticket=args.ticket)
78
+ elif args.command == "models":
79
+ from .commands.models import run_models
80
+
81
+ run_models(provider=args.provider)
82
+ elif args.command == "config":
83
+ from .commands.config import run_config
84
+
85
+ run_config(show=args.show, set_key=args.set_key)
86
+ elif args.command == "version":
87
+ print(f"karajan v{__version__}")
88
+ elif args.command == "profiles":
89
+ from .commands.profiles import run_profiles
90
+
91
+ run_profiles()
92
+ elif args.command == "logs":
93
+ from .commands.logs import run_logs
94
+
95
+ run_logs(ticket=args.ticket, tail=args.tail, summary=args.summary)
96
+ else:
97
+ parser.error(f"Unknown command: {args.command}")
98
+
99
+
100
+ if __name__ == "__main__":
101
+ app()
102
+
@@ -0,0 +1,2 @@
1
+ """Karajan command implementations."""
2
+
@@ -0,0 +1,89 @@
1
+ """Show or update Karajan config."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import tomllib
6
+ from pathlib import Path
7
+
8
+ from ..config import dump_config_toml, load_config
9
+
10
+
11
+ def run_config(show: bool = False, set_key: str = None):
12
+ config_path = Path(".karajan/config.toml")
13
+
14
+ if set_key:
15
+ _set_config_value(config_path, set_key)
16
+ return
17
+
18
+ if show or not config_path.exists():
19
+ config = load_config()
20
+ print(dump_config_toml(config.model_dump()))
21
+ return
22
+
23
+ print(config_path.read_text(encoding="utf-8"))
24
+
25
+
26
+ def _set_config_value(config_path: Path, assignment: str):
27
+ if not config_path.exists():
28
+ print("No .karajan/config.toml found. Run karajan init first.")
29
+ raise SystemExit(1)
30
+ if "=" not in assignment:
31
+ print(f"Invalid config assignment: {assignment}")
32
+ raise SystemExit(1)
33
+ key, raw_value = (part.strip() for part in assignment.split("=", 1))
34
+ parts = [part for part in key.split(".") if part]
35
+ if not key or len(parts) != len(key.split(".")) or len(parts) > 2:
36
+ print(f"Invalid config path: {key}")
37
+ raise SystemExit(1)
38
+ with config_path.open("rb") as handle:
39
+ config = tomllib.load(handle)
40
+ target = _coerce_config_value(raw_value)
41
+ if len(parts) == 1:
42
+ if isinstance(config.get(parts[0]), dict):
43
+ print(f"Invalid config path: {key}")
44
+ raise SystemExit(1)
45
+ config[parts[0]] = target
46
+ else:
47
+ section, field = parts
48
+ section_data = config.get(section)
49
+ if section_data is None:
50
+ config[section] = {field: target}
51
+ elif isinstance(section_data, dict):
52
+ section_data[field] = target
53
+ else:
54
+ print(f"Invalid config path: {key}")
55
+ raise SystemExit(1)
56
+ _write_config(config_path, config)
57
+ print(f"✅ Set {key} = {_format_config_value(target)}")
58
+
59
+
60
+ def _coerce_config_value(raw_value: str):
61
+ value = raw_value.strip()
62
+ lowered = value.lower()
63
+ if lowered == "true":
64
+ return True
65
+ if lowered == "false":
66
+ return False
67
+ try:
68
+ return int(value)
69
+ except ValueError:
70
+ try:
71
+ return float(value)
72
+ except ValueError:
73
+ return value
74
+
75
+
76
+ def _format_config_value(value) -> str:
77
+ if isinstance(value, bool):
78
+ return "true" if value else "false"
79
+ return str(value)
80
+
81
+
82
+ def _write_config(config_path: Path, config: dict):
83
+ try:
84
+ import tomli_w
85
+ except ModuleNotFoundError:
86
+ config_path.write_text(dump_config_toml(config), encoding="utf-8")
87
+ return
88
+ with config_path.open("wb") as handle:
89
+ tomli_w.dump(config, handle)