Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
name: Build and Format Check

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
paths:
- 'javascript/**'
- '.github/workflows/javascript-check.yml'

jobs:
build-and-format:
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/python-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Python Checks

on:
pull_request:
paths:
- 'python/**'
- '.github/workflows/python-checks.yml'

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Run ruff format check
working-directory: ./python
run: uvx ruff format --check .

- name: Run ruff lint
working-directory: ./python
run: uvx ruff check .
Empty file added python/forevervm/__init__.py
Empty file.
59 changes: 59 additions & 0 deletions python/forevervm/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import argparse
from forevervm.commands.login import login


def machine_create(args):
print("Machine create command")


def machine_list(args):
print("Machine list command")


def machine_repl(args):
print("Machine REPL command")


def main():
parser = argparse.ArgumentParser(description="ForeverVM CLI")
subparsers = parser.add_subparsers(dest="command", help="Available commands")

# Login command
_login_parser = subparsers.add_parser("login", help="Log in to ForeverVM")

# Machine commands
machine_parser = subparsers.add_parser(
"machine", help="Machine management commands"
)
machine_subparsers = machine_parser.add_subparsers(
dest="machine_command", help="Machine commands"
)

# Machine create
_create_parser = machine_subparsers.add_parser("create", help="Create a new machine")

# Machine list
_list_parser = machine_subparsers.add_parser("list", help="List all machines")

# Machine REPL
_repl_parser = machine_subparsers.add_parser(
"repl", help="Start a REPL session with a machine"
)

args = parser.parse_args()

if args.command == "login":
login()
elif args.command == "machine":
if args.machine_command == "create":
machine_create(args)
elif args.machine_command == "list":
machine_list(args)
elif args.machine_command == "repl":
machine_repl(args)
else:
parser.print_help()


if __name__ == "__main__":
main()
Binary file not shown.
Binary file not shown.
Binary file not shown.
29 changes: 29 additions & 0 deletions python/forevervm/client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import requests
from forevervm.client.types import WhoamiResponse

API_BASE_URL = "https://api.forevervm.com"


class ForeverVM:
def __init__(self, token, base_url=API_BASE_URL):
self.token = token
self.base_url = base_url

def get(self, path):
result = requests.get(
f"{self.base_url}{path}", headers={"Authorization": f"Bearer {self.token}"}
)
result.raise_for_status()
return result

def post(self, path, data):
result = requests.post(
f"{self.base_url}{path}",
headers={"Authorization": f"Bearer {self.token}"},
json=data,
)
result.raise_for_status()
return result

def whoami(self) -> WhoamiResponse:
return self.get("/v1/whoami").json()
Binary file not shown.
Binary file not shown.
5 changes: 5 additions & 0 deletions python/forevervm/client/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from typing import TypedDict


class WhoamiResponse(TypedDict):
account: str
Empty file.
Binary file not shown.
Binary file not shown.
36 changes: 36 additions & 0 deletions python/forevervm/commands/login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from getpass import getpass
from forevervm.client import ForeverVM
from ..config import ConfigManager


def login():
config_manager = ConfigManager()

# Check if already logged in
config = config_manager.load_config()
if config and "token" in config:
token = config["token"]
try:
sdk = ForeverVM(token)
account = sdk.whoami()
print(
f"Already logged in as {account['account']}. Use logout to log out first if you would like to change accounts."
)
return
except Exception:
# Token might be invalid, continue with login
pass

# Prompt for token
token = getpass("Enter your token: ")

# Verify token
try:
sdk = ForeverVM(token)
account = sdk.whoami()

# Save config
config_manager.save_config({"token": token})
print(f"Successfully logged in as {account['account']}")
except Exception as e:
print(f"Login failed: {str(e)}")
26 changes: 26 additions & 0 deletions python/forevervm/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os
import json
from typing import TypedDict


class CliConfig(TypedDict):
token: str


class ConfigManager:
def __init__(self):
self.config_dir = os.path.expanduser("~/.config/forevervm")
self.config_file = os.path.join(self.config_dir, "config.json")
os.makedirs(self.config_dir, exist_ok=True)

def load_config(self) -> CliConfig | None:
try:
with open(self.config_file, "r") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return None

def save_config(self, config: CliConfig):
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
with open(self.config_file, "w") as f:
json.dump(config, f)