Repository: arnaudmiribel/streamlit-kickoff-cli (language: python)

Issue:
Show main file name in `st list`
Shows the Streamlit apps' main file name in the output of `st list`:

```
$python3 main.py list

Let's look at your apps running locally...
                       App URL          Main file
App ID                                           
6487    http://localhost:8501              yes.py
7121    http://localhost:8502             user.py
9239    http://localhost:8505   get_mainscript.py
```


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8618aa7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,134 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# Streamlit
+.streamlit/secrets.toml
+
+.DS_Store
diff --git a/README.md b/README.md
index 7fba14b..1fab6a3 100644
--- a/README.md
+++ b/README.md
@@ -1,77 +1,55 @@
-# 🎈 Kick-off your Streamlit projects with `st` CLI
+# streamlit-kickoff-cli 👞
 
-Just hit `st` in your new project directory and it will:
-- Create a basic Streamlit project structure in your directory:
-```
-.
-├── .gitignore         # basic .gitignore to ignore Streamlit secrets!
-├── .streamlit
-│   └── secrets.toml   # empty Streamlit Secrets
-├── README.md          # basic README
-├── requirements.txt   # empty
-└── streamlit_app.py   # basic Streamlit template app
-```
-- Open the project & script in VS Code
-- Run the Streamlit app
-- Open the app in your browser
-
-So you're ready to start crafting the app!
-
-✨ It's as simple as:
+[![PyPI](https://img.shields.io/pypi/v/streamlit-kickoff-cli)](https://pypi.org/project/streamlit-kickoff-cli/)
 
-```bash
-$ st
-```
+**A simple CLI to kickoff and manage Streamlit projects**
 
-![downloaded-GIF (1)](https://user-images.githubusercontent.com/7164864/145828632-052ef856-6fb4-4823-9405-9c822fead2fd.gif)
+`stk` is a command-line interface that helps you create, manage and iterate on your Streamlit projects!
 
+---
 
-## 🚀 Usage
+<p align="center">
+    <img src="https://user-images.githubusercontent.com/7164864/186680966-f70851a6-867a-4608-a52c-aa139d0ebf20.gif"></img>
+</p>
 
-### Prerequisites
+---
 
-This is a working setup using OSX & VS Code.
+## Installation
 
-### Install
+This is a working setup using Mac OSX & VS Code.
 
-**Using pip:**
-
-Run:
-```
-$ pip install st-kickoff
+```bash
+pip install streamlit-kickoff-cli
 ```
 
-
-### Documentation
+## Usage
 
 ```
-$ st --help
+$ stk --help
 
-Usage: st [OPTIONS]
+Usage: stk [OPTIONS] COMMAND [ARGS]...
 
-Options:
-  -p, --path TEXT                 Path where you want to create your Streamlit
-                                  project. (Default: ".")
+  Welcome to stk 👞
 
-  --open_project_in_vs_code TEXT  Open VS code with the newly created file. (Default: True)
-  --run_app TEXT                  Run Streamlit script (Default: True)
-  --open_app_in_browser TEXT      Open Streamlit app in browser (Default: True)
-  --help                          Show this message and exit.
-```
+  This is a simple CLI to help you kick off and maintain Streamlit projects as
+  fast as possible!
 
-### Get started!
-
-Make a new directory, `cd` in and run:
+Options:
+  --help  Show this message and exit.
 
-```
-$ st 
+Commands:
+  new   🆕 Create a new Streamlit project
+  dev   👩‍💻 Dev time! Opens VS Code and your app in Chrome!
+  kick  🚀 New app + dev set up NOW!
+  list  🤯 List running Streamlit apps under ports 85**
+  kill  🔫 Kill a given Streamlit app running locally!
 ```
 
-### Troubleshooting
+## Troubleshooting
 
 - Make sure your CLI can access VS Code. See [this link](https://stackoverflow.com/a/40129135/6159698).
 
-- If you get `xcrun: error: invalid active developer path`... error:  
+- If you get `xcrun: error: invalid active developer path`... error:
 Visit https://apple.stackexchange.com/a/254381 or run:
 ```
 xcode-select --install
diff --git a/build/lib/source/__init__.py b/build/lib/source/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/build/lib/source/main.py b/build/lib/source/main.py
deleted file mode 100644
index 024119b..0000000
--- a/build/lib/source/main.py
+++ /dev/null
@@ -1,198 +0,0 @@
-import click
-import os
-import webbrowser
-from pathlib import Path
-import subprocess
-from urllib.parse import urlparse
-import re
-from validators import url
-
-SCRIPT_TEMPLATE = """import streamlit as st
-
-st.title("🎈 My new app!")
-st.write("Welcome to your new app. Have fun editing it")
-st.balloons()
-"""
-
-README_TEMPLATE = """
-[![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](DEPLOYED_APP_URL)
-
-# 🎈 My new app
-
-Some super description of my app
-"""
-
-GITIGNORE_TEMPLATE = """# Byte-compiled / optimized / DLL files
-__pycache__/
-
-# Notebook
-.ipynb_checkpoints
-profile_default/
-ipython_config.py
-
-# virtualenv
-.venv/
-
-# Streamlit secrets
-.streamlit/secret.toml
-"""
-
-
-def get_webbrowser(os="macos"):
-    return webbrowser.get()
-
-
-def header():
-    click.echo(
-        click.style("\n\n🎈 Welcome to `st` 🎈\n\n", fg="red", bold=True),
-        nl=True,
-    )
-
-
-def new_step(text):
-    click.echo(click.style(f"➕ {text}", fg="green", bold=True), nl=True)
-
-
-def warning(text):
-    click.echo(click.style(f"{text}", fg="red", bold=True), nl=True)
-
-
-def git_paths(target):
-    path = urlparse(target).path
-    clone_path = re.split("/blob/", target)[0]
-    cd_path = clone_path.strip("/").rsplit("/", 1)[-1]
-    main_script_path = re.split("/blob/[a-z]+/", path)[-1]
-    requirements_path = main_script_path.rsplit("/", 1)[0]
-    return clone_path, cd_path, main_script_path, requirements_path
-
-@click.command()
-@click.option(
-    "-p",
-    "--path",
-    help="Path where you want to create your Streamlit project.",
-    default=".",
-)
-@click.option(
-    "--open_project_in_vs_code",
-    default=True,
-    help="Open VS code with the newly created file.",
-)
-@click.option(
-    "--run_app",
-    default=True,
-    help="Run Streamlit script",
-)
-@click.option(
-    "--open_app_in_browser",
-    default=True,
-    help="Open Streamlit app in browser",
-)
-
-@click.argument("target", required=False)
-def go(
-    target: str,
-    path: str,
-    open_project_in_vs_code: bool,
-    open_app_in_browser: bool,
-    run_app: bool,
-):
-
-    if target is not None and url(target):
-        header()
-        clone_path, cd_path, main_script_path, requirements_path = git_paths(target)
-
-        # Clone repo
-        new_step(f"Cloning repo {clone_path}")
-        os.system(f"git clone {clone_path} -q")
-
-        # Change directory to repo
-        new_step(f"Changing directory to {cd_path}")
-        os.chdir(cd_path)
-
-        # Create .gitignore
-        new_step(f"Adding .gitignore to {cd_path}...")
-        with open(".gitignore", "w") as f:
-            f.write(GITIGNORE_TEMPLATE)
-
-        # Create a new environment with venv
-        new_step("Creating a new environment with venv")
-        os.system("python -m venv .venv")
-        os.system("source .venv/bin/activate")
-
-        # Install dependencies
-        new_step("Installing dependencies")
-        os.system(f"pip install --quiet -r {os.path.join(requirements_path, 'requirements.txt')}")
-
-        # Open project in VS Code.
-        if open_project_in_vs_code:
-            new_step("Opening project in VS Code...")
-            os.system(f'code .')
-            os.system(f'code {main_script_path}')
-
-        # Run app
-        if run_app:
-            new_step("Running app...")
-            out = subprocess.check_output(["streamlit", "run", main_script_path])
-            new_step(out)
-
-
-        # Open app in browser.
-        if open_app_in_browser:
-            browser = get_webbrowser()
-            new_step("Opening app in the browser...")
-            browser.open_new_tab()
-
-        new_step("Closing...")
-    
-    else:
-        header()
-
-        project_path = Path(path)
-        streamlit_script_path = Path(path) / "streamlit_app.py"
-
-        # Create `streamlit_app.py`
-        if not os.path.exists(streamlit_script_path):
-            new_step(f"Creating new Streamlit script at {streamlit_script_path}...")
-            with open(streamlit_script_path, "w") as f:
-                f.write(SCRIPT_TEMPLATE)
-        else:
-            click.echo(click.style("Streamlit script already exists!"))
-
-        # Create secrets
-        new_step(f"Adding secrets to {streamlit_script_path}...")
-        (project_path / ".streamlit").mkdir(parents=False, exist_ok=False)
-        (project_path / ".streamlit" / "secrets.toml").touch()
-
-        # Create requirements
-        new_step(f"Adding requirements to {streamlit_script_path}...")
-        (project_path / "requirements.txt").touch()
-
-        # Create .gitignore
-        new_step(f"Adding .gitignore to {streamlit_script_path}...")
-        with open(project_path / ".gitignore", "w") as f:
-            f.write(GITIGNORE_TEMPLATE)
-
-        # Create README
-        new_step(f"Adding README.md to {streamlit_script_path}...")
-        with open(project_path / "README.md", "w") as f:
-            f.write(README_TEMPLATE)
-
-        # Open project in VS Code.
-        if open_project_in_vs_code:
-            new_step("Opening project in VS Code...")
-            os.system(f'code "{project_path}"')
-            os.system(f'code "{streamlit_script_path}"')
-
-        # Run app.
-        if run_app:
-            new_step("Running Streamlit app")
-            out = subprocess.check_output(["streamlit", "run", streamlit_script_path])
-            new_step(out)
-
-        # Open app in browser.
-        if open_app_in_browser:
-            browser = get_webbrowser()
-            new_step("Opening app in the browser...")
-            browser.open_new_tab()
-
-        new_step("Closing...")
\ No newline at end of file
diff --git a/build/lib/st/__init__.py b/build/lib/st/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/build/lib/st/main.py b/build/lib/st/main.py
deleted file mode 100644
index 59e6fd4..0000000
--- a/build/lib/st/main.py
+++ /dev/null
@@ -1,93 +0,0 @@
-import click
-import os
-import webbrowser
-from pathlib import Path
-
-SCRIPT_TEMPLATE = """import streamlit as st
-
-st.title("🎈 My new app!")
-st.write("Welcome to your new app. Have fun editing it")
-st.balloons()
-"""
-
-
-def get_webbrowser(os="macos"):
-    return webbrowser.get()
-
-
-def header():
-    click.echo(
-        click.style("\n\n🎈 Welcome to `st` 🎈\n\n", fg="red", bold=True),
-        nl=True,
-    )
-
-
-def new_step(text):
-    click.echo(click.style(f"➕ {text}", fg="green", bold=True), nl=True)
-
-
-def warning(text):
-    click.echo(click.style(f"{text}", fg="red", bold=True), nl=True)
-
-
-@click.command()
-@click.option(
-    "-p",
-    "--path",
-    help="Path where you want to create your Streamlit project.",
-)
-@click.option(
-    "--open_project_in_vs_code",
-    default=1,
-    help="Open VS code with the newly created file.",
-)
-@click.option(
-    "--run_app",
-    default=1,
-    help="Run Streamlit script",
-)
-@click.option(
-    "--open_app_in_browser",
-    default=1,
-    help="Open Streamlit app in browser",
-)
-def go(
-    path: str,
-    open_project_in_vs_code: int,
-    open_app_in_browser: int,
-    run_app: int,
-):
-
-    header()
-
-    streamlit_script_path = Path(path) / "streamlit_app.py"
-
-    # Create `streamlit_app.py`
-    if not os.path.exists(streamlit_script_path):
-        new_step(f"Creating new Streamlit script at {streamlit_script_path}...")
-        with open(streamlit_script_path, "w") as f:
-            f.write(SCRIPT_TEMPLATE)
-    else:
-        click.echo(click.style("Streamlit script already exists!"))
-
-    # Open project in VS Code.
-    if open_project_in_vs_code:
-        new_step("Opening project in VS Code...")
-        os.system(f'code "{streamlit_script_path}"')
-
-    # Run app.
-    if run_app:
-        new_step("Running Streamlit app")
-        import subprocess
-
-        out = subprocess.check_output(["streamlit", "run", streamlit_script_path])
-
-        new_step(out)
-
-    # Open app in browser.
-    if open_app_in_browser:
-        browser = get_webbrowser()
-        new_step("Opening app in the browser...")
-        browser.open_new_tab()
-
-    new_step("Closing...")
\ No newline at end of file
diff --git a/build/lib/st/st.py b/build/lib/st/st.py
deleted file mode 100644
index 403fa01..0000000
--- a/build/lib/st/st.py
+++ /dev/null
@@ -1,97 +0,0 @@
-import click
-import os
-import webbrowser
-from pathlib import Path
-
-SCRIPT_TEMPLATE = """import streamlit as st
-
-st.title("🎈 My new app!")
-st.write("Welcome to your new app. Have fun editing it")
-st.balloons()
-"""
-
-
-def get_webbrowser(os="macos"):
-    return webbrowser.get()
-
-
-def header():
-    click.echo(
-        click.style("\n\n🎈 Welcome to `st` 🎈\n\n", fg="red", bold=True),
-        nl=True,
-    )
-
-
-def new_step(text):
-    click.echo(click.style(f"➕ {text}", fg="green", bold=True), nl=True)
-
-
-def warning(text):
-    click.echo(click.style(f"{text}", fg="red", bold=True), nl=True)
-
-
-@click.command()
-@click.option(
-    "-p",
-    "--path",
-    help="Path where you want to create your Streamlit project.",
-)
-@click.option(
-    "--open_project_in_vs_code",
-    default=1,
-    help="Open VS code with the newly created file.",
-)
-@click.option(
-    "--run_app",
-    default=1,
-    help="Run Streamlit script",
-)
-@click.option(
-    "--open_app_in_browser",
-    default=1,
-    help="Open Streamlit app in browser",
-)
-def go(
-    path: str,
-    open_project_in_vs_code: int,
-    open_app_in_browser: int,
-    run_app: int,
-):
-
-    header()
-
-    streamlit_script_path = Path(path) / "streamlit_app.py"
-
-    # Create `streamlit_app.py`
-    if not os.path.exists(streamlit_script_path):
-        new_step(f"Creating new Streamlit script at {streamlit_script_path}...")
-        with open(streamlit_script_path, "w") as f:
-            f.write(SCRIPT_TEMPLATE)
-    else:
-        click.echo(click.style("Streamlit script already exists!"))
-
-    # Open project in VS Code.
-    if open_project_in_vs_code:
-        new_step("Opening project in VS Code...")
-        os.system(f'code "{streamlit_script_path}"')
-
-    # Run app.
-    if run_app:
-        new_step("Running Streamlit app")
-        import subprocess
-
-        out = subprocess.check_output(["streamlit", "run", streamlit_script_path])
-
-        new_step(out)
-
-    # Open app in browser.
-    if open_app_in_browser:
-        browser = get_webbrowser()
-        new_step("Opening app in the browser...")
-        browser.open_new_tab()
-
-    new_step("Closing...")
-
-
-if __name__ == "__main__":
-    go()
\ No newline at end of file
diff --git a/dist/st-kickoff-0.0.1.tar.gz b/dist/st-kickoff-0.0.1.tar.gz
deleted file mode 100644
index df7ac22..0000000
Binary files a/dist/st-kickoff-0.0.1.tar.gz and /dev/null differ
diff --git a/dist/st-kickoff-0.1.tar.gz b/dist/st-kickoff-0.1.tar.gz
deleted file mode 100644
index b6a4c55..0000000
Binary files a/dist/st-kickoff-0.1.tar.gz and /dev/null differ
diff --git a/dist/st-kickoff-0.2.tar.gz b/dist/st-kickoff-0.2.tar.gz
deleted file mode 100644
index 007fc33..0000000
Binary files a/dist/st-kickoff-0.2.tar.gz and /dev/null differ
diff --git a/dist/st_kickoff-0.0.1-py3-none-any.whl b/dist/st_kickoff-0.0.1-py3-none-any.whl
deleted file mode 100644
index d57a725..0000000
Binary files a/dist/st_kickoff-0.0.1-py3-none-any.whl and /dev/null differ
diff --git a/dist/st_kickoff-0.0.1-py3.7.egg b/dist/st_kickoff-0.0.1-py3.7.egg
deleted file mode 100644
index 0cdbc84..0000000
Binary files a/dist/st_kickoff-0.0.1-py3.7.egg and /dev/null differ
diff --git a/dist/st_kickoff-0.1-py3-none-any.whl b/dist/st_kickoff-0.1-py3-none-any.whl
deleted file mode 100644
index e38df47..0000000
Binary files a/dist/st_kickoff-0.1-py3-none-any.whl and /dev/null differ
diff --git a/dist/st_kickoff-0.2-py3-none-any.whl b/dist/st_kickoff-0.2-py3-none-any.whl
deleted file mode 100644
index d8edbbd..0000000
Binary files a/dist/st_kickoff-0.2-py3-none-any.whl and /dev/null differ
diff --git a/requirements.txt b/requirements.txt
index 4593e65..148ed57 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
 click==7.1.2
 streamlit
+cookiecutter
diff --git a/setup.py b/setup.py
index 0eeab57..46307fc 100644
--- a/setup.py
+++ b/setup.py
@@ -1,29 +1,34 @@
-from setuptools import setup, find_packages
+from setuptools import find_packages, setup
 
 with open("requirements.txt") as f:
     requirements = f.readlines()
 
-long_description = "st is a CLI that helps you kick-off a new Streamlit  \
-    project so you can start crafting the app as soon as possible!"
+description = (
+    "stk is a CLI that helps you kickoff a new Streamlit project"
+    " so you can start crafting the app as soon as possible!"
+)
+
+long_description = (
+    description
+    + " It also includes convenient commands like `dev` to open your app in VS"
+    " Code + browser automatically and `list` to list all your running"
+    " apps... Not forgetting `kill` if you want to turn off an app!"
+)
 
 setup(
-    name="st-kickoff",
-    version="0.2",
+    name="streamlit-kickoff-cli",
+    version="0.1",
     author="Arnaud Miribel",
     author_email="arnaudmiribel@gmail.com",
-    url="https://github.com/arnaudmiribel/st",
-    description="st is a CLI that helps you kick-off a new Streamlit project \
-         so you can start crafting the app as soon as possible!",
+    url="https://github.com/arnaudmiribel/streamlit-kickoff-cli",
+    description=description,
     long_description=long_description,
     long_description_content_type="text/markdown",
     license="MIT",
     packages=find_packages(),
-    entry_points={"console_scripts": ["st = source.main:go"]},
-    classifiers=(
-        "Programming Language :: Python :: 3",
-        "License :: OSI Approved :: MIT License",
-    ),
+    entry_points={"console_scripts": ["stk = source.main"]},
+    classifiers=("Programming Language :: Python :: 3",),
     keywords="streamlit cli",
     install_requires=requirements,
     zip_safe=False,
-)
\ No newline at end of file
+)
diff --git a/source/__init__.py b/source/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/source/main.py b/source/main.py
deleted file mode 100644
index 024119b..0000000
--- a/source/main.py
+++ /dev/null
@@ -1,198 +0,0 @@
-import click
-import os
-import webbrowser
-from pathlib import Path
-import subprocess
-from urllib.parse import urlparse
-import re
-from validators import url
-
-SCRIPT_TEMPLATE = """import streamlit as st
-
-st.title("🎈 My new app!")
-st.write("Welcome to your new app. Have fun editing it")
-st.balloons()
-"""
-
-README_TEMPLATE = """
-[![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](DEPLOYED_APP_URL)
-
-# 🎈 My new app
-
-Some super description of my app
-"""
-
-GITIGNORE_TEMPLATE = """# Byte-compiled / optimized / DLL files
-__pycache__/
-
-# Notebook
-.ipynb_checkpoints
-profile_default/
-ipython_config.py
-
-# virtualenv
-.venv/
-
-# Streamlit secrets
-.streamlit/secret.toml
-"""
-
-
-def get_webbrowser(os="macos"):
-    return webbrowser.get()
-
-
-def header():
-    click.echo(
-        click.style("\n\n🎈 Welcome to `st` 🎈\n\n", fg="red", bold=True),
-        nl=True,
-    )
-
-
-def new_step(text):
-    click.echo(click.style(f"➕ {text}", fg="green", bold=True), nl=True)
-
-
-def warning(text):
-    click.echo(click.style(f"{text}", fg="red", bold=True), nl=True)
-
-
-def git_paths(target):
-    path = urlparse(target).path
-    clone_path = re.split("/blob/", target)[0]
-    cd_path = clone_path.strip("/").rsplit("/", 1)[-1]
-    main_script_path = re.split("/blob/[a-z]+/", path)[-1]
-    requirements_path = main_script_path.rsplit("/", 1)[0]
-    return clone_path, cd_path, main_script_path, requirements_path
-
-@click.command()
-@click.option(
-    "-p",
-    "--path",
-    help="Path where you want to create your Streamlit project.",
-    default=".",
-)
-@click.option(
-    "--open_project_in_vs_code",
-    default=True,
-    help="Open VS code with the newly created file.",
-)
-@click.option(
-    "--run_app",
-    default=True,
-    help="Run Streamlit script",
-)
-@click.option(
-    "--open_app_in_browser",
-    default=True,
-    help="Open Streamlit app in browser",
-)
-
-@click.argument("target", required=False)
-def go(
-    target: str,
-    path: str,
-    open_project_in_vs_code: bool,
-    open_app_in_browser: bool,
-    run_app: bool,
-):
-
-    if target is not None and url(target):
-        header()
-        clone_path, cd_path, main_script_path, requirements_path = git_paths(target)
-
-        # Clone repo
-        new_step(f"Cloning repo {clone_path}")
-        os.system(f"git clone {clone_path} -q")
-
-        # Change directory to repo
-        new_step(f"Changing directory to {cd_path}")
-        os.chdir(cd_path)
-
-        # Create .gitignore
-        new_step(f"Adding .gitignore to {cd_path}...")
-        with open(".gitignore", "w") as f:
-            f.write(GITIGNORE_TEMPLATE)
-
-        # Create a new environment with venv
-        new_step("Creating a new environment with venv")
-        os.system("python -m venv .venv")
-        os.system("source .venv/bin/activate")
-
-        # Install dependencies
-        new_step("Installing dependencies")
-        os.system(f"pip install --quiet -r {os.path.join(requirements_path, 'requirements.txt')}")
-
-        # Open project in VS Code.
-        if open_project_in_vs_code:
-            new_step("Opening project in VS Code...")
-            os.system(f'code .')
-            os.system(f'code {main_script_path}')
-
-        # Run app
-        if run_app:
-            new_step("Running app...")
-            out = subprocess.check_output(["streamlit", "run", main_script_path])
-            new_step(out)
-
-
-        # Open app in browser.
-        if open_app_in_browser:
-            browser = get_webbrowser()
-            new_step("Opening app in the browser...")
-            browser.open_new_tab()
-
-        new_step("Closing...")
-    
-    else:
-        header()
-
-        project_path = Path(path)
-        streamlit_script_path = Path(path) / "streamlit_app.py"
-
-        # Create `streamlit_app.py`
-        if not os.path.exists(streamlit_script_path):
-            new_step(f"Creating new Streamlit script at {streamlit_script_path}...")
-            with open(streamlit_script_path, "w") as f:
-                f.write(SCRIPT_TEMPLATE)
-        else:
-            click.echo(click.style("Streamlit script already exists!"))
-
-        # Create secrets
-        new_step(f"Adding secrets to {streamlit_script_path}...")
-        (project_path / ".streamlit").mkdir(parents=False, exist_ok=False)
-        (project_path / ".streamlit" / "secrets.toml").touch()
-
-        # Create requirements
-        new_step(f"Adding requirements to {streamlit_script_path}...")
-        (project_path / "requirements.txt").touch()
-
-        # Create .gitignore
-        new_step(f"Adding .gitignore to {streamlit_script_path}...")
-        with open(project_path / ".gitignore", "w") as f:
-            f.write(GITIGNORE_TEMPLATE)
-
-        # Create README
-        new_step(f"Adding README.md to {streamlit_script_path}...")
-        with open(project_path / "README.md", "w") as f:
-            f.write(README_TEMPLATE)
-
-        # Open project in VS Code.
-        if open_project_in_vs_code:
-            new_step("Opening project in VS Code...")
-            os.system(f'code "{project_path}"')
-            os.system(f'code "{streamlit_script_path}"')
-
-        # Run app.
-        if run_app:
-            new_step("Running Streamlit app")
-            out = subprocess.check_output(["streamlit", "run", streamlit_script_path])
-            new_step(out)
-
-        # Open app in browser.
-        if open_app_in_browser:
-            browser = get_webbrowser()
-            new_step("Opening app in the browser...")
-            browser.open_new_tab()
-
-        new_step("Closing...")
\ No newline at end of file
diff --git a/st_kickoff.egg-info/PKG-INFO b/st_kickoff.egg-info/PKG-INFO
deleted file mode 100644
index cde0384..0000000
--- a/st_kickoff.egg-info/PKG-INFO
+++ /dev/null
@@ -1,17 +0,0 @@
-Metadata-Version: 2.1
-Name: st-kickoff
-Version: 0.2
-Summary: st is a CLI that helps you kick-off a new Streamlit project          so you can start crafting the app as soon as possible!
-Home-page: https://github.com/arnaudmiribel/st
-Author: Arnaud Miribel
-Author-email: arnaudmiribel@gmail.com
-License: MIT
-Keywords: streamlit cli
-Platform: UNKNOWN
-Classifier: Programming Language :: Python :: 3
-Classifier: License :: OSI Approved :: MIT License
-Description-Content-Type: text/markdown
-License-File: LICENSE
-
-st is a CLI that helps you kick-off a new Streamlit      project so you can start crafting the app as soon as possible!
-
diff --git a/st_kickoff.egg-info/SOURCES.txt b/st_kickoff.egg-info/SOURCES.txt
deleted file mode 100644
index d245d5c..0000000
--- a/st_kickoff.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-LICENSE
-README.md
-setup.py
-source/__init__.py
-source/main.py
-st_kickoff.egg-info/PKG-INFO
-st_kickoff.egg-info/SOURCES.txt
-st_kickoff.egg-info/dependency_links.txt
-st_kickoff.egg-info/entry_points.txt
-st_kickoff.egg-info/not-zip-safe
-st_kickoff.egg-info/requires.txt
-st_kickoff.egg-info/top_level.txt
\ No newline at end of file
diff --git a/st_kickoff.egg-info/dependency_links.txt b/st_kickoff.egg-info/dependency_links.txt
deleted file mode 100644
index 8b13789..0000000
--- a/st_kickoff.egg-info/dependency_links.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-
diff --git a/st_kickoff.egg-info/entry_points.txt b/st_kickoff.egg-info/entry_points.txt
deleted file mode 100644
index 2fed8f8..0000000
--- a/st_kickoff.egg-info/entry_points.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-[console_scripts]
-st = source.main:go
-
diff --git a/st_kickoff.egg-info/not-zip-safe b/st_kickoff.egg-info/not-zip-safe
deleted file mode 100644
index 8b13789..0000000
--- a/st_kickoff.egg-info/not-zip-safe
+++ /dev/null
@@ -1,1 +0,0 @@
-
diff --git a/st_kickoff.egg-info/requires.txt b/st_kickoff.egg-info/requires.txt
deleted file mode 100644
index 4593e65..0000000
--- a/st_kickoff.egg-info/requires.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-click==7.1.2
-streamlit
diff --git a/st_kickoff.egg-info/top_level.txt b/st_kickoff.egg-info/top_level.txt
deleted file mode 100644
index 5a18cd2..0000000
--- a/st_kickoff.egg-info/top_level.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-source
diff --git a/streamlit_kickoff_cli/main.py b/streamlit_kickoff_cli/main.py
new file mode 100644
index 0000000..b8f08c8
--- /dev/null
+++ b/streamlit_kickoff_cli/main.py
@@ -0,0 +1,406 @@
+import io
+import os
+import re
+import subprocess
+import sys
+import webbrowser
+from pathlib import Path
+from urllib.parse import urlparse
+
+import click
+import pandas as pd
+from cookiecutter.main import cookiecutter
+from validators import url
+
+DEFAULT_REPOSITORY = (
+    "https://github.com/arnaudmiribel/st-template/blob/main/streamlit_app.py"
+)
+
+COOKIECUTTER_DEFAULT_DIRECTORY = "app"
+COOKIECUTTER_REPOSITORY = (
+    "https://github.com/arnaudmiribel/st-cookiecutter.git"
+)
+
+# Subclassing Click CLI to display commands in a specific order
+class NaturalOrderGroup(click.Group):
+    def list_commands(self, ctx):
+        return self.commands.keys()
+
+
+def get_webbrowser(os="macos"):
+    return webbrowser.get()
+
+
+def header():
+    click.echo(
+        click.style(
+            "\n\nWelcome to streamlit-kickoff-cli (stk) 👞\n\n",
+            fg="red",
+            bold=True,
+        ),
+        nl=True,
+    )
+
+
+def choice(text):
+    click.echo(
+        click.style(f"\n{text}", bold=True),
+        nl=True,
+    )
+
+
+def new_step(text):
+    click.echo(click.style(f"➕ {text}", bold=False), nl=True)
+
+
+def warning(text):
+    click.echo(click.style(f"\n⚠️ {text}", fg="red", bold=True), nl=True)
+
+
+def error(text):
+    click.echo(click.style(f"\n💀 {text}", fg="red", bold=True), nl=True)
+
+
+def success(text):
+    click.echo(click.style(f"\n🎉 {text}\n", bold=True), nl=True)
+
+
+def _target_is_valid(target: str) -> bool:
+    if target is not None and url(target):
+        if "github" in target and target.endswith(".py"):
+            return True
+
+    return False
+
+
+def target_is_valid(target: str):
+    """Checks if target is valid. Otherwise throw exception and exit.
+
+    Args:
+        target (str): Target repository URL
+    """
+    if not _target_is_valid(target=target):
+        error(
+            "Input target must be a GitHub URL that points to the main .py"
+            " script"
+        )
+        sys.exit()
+
+
+def parse_target(target: str):
+    """Parse the target repository URL into useful pieces
+
+    Args:
+        target (str): Target repository URL
+
+    Returns:
+        str: Repository GitHub URL
+        pathlib.Path: Project path
+        pathlib.Path: Streamlit main script path
+        pathlib.Path: Requirements file path
+    """
+    repository_url = urlparse(target).path
+    repository_url = re.split("/blob/", target)[0]
+    project_path = Path(repository_url.strip("/").rsplit("/", 1)[-1])
+    streamlit_script_path = (
+        project_path / re.split("/blob/[a-z]+/", target)[-1]
+    )
+    requirements_path = project_path / "requirements.txt"
+
+    return (
+        repository_url,
+        project_path,
+        streamlit_script_path,
+        requirements_path,
+    )
+
+
+@click.group(cls=NaturalOrderGroup)
+def main():
+    """Welcome to stk 👞
+
+    This is a simple CLI to help you kick off and
+    maintain Streamlit projects as fast as possible!
+    """
+
+
+def _new():
+    choice("🆕 You just asked for a `new` Streamlit project. Let's go!")
+
+    directory_name = click.prompt(
+        "New directory name",
+        type=str,
+        default=COOKIECUTTER_DEFAULT_DIRECTORY,
+    )
+
+    if click.confirm("Want to customize the template?", default=False):
+        app_title = click.prompt(
+            "Title of your app", type=str, default="Balloons"
+        )
+        app_uses_secrets = click.confirm(
+            "Will you be using secrets?", default=True
+        )
+        app_is_multi_page = click.confirm(
+            "Will your app use more than one page?", default=True
+        )
+        app_uses_snowflake = click.confirm(
+            "Are you connecting to Snowflake in this app?", default=False
+        )
+
+        cookiecutter(
+            COOKIECUTTER_REPOSITORY,
+            no_input=True,
+            extra_context={
+                "app_name": directory_name,
+                "app_title": app_title,
+                "app_uses_secrets": int(app_uses_secrets),
+                "app_is_multi_page": int(app_is_multi_page),
+                "app_uses_snowflake": int(app_uses_snowflake),
+            },
+        )
+
+    else:
+        click.echo(
+            "Fine! We will use default values and create your new Streamlit"
+            f" app in '{directory_name}'"
+        )
+        cookiecutter(
+            COOKIECUTTER_REPOSITORY,
+            no_input=True,
+        )
+
+    success(
+        "Successfully created your new Streamlit app in directory"
+        f" '{directory_name}/'!"
+    )
+
+    return directory_name
+
+
+@main.command()
+def new():
+    """🆕 Create a new Streamlit project"""
+    return _new()
+
+
+def _dev(path: str = "streamlit_app.py"):
+    choice("👩‍💻 Dev time! Let's open VS Code and your app in Chrome!")
+
+    path_ = Path(path)
+
+    if path_.is_file():
+
+        # Open project in VS Code.
+        new_step("Opening project in VS Code...")
+        os.system(f"code . --new-window")
+        os.system(f"code {path} --reuse-window")
+
+        # Run app.
+        new_step("Running app...")
+        out = subprocess.check_output(["streamlit", "run", path])
+        new_step(out)
+
+        # Open app in browser.
+        browser = get_webbrowser()
+        new_step("Opening app in the browser...")
+        browser.open_new_tab()
+
+        # TODO: AppleScript to get the two windows side-by-side!
+
+    else:
+        error(
+            f"Couldn't find the app script {path} in this directory. \nUse"
+            " '-p' to specify a custom path"
+        )
+
+
+@main.command()
+@click.option(
+    "-p",
+    "--path",
+    default="streamlit_app.py",
+    help="Path to the main script of the app (e.g. 'streamlit_app.py')",
+    type=str,
+)
+def dev(path: str):
+    """👩‍💻 Dev time! Opens VS Code and your app in Chrome!"""
+    _dev(path)
+
+
+@main.command()
+def kick():
+    """🚀 New app + dev set up NOW!"""
+
+    choice("🚀 New app + dev set up NOW!")
+
+    directory_name = _new()
+    if directory_name:
+        os.chdir(directory_name)
+        _dev()
+
+
+def get_list() -> pd.DataFrame:
+    lsof = subprocess.check_output(
+        "lsof -nP -iTCP -sTCP:LISTEN",
+        shell=True,
+        stderr=subprocess.STDOUT,
+    )
+
+    df = pd.read_fwf(io.BytesIO(lsof))
+    df = df[df.COMMAND.eq("Python")]
+    df["PORT"] = df.NAME.apply(lambda d: d.split("(LISTEN)")[0].split(":")[1])
+    df = df[df.PORT.str.startswith("85")]
+    df["URL"] = "http://localhost:" + df.PORT
+    df = df.rename(columns={"PID": "App ID", "URL": "App URL"})
+    return df
+
+
+@main.command()
+def list():
+    """🤯 List running Streamlit apps under ports 85**"""
+
+    choice("Let's look at your apps running locally...")
+
+    df = get_list()
+    if df.empty:
+        click.echo("Found no app running!")
+    else:
+        click.echo(
+            df[["App ID", "App URL"]].set_index("App ID").drop_duplicates()
+        )
+
+
+@main.command()
+@click.option(
+    "-id", "--id", default=None, help="Kill a given app ID", type=int
+)
+@click.option(
+    "-a",
+    "--all",
+    is_flag=True,
+    default=False,
+    help="Kill all Streamlit apps",
+    type=bool,
+)
+def kill(id: int, all: bool):
+    """🔫 Kill a given Streamlit app running locally!"""
+
+    if (id is not None and all) or (id is None and not all):
+        error(
+            "You must either input the app process ID --id or choose --all to"
+            " kill all apps at once."
+        )
+        sys.exit()
+
+    df = get_list()
+
+    all_ids = df["App ID"].unique()
+    if id is not None:
+        if id in all_ids:
+            click.echo(f"Killing {id}...")
+            os.system(f"kill -9 {id}")
+        else:
+            error(
+                f"This app ID is not valid. Valid IDs are {all_ids}. Use `st"
+                " list` to learn more."
+            )
+
+    if all:
+        click.echo(f"Killing all apps...")
+        os.system(f"kill -9 {' '.join(map(str, all_ids))}")
+        click.echo(f"Succesfully killed ")
+
+
+# @main.command()
+# @click.argument(
+#     "target",
+#     help="URL to Streamlit script in GitHub repository URL.",
+#     default=DEFAULT_REPOSITORY,
+# )
+# @click.option(
+#     "-p",
+#     "--path",
+#     help="Path where you want to create your Streamlit project.",
+#     default="app",
+# )
+# @click.option(
+#     "--open_project_in_vs_code",
+#     default=True,
+#     help="Open VS code with the newly created file.",
+# )
+# @click.option(
+#     "--run_app",
+#     default=True,
+#     help="Run Streamlit script",
+# )
+# @click.option(
+#     "--open_app_in_browser",
+#     default=True,
+#     help="Open Streamlit app in browser",
+# )
+# def clone(
+#     target: str,
+#     path: str,
+#     open_project_in_vs_code: bool,
+#     open_app_in_browser: bool,
+#     run_app: bool,
+# ):
+#     """👯 Clone an existing Streamlit project"""
+
+#     if target is not None:
+
+#         target_is_valid(target)
+
+#         (
+#             repository_url,
+#             project_path,
+#             streamlit_script_path,
+#             requirements_path,
+#         ) = parse_target(target)
+
+#         if path:
+#             project_path = Path(path)
+
+#         # Clone repo
+#         new_step(f"Cloning repo {repository_url}")
+#         os.system(f"git clone {repository_url} {project_path} -q")
+
+#         # Change directory to repo
+#         if path != ".":
+#             new_step(f"Changing directory to '{project_path}/'")
+#             os.chdir(project_path.name)
+
+#         # Create a new environment with venv
+#         new_step("Creating a new environment with venv")
+#         os.system("python3 -m venv .venv")
+#         os.system("source .venv/bin/activate")
+
+#         # Install dependencies
+#         new_step("Installing dependencies")
+#         os.system(f"pip3 install --quiet -r {requirements_path.name}")
+
+#         # Open project in VS Code.
+#         if open_project_in_vs_code:
+#             new_step("Opening project in VS Code...")
+#             os.system(f"code .")
+#             os.system(f"code {streamlit_script_path.name}")
+
+#         # Run app
+#         if run_app:
+#             new_step("Running app...")
+#             out = subprocess.check_output(
+#                 ["streamlit", "run", streamlit_script_path.name]
+#             )
+#             new_step(out)
+
+#         # Open app in browser.
+#         if open_app_in_browser:
+#             browser = get_webbrowser()
+#             new_step("Opening app in the browser...")
+#             browser.open_new_tab()
+
+#         new_step("Closing...")
+
+
+if __name__ == "__main__":
+    main()


Test patch:
diff --git a/streamlit_kickoff_cli/tests/test_parser.py b/streamlit_kickoff_cli/tests/test_parser.py
new file mode 100644
index 0000000..f3326ac
--- /dev/null
+++ b/streamlit_kickoff_cli/tests/test_parser.py
@@ -0,0 +1,41 @@
+from pathlib import Path
+
+from ..main import _target_is_valid, parse_target
+
+
+def test_target_is_valid():
+    assert _target_is_valid(
+        "https://github.com/streamlit/streamlit-example/blob/master/streamlit_app.py"
+    )
+    assert not _target_is_valid(
+        "https://github.com/streamlit/streamlit-example/blob/master/streamlit_app"
+    )
+    assert not _target_is_valid(
+        "https://github.com/streamlit/streamlit-example"
+    )
+    assert not _target_is_valid("https://google.com/")
+    assert not _target_is_valid("foo")
+
+
+def test_parse_git_url():
+
+    (
+        repository_url,
+        project_path,
+        streamlit_script_path,
+        requirements_path,
+    ) = parse_target(
+        "https://github.com/streamlit/streamlit-example/blob/master/streamlit_app.py"
+    )
+
+    print(repository_url)
+    print(project_path)
+    print(streamlit_script_path)
+    print(requirements_path)
+
+    assert (
+        project_path == Path("streamlit-example")
+        and repository_url == "https://github.com/streamlit/streamlit-example"
+        and streamlit_script_path == (project_path / "streamlit_app.py")
+        and requirements_path == (Path(project_path) / "requirements.txt")
+    )