loopflow 0.2.2__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.
- loopflow-0.2.2/.gitignore +8 -0
- loopflow-0.2.2/PKG-INFO +143 -0
- loopflow-0.2.2/README.md +116 -0
- loopflow-0.2.2/pyproject.toml +49 -0
- loopflow-0.2.2/src/loopflow/__init__.py +1 -0
- loopflow-0.2.2/src/loopflow/builtins/__init__.py +15 -0
- loopflow-0.2.2/src/loopflow/builtins/commit_message.txt +24 -0
- loopflow-0.2.2/src/loopflow/builtins/pr_message.txt +46 -0
- loopflow-0.2.2/src/loopflow/cli/__init__.py +74 -0
- loopflow-0.2.2/src/loopflow/cli/meta.py +158 -0
- loopflow-0.2.2/src/loopflow/cli/pr.py +237 -0
- loopflow-0.2.2/src/loopflow/cli/run.py +212 -0
- loopflow-0.2.2/src/loopflow/cli/wt.py +143 -0
- loopflow-0.2.2/src/loopflow/config.py +89 -0
- loopflow-0.2.2/src/loopflow/context.py +172 -0
- loopflow-0.2.2/src/loopflow/files.py +160 -0
- loopflow-0.2.2/src/loopflow/git.py +334 -0
- loopflow-0.2.2/src/loopflow/launcher.py +116 -0
- loopflow-0.2.2/src/loopflow/llm_http.py +96 -0
- loopflow-0.2.2/src/loopflow/pipeline.py +71 -0
- loopflow-0.2.2/src/loopflow/tokens.py +195 -0
loopflow-0.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: loopflow
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Arrange LLMs to code in harmony
|
|
5
|
+
Author: Jack
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: ai,claude,cli,coding,llm
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: pathspec>=0.11.0
|
|
19
|
+
Requires-Dist: pydantic-ai-slim[anthropic]>=1.0.0
|
|
20
|
+
Requires-Dist: pydantic>=2.12.5
|
|
21
|
+
Requires-Dist: pyyaml>=6.0
|
|
22
|
+
Requires-Dist: tiktoken>=0.7.0
|
|
23
|
+
Requires-Dist: typer>=0.9.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Loopflow
|
|
29
|
+
|
|
30
|
+
Run LLM coding tasks from reusable prompt files.
|
|
31
|
+
|
|
32
|
+
macOS only. Currently uses Claude Code; other backends planned.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install loopflow
|
|
38
|
+
lf meta install # installs Claude Code via npm
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Why Worktrees?
|
|
42
|
+
|
|
43
|
+
Loopflow is designed for running background agents while you work on something else. That means isolated branches - you can't have an agent committing to the branch you're actively editing.
|
|
44
|
+
|
|
45
|
+
The workflow: create a worktree, run tasks there, merge when ready. You can have multiple features in flight at once.
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
lf wt create my-feature # branch + worktree, opens IDEs
|
|
51
|
+
cd .lf/worktrees/my-feature
|
|
52
|
+
|
|
53
|
+
lf design # interactive: figure out what to build
|
|
54
|
+
lf ship # batch: implement, review, test, commit, open PR
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`lf design` runs `.lf/design.lf`. `lf ship` runs the `ship` pipeline from `.lf/config.yaml`.
|
|
58
|
+
|
|
59
|
+
## Tasks
|
|
60
|
+
|
|
61
|
+
Tasks are prompt files in `.lf/`. Here's an example:
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
# .lf/review.lf
|
|
65
|
+
|
|
66
|
+
Review the diff on the current branch against `main` and fix any issues found.
|
|
67
|
+
|
|
68
|
+
The deliverable is the fixes themselves, not a written review.
|
|
69
|
+
|
|
70
|
+
## What to look for
|
|
71
|
+
|
|
72
|
+
- Style guide violations (read STYLE.md)
|
|
73
|
+
- Bugs, logic errors, edge cases
|
|
74
|
+
- Unnecessary complexity
|
|
75
|
+
- Missing tests
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Run tasks by name:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
lf review # run .lf/review.lf
|
|
82
|
+
lf review -x src/utils.py # add context files
|
|
83
|
+
lf : "fix the typo" # inline prompt, no task file
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
All `.md` files at repo root (README, STYLE, etc.) are included as context automatically.
|
|
87
|
+
|
|
88
|
+
## Pipelines
|
|
89
|
+
|
|
90
|
+
Chain tasks in `.lf/config.yaml`:
|
|
91
|
+
|
|
92
|
+
```yaml
|
|
93
|
+
pipelines:
|
|
94
|
+
ship:
|
|
95
|
+
tasks: [implement, review, test, commit]
|
|
96
|
+
pr: true # open PR when done
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
lf ship # runs each task, auto-commits between steps
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Worktrees
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
lf wt create auth # .lf/worktrees/auth/, opens Warp + Cursor
|
|
107
|
+
lf wt list # show all worktrees
|
|
108
|
+
lf wt clean # remove merged branches
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Configuration
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
# .lf/config.yaml
|
|
115
|
+
push: true # auto-push after commits
|
|
116
|
+
pr: false # open PR after pipelines
|
|
117
|
+
|
|
118
|
+
ide:
|
|
119
|
+
warp: true
|
|
120
|
+
cursor: true
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Options
|
|
124
|
+
|
|
125
|
+
| Option | Description |
|
|
126
|
+
|--------|-------------|
|
|
127
|
+
| `-p, --print` | Batch mode (non-interactive) |
|
|
128
|
+
| `-x, --context` | Add context files |
|
|
129
|
+
| `-w, --worktree` | Create worktree and run task there |
|
|
130
|
+
| `-c, --copy` | Copy prompt to clipboard, show token breakdown |
|
|
131
|
+
|
|
132
|
+
## Commands
|
|
133
|
+
|
|
134
|
+
| Command | Description |
|
|
135
|
+
|---------|-------------|
|
|
136
|
+
| `lf <task>` | Run a task from `.lf/` |
|
|
137
|
+
| `lf <pipeline>` | Run a pipeline |
|
|
138
|
+
| `lf : "prompt"` | Inline prompt |
|
|
139
|
+
| `lf wt create/list/clean` | Worktree management |
|
|
140
|
+
| `lf pr create` | Open GitHub PR |
|
|
141
|
+
| `lf pr land [-a]` | Squash-merge to main |
|
|
142
|
+
| `lf meta install` | Install Claude Code |
|
|
143
|
+
| `lf meta doctor` | Check dependencies |
|
loopflow-0.2.2/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Loopflow
|
|
2
|
+
|
|
3
|
+
Run LLM coding tasks from reusable prompt files.
|
|
4
|
+
|
|
5
|
+
macOS only. Currently uses Claude Code; other backends planned.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install loopflow
|
|
11
|
+
lf meta install # installs Claude Code via npm
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Why Worktrees?
|
|
15
|
+
|
|
16
|
+
Loopflow is designed for running background agents while you work on something else. That means isolated branches - you can't have an agent committing to the branch you're actively editing.
|
|
17
|
+
|
|
18
|
+
The workflow: create a worktree, run tasks there, merge when ready. You can have multiple features in flight at once.
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
lf wt create my-feature # branch + worktree, opens IDEs
|
|
24
|
+
cd .lf/worktrees/my-feature
|
|
25
|
+
|
|
26
|
+
lf design # interactive: figure out what to build
|
|
27
|
+
lf ship # batch: implement, review, test, commit, open PR
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`lf design` runs `.lf/design.lf`. `lf ship` runs the `ship` pipeline from `.lf/config.yaml`.
|
|
31
|
+
|
|
32
|
+
## Tasks
|
|
33
|
+
|
|
34
|
+
Tasks are prompt files in `.lf/`. Here's an example:
|
|
35
|
+
|
|
36
|
+
```markdown
|
|
37
|
+
# .lf/review.lf
|
|
38
|
+
|
|
39
|
+
Review the diff on the current branch against `main` and fix any issues found.
|
|
40
|
+
|
|
41
|
+
The deliverable is the fixes themselves, not a written review.
|
|
42
|
+
|
|
43
|
+
## What to look for
|
|
44
|
+
|
|
45
|
+
- Style guide violations (read STYLE.md)
|
|
46
|
+
- Bugs, logic errors, edge cases
|
|
47
|
+
- Unnecessary complexity
|
|
48
|
+
- Missing tests
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Run tasks by name:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
lf review # run .lf/review.lf
|
|
55
|
+
lf review -x src/utils.py # add context files
|
|
56
|
+
lf : "fix the typo" # inline prompt, no task file
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
All `.md` files at repo root (README, STYLE, etc.) are included as context automatically.
|
|
60
|
+
|
|
61
|
+
## Pipelines
|
|
62
|
+
|
|
63
|
+
Chain tasks in `.lf/config.yaml`:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
pipelines:
|
|
67
|
+
ship:
|
|
68
|
+
tasks: [implement, review, test, commit]
|
|
69
|
+
pr: true # open PR when done
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
lf ship # runs each task, auto-commits between steps
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Worktrees
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
lf wt create auth # .lf/worktrees/auth/, opens Warp + Cursor
|
|
80
|
+
lf wt list # show all worktrees
|
|
81
|
+
lf wt clean # remove merged branches
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
# .lf/config.yaml
|
|
88
|
+
push: true # auto-push after commits
|
|
89
|
+
pr: false # open PR after pipelines
|
|
90
|
+
|
|
91
|
+
ide:
|
|
92
|
+
warp: true
|
|
93
|
+
cursor: true
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Options
|
|
97
|
+
|
|
98
|
+
| Option | Description |
|
|
99
|
+
|--------|-------------|
|
|
100
|
+
| `-p, --print` | Batch mode (non-interactive) |
|
|
101
|
+
| `-x, --context` | Add context files |
|
|
102
|
+
| `-w, --worktree` | Create worktree and run task there |
|
|
103
|
+
| `-c, --copy` | Copy prompt to clipboard, show token breakdown |
|
|
104
|
+
|
|
105
|
+
## Commands
|
|
106
|
+
|
|
107
|
+
| Command | Description |
|
|
108
|
+
|---------|-------------|
|
|
109
|
+
| `lf <task>` | Run a task from `.lf/` |
|
|
110
|
+
| `lf <pipeline>` | Run a pipeline |
|
|
111
|
+
| `lf : "prompt"` | Inline prompt |
|
|
112
|
+
| `lf wt create/list/clean` | Worktree management |
|
|
113
|
+
| `lf pr create` | Open GitHub PR |
|
|
114
|
+
| `lf pr land [-a]` | Squash-merge to main |
|
|
115
|
+
| `lf meta install` | Install Claude Code |
|
|
116
|
+
| `lf meta doctor` | Check dependencies |
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "loopflow"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Arrange LLMs to code in harmony"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [{ name = "Jack" }]
|
|
9
|
+
keywords = ["llm", "claude", "ai", "coding", "cli"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Environment :: Console",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Topic :: Software Development",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"typer>=0.9.0",
|
|
23
|
+
"pathspec>=0.11.0",
|
|
24
|
+
"pyyaml>=6.0",
|
|
25
|
+
"pydantic>=2.12.5",
|
|
26
|
+
"pydantic-ai-slim[anthropic]>=1.0.0",
|
|
27
|
+
"tiktoken>=0.7.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=7.0.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
lf = "loopflow.cli:main"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["hatchling"]
|
|
40
|
+
build-backend = "hatchling.build"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/loopflow"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.sdist]
|
|
46
|
+
include = ["src/loopflow"]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.version]
|
|
49
|
+
path = "src/loopflow/__init__.py"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.2"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Builtin prompts for loopflow commands.
|
|
2
|
+
|
|
3
|
+
These are prompts used by loopflow's own commands (lf pr create, etc.),
|
|
4
|
+
not user-defined tasks in .lf/.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
_BUILTINS_DIR = Path(__file__).parent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_builtin_prompt(name: str) -> str:
|
|
13
|
+
"""Get a builtin prompt by name. Raises FileNotFoundError if not found."""
|
|
14
|
+
prompt_file = _BUILTINS_DIR / f"{name}.txt"
|
|
15
|
+
return prompt_file.read_text()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Generate a commit message for the staged changes.
|
|
2
|
+
|
|
3
|
+
Review the diff and write a concise commit message.
|
|
4
|
+
|
|
5
|
+
## Output format
|
|
6
|
+
|
|
7
|
+
Return a structured response with:
|
|
8
|
+
- **title**: lowercase, with optional area prefix (e.g. `llm_http: add structured output`)
|
|
9
|
+
- **body**: brief explanation if needed, otherwise empty string
|
|
10
|
+
|
|
11
|
+
## Title style
|
|
12
|
+
|
|
13
|
+
Titles are lowercase and concise. Use an area prefix when changes are focused on a specific module or feature area.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
- `llm_http: add structured output for pr messages`
|
|
17
|
+
- `pr workflow: add -a flag to commit and push`
|
|
18
|
+
- `fix typo in readme`
|
|
19
|
+
|
|
20
|
+
## Body style
|
|
21
|
+
|
|
22
|
+
Keep it brief—one sentence or a few bullets if the change needs explanation. Empty string is fine for self-explanatory changes.
|
|
23
|
+
|
|
24
|
+
Most commits don't need a body. Only add one if the "why" isn't obvious from the title.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Generate a PR title and body for the changes on this branch.
|
|
2
|
+
|
|
3
|
+
Review the diff against main and summarize what changed and why.
|
|
4
|
+
|
|
5
|
+
## Output format
|
|
6
|
+
|
|
7
|
+
Return a structured response with:
|
|
8
|
+
- **title**: lowercase, with optional area prefix (e.g. `llm_http: add structured output`)
|
|
9
|
+
- **body**: markdown with headers, code blocks for commands, and bullet lists
|
|
10
|
+
|
|
11
|
+
## Title style
|
|
12
|
+
|
|
13
|
+
Titles are lowercase and concise. Use an area prefix when changes are focused on a specific module or feature area. The area can be new or existing.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
- `llm_http: add structured output for pr messages`
|
|
17
|
+
- `pr workflow: add -a flag to commit and push`
|
|
18
|
+
- `fix worktree cleanup on branch delete`
|
|
19
|
+
|
|
20
|
+
## Body style
|
|
21
|
+
|
|
22
|
+
Use markdown headers to organize the body. Open with a "Usage" or "Try it" section showing commands in code blocks. Then explain what changed.
|
|
23
|
+
|
|
24
|
+
Structure:
|
|
25
|
+
1. **Usage section** (header + code block) - how to try it, run it, or see it in action
|
|
26
|
+
2. **Summary** - one paragraph explaining what this PR does and why
|
|
27
|
+
3. **Changes** (optional) - bullet list of notable changes if helpful
|
|
28
|
+
|
|
29
|
+
Keep it medium length. Stay high-level; don't enumerate every file.
|
|
30
|
+
|
|
31
|
+
Example body:
|
|
32
|
+
```
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
\`\`\`bash
|
|
36
|
+
lf pr create -a
|
|
37
|
+
lf pr update -a
|
|
38
|
+
\`\`\`
|
|
39
|
+
|
|
40
|
+
PR create and update now generate title/body via Claude API instead of reading from .lf/COMMIT. The -a flag adds, commits, and pushes before creating/updating.
|
|
41
|
+
|
|
42
|
+
## Changes
|
|
43
|
+
|
|
44
|
+
- New llm_http module for structured LLM responses
|
|
45
|
+
- Builtin prompts stored in package, separate from user .lf/ tasks
|
|
46
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Loopflow CLI: Arrange LLMs to code in harmony."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from loopflow.config import ConfigError, load_config
|
|
8
|
+
from loopflow.context import find_worktree_root, gather_task
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
name="lf",
|
|
12
|
+
help="Arrange LLMs to code in harmony.",
|
|
13
|
+
no_args_is_help=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Import and register subcommands
|
|
17
|
+
from loopflow.cli import run as run_module
|
|
18
|
+
from loopflow.cli import wt, pr, meta
|
|
19
|
+
|
|
20
|
+
app.add_typer(wt.app, name="wt")
|
|
21
|
+
app.add_typer(pr.app, name="pr")
|
|
22
|
+
app.add_typer(meta.app, name="meta")
|
|
23
|
+
|
|
24
|
+
# Register top-level commands
|
|
25
|
+
app.command()(run_module.run)
|
|
26
|
+
app.command()(run_module.inline)
|
|
27
|
+
app.command(name="pipeline")(run_module.pipeline)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main():
|
|
31
|
+
"""Entry point that supports 'lf <task>' and 'lf <pipeline>' shorthand."""
|
|
32
|
+
known_commands = {"run", "pipeline", "inline", "wt", "pr", "meta", "--help", "-h"}
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
if len(sys.argv) > 1:
|
|
36
|
+
first_arg = sys.argv[1]
|
|
37
|
+
|
|
38
|
+
# Inline prompt: lf : "prompt"
|
|
39
|
+
if first_arg == ":":
|
|
40
|
+
sys.argv.pop(1)
|
|
41
|
+
sys.argv.insert(1, "inline")
|
|
42
|
+
elif first_arg not in known_commands:
|
|
43
|
+
name = sys.argv[1]
|
|
44
|
+
repo_root = find_worktree_root()
|
|
45
|
+
config = load_config(repo_root) if repo_root else None
|
|
46
|
+
|
|
47
|
+
has_pipeline = config and name in config.pipelines
|
|
48
|
+
has_task = repo_root and gather_task(repo_root, name) is not None
|
|
49
|
+
|
|
50
|
+
if has_pipeline and has_task:
|
|
51
|
+
typer.echo(
|
|
52
|
+
f"Error: '{name}' exists as both a pipeline and a task. "
|
|
53
|
+
"Remove one to resolve the conflict.",
|
|
54
|
+
err=True,
|
|
55
|
+
)
|
|
56
|
+
raise SystemExit(1)
|
|
57
|
+
|
|
58
|
+
if has_pipeline:
|
|
59
|
+
sys.argv.insert(1, "pipeline")
|
|
60
|
+
elif has_task:
|
|
61
|
+
sys.argv.insert(1, "run")
|
|
62
|
+
else:
|
|
63
|
+
typer.echo(f"Error: No task or pipeline named '{name}'", err=True)
|
|
64
|
+
typer.echo(f"Create .lf/{name}.lf or add '{name}' to pipelines in .lf/config.yaml", err=True)
|
|
65
|
+
raise SystemExit(1)
|
|
66
|
+
|
|
67
|
+
app()
|
|
68
|
+
except ConfigError as e:
|
|
69
|
+
typer.echo(f"Error: {e}", err=True)
|
|
70
|
+
raise SystemExit(1)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
main()
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Setup and diagnostics commands."""
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from loopflow.config import load_config
|
|
10
|
+
from loopflow.context import find_worktree_root
|
|
11
|
+
from loopflow.launcher import check_claude_available
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(help="Setup and diagnostics.")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _install_node() -> bool:
|
|
17
|
+
"""Attempt to install Node.js via Homebrew on macOS."""
|
|
18
|
+
if platform.system() != "Darwin":
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
if not shutil.which("brew"):
|
|
22
|
+
typer.echo("Homebrew not found. Install from https://brew.sh", err=True)
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
typer.echo("Installing Node.js via Homebrew...")
|
|
26
|
+
result = subprocess.run(["brew", "install", "node"], capture_output=True)
|
|
27
|
+
return result.returncode == 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _install_cask(name: str) -> bool:
|
|
31
|
+
"""Install a Homebrew cask. Returns success."""
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
["brew", "install", "--cask", name],
|
|
34
|
+
capture_output=True,
|
|
35
|
+
)
|
|
36
|
+
return result.returncode == 0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command()
|
|
40
|
+
def version():
|
|
41
|
+
"""Show loopflow version."""
|
|
42
|
+
from loopflow import __version__
|
|
43
|
+
|
|
44
|
+
typer.echo(f"loopflow {__version__}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.command()
|
|
48
|
+
def install():
|
|
49
|
+
"""Install loopflow dependencies based on config. macOS only."""
|
|
50
|
+
if platform.system() != "Darwin":
|
|
51
|
+
typer.echo("Error: lf install only supports macOS", err=True)
|
|
52
|
+
typer.echo("Install dependencies manually.", err=True)
|
|
53
|
+
raise typer.Exit(1)
|
|
54
|
+
|
|
55
|
+
if not shutil.which("brew"):
|
|
56
|
+
typer.echo("Error: Homebrew not found. Install from https://brew.sh", err=True)
|
|
57
|
+
raise typer.Exit(1)
|
|
58
|
+
|
|
59
|
+
# Load config to check what's needed
|
|
60
|
+
repo_root = find_worktree_root()
|
|
61
|
+
config = load_config(repo_root) if repo_root else None
|
|
62
|
+
ide = config.ide if config else None
|
|
63
|
+
|
|
64
|
+
# Node.js (required for Claude Code)
|
|
65
|
+
if not shutil.which("npm"):
|
|
66
|
+
typer.echo("Installing Node.js...")
|
|
67
|
+
if _install_node() and shutil.which("npm"):
|
|
68
|
+
typer.echo("✓ Node.js installed")
|
|
69
|
+
else:
|
|
70
|
+
typer.echo("✗ Could not install Node.js", err=True)
|
|
71
|
+
raise typer.Exit(1)
|
|
72
|
+
else:
|
|
73
|
+
typer.echo("✓ Node.js")
|
|
74
|
+
|
|
75
|
+
# Claude Code (always required)
|
|
76
|
+
if check_claude_available():
|
|
77
|
+
typer.echo("✓ Claude Code")
|
|
78
|
+
else:
|
|
79
|
+
typer.echo("Installing Claude Code...")
|
|
80
|
+
result = subprocess.run(
|
|
81
|
+
["npm", "install", "-g", "@anthropic-ai/claude-code"],
|
|
82
|
+
capture_output=True,
|
|
83
|
+
text=True,
|
|
84
|
+
)
|
|
85
|
+
if result.returncode == 0:
|
|
86
|
+
typer.echo("✓ Claude Code installed")
|
|
87
|
+
else:
|
|
88
|
+
typer.echo(f"✗ Could not install Claude Code: {result.stderr}", err=True)
|
|
89
|
+
raise typer.Exit(1)
|
|
90
|
+
|
|
91
|
+
# Warp (if enabled in config, default true)
|
|
92
|
+
if not ide or ide.warp:
|
|
93
|
+
if shutil.which("warp"):
|
|
94
|
+
typer.echo("✓ Warp")
|
|
95
|
+
else:
|
|
96
|
+
typer.echo("Installing Warp...")
|
|
97
|
+
if _install_cask("warp"):
|
|
98
|
+
typer.echo("✓ Warp installed")
|
|
99
|
+
else:
|
|
100
|
+
typer.echo("✗ Could not install Warp", err=True)
|
|
101
|
+
|
|
102
|
+
# Cursor (if enabled in config, default true)
|
|
103
|
+
if not ide or ide.cursor:
|
|
104
|
+
if shutil.which("cursor"):
|
|
105
|
+
typer.echo("✓ Cursor")
|
|
106
|
+
else:
|
|
107
|
+
typer.echo("Installing Cursor...")
|
|
108
|
+
if _install_cask("cursor"):
|
|
109
|
+
typer.echo("✓ Cursor installed")
|
|
110
|
+
else:
|
|
111
|
+
typer.echo("✗ Could not install Cursor", err=True)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.command()
|
|
115
|
+
def doctor():
|
|
116
|
+
"""Check loopflow dependencies based on config."""
|
|
117
|
+
all_ok = True
|
|
118
|
+
|
|
119
|
+
# Load config to check what's needed
|
|
120
|
+
repo_root = find_worktree_root()
|
|
121
|
+
config = load_config(repo_root) if repo_root else None
|
|
122
|
+
ide = config.ide if config else None
|
|
123
|
+
|
|
124
|
+
# Required
|
|
125
|
+
if shutil.which("npm"):
|
|
126
|
+
typer.echo("✓ npm")
|
|
127
|
+
else:
|
|
128
|
+
typer.echo("✗ npm - Install Node.js: https://nodejs.org")
|
|
129
|
+
all_ok = False
|
|
130
|
+
|
|
131
|
+
if check_claude_available():
|
|
132
|
+
typer.echo("✓ claude")
|
|
133
|
+
else:
|
|
134
|
+
typer.echo("✗ claude - Run: lf meta install")
|
|
135
|
+
all_ok = False
|
|
136
|
+
|
|
137
|
+
# IDE tools (based on config)
|
|
138
|
+
if not ide or ide.warp:
|
|
139
|
+
if shutil.which("warp"):
|
|
140
|
+
typer.echo("✓ warp")
|
|
141
|
+
else:
|
|
142
|
+
typer.echo("✗ warp - Run: lf meta install")
|
|
143
|
+
all_ok = False
|
|
144
|
+
|
|
145
|
+
if not ide or ide.cursor:
|
|
146
|
+
if shutil.which("cursor"):
|
|
147
|
+
typer.echo("✓ cursor")
|
|
148
|
+
else:
|
|
149
|
+
typer.echo("✗ cursor - Run: lf meta install")
|
|
150
|
+
all_ok = False
|
|
151
|
+
|
|
152
|
+
# Optional: gh for PR creation
|
|
153
|
+
if shutil.which("gh"):
|
|
154
|
+
typer.echo("✓ gh (optional)")
|
|
155
|
+
else:
|
|
156
|
+
typer.echo("- gh (optional): brew install gh")
|
|
157
|
+
|
|
158
|
+
raise typer.Exit(0 if all_ok else 1)
|