galangal-orchestrate 0.2.11__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.

Potentially problematic release.


This version of galangal-orchestrate might be problematic. Click here for more details.

Files changed (49) hide show
  1. galangal/__init__.py +8 -0
  2. galangal/__main__.py +6 -0
  3. galangal/ai/__init__.py +6 -0
  4. galangal/ai/base.py +55 -0
  5. galangal/ai/claude.py +278 -0
  6. galangal/ai/gemini.py +38 -0
  7. galangal/cli.py +296 -0
  8. galangal/commands/__init__.py +42 -0
  9. galangal/commands/approve.py +187 -0
  10. galangal/commands/complete.py +268 -0
  11. galangal/commands/init.py +173 -0
  12. galangal/commands/list.py +20 -0
  13. galangal/commands/pause.py +40 -0
  14. galangal/commands/prompts.py +98 -0
  15. galangal/commands/reset.py +43 -0
  16. galangal/commands/resume.py +29 -0
  17. galangal/commands/skip.py +216 -0
  18. galangal/commands/start.py +144 -0
  19. galangal/commands/status.py +62 -0
  20. galangal/commands/switch.py +28 -0
  21. galangal/config/__init__.py +13 -0
  22. galangal/config/defaults.py +133 -0
  23. galangal/config/loader.py +113 -0
  24. galangal/config/schema.py +155 -0
  25. galangal/core/__init__.py +18 -0
  26. galangal/core/artifacts.py +66 -0
  27. galangal/core/state.py +248 -0
  28. galangal/core/tasks.py +170 -0
  29. galangal/core/workflow.py +835 -0
  30. galangal/prompts/__init__.py +5 -0
  31. galangal/prompts/builder.py +166 -0
  32. galangal/prompts/defaults/design.md +54 -0
  33. galangal/prompts/defaults/dev.md +39 -0
  34. galangal/prompts/defaults/docs.md +46 -0
  35. galangal/prompts/defaults/pm.md +75 -0
  36. galangal/prompts/defaults/qa.md +49 -0
  37. galangal/prompts/defaults/review.md +65 -0
  38. galangal/prompts/defaults/security.md +68 -0
  39. galangal/prompts/defaults/test.md +59 -0
  40. galangal/ui/__init__.py +5 -0
  41. galangal/ui/console.py +123 -0
  42. galangal/ui/tui.py +1065 -0
  43. galangal/validation/__init__.py +5 -0
  44. galangal/validation/runner.py +395 -0
  45. galangal_orchestrate-0.2.11.dist-info/METADATA +278 -0
  46. galangal_orchestrate-0.2.11.dist-info/RECORD +49 -0
  47. galangal_orchestrate-0.2.11.dist-info/WHEEL +4 -0
  48. galangal_orchestrate-0.2.11.dist-info/entry_points.txt +2 -0
  49. galangal_orchestrate-0.2.11.dist-info/licenses/LICENSE +674 -0
galangal/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """
2
+ Galangal Orchestrate - AI-driven development workflow orchestrator.
3
+
4
+ A deterministic workflow system that guides AI assistants through
5
+ structured development stages: PM -> DESIGN -> DEV -> TEST -> QA -> REVIEW -> DOCS.
6
+ """
7
+
8
+ __version__ = "0.2.11"
galangal/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m galangal`."""
2
+
3
+ from galangal.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,6 @@
1
+ """AI backend abstractions."""
2
+
3
+ from galangal.ai.base import AIBackend
4
+ from galangal.ai.claude import ClaudeBackend
5
+
6
+ __all__ = ["AIBackend", "ClaudeBackend"]
galangal/ai/base.py ADDED
@@ -0,0 +1,55 @@
1
+ """
2
+ Abstract base class for AI backends.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Optional, TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from galangal.ui.tui import StageUI
10
+
11
+
12
+ class AIBackend(ABC):
13
+ """Abstract base class for AI backends."""
14
+
15
+ @abstractmethod
16
+ def invoke(
17
+ self,
18
+ prompt: str,
19
+ timeout: int = 14400,
20
+ max_turns: int = 200,
21
+ ui: Optional["StageUI"] = None,
22
+ ) -> tuple[bool, str]:
23
+ """
24
+ Invoke the AI with a prompt for a full stage execution.
25
+
26
+ Args:
27
+ prompt: The full prompt to send
28
+ timeout: Maximum time in seconds
29
+ max_turns: Maximum conversation turns
30
+ ui: Optional TUI for progress display
31
+
32
+ Returns:
33
+ (success, output) tuple
34
+ """
35
+ pass
36
+
37
+ @abstractmethod
38
+ def generate_text(self, prompt: str, timeout: int = 30) -> str:
39
+ """
40
+ Simple text generation (for PR titles, commit messages, task names).
41
+
42
+ Args:
43
+ prompt: The prompt to send
44
+ timeout: Maximum time in seconds
45
+
46
+ Returns:
47
+ Generated text, or empty string on failure
48
+ """
49
+ pass
50
+
51
+ @property
52
+ @abstractmethod
53
+ def name(self) -> str:
54
+ """Return the backend name."""
55
+ pass
galangal/ai/claude.py ADDED
@@ -0,0 +1,278 @@
1
+ """
2
+ Claude CLI backend implementation.
3
+ """
4
+
5
+ import json
6
+ import select
7
+ import subprocess
8
+ import time
9
+ from typing import Optional, TYPE_CHECKING
10
+
11
+ from galangal.ai.base import AIBackend
12
+ from galangal.config.loader import get_project_root
13
+
14
+ if TYPE_CHECKING:
15
+ from galangal.ui.tui import StageUI
16
+
17
+ # Reference to global pause flag
18
+ _pause_requested = False
19
+
20
+
21
+ def set_pause_requested(value: bool) -> None:
22
+ """Set the pause requested flag."""
23
+ global _pause_requested
24
+ _pause_requested = value
25
+
26
+
27
+ def get_pause_requested() -> bool:
28
+ """Get the pause requested flag."""
29
+ return _pause_requested
30
+
31
+
32
+ class ClaudeBackend(AIBackend):
33
+ """Claude CLI backend."""
34
+
35
+ @property
36
+ def name(self) -> str:
37
+ return "claude"
38
+
39
+ def invoke(
40
+ self,
41
+ prompt: str,
42
+ timeout: int = 14400,
43
+ max_turns: int = 200,
44
+ ui: Optional["StageUI"] = None,
45
+ ) -> tuple[bool, str]:
46
+ """Invoke Claude Code with a prompt."""
47
+ cmd = [
48
+ "claude",
49
+ "-p",
50
+ prompt,
51
+ "--output-format",
52
+ "stream-json",
53
+ "--verbose",
54
+ "--max-turns",
55
+ str(max_turns),
56
+ "--permission-mode",
57
+ "acceptEdits",
58
+ ]
59
+
60
+ try:
61
+ process = subprocess.Popen(
62
+ cmd,
63
+ cwd=get_project_root(),
64
+ stdout=subprocess.PIPE,
65
+ stderr=subprocess.PIPE,
66
+ text=True,
67
+ )
68
+
69
+ output_lines: list[str] = []
70
+ last_status_time = time.time()
71
+ start_time = time.time()
72
+ pending_tools: list[tuple[str, str]] = []
73
+
74
+ if ui:
75
+ ui.set_status("starting", "initializing Claude")
76
+
77
+ while True:
78
+ retcode = process.poll()
79
+
80
+ if process.stdout:
81
+ ready, _, _ = select.select([process.stdout], [], [], 0.5)
82
+
83
+ if ready:
84
+ line = process.stdout.readline()
85
+ if line:
86
+ output_lines.append(line)
87
+ if ui:
88
+ ui.add_raw_line(line)
89
+ self._process_stream_line(line, ui, pending_tools)
90
+ else:
91
+ idle_time = time.time() - last_status_time
92
+ if idle_time > 3 and ui:
93
+ if pending_tools:
94
+ tool_name = pending_tools[-1][1]
95
+ ui.set_status("waiting", f"{tool_name}...")
96
+ else:
97
+ ui.set_status("waiting", "API response")
98
+ last_status_time = time.time()
99
+
100
+ if retcode is not None:
101
+ break
102
+
103
+ if _pause_requested:
104
+ process.terminate()
105
+ try:
106
+ process.wait(timeout=5)
107
+ except subprocess.TimeoutExpired:
108
+ process.kill()
109
+ if ui:
110
+ ui.add_activity("Paused by user request", "⏸️")
111
+ ui.finish(success=False)
112
+ return False, "PAUSED: User requested pause"
113
+
114
+ if time.time() - start_time > timeout:
115
+ process.kill()
116
+ if ui:
117
+ ui.add_activity(f"Timeout after {timeout}s", "❌")
118
+ return False, f"Claude timed out after {timeout}s"
119
+
120
+ remaining_out, _ = process.communicate(timeout=10)
121
+ if remaining_out:
122
+ output_lines.append(remaining_out)
123
+
124
+ full_output = "".join(output_lines)
125
+
126
+ if "max turns" in full_output.lower() or "reached max" in full_output.lower():
127
+ if ui:
128
+ ui.add_activity("Max turns reached", "❌")
129
+ return (
130
+ False,
131
+ "Claude reached max turns limit - task may be too complex or stuck in a loop",
132
+ )
133
+
134
+ result_text = ""
135
+ for line in output_lines:
136
+ try:
137
+ data = json.loads(line.strip())
138
+ if data.get("type") == "result":
139
+ result_text = data.get("result", "")
140
+ if ui:
141
+ ui.set_turns(data.get("num_turns", 0))
142
+ break
143
+ except (json.JSONDecodeError, KeyError):
144
+ pass
145
+
146
+ if process.returncode == 0:
147
+ return True, result_text or full_output
148
+ return False, f"Claude failed (exit {process.returncode}):\n{full_output}"
149
+
150
+ except subprocess.TimeoutExpired:
151
+ process.kill()
152
+ return False, f"Claude timed out after {timeout}s"
153
+ except Exception as e:
154
+ return False, f"Claude invocation error: {e}"
155
+
156
+ def _process_stream_line(
157
+ self,
158
+ line: str,
159
+ ui: Optional["StageUI"],
160
+ pending_tools: list[tuple[str, str]],
161
+ ) -> None:
162
+ """Process a single line of streaming output."""
163
+ try:
164
+ data = json.loads(line.strip())
165
+ msg_type = data.get("type", "")
166
+
167
+ if msg_type == "assistant" and "tool_use" in str(data):
168
+ self._handle_assistant_message(data, ui, pending_tools)
169
+ elif msg_type == "user":
170
+ self._handle_user_message(data, ui, pending_tools)
171
+ elif msg_type == "system":
172
+ self._handle_system_message(data, ui)
173
+
174
+ except (json.JSONDecodeError, KeyError, TypeError):
175
+ pass
176
+
177
+ def _handle_assistant_message(
178
+ self,
179
+ data: dict,
180
+ ui: Optional["StageUI"],
181
+ pending_tools: list[tuple[str, str]],
182
+ ) -> None:
183
+ """Handle assistant message with tool use."""
184
+ content = data.get("message", {}).get("content", [])
185
+
186
+ for item in content:
187
+ if item.get("type") == "tool_use":
188
+ tool_name = item.get("name", "")
189
+ tool_id = item.get("id", "")
190
+ if tool_id:
191
+ pending_tools.append((tool_id, tool_name))
192
+
193
+ if ui:
194
+ if tool_name in ["Write", "Edit"]:
195
+ tool_input = item.get("input", {})
196
+ file_path = tool_input.get("file_path", "") or tool_input.get("path", "")
197
+ if file_path:
198
+ short_path = file_path.split("/")[-1] if "/" in file_path else file_path
199
+ ui.add_activity(f"{tool_name}: {short_path}", "✏️")
200
+ ui.set_status("writing", short_path)
201
+
202
+ elif tool_name == "Read":
203
+ tool_input = item.get("input", {})
204
+ file_path = tool_input.get("file_path", "") or tool_input.get("path", "")
205
+ if file_path:
206
+ short_path = file_path.split("/")[-1] if "/" in file_path else file_path
207
+ ui.add_activity(f"Read: {short_path}", "📖")
208
+ ui.set_status("reading", short_path)
209
+
210
+ elif tool_name == "Bash":
211
+ cmd_preview = item.get("input", {}).get("command", "")[:140]
212
+ ui.add_activity(f"Bash: {cmd_preview}", "🔧")
213
+ ui.set_status("running", "bash")
214
+
215
+ elif tool_name in ["Grep", "Glob"]:
216
+ pattern = item.get("input", {}).get("pattern", "")[:80]
217
+ ui.add_activity(f"{tool_name}: {pattern}", "🔍")
218
+ ui.set_status("searching", pattern[:40])
219
+
220
+ elif tool_name == "Task":
221
+ desc = item.get("input", {}).get("description", "agent")
222
+ ui.add_activity(f"Task: {desc}", "🤖")
223
+ ui.set_status("agent", desc[:25])
224
+
225
+ elif tool_name not in ["TodoWrite"]:
226
+ ui.add_activity(f"{tool_name}", "⚡")
227
+ ui.set_status("executing", tool_name)
228
+
229
+ elif item.get("type") == "thinking":
230
+ if ui:
231
+ ui.set_status("thinking")
232
+
233
+ def _handle_user_message(
234
+ self,
235
+ data: dict,
236
+ ui: Optional["StageUI"],
237
+ pending_tools: list[tuple[str, str]],
238
+ ) -> None:
239
+ """Handle user message with tool results."""
240
+ content = data.get("message", {}).get("content", [])
241
+
242
+ for item in content:
243
+ if item.get("type") == "tool_result":
244
+ tool_id = item.get("tool_use_id", "")
245
+ is_error = item.get("is_error", False)
246
+ pending_tools[:] = [
247
+ (tid, tname) for tid, tname in pending_tools if tid != tool_id
248
+ ]
249
+ if is_error and ui:
250
+ ui.set_status("error", "tool failed")
251
+
252
+ def _handle_system_message(self, data: dict, ui: Optional["StageUI"]) -> None:
253
+ """Handle system messages."""
254
+ message = data.get("message", "")
255
+ subtype = data.get("subtype", "")
256
+
257
+ if "rate" in message.lower():
258
+ if ui:
259
+ ui.add_activity("Rate limited - waiting", "🚦")
260
+ ui.set_status("rate_limited", "waiting...")
261
+ elif subtype and ui:
262
+ ui.set_status(subtype)
263
+
264
+ def generate_text(self, prompt: str, timeout: int = 30) -> str:
265
+ """Simple text generation."""
266
+ try:
267
+ result = subprocess.run(
268
+ ["claude", "-p", prompt, "--output-format", "text"],
269
+ cwd=get_project_root(),
270
+ capture_output=True,
271
+ text=True,
272
+ timeout=timeout,
273
+ )
274
+ if result.returncode == 0 and result.stdout.strip():
275
+ return result.stdout.strip()
276
+ except (subprocess.TimeoutExpired, Exception):
277
+ pass
278
+ return ""
galangal/ai/gemini.py ADDED
@@ -0,0 +1,38 @@
1
+ """
2
+ Gemini backend implementation (stub for future use).
3
+ """
4
+
5
+ from typing import Optional, TYPE_CHECKING
6
+
7
+ from galangal.ai.base import AIBackend
8
+
9
+ if TYPE_CHECKING:
10
+ from galangal.ui.tui import StageUI
11
+
12
+
13
+ class GeminiBackend(AIBackend):
14
+ """
15
+ Gemini backend (stub implementation).
16
+
17
+ TODO: Implement when Gemini CLI or API support is added.
18
+ """
19
+
20
+ @property
21
+ def name(self) -> str:
22
+ return "gemini"
23
+
24
+ def invoke(
25
+ self,
26
+ prompt: str,
27
+ timeout: int = 14400,
28
+ max_turns: int = 200,
29
+ ui: Optional["StageUI"] = None,
30
+ ) -> tuple[bool, str]:
31
+ """Invoke Gemini with a prompt."""
32
+ # TODO: Implement Gemini invocation
33
+ return False, "Gemini backend not yet implemented"
34
+
35
+ def generate_text(self, prompt: str, timeout: int = 30) -> str:
36
+ """Simple text generation with Gemini."""
37
+ # TODO: Implement Gemini text generation
38
+ return ""
galangal/cli.py ADDED
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Galangal Orchestrate - AI-Driven Development Workflow CLI
4
+
5
+ Usage:
6
+ galangal init - Initialize in current project
7
+ galangal start "task description" - Start new task
8
+ galangal start "desc" --name my-task - Start with explicit name
9
+ galangal list - List all tasks
10
+ galangal switch <task-name> - Switch active task
11
+ galangal status - Show active task status
12
+ galangal resume - Continue active task
13
+ galangal pause - Pause task for break/shutdown
14
+ galangal approve - Record plan approval
15
+ galangal approve-design - Record design review
16
+ galangal skip-design - Skip design for trivial tasks
17
+ galangal skip-security - Skip security for non-code changes
18
+ galangal reset - Delete active task
19
+ galangal complete - Move task to done/, create PR
20
+ galangal prompts export - Export default prompts for customization
21
+ """
22
+
23
+ import argparse
24
+ import sys
25
+
26
+
27
+ def main() -> int:
28
+ parser = argparse.ArgumentParser(
29
+ description="Galangal Orchestrate - AI-Driven Development Workflow",
30
+ formatter_class=argparse.RawDescriptionHelpFormatter,
31
+ epilog="""
32
+ Examples:
33
+ galangal init
34
+ galangal start "Add user authentication"
35
+ galangal start "Add auth" --name add-auth-feature
36
+ galangal list
37
+ galangal switch add-auth-feature
38
+ galangal status
39
+ galangal resume
40
+ galangal pause
41
+ galangal approve
42
+ galangal approve-design
43
+ galangal skip-design
44
+ galangal skip-to DEV
45
+ galangal skip-to TEST --resume
46
+ galangal complete
47
+ galangal reset
48
+ galangal prompts export
49
+
50
+ Task Types:
51
+ At task start, you'll select from:
52
+ [1] Feature - New functionality (full workflow)
53
+ [2] Bug Fix - Fix broken behavior (skip design)
54
+ [3] Refactor - Restructure code (skip design, security)
55
+ [4] Chore - Dependencies, config, tooling
56
+ [5] Docs - Documentation only (minimal stages)
57
+ [6] Hotfix - Critical fix (expedited)
58
+
59
+ Workflow:
60
+ PM -> DESIGN -> PREFLIGHT -> DEV -> MIGRATION* -> TEST ->
61
+ CONTRACT* -> QA -> BENCHMARK* -> SECURITY -> REVIEW -> DOCS -> COMPLETE
62
+
63
+ * = Conditional stages (auto-skipped if condition not met)
64
+
65
+ Tip: Press Ctrl+C during execution to pause gracefully.
66
+ """,
67
+ )
68
+
69
+ subparsers = parser.add_subparsers(dest="command", required=True)
70
+
71
+ # init
72
+ init_parser = subparsers.add_parser("init", help="Initialize galangal in current project")
73
+ init_parser.set_defaults(func=_cmd_init)
74
+
75
+ # start
76
+ start_parser = subparsers.add_parser("start", help="Start new task")
77
+ start_parser.add_argument(
78
+ "description", nargs="*", help="Task description (prompted if not provided)"
79
+ )
80
+ start_parser.add_argument(
81
+ "--name", "-n", help="Task name (auto-generated if not provided)"
82
+ )
83
+ start_parser.add_argument(
84
+ "--type", "-t",
85
+ choices=["feature", "bugfix", "refactor", "chore", "docs", "hotfix", "1", "2", "3", "4", "5", "6"],
86
+ help="Task type (skip interactive selection)"
87
+ )
88
+ start_parser.set_defaults(func=_cmd_start)
89
+
90
+ # list
91
+ list_parser = subparsers.add_parser("list", help="List all tasks")
92
+ list_parser.set_defaults(func=_cmd_list)
93
+
94
+ # switch
95
+ switch_parser = subparsers.add_parser("switch", help="Switch active task")
96
+ switch_parser.add_argument("task_name", help="Task name to switch to")
97
+ switch_parser.set_defaults(func=_cmd_switch)
98
+
99
+ # resume
100
+ resume_parser = subparsers.add_parser("resume", help="Resume active task")
101
+ resume_parser.set_defaults(func=_cmd_resume)
102
+
103
+ # pause
104
+ pause_parser = subparsers.add_parser("pause", help="Pause task for break/shutdown")
105
+ pause_parser.set_defaults(func=_cmd_pause)
106
+
107
+ # status
108
+ status_parser = subparsers.add_parser("status", help="Show active task status")
109
+ status_parser.set_defaults(func=_cmd_status)
110
+
111
+ # approve
112
+ approve_parser = subparsers.add_parser("approve", help="Record plan approval")
113
+ approve_parser.set_defaults(func=_cmd_approve)
114
+
115
+ # approve-design
116
+ approve_design_parser = subparsers.add_parser(
117
+ "approve-design", help="Record design review approval"
118
+ )
119
+ approve_design_parser.set_defaults(func=_cmd_approve_design)
120
+
121
+ # skip-design
122
+ skip_design_parser = subparsers.add_parser(
123
+ "skip-design", help="Skip design stage for trivial tasks"
124
+ )
125
+ skip_design_parser.set_defaults(func=_cmd_skip_design)
126
+
127
+ # skip-security
128
+ skip_security_parser = subparsers.add_parser(
129
+ "skip-security", help="Skip security stage for non-code changes"
130
+ )
131
+ skip_security_parser.set_defaults(func=_cmd_skip_security)
132
+
133
+ # skip-migration
134
+ skip_migration_parser = subparsers.add_parser(
135
+ "skip-migration", help="Skip migration stage"
136
+ )
137
+ skip_migration_parser.set_defaults(func=_cmd_skip_migration)
138
+
139
+ # skip-contract
140
+ skip_contract_parser = subparsers.add_parser(
141
+ "skip-contract", help="Skip contract stage"
142
+ )
143
+ skip_contract_parser.set_defaults(func=_cmd_skip_contract)
144
+
145
+ # skip-benchmark
146
+ skip_benchmark_parser = subparsers.add_parser(
147
+ "skip-benchmark", help="Skip benchmark stage"
148
+ )
149
+ skip_benchmark_parser.set_defaults(func=_cmd_skip_benchmark)
150
+
151
+ # skip-to
152
+ skip_to_parser = subparsers.add_parser(
153
+ "skip-to", help="Jump to a specific stage (for debugging/re-running)"
154
+ )
155
+ skip_to_parser.add_argument(
156
+ "stage", help="Target stage (e.g., DEV, TEST, SECURITY)"
157
+ )
158
+ skip_to_parser.add_argument(
159
+ "--force", "-f", action="store_true", help="Skip confirmation"
160
+ )
161
+ skip_to_parser.add_argument(
162
+ "--resume", "-r", action="store_true", help="Resume workflow immediately after jumping"
163
+ )
164
+ skip_to_parser.set_defaults(func=_cmd_skip_to)
165
+
166
+ # reset
167
+ reset_parser = subparsers.add_parser("reset", help="Delete active task")
168
+ reset_parser.add_argument(
169
+ "--force", "-f", action="store_true", help="Skip confirmation"
170
+ )
171
+ reset_parser.set_defaults(func=_cmd_reset)
172
+
173
+ # complete
174
+ complete_parser = subparsers.add_parser(
175
+ "complete", help="Move completed task to done/, create PR"
176
+ )
177
+ complete_parser.add_argument(
178
+ "--force", "-f", action="store_true", help="Continue on commit errors"
179
+ )
180
+ complete_parser.set_defaults(func=_cmd_complete)
181
+
182
+ # prompts
183
+ prompts_parser = subparsers.add_parser("prompts", help="Manage prompts")
184
+ prompts_subparsers = prompts_parser.add_subparsers(dest="prompts_command")
185
+ prompts_export = prompts_subparsers.add_parser(
186
+ "export", help="Export default prompts for customization"
187
+ )
188
+ prompts_export.set_defaults(func=_cmd_prompts_export)
189
+ prompts_show = prompts_subparsers.add_parser(
190
+ "show", help="Show effective prompt for a stage"
191
+ )
192
+ prompts_show.add_argument("stage", help="Stage name (e.g., pm, dev, test)")
193
+ prompts_show.set_defaults(func=_cmd_prompts_show)
194
+
195
+ args = parser.parse_args()
196
+ return args.func(args)
197
+
198
+
199
+ # Command wrappers that import lazily to speed up CLI startup
200
+ def _cmd_init(args):
201
+ from galangal.commands.init import cmd_init
202
+ return cmd_init(args)
203
+
204
+
205
+ def _cmd_start(args):
206
+ from galangal.commands.start import cmd_start
207
+ return cmd_start(args)
208
+
209
+
210
+ def _cmd_list(args):
211
+ from galangal.commands.list import cmd_list
212
+ return cmd_list(args)
213
+
214
+
215
+ def _cmd_switch(args):
216
+ from galangal.commands.switch import cmd_switch
217
+ return cmd_switch(args)
218
+
219
+
220
+ def _cmd_resume(args):
221
+ from galangal.commands.resume import cmd_resume
222
+ return cmd_resume(args)
223
+
224
+
225
+ def _cmd_pause(args):
226
+ from galangal.commands.pause import cmd_pause
227
+ return cmd_pause(args)
228
+
229
+
230
+ def _cmd_status(args):
231
+ from galangal.commands.status import cmd_status
232
+ return cmd_status(args)
233
+
234
+
235
+ def _cmd_approve(args):
236
+ from galangal.commands.approve import cmd_approve
237
+ return cmd_approve(args)
238
+
239
+
240
+ def _cmd_approve_design(args):
241
+ from galangal.commands.approve import cmd_approve_design
242
+ return cmd_approve_design(args)
243
+
244
+
245
+ def _cmd_skip_design(args):
246
+ from galangal.commands.skip import cmd_skip_design
247
+ return cmd_skip_design(args)
248
+
249
+
250
+ def _cmd_skip_security(args):
251
+ from galangal.commands.skip import cmd_skip_security
252
+ return cmd_skip_security(args)
253
+
254
+
255
+ def _cmd_skip_migration(args):
256
+ from galangal.commands.skip import cmd_skip_migration
257
+ return cmd_skip_migration(args)
258
+
259
+
260
+ def _cmd_skip_contract(args):
261
+ from galangal.commands.skip import cmd_skip_contract
262
+ return cmd_skip_contract(args)
263
+
264
+
265
+ def _cmd_skip_benchmark(args):
266
+ from galangal.commands.skip import cmd_skip_benchmark
267
+ return cmd_skip_benchmark(args)
268
+
269
+
270
+ def _cmd_skip_to(args):
271
+ from galangal.commands.skip import cmd_skip_to
272
+ return cmd_skip_to(args)
273
+
274
+
275
+ def _cmd_reset(args):
276
+ from galangal.commands.reset import cmd_reset
277
+ return cmd_reset(args)
278
+
279
+
280
+ def _cmd_complete(args):
281
+ from galangal.commands.complete import cmd_complete
282
+ return cmd_complete(args)
283
+
284
+
285
+ def _cmd_prompts_export(args):
286
+ from galangal.commands.prompts import cmd_prompts_export
287
+ return cmd_prompts_export(args)
288
+
289
+
290
+ def _cmd_prompts_show(args):
291
+ from galangal.commands.prompts import cmd_prompts_show
292
+ return cmd_prompts_show(args)
293
+
294
+
295
+ if __name__ == "__main__":
296
+ sys.exit(main())