up-cli 0.2.0__py3-none-any.whl → 0.5.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.
- up/__init__.py +1 -1
- up/ai_cli.py +229 -0
- up/cli.py +54 -9
- up/commands/agent.py +521 -0
- up/commands/bisect.py +343 -0
- up/commands/branch.py +350 -0
- up/commands/init.py +195 -6
- up/commands/learn.py +1392 -32
- up/commands/memory.py +545 -0
- up/commands/provenance.py +267 -0
- up/commands/review.py +239 -0
- up/commands/start.py +752 -42
- up/commands/status.py +173 -18
- up/commands/sync.py +317 -0
- up/commands/vibe.py +304 -0
- up/context.py +64 -10
- up/core/__init__.py +69 -0
- up/core/checkpoint.py +479 -0
- up/core/provenance.py +364 -0
- up/core/state.py +678 -0
- up/events.py +512 -0
- up/git/__init__.py +37 -0
- up/git/utils.py +270 -0
- up/git/worktree.py +331 -0
- up/learn/__init__.py +155 -0
- up/learn/analyzer.py +227 -0
- up/learn/plan.py +374 -0
- up/learn/research.py +511 -0
- up/learn/utils.py +117 -0
- up/memory.py +1096 -0
- up/parallel.py +551 -0
- up/templates/config/__init__.py +1 -1
- up/templates/docs/SKILL.md +28 -0
- up/templates/docs/__init__.py +341 -0
- up/templates/docs/standards/HEADERS.md +24 -0
- up/templates/docs/standards/STRUCTURE.md +18 -0
- up/templates/docs/standards/TEMPLATES.md +19 -0
- up/templates/loop/__init__.py +92 -32
- up/ui/__init__.py +14 -0
- up/ui/loop_display.py +650 -0
- up/ui/theme.py +137 -0
- {up_cli-0.2.0.dist-info → up_cli-0.5.0.dist-info}/METADATA +160 -15
- up_cli-0.5.0.dist-info/RECORD +55 -0
- up_cli-0.2.0.dist-info/RECORD +0 -23
- {up_cli-0.2.0.dist-info → up_cli-0.5.0.dist-info}/WHEEL +0 -0
- {up_cli-0.2.0.dist-info → up_cli-0.5.0.dist-info}/entry_points.txt +0 -0
up/__init__.py
CHANGED
up/ai_cli.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""AI CLI utilities - shared functions for Claude and Cursor agent CLIs."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# =============================================================================
|
|
14
|
+
# Exceptions
|
|
15
|
+
# =============================================================================
|
|
16
|
+
|
|
17
|
+
class AICliError(Exception):
|
|
18
|
+
"""Base exception for AI CLI operations."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AICliNotFoundError(AICliError):
|
|
23
|
+
"""No AI CLI is installed or available."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AICliTimeoutError(AICliError):
|
|
28
|
+
"""AI CLI command timed out."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, message: str, timeout: int):
|
|
31
|
+
super().__init__(message)
|
|
32
|
+
self.timeout = timeout
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AICliExecutionError(AICliError):
|
|
36
|
+
"""AI CLI command failed to execute."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, message: str, returncode: int, stderr: str = ""):
|
|
39
|
+
super().__init__(message)
|
|
40
|
+
self.returncode = returncode
|
|
41
|
+
self.stderr = stderr
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# =============================================================================
|
|
45
|
+
# CLI Detection
|
|
46
|
+
# =============================================================================
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def check_ai_cli() -> tuple[str, bool]:
|
|
50
|
+
"""Check which AI CLI is available.
|
|
51
|
+
|
|
52
|
+
Checks for Claude CLI first, then Cursor agent CLI.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
(cli_name, available) - e.g., ("claude", True) or ("agent", True)
|
|
56
|
+
"""
|
|
57
|
+
# Check for Claude CLI first
|
|
58
|
+
if shutil.which("claude"):
|
|
59
|
+
return "claude", True
|
|
60
|
+
|
|
61
|
+
# Check for Cursor Agent CLI
|
|
62
|
+
# See: https://cursor.com/docs/cli/overview
|
|
63
|
+
if shutil.which("agent"):
|
|
64
|
+
return "agent", True
|
|
65
|
+
|
|
66
|
+
return "", False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def run_ai_prompt(
|
|
70
|
+
workspace: Path,
|
|
71
|
+
prompt: str,
|
|
72
|
+
cli_name: str,
|
|
73
|
+
timeout: int = 180,
|
|
74
|
+
silent: bool = False
|
|
75
|
+
) -> Optional[str]:
|
|
76
|
+
"""Run a prompt through AI CLI and return the response.
|
|
77
|
+
|
|
78
|
+
Supports both Claude CLI and Cursor agent CLI.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
workspace: Working directory
|
|
82
|
+
prompt: The prompt to send
|
|
83
|
+
cli_name: "claude" or "agent"
|
|
84
|
+
timeout: Timeout in seconds (default 3 minutes)
|
|
85
|
+
silent: If True, don't print warning messages on failure
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Response text or None if failed
|
|
89
|
+
"""
|
|
90
|
+
# Verify CLI is available
|
|
91
|
+
if not shutil.which(cli_name):
|
|
92
|
+
if not silent:
|
|
93
|
+
console.print(f"[yellow]AI CLI '{cli_name}' not found, using basic analysis[/]")
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
if cli_name == "claude":
|
|
98
|
+
# Claude CLI: claude -p "prompt"
|
|
99
|
+
cmd = ["claude", "-p", prompt]
|
|
100
|
+
else:
|
|
101
|
+
# Cursor agent CLI: agent -p "prompt" --output-format text
|
|
102
|
+
# See: https://cursor.com/docs/cli/overview
|
|
103
|
+
cmd = ["agent", "-p", prompt, "--output-format", "text"]
|
|
104
|
+
|
|
105
|
+
result = subprocess.run(
|
|
106
|
+
cmd,
|
|
107
|
+
capture_output=True,
|
|
108
|
+
text=True,
|
|
109
|
+
timeout=timeout,
|
|
110
|
+
cwd=workspace
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
114
|
+
return result.stdout.strip()
|
|
115
|
+
|
|
116
|
+
if not silent and result.stderr:
|
|
117
|
+
console.print(f"[yellow]AI returned error: {result.stderr[:200]}[/]")
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
except subprocess.TimeoutExpired:
|
|
121
|
+
if not silent:
|
|
122
|
+
console.print(f"[yellow]AI analysis timed out ({timeout}s), using basic analysis[/]")
|
|
123
|
+
return None
|
|
124
|
+
except FileNotFoundError:
|
|
125
|
+
if not silent:
|
|
126
|
+
console.print(f"[yellow]AI CLI '{cli_name}' not found, using basic analysis[/]")
|
|
127
|
+
return None
|
|
128
|
+
except Exception as e:
|
|
129
|
+
if not silent:
|
|
130
|
+
console.print(f"[yellow]AI error: {e}, using basic analysis[/]")
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def run_ai_task(
|
|
135
|
+
workspace: Path,
|
|
136
|
+
prompt: str,
|
|
137
|
+
cli_name: str,
|
|
138
|
+
timeout: int = 600,
|
|
139
|
+
max_tokens: int = 0,
|
|
140
|
+
raise_on_error: bool = False
|
|
141
|
+
) -> tuple[bool, str]:
|
|
142
|
+
"""Run an AI task (like implementing code) and return success status.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
workspace: Working directory
|
|
146
|
+
prompt: The task prompt
|
|
147
|
+
cli_name: "claude" or "agent"
|
|
148
|
+
timeout: Timeout in seconds (default 10 minutes)
|
|
149
|
+
max_tokens: Max output tokens (0 = no limit/use CLI default)
|
|
150
|
+
raise_on_error: If True, raise exceptions instead of returning (False, error)
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
(success, output) tuple
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
AICliNotFoundError: If raise_on_error and CLI not found
|
|
157
|
+
AICliTimeoutError: If raise_on_error and command times out
|
|
158
|
+
AICliExecutionError: If raise_on_error and command fails
|
|
159
|
+
"""
|
|
160
|
+
# Verify CLI is available
|
|
161
|
+
if not shutil.which(cli_name):
|
|
162
|
+
error_msg = f"AI CLI '{cli_name}' not found in PATH"
|
|
163
|
+
if raise_on_error:
|
|
164
|
+
raise AICliNotFoundError(error_msg)
|
|
165
|
+
return False, error_msg
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
if cli_name == "claude":
|
|
169
|
+
# Claude CLI: claude -p "prompt" [--max-tokens N]
|
|
170
|
+
cmd = ["claude", "-p", prompt]
|
|
171
|
+
# Note: Claude CLI doesn't use --max-tokens, it uses model limits
|
|
172
|
+
# The output is effectively unlimited for code tasks
|
|
173
|
+
else:
|
|
174
|
+
# Cursor agent with text output for automation
|
|
175
|
+
cmd = ["agent", "-p", prompt, "--output-format", "text"]
|
|
176
|
+
|
|
177
|
+
result = subprocess.run(
|
|
178
|
+
cmd,
|
|
179
|
+
capture_output=True,
|
|
180
|
+
text=True,
|
|
181
|
+
timeout=timeout,
|
|
182
|
+
cwd=workspace
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if result.returncode == 0:
|
|
186
|
+
return True, result.stdout
|
|
187
|
+
else:
|
|
188
|
+
stderr = result.stderr or "Unknown error"
|
|
189
|
+
if raise_on_error:
|
|
190
|
+
raise AICliExecutionError(
|
|
191
|
+
f"AI task failed with exit code {result.returncode}",
|
|
192
|
+
returncode=result.returncode,
|
|
193
|
+
stderr=stderr
|
|
194
|
+
)
|
|
195
|
+
return False, stderr
|
|
196
|
+
|
|
197
|
+
except subprocess.TimeoutExpired:
|
|
198
|
+
error_msg = f"AI task timed out ({timeout}s)"
|
|
199
|
+
if raise_on_error:
|
|
200
|
+
raise AICliTimeoutError(error_msg, timeout=timeout)
|
|
201
|
+
return False, error_msg
|
|
202
|
+
except FileNotFoundError:
|
|
203
|
+
# This shouldn't happen since we check with shutil.which, but handle anyway
|
|
204
|
+
error_msg = f"AI CLI '{cli_name}' not found"
|
|
205
|
+
if raise_on_error:
|
|
206
|
+
raise AICliNotFoundError(error_msg)
|
|
207
|
+
return False, error_msg
|
|
208
|
+
except (AICliError,):
|
|
209
|
+
# Re-raise our custom exceptions
|
|
210
|
+
raise
|
|
211
|
+
except Exception as e:
|
|
212
|
+
error_msg = f"Unexpected error: {e}"
|
|
213
|
+
if raise_on_error:
|
|
214
|
+
raise AICliExecutionError(error_msg, returncode=-1, stderr=str(e))
|
|
215
|
+
return False, error_msg
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def get_ai_cli_install_instructions() -> str:
|
|
219
|
+
"""Get installation instructions for AI CLIs."""
|
|
220
|
+
return """Install one of these AI CLIs:
|
|
221
|
+
|
|
222
|
+
Claude CLI:
|
|
223
|
+
npm install -g @anthropic-ai/claude-code
|
|
224
|
+
|
|
225
|
+
Cursor Agent CLI:
|
|
226
|
+
curl https://cursor.com/install -fsS | bash
|
|
227
|
+
|
|
228
|
+
See: https://cursor.com/docs/cli/overview
|
|
229
|
+
"""
|
up/cli.py
CHANGED
|
@@ -6,21 +6,29 @@ from rich.console import Console
|
|
|
6
6
|
from up.commands.init import init_cmd
|
|
7
7
|
from up.commands.new import new_cmd
|
|
8
8
|
from up.commands.status import status_cmd
|
|
9
|
-
from up.
|
|
9
|
+
from up.learn import learn_cmd
|
|
10
10
|
from up.commands.summarize import summarize_cmd
|
|
11
11
|
from up.commands.dashboard import dashboard_cmd
|
|
12
12
|
from up.commands.start import start_cmd
|
|
13
|
+
from up.commands.memory import memory_cmd
|
|
14
|
+
from up.commands.sync import sync_cmd, hooks_cmd
|
|
15
|
+
from up.commands.vibe import save_cmd, reset_cmd, diff_cmd
|
|
16
|
+
from up.commands.agent import agent as agent_group
|
|
17
|
+
from up.commands.bisect import bisect_cmd, history_cmd
|
|
18
|
+
from up.commands.provenance import provenance as provenance_group
|
|
19
|
+
from up.commands.review import review_cmd
|
|
20
|
+
from up.commands.branch import branch as branch_group
|
|
13
21
|
|
|
14
22
|
console = Console()
|
|
15
23
|
|
|
16
24
|
|
|
17
25
|
@click.group()
|
|
18
|
-
@click.version_option(version="0.
|
|
26
|
+
@click.version_option(version="0.4.0", prog_name="up")
|
|
19
27
|
def main():
|
|
20
|
-
"""up - AI-
|
|
28
|
+
"""up - Verifiable, observable AI-assisted development.
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
Build tools using vibe coding with safety rails, resulting in
|
|
31
|
+
stable, high-performance, and modern software engineering.
|
|
24
32
|
|
|
25
33
|
\b
|
|
26
34
|
Quick Start:
|
|
@@ -28,25 +36,62 @@ def main():
|
|
|
28
36
|
up init Initialize in existing project
|
|
29
37
|
up start Start the product loop
|
|
30
38
|
up status Show system health
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
|
|
40
|
+
\b
|
|
41
|
+
Vibe Coding Safety Rails:
|
|
42
|
+
up save Checkpoint before AI work
|
|
43
|
+
up reset Restore to checkpoint
|
|
44
|
+
up diff Review AI changes
|
|
45
|
+
up start --parallel Run multiple tasks in parallel
|
|
34
46
|
|
|
35
47
|
\b
|
|
36
48
|
Project Templates:
|
|
37
49
|
up new api --template fastapi FastAPI backend
|
|
38
50
|
up new app --template nextjs Next.js frontend
|
|
39
51
|
up new lib --template python-lib Python library
|
|
52
|
+
|
|
53
|
+
\b
|
|
54
|
+
Memory & Learning:
|
|
55
|
+
up memory search <q> Semantic search
|
|
56
|
+
up memory record Record learnings/decisions
|
|
57
|
+
up learn Auto-improve with AI
|
|
58
|
+
|
|
59
|
+
\b
|
|
60
|
+
System:
|
|
61
|
+
up hooks Install git hooks for auto-sync
|
|
62
|
+
up sync Manual sync all systems
|
|
63
|
+
up dashboard Live monitoring
|
|
40
64
|
"""
|
|
41
65
|
pass
|
|
42
66
|
|
|
43
67
|
|
|
68
|
+
# Project commands
|
|
44
69
|
main.add_command(init_cmd, name="init")
|
|
45
70
|
main.add_command(new_cmd, name="new")
|
|
46
|
-
main.add_command(start_cmd, name="start")
|
|
47
71
|
main.add_command(status_cmd, name="status")
|
|
72
|
+
|
|
73
|
+
# Vibe coding commands
|
|
74
|
+
main.add_command(start_cmd, name="start")
|
|
75
|
+
main.add_command(save_cmd, name="save")
|
|
76
|
+
main.add_command(reset_cmd, name="reset")
|
|
77
|
+
main.add_command(diff_cmd, name="diff")
|
|
78
|
+
main.add_command(agent_group, name="agent")
|
|
79
|
+
|
|
80
|
+
# Debugging commands
|
|
81
|
+
main.add_command(bisect_cmd, name="bisect")
|
|
82
|
+
main.add_command(history_cmd, name="history")
|
|
83
|
+
main.add_command(provenance_group, name="provenance")
|
|
84
|
+
main.add_command(review_cmd, name="review")
|
|
85
|
+
main.add_command(branch_group, name="branch")
|
|
86
|
+
|
|
87
|
+
# System commands
|
|
88
|
+
main.add_command(sync_cmd, name="sync")
|
|
89
|
+
main.add_command(hooks_cmd, name="hooks")
|
|
48
90
|
main.add_command(dashboard_cmd, name="dashboard")
|
|
91
|
+
|
|
92
|
+
# Learning & memory
|
|
49
93
|
main.add_command(learn_cmd, name="learn")
|
|
94
|
+
main.add_command(memory_cmd, name="memory")
|
|
50
95
|
main.add_command(summarize_cmd, name="summarize")
|
|
51
96
|
|
|
52
97
|
|