myg 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.
- myg-0.1.0/.gitignore +170 -0
- myg-0.1.0/PKG-INFO +62 -0
- myg-0.1.0/README.md +32 -0
- myg-0.1.0/myg/__init__.py +3 -0
- myg-0.1.0/myg/cli.py +59 -0
- myg-0.1.0/myg/commands/__init__.py +0 -0
- myg-0.1.0/myg/commands/configure.py +136 -0
- myg-0.1.0/myg/commands/db.py +64 -0
- myg-0.1.0/myg/commands/deploy.py +38 -0
- myg-0.1.0/myg/commands/dev.py +79 -0
- myg-0.1.0/myg/commands/doctor.py +79 -0
- myg-0.1.0/myg/commands/eject.py +49 -0
- myg-0.1.0/myg/commands/init.py +165 -0
- myg-0.1.0/myg/commands/prompt.py +97 -0
- myg-0.1.0/myg/commands/templates.py +43 -0
- myg-0.1.0/myg/commands/update.py +30 -0
- myg-0.1.0/myg/context.py +142 -0
- myg-0.1.0/myg/llm.py +81 -0
- myg-0.1.0/myg/neon.py +63 -0
- myg-0.1.0/myg/render.py +119 -0
- myg-0.1.0/myg/wizard.py +153 -0
- myg-0.1.0/pyproject.toml +49 -0
- myg-0.1.0/tests/test_cli.py +59 -0
- myg-0.1.0/tests/test_context.py +46 -0
- myg-0.1.0/tests/test_llm.py +64 -0
- myg-0.1.0/tests/test_neon.py +28 -0
- myg-0.1.0/tests/test_render.py +55 -0
myg-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# poetry
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
102
|
+
#poetry.lock
|
|
103
|
+
|
|
104
|
+
# pdm
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
106
|
+
#pdm.lock
|
|
107
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
108
|
+
# in version control.
|
|
109
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
110
|
+
.pdm.toml
|
|
111
|
+
|
|
112
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
113
|
+
__pypackages__/
|
|
114
|
+
|
|
115
|
+
# Celery stuff
|
|
116
|
+
celerybeat-schedule
|
|
117
|
+
celerybeat.pid
|
|
118
|
+
|
|
119
|
+
# SageMath parsed files
|
|
120
|
+
*.sage.py
|
|
121
|
+
|
|
122
|
+
# Environments
|
|
123
|
+
.env
|
|
124
|
+
.venv
|
|
125
|
+
env/
|
|
126
|
+
venv/
|
|
127
|
+
ENV/
|
|
128
|
+
env.bak/
|
|
129
|
+
venv.bak/
|
|
130
|
+
|
|
131
|
+
# Spyder project settings
|
|
132
|
+
.spyderproject
|
|
133
|
+
.spyproject
|
|
134
|
+
|
|
135
|
+
# Rope project settings
|
|
136
|
+
.ropeproject
|
|
137
|
+
|
|
138
|
+
# mkdocs documentation
|
|
139
|
+
/site
|
|
140
|
+
|
|
141
|
+
# mypy
|
|
142
|
+
.mypy_cache/
|
|
143
|
+
.dmypy.json
|
|
144
|
+
dmypy.json
|
|
145
|
+
|
|
146
|
+
# Pyre type checker
|
|
147
|
+
.pyre/
|
|
148
|
+
|
|
149
|
+
# pytype static type analyzer
|
|
150
|
+
.pytype/
|
|
151
|
+
|
|
152
|
+
# Cython debug symbols
|
|
153
|
+
cython_debug/
|
|
154
|
+
|
|
155
|
+
# PyCharm
|
|
156
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
157
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
158
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
159
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
160
|
+
#.idea/
|
|
161
|
+
|
|
162
|
+
# myg tooling
|
|
163
|
+
.ruff_cache/
|
|
164
|
+
basedpyright/
|
|
165
|
+
node_modules/
|
|
166
|
+
.next/
|
|
167
|
+
dist/
|
|
168
|
+
*.rej
|
|
169
|
+
*.orig
|
|
170
|
+
.devin/
|
myg-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: myg
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: myg — your guy for agent apps. Scaffold a full CopilotKit + LangGraph agent app in one command.
|
|
5
|
+
Project-URL: Homepage, https://github.com/control-shift-escape-co/myguy
|
|
6
|
+
Project-URL: Documentation, https://control-shift-escape-co.github.io/myguy
|
|
7
|
+
Project-URL: Issues, https://github.com/control-shift-escape-co/myguy/issues
|
|
8
|
+
Author: Abhi Avadhani
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: agents,cli,copilotkit,langgraph,scaffold
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: cookiecutter>=2.6
|
|
20
|
+
Requires-Dist: cruft>=2.15
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: pydantic>=2.8
|
|
23
|
+
Requires-Dist: questionary>=2.0
|
|
24
|
+
Requires-Dist: rich>=13.7
|
|
25
|
+
Requires-Dist: tomli-w>=1.0
|
|
26
|
+
Requires-Dist: typer>=0.15
|
|
27
|
+
Provides-Extra: agent
|
|
28
|
+
Requires-Dist: claude-agent-sdk>=0.1.0; extra == 'agent'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# myg
|
|
32
|
+
|
|
33
|
+
Your guy for agent apps.
|
|
34
|
+
|
|
35
|
+
`myg init` scaffolds a production-shaped single-page agent app — Next.js + CopilotKit frontend, FastAPI + LangGraph backend (AG-UI), Postgres thread persistence, optional Kong AI Gateway — ready for `docker compose up` or a Railway deploy.
|
|
36
|
+
|
|
37
|
+
See the main repo for docs, contributing, and the full story: <https://github.com/control-shift-escape-co/myguy>
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uvx myg init # recommended — no install
|
|
43
|
+
pipx run myg init # pipx
|
|
44
|
+
pip install myg # classic
|
|
45
|
+
npm create myg # npm folks — delegates to uvx/pipx
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Commands
|
|
49
|
+
|
|
50
|
+
| Command | What it does |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `myg init` | Wizard → renders the template → optional instant Neon DB → next steps |
|
|
53
|
+
| `myg configure` | Edit `myg.toml` and re-render template files (`get`/`set`/`--ai`) |
|
|
54
|
+
| `myg prompt "…"` | Agentic edits to an existing app (Claude Agent SDK, or `--backend devin`) |
|
|
55
|
+
| `myg dev` | `docker compose up` — or runs both apps natively |
|
|
56
|
+
| `myg db` | Provisions an instant Neon Postgres + prints the claim URL |
|
|
57
|
+
| `myg deploy` | Deploys to Railway (CLI detected) or prints the guided steps |
|
|
58
|
+
| `myg doctor` | Checks docker/node/uv/keys/ports |
|
|
59
|
+
| `myg update` | Pulls template upgrades into your project (cruft) |
|
|
60
|
+
| `myg templates` | Lists built-in + community templates |
|
|
61
|
+
|
|
62
|
+
MIT licensed.
|
myg-0.1.0/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# myg
|
|
2
|
+
|
|
3
|
+
Your guy for agent apps.
|
|
4
|
+
|
|
5
|
+
`myg init` scaffolds a production-shaped single-page agent app — Next.js + CopilotKit frontend, FastAPI + LangGraph backend (AG-UI), Postgres thread persistence, optional Kong AI Gateway — ready for `docker compose up` or a Railway deploy.
|
|
6
|
+
|
|
7
|
+
See the main repo for docs, contributing, and the full story: <https://github.com/control-shift-escape-co/myguy>
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uvx myg init # recommended — no install
|
|
13
|
+
pipx run myg init # pipx
|
|
14
|
+
pip install myg # classic
|
|
15
|
+
npm create myg # npm folks — delegates to uvx/pipx
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
| Command | What it does |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `myg init` | Wizard → renders the template → optional instant Neon DB → next steps |
|
|
23
|
+
| `myg configure` | Edit `myg.toml` and re-render template files (`get`/`set`/`--ai`) |
|
|
24
|
+
| `myg prompt "…"` | Agentic edits to an existing app (Claude Agent SDK, or `--backend devin`) |
|
|
25
|
+
| `myg dev` | `docker compose up` — or runs both apps natively |
|
|
26
|
+
| `myg db` | Provisions an instant Neon Postgres + prints the claim URL |
|
|
27
|
+
| `myg deploy` | Deploys to Railway (CLI detected) or prints the guided steps |
|
|
28
|
+
| `myg doctor` | Checks docker/node/uv/keys/ports |
|
|
29
|
+
| `myg update` | Pulls template upgrades into your project (cruft) |
|
|
30
|
+
| `myg templates` | Lists built-in + community templates |
|
|
31
|
+
|
|
32
|
+
MIT licensed.
|
myg-0.1.0/myg/cli.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""myg CLI — your guy for agent apps."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
# Windows consoles default to cp1252 — unicode output must not depend on locale.
|
|
8
|
+
for stream in (sys.stdout, sys.stderr):
|
|
9
|
+
if getattr(stream, "encoding", "") and stream.encoding.lower() not in ("utf-8", "utf8"):
|
|
10
|
+
stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
11
|
+
|
|
12
|
+
import typer # noqa: E402
|
|
13
|
+
|
|
14
|
+
from myg import __version__ # noqa: E402
|
|
15
|
+
from myg.commands import ( # noqa: E402
|
|
16
|
+
configure,
|
|
17
|
+
db,
|
|
18
|
+
deploy,
|
|
19
|
+
dev,
|
|
20
|
+
doctor,
|
|
21
|
+
eject,
|
|
22
|
+
init,
|
|
23
|
+
prompt,
|
|
24
|
+
templates,
|
|
25
|
+
update,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
app = typer.Typer(
|
|
29
|
+
name="myg",
|
|
30
|
+
help="Your guy for agent apps — scaffold a CopilotKit + LangGraph agent in one command.",
|
|
31
|
+
no_args_is_help=True,
|
|
32
|
+
rich_markup_mode="rich",
|
|
33
|
+
add_completion=False,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
app.command()(init.init)
|
|
37
|
+
app.command()(configure.configure)
|
|
38
|
+
app.command()(db.db)
|
|
39
|
+
app.command("db-claim")(db.claim_url)
|
|
40
|
+
app.command()(prompt.prompt)
|
|
41
|
+
app.command()(dev.dev)
|
|
42
|
+
app.command()(deploy.deploy)
|
|
43
|
+
app.command()(doctor.doctor)
|
|
44
|
+
app.command()(update.update)
|
|
45
|
+
app.command()(eject.eject)
|
|
46
|
+
app.command()(templates.templates)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@app.callback(invoke_without_command=True)
|
|
50
|
+
def main(
|
|
51
|
+
version: bool = typer.Option(False, "--version", "-V", help="Print version"),
|
|
52
|
+
) -> None:
|
|
53
|
+
if version:
|
|
54
|
+
typer.echo(f"myg {__version__}")
|
|
55
|
+
raise typer.Exit()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
app()
|
|
File without changes
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""`myg configure` — edit myg.toml values and re-render template files in place."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import tomllib
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
import questionary
|
|
11
|
+
import tomli_w
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
|
|
15
|
+
from myg import render, wizard
|
|
16
|
+
from myg.context import MygContext, find_project_root
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
_FLAT_KEYS = {
|
|
21
|
+
"name": "project_name",
|
|
22
|
+
"pattern": "agent_pattern",
|
|
23
|
+
"model": "model",
|
|
24
|
+
"prompt": "system_prompt",
|
|
25
|
+
"system_prompt": "system_prompt",
|
|
26
|
+
"style": "ui_style",
|
|
27
|
+
"db": "db",
|
|
28
|
+
"gateway": "gateway",
|
|
29
|
+
}
|
|
30
|
+
_SECTION_KEYS = {
|
|
31
|
+
"agent.pattern": "agent_pattern",
|
|
32
|
+
"agent.model": "model",
|
|
33
|
+
"agent.system_prompt": "system_prompt",
|
|
34
|
+
"ui.style": "ui_style",
|
|
35
|
+
"ui.primary_color": "primary_color",
|
|
36
|
+
"ui.secondary_color": "secondary_color",
|
|
37
|
+
"db.provider": "db",
|
|
38
|
+
"gateway.mode": "gateway",
|
|
39
|
+
"project.name": "project_name",
|
|
40
|
+
"project.description": "description",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def configure(
|
|
45
|
+
set_: Annotated[
|
|
46
|
+
list[str] | None,
|
|
47
|
+
typer.Option("--set", "-s", help="key=value, e.g. agent.pattern=supervisor"),
|
|
48
|
+
] = None,
|
|
49
|
+
get: Annotated[str | None, typer.Option("--get", "-g", help="Print a config value")] = None,
|
|
50
|
+
ai: Annotated[bool, typer.Option("--ai", help="Reconfigure by chatting with an LLM")] = False,
|
|
51
|
+
github_style: Annotated[
|
|
52
|
+
str | None, typer.Option("--github-style", help="Extract UI style from a GitHub repo")
|
|
53
|
+
] = None,
|
|
54
|
+
project_dir: Annotated[Path | None, typer.Option("--dir", "-d")] = None,
|
|
55
|
+
no_render: Annotated[bool, typer.Option("--no-render", help="Only update myg.toml")] = False,
|
|
56
|
+
yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts")] = False,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Change config on an existing generated app."""
|
|
59
|
+
root = project_dir or find_project_root()
|
|
60
|
+
toml_path = root / "myg.toml"
|
|
61
|
+
ctx = MygContext.from_myg_toml(toml_path)
|
|
62
|
+
|
|
63
|
+
if get:
|
|
64
|
+
key = _SECTION_KEYS.get(get, _FLAT_KEYS.get(get, get))
|
|
65
|
+
console.print(getattr(ctx, key))
|
|
66
|
+
raise typer.Exit()
|
|
67
|
+
|
|
68
|
+
changed = False
|
|
69
|
+
if github_style:
|
|
70
|
+
with console.status(f"Extracting style from {github_style}…"):
|
|
71
|
+
for key, value in wizard.style_from_github(github_style).items():
|
|
72
|
+
setattr(ctx, key, value)
|
|
73
|
+
changed = True
|
|
74
|
+
for assignment in set_ or []:
|
|
75
|
+
key, _, value = assignment.partition("=")
|
|
76
|
+
attr = _SECTION_KEYS.get(key.strip(), _FLAT_KEYS.get(key.strip(), key.strip()))
|
|
77
|
+
if not hasattr(ctx, attr):
|
|
78
|
+
console.print(f"[red]Unknown config key: {key}[/red]")
|
|
79
|
+
raise typer.Exit(2)
|
|
80
|
+
setattr(ctx, attr, value.strip())
|
|
81
|
+
changed = True
|
|
82
|
+
if ai:
|
|
83
|
+
ctx = wizard.run_assist(ctx)
|
|
84
|
+
changed = True
|
|
85
|
+
if not changed:
|
|
86
|
+
console.print("Nothing to do — pass --set, --ai or --github-style.")
|
|
87
|
+
raise typer.Exit()
|
|
88
|
+
|
|
89
|
+
if not no_render and _git_dirty(root) and not (yes or _confirm_overwrite()):
|
|
90
|
+
console.print("Aborted — nothing written.")
|
|
91
|
+
raise typer.Abort()
|
|
92
|
+
|
|
93
|
+
ctx.write_myg_toml(toml_path)
|
|
94
|
+
console.print("[green]✓[/green] myg.toml updated")
|
|
95
|
+
if no_render:
|
|
96
|
+
return
|
|
97
|
+
with console.status("Re-rendering template files…"):
|
|
98
|
+
render.rerender(root, ctx.cookiecutter_context())
|
|
99
|
+
console.print("[green]✓[/green] Template files re-rendered")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _confirm_overwrite() -> bool:
|
|
103
|
+
try:
|
|
104
|
+
return bool(
|
|
105
|
+
questionary.confirm(
|
|
106
|
+
"Uncommitted changes detected — re-render may overwrite template files. Continue?",
|
|
107
|
+
default=False,
|
|
108
|
+
).ask()
|
|
109
|
+
)
|
|
110
|
+
except Exception:
|
|
111
|
+
console.print(
|
|
112
|
+
"[yellow]Non-interactive shell — pass --yes to re-render over a dirty tree.[/yellow]"
|
|
113
|
+
)
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _git_dirty(root: Path) -> bool:
|
|
118
|
+
try:
|
|
119
|
+
out = subprocess.run(
|
|
120
|
+
["git", "status", "--porcelain"], cwd=root, capture_output=True, text=True, timeout=10
|
|
121
|
+
)
|
|
122
|
+
return bool(out.stdout.strip())
|
|
123
|
+
except Exception:
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def read_db_section(root: Path) -> dict:
|
|
128
|
+
data = tomllib.loads((root / "myg.toml").read_text(encoding="utf-8"))
|
|
129
|
+
return data.get("db", {})
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def update_db_section(root: Path, values: dict) -> None:
|
|
133
|
+
path = root / "myg.toml"
|
|
134
|
+
data = tomllib.loads(path.read_text(encoding="utf-8"))
|
|
135
|
+
data.setdefault("db", {}).update(values)
|
|
136
|
+
path.write_text(tomli_w.dumps(data), encoding="utf-8")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""`myg db` — instant Neon Postgres for a generated project."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
|
|
12
|
+
from myg import neon
|
|
13
|
+
from myg.commands.configure import read_db_section, update_db_section
|
|
14
|
+
from myg.context import find_project_root
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def db(
|
|
20
|
+
db_id: Annotated[
|
|
21
|
+
str | None, typer.Option("--status", help="Check status of a provisioned DB id")
|
|
22
|
+
] = None,
|
|
23
|
+
project_dir: Annotated[Path | None, typer.Option("--dir", "-d")] = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Provision an instant Neon Postgres (no account needed) and wire it into .env."""
|
|
26
|
+
if db_id:
|
|
27
|
+
console.print_json(data=neon.status(db_id))
|
|
28
|
+
raise typer.Exit()
|
|
29
|
+
|
|
30
|
+
root = project_dir or find_project_root()
|
|
31
|
+
with console.status("Provisioning instant Neon Postgres…"):
|
|
32
|
+
ndb = neon.provision(ref=root.name)
|
|
33
|
+
env_file = neon.write_database_url(root, ndb.connection_string)
|
|
34
|
+
update_db_section(
|
|
35
|
+
root,
|
|
36
|
+
{
|
|
37
|
+
"provider": "neon",
|
|
38
|
+
"project_id": ndb.id,
|
|
39
|
+
"claim_url": ndb.claim_url,
|
|
40
|
+
"expires_at": ndb.expires_at,
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
console.print(f"[green]✓[/green] DATABASE_URL written to {env_file}")
|
|
44
|
+
console.print(
|
|
45
|
+
Panel(
|
|
46
|
+
f"[bold]This DB expires in 72h unless claimed.[/bold]\n\n"
|
|
47
|
+
"Claim it (free Neon account, no card):\n"
|
|
48
|
+
f"[link={ndb.claim_url}]{ndb.claim_url}[/link]\n\n"
|
|
49
|
+
f"DB id: {ndb.id} (check later: myg db --status {ndb.id})",
|
|
50
|
+
title="⚡ instant postgres",
|
|
51
|
+
border_style="yellow",
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def claim_url(project_dir: Annotated[Path | None, typer.Option("--dir", "-d")] = None) -> None:
|
|
57
|
+
"""Reprint the claim URL stored in myg.toml."""
|
|
58
|
+
root = project_dir or find_project_root()
|
|
59
|
+
info = read_db_section(root)
|
|
60
|
+
url = info.get("claim_url")
|
|
61
|
+
if not url:
|
|
62
|
+
console.print("[yellow]No claim URL recorded — run `myg db` first.[/yellow]")
|
|
63
|
+
raise typer.Exit(1)
|
|
64
|
+
console.print(url)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""`myg deploy` — ship the generated app to Railway."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
|
|
14
|
+
from myg.context import find_project_root
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def deploy(project_dir: Annotated[Path | None, typer.Option("--dir", "-d")] = None) -> None:
|
|
20
|
+
"""Deploy to Railway — uses the railway CLI if installed, else prints the guide."""
|
|
21
|
+
root = (project_dir or find_project_root()).resolve()
|
|
22
|
+
if shutil.which("railway"):
|
|
23
|
+
console.print("Handing off to [bold]railway up[/bold] — follow its prompts.")
|
|
24
|
+
raise typer.Exit(subprocess.run(["railway", "up"], cwd=root).returncode)
|
|
25
|
+
console.print(
|
|
26
|
+
Panel(
|
|
27
|
+
"Railway CLI not found — no problem, the dashboard flow is 2 minutes:\n\n"
|
|
28
|
+
"1. Push this project to GitHub\n"
|
|
29
|
+
"2. railway.app → New Project → Deploy from GitHub repo\n"
|
|
30
|
+
"3. Add TWO services with root dirs: "
|
|
31
|
+
"[bold]apps/web[/bold] and [bold]apps/agent[/bold]\n"
|
|
32
|
+
"4. Add a Postgres plugin (or reuse Neon) → set DATABASE_URL on agent\n"
|
|
33
|
+
"5. Set env vars on each service (see .env.example + RAILWAY.md)\n\n"
|
|
34
|
+
"Or: npm i -g @railway/cli && railway login && myg deploy",
|
|
35
|
+
title="deploy to railway",
|
|
36
|
+
border_style="blue",
|
|
37
|
+
)
|
|
38
|
+
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""`myg dev` — run the generated app: docker compose when available, else natively."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import signal
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Annotated
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
|
|
14
|
+
from myg.context import find_project_root
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def dev(
|
|
20
|
+
detach: Annotated[bool, typer.Option("-d", "--detach", help="Compose in background")] = False,
|
|
21
|
+
project_dir: Annotated[Path | None, typer.Option("--dir", "-d")] = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Start the generated app locally."""
|
|
24
|
+
root = (project_dir or find_project_root()).resolve()
|
|
25
|
+
if shutil.which("docker") and _compose_up(root, detach):
|
|
26
|
+
return
|
|
27
|
+
console.print(
|
|
28
|
+
"[yellow]Docker not found — starting apps natively (needs uv + node/pnpm).[/yellow]"
|
|
29
|
+
)
|
|
30
|
+
_native(root)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _compose_up(root: Path, detach: bool) -> bool:
|
|
34
|
+
cmd = ["docker", "compose", "up"] + (["-d"] if detach else [])
|
|
35
|
+
try:
|
|
36
|
+
subprocess.run(cmd, cwd=root, check=True)
|
|
37
|
+
if detach:
|
|
38
|
+
console.print("[green]✓[/green] Running — http://localhost:3000")
|
|
39
|
+
return True
|
|
40
|
+
except subprocess.CalledProcessError as exc:
|
|
41
|
+
console.print(f"[red]docker compose failed ({exc.returncode})[/red]")
|
|
42
|
+
raise typer.Exit(exc.returncode or 1) from None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _native(root: Path) -> None:
|
|
46
|
+
uv, pnpm, npm = shutil.which("uv"), shutil.which("pnpm"), shutil.which("npm")
|
|
47
|
+
if not uv or not (pnpm or npm):
|
|
48
|
+
console.print(
|
|
49
|
+
"[red]Need `uv` and `pnpm`/`npm` for native dev, or Docker for compose.[/red]"
|
|
50
|
+
)
|
|
51
|
+
raise typer.Exit(1)
|
|
52
|
+
agent = subprocess.Popen(
|
|
53
|
+
[
|
|
54
|
+
uv,
|
|
55
|
+
"run",
|
|
56
|
+
"--directory",
|
|
57
|
+
str(root / "apps/agent"),
|
|
58
|
+
"uvicorn",
|
|
59
|
+
"agent.main:app",
|
|
60
|
+
"--host",
|
|
61
|
+
"127.0.0.1",
|
|
62
|
+
"--port",
|
|
63
|
+
"8123",
|
|
64
|
+
"--reload",
|
|
65
|
+
],
|
|
66
|
+
cwd=root,
|
|
67
|
+
)
|
|
68
|
+
pm = pnpm or npm
|
|
69
|
+
web = subprocess.Popen([pm, "--dir", str(root / "apps/web"), "dev"], cwd=root, shell=True)
|
|
70
|
+
console.print("agent → http://127.0.0.1:8123 · web → http://localhost:3000 (Ctrl+C to stop)")
|
|
71
|
+
try:
|
|
72
|
+
web.wait()
|
|
73
|
+
except KeyboardInterrupt:
|
|
74
|
+
pass
|
|
75
|
+
finally:
|
|
76
|
+
for proc in (web, agent):
|
|
77
|
+
proc.send_signal(signal.SIGINT)
|
|
78
|
+
web.wait(timeout=10)
|
|
79
|
+
agent.wait(timeout=10)
|