From 72b03d43f18170363fa031c82cfd721afc141d00 Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Sat, 30 May 2026 23:32:48 +0530 Subject: [PATCH 01/15] refactor: restructure backend into layered app/ package --- .gitignore | 4 +- api/db/database.py | 20 -------- api/main.py | 42 ----------------- app/__init__.py | 8 ++++ {api => app/api}/__init__.py | 0 {api => app/api}/deps.py | 2 +- app/api/router.py | 12 +++++ {api => app/api}/routes/__init__.py | 0 {api => app/api}/routes/forms.py | 24 +++++----- {api => app/api}/routes/templates.py | 15 +++--- {api/db => app/api/schemas}/__init__.py | 0 {api => app/api}/schemas/common.py | 0 {api => app/api}/schemas/forms.py | 1 - {api => app/api}/schemas/templates.py | 0 {api/errors => app/core}/__init__.py | 0 app/core/config.py | 47 +++++++++++++++++++ {api/schemas => app/core/errors}/__init__.py | 0 {api => app/core}/errors/base.py | 0 {api => app/core}/errors/handlers.py | 2 +- app/core/lifespan.py | 17 +++++++ app/core/logging.py | 14 ++++++ {src => app/db}/__init__.py | 0 app/db/database.py | 14 ++++++ {api => app}/db/init_db.py | 34 +++++++++----- {api => app}/db/repositories.py | 2 +- app/main.py | 34 ++++++++++++++ app/models/__init__.py | 5 ++ {api/db => app/models}/models.py | 0 app/services/__init__.py | 0 {src => app/services}/controller.py | 2 +- {src => app/services}/file_manipulator.py | 4 +- {src => app/services}/filler.py | 2 +- {src => app/services}/inputs/input.txt | 0 {src => app/services}/llm.py | 7 +-- .../services}/outputs/test_output_1.json | 0 {src => app/services}/prompt.txt | 0 .../services}/test/example_template.json | 0 {src => app/services}/test/test_output_1.json | 0 examples/pipeline_demo.py | 27 +++++++++++ src/main.py | 42 ----------------- tests/conftest.py | 10 ++-- tests/test_api.py | 2 +- {src/test => tests}/test_model.py | 0 43 files changed, 238 insertions(+), 155 deletions(-) delete mode 100644 api/db/database.py delete mode 100644 api/main.py create mode 100644 app/__init__.py rename {api => app/api}/__init__.py (100%) rename {api => app/api}/deps.py (51%) create mode 100644 app/api/router.py rename {api => app/api}/routes/__init__.py (100%) rename {api => app/api}/routes/forms.py (85%) rename {api => app/api}/routes/templates.py (95%) rename {api/db => app/api/schemas}/__init__.py (100%) rename {api => app/api}/schemas/common.py (100%) rename {api => app/api}/schemas/forms.py (90%) rename {api => app/api}/schemas/templates.py (100%) rename {api/errors => app/core}/__init__.py (100%) create mode 100644 app/core/config.py rename {api/schemas => app/core/errors}/__init__.py (100%) rename {api => app/core}/errors/base.py (100%) rename {api => app/core}/errors/handlers.py (88%) create mode 100644 app/core/lifespan.py create mode 100644 app/core/logging.py rename {src => app/db}/__init__.py (100%) create mode 100644 app/db/database.py rename {api => app}/db/init_db.py (65%) rename {api => app}/db/repositories.py (93%) create mode 100644 app/main.py create mode 100644 app/models/__init__.py rename {api/db => app/models}/models.py (100%) create mode 100644 app/services/__init__.py rename {src => app/services}/controller.py (87%) rename {src => app/services}/file_manipulator.py (96%) rename {src => app/services}/filler.py (97%) rename {src => app/services}/inputs/input.txt (100%) rename {src => app/services}/llm.py (94%) rename {src => app/services}/outputs/test_output_1.json (100%) rename {src => app/services}/prompt.txt (100%) rename {src => app/services}/test/example_template.json (100%) rename {src => app/services}/test/test_output_1.json (100%) create mode 100644 examples/pipeline_demo.py delete mode 100644 src/main.py rename {src/test => tests}/test_model.py (100%) diff --git a/.gitignore b/.gitignore index b5a9a90..268a0ff 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,6 @@ src/inputs/*.pdf frontend/release/ # Local Claude Code instructions -CLAUDE.md \ No newline at end of file +CLAUDE.md + +*temp/ \ No newline at end of file diff --git a/api/db/database.py b/api/db/database.py deleted file mode 100644 index 97e6e62..0000000 --- a/api/db/database.py +++ /dev/null @@ -1,20 +0,0 @@ -import os -from sqlmodel import create_engine, Session - -# Define path to the database in the user's home directory -HOME_DIR = os.path.expanduser("~") -APP_DIR = os.path.join(HOME_DIR, ".fireform") -os.makedirs(APP_DIR, exist_ok=True) -DB_PATH = os.path.join(APP_DIR, "fireform.db") - -DATABASE_URL = f"sqlite:///{DB_PATH}" - -engine = create_engine( - DATABASE_URL, - echo=True, - connect_args={"check_same_thread": False}, -) - -def get_session(): - with Session(engine) as session: - yield session \ No newline at end of file diff --git a/api/main.py b/api/main.py deleted file mode 100644 index 0c30bc5..0000000 --- a/api/main.py +++ /dev/null @@ -1,42 +0,0 @@ -from contextlib import asynccontextmanager -import os - -# Disable CUDA to prevent PyTorch from trying to find NVIDIA drivers on Mac Silicon / Docker -os.environ["CUDA_VISIBLE_DEVICES"] = "" - -from fastapi import FastAPI -from api.routes import templates, forms -from api.db.init_db import init_db -from api.errors.handlers import register_exception_handlers -from fastapi.middleware.cors import CORSMiddleware -from api.routes import forms, templates - -@asynccontextmanager -async def lifespan(app: FastAPI): - # Startup: Initialize the database and seed it if necessary - print("Initializing database...") - init_db() - yield - # Shutdown logic goes here if needed - -app = FastAPI(lifespan=lifespan) - -register_exception_handlers(app) - -default_origins = "http://127.0.0.1:5173,http://localhost:5173" -allowed_origins = [ - origin.strip() - for origin in os.getenv("FRONTEND_ORIGINS", default_origins).split(",") - if origin.strip() -] - -app.add_middleware( - CORSMiddleware, - allow_origins=allowed_origins, - allow_credentials=False, - allow_methods=["*"], - allow_headers=["*"], -) - -app.include_router(templates.router) -app.include_router(forms.router) diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..de9379b --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,8 @@ +"""FireForm backend application package.""" + +import os + +# Force CPU before any service module imports torch / rfdetr. Prevents PyTorch +# from probing for NVIDIA drivers on Mac Silicon and inside Docker. Runs here so +# it is guaranteed to execute before `app.main` imports the service layer. +os.environ["CUDA_VISIBLE_DEVICES"] = "" diff --git a/api/__init__.py b/app/api/__init__.py similarity index 100% rename from api/__init__.py rename to app/api/__init__.py diff --git a/api/deps.py b/app/api/deps.py similarity index 51% rename from api/deps.py rename to app/api/deps.py index c2a0b74..4aa9614 100644 --- a/api/deps.py +++ b/app/api/deps.py @@ -1,4 +1,4 @@ -from api.db.database import get_session +from app.db.database import get_session def get_db(): yield from get_session() \ No newline at end of file diff --git a/app/api/router.py b/app/api/router.py new file mode 100644 index 0000000..ba9c0b5 --- /dev/null +++ b/app/api/router.py @@ -0,0 +1,12 @@ +"""Aggregates every route module into a single API router. + +Add new feature routers here; main.py only mounts this one router. +""" + +from fastapi import APIRouter + +from app.api.routes import forms, templates + +api_router = APIRouter() +api_router.include_router(templates.router) +api_router.include_router(forms.router) diff --git a/api/routes/__init__.py b/app/api/routes/__init__.py similarity index 100% rename from api/routes/__init__.py rename to app/api/routes/__init__.py diff --git a/api/routes/forms.py b/app/api/routes/forms.py similarity index 85% rename from api/routes/forms.py rename to app/api/routes/forms.py index 74069ea..7af5da6 100644 --- a/api/routes/forms.py +++ b/app/api/routes/forms.py @@ -1,19 +1,19 @@ -import os - import requests from fastapi import APIRouter, Depends, File, UploadFile from sqlmodel import Session -from api.deps import get_db -from api.schemas.forms import ( + +from app.api.deps import get_db +from app.api.schemas.forms import ( FormFill, FormFillResponse, ModelsResponse, TranscriptionResponse, ) -from api.db.repositories import create_form, get_template -from api.db.models import FormSubmission -from api.errors.base import AppError -from src.controller import Controller +from app.core.config import OLLAMA_HOST, OLLAMA_MODEL, WHISPER_HOST +from app.core.errors.base import AppError +from app.db.repositories import create_form, get_template +from app.models import FormSubmission +from app.services.controller import Controller router = APIRouter(prefix="/forms", tags=["forms"]) @@ -48,12 +48,11 @@ def list_models(): """List the Whisper-independent extraction models available in the local Ollama instance, plus the configured default. Used by the Fill Form UI's model picker. Falls back to just the default if Ollama is unreachable.""" - default_model = os.getenv("OLLAMA_MODEL", "qwen2.5:1.5b") - ollama_host = os.getenv("OLLAMA_HOST", "http://localhost:11434").rstrip("/") + default_model = OLLAMA_MODEL models: list[str] = [] try: - response = requests.get(f"{ollama_host}/api/tags", timeout=10) + response = requests.get(f"{OLLAMA_HOST}/api/tags", timeout=10) response.raise_for_status() models = [m["name"] for m in response.json().get("models", []) if m.get("name")] except requests.exceptions.RequestException: @@ -75,8 +74,7 @@ def transcribe(audio: UploadFile = File(...)): audio is streamed straight through to the local STT service and never persisted — no PII leaves the machine. """ - whisper_host = os.getenv("WHISPER_HOST", "http://localhost:9000").rstrip("/") - whisper_url = f"{whisper_host}/asr" + whisper_url = f"{WHISPER_HOST}/asr" files = { "audio_file": ( diff --git a/api/routes/templates.py b/app/api/routes/templates.py similarity index 95% rename from api/routes/templates.py rename to app/api/routes/templates.py index 64db2e0..cc98370 100644 --- a/api/routes/templates.py +++ b/app/api/routes/templates.py @@ -5,21 +5,22 @@ from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile from fastapi.responses import FileResponse from sqlmodel import Session -from api.deps import get_db -from api.schemas.templates import ( + +from app.api.deps import get_db +from app.api.schemas.templates import ( TemplateCreate, TemplateResponse, TemplateUploadResponse, MakeFillableRequest, MakeFillableResponse, ) -from api.db.repositories import create_template, list_templates -from api.db.models import Template -from src.controller import Controller +from app.core.config import BASE_DIR, DEFAULT_TEMPLATE_DIR +from app.db.repositories import create_template, list_templates +from app.models import Template +from app.services.controller import Controller router = APIRouter(prefix="/templates", tags=["templates"]) -PROJECT_ROOT = Path(__file__).resolve().parents[2] -DEFAULT_TEMPLATE_DIR = "src/inputs" +PROJECT_ROOT = BASE_DIR def _resolve_target_directory(directory: str) -> Path: diff --git a/api/db/__init__.py b/app/api/schemas/__init__.py similarity index 100% rename from api/db/__init__.py rename to app/api/schemas/__init__.py diff --git a/api/schemas/common.py b/app/api/schemas/common.py similarity index 100% rename from api/schemas/common.py rename to app/api/schemas/common.py diff --git a/api/schemas/forms.py b/app/api/schemas/forms.py similarity index 90% rename from api/schemas/forms.py rename to app/api/schemas/forms.py index ca99651..6a833c8 100644 --- a/api/schemas/forms.py +++ b/app/api/schemas/forms.py @@ -4,7 +4,6 @@ class FormFill(BaseModel): template_id: int input_text: str # Optional Ollama model override for this fill; falls back to OLLAMA_MODEL. - # Not persisted (no DB column) — excluded before building FormSubmission. model: str | None = None @field_validator("input_text") diff --git a/api/schemas/templates.py b/app/api/schemas/templates.py similarity index 100% rename from api/schemas/templates.py rename to app/api/schemas/templates.py diff --git a/api/errors/__init__.py b/app/core/__init__.py similarity index 100% rename from api/errors/__init__.py rename to app/core/__init__.py diff --git a/app/core/config.py b/app/core/config.py new file mode 100644 index 0000000..055bbad --- /dev/null +++ b/app/core/config.py @@ -0,0 +1,47 @@ +"""Central configuration. + +Single source of truth for paths, the database URL, external service hosts and +CORS. Read environment once here so the rest of the app imports settings instead +of calling os.getenv() in scattered places. +""" + +import os +from pathlib import Path + +# Repo root. config.py lives at app/core/config.py -> parents[2] is the repo root. +BASE_DIR = Path(__file__).resolve().parents[2] + +# --- App metadata --------------------------------------------------------- +APP_TITLE = "FireForm API" +APP_VERSION = "1.1.0" + +# --- Runtime data paths --------------------------------------------------- +# Uploaded templates and generated PDFs. Project-relative paths the API echoes +# back to the client are resolved against BASE_DIR (the "inside the project" +# guard in the templates routes). Override the data dir with FIREFORM_DATA_DIR. +DATA_DIR = Path(os.getenv("FIREFORM_DATA_DIR", BASE_DIR / "data")).resolve() + +# Directory new uploads land in, as a project-relative string (was "src/inputs" +# before the restructure). Override with FIREFORM_TEMPLATE_DIR. +DEFAULT_TEMPLATE_DIR = os.getenv("FIREFORM_TEMPLATE_DIR", "data/inputs") + +# --- Database ------------------------------------------------------------- +# Keep the SQLite file in the user's home so it survives container rebuilds. +_APP_HOME = Path(os.path.expanduser("~")) / ".fireform" +_APP_HOME.mkdir(parents=True, exist_ok=True) +DB_PATH = Path(os.getenv("FIREFORM_DB_PATH", _APP_HOME / "fireform.db")) +DATABASE_URL = f"sqlite:///{DB_PATH}" +DB_ECHO = os.getenv("FIREFORM_DB_ECHO", "true").lower() == "true" + +# --- External services ---------------------------------------------------- +OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://localhost:11434").rstrip("/") +OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "qwen2.5:1.5b") +WHISPER_HOST = os.getenv("WHISPER_HOST", "http://localhost:9000").rstrip("/") + +# --- CORS ----------------------------------------------------------------- +_DEFAULT_ORIGINS = "http://127.0.0.1:5173,http://localhost:5173" +ALLOWED_ORIGINS = [ + origin.strip() + for origin in os.getenv("FRONTEND_ORIGINS", _DEFAULT_ORIGINS).split(",") + if origin.strip() +] diff --git a/api/schemas/__init__.py b/app/core/errors/__init__.py similarity index 100% rename from api/schemas/__init__.py rename to app/core/errors/__init__.py diff --git a/api/errors/base.py b/app/core/errors/base.py similarity index 100% rename from api/errors/base.py rename to app/core/errors/base.py diff --git a/api/errors/handlers.py b/app/core/errors/handlers.py similarity index 88% rename from api/errors/handlers.py rename to app/core/errors/handlers.py index 903e744..0285ddb 100644 --- a/api/errors/handlers.py +++ b/app/core/errors/handlers.py @@ -1,6 +1,6 @@ from fastapi import Request from fastapi.responses import JSONResponse -from api.errors.base import AppError +from app.core.errors.base import AppError def register_exception_handlers(app): @app.exception_handler(AppError) diff --git a/app/core/lifespan.py b/app/core/lifespan.py new file mode 100644 index 0000000..7f84273 --- /dev/null +++ b/app/core/lifespan.py @@ -0,0 +1,17 @@ +"""Application lifespan: startup and shutdown hooks.""" + +import logging +from contextlib import asynccontextmanager + +from fastapi import FastAPI + +from app.db.init_db import init_db + +logger = logging.getLogger(__name__) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + logger.info("Initializing database...") + init_db() + yield diff --git a/app/core/logging.py b/app/core/logging.py new file mode 100644 index 0000000..48d28a8 --- /dev/null +++ b/app/core/logging.py @@ -0,0 +1,14 @@ +"""Logging setup. Call setup_logging() once at app startup.""" + +import logging + + +def setup_logging(level: str = "INFO") -> None: + logging.basicConfig( + level=level, + format="%(asctime)s %(levelname)-8s %(name)s: %(message)s", + ) + + +def get_logger(name: str) -> logging.Logger: + return logging.getLogger(name) diff --git a/src/__init__.py b/app/db/__init__.py similarity index 100% rename from src/__init__.py rename to app/db/__init__.py diff --git a/app/db/database.py b/app/db/database.py new file mode 100644 index 0000000..0437afd --- /dev/null +++ b/app/db/database.py @@ -0,0 +1,14 @@ +from sqlmodel import Session, create_engine + +from app.core.config import DATABASE_URL, DB_ECHO + +engine = create_engine( + DATABASE_URL, + echo=DB_ECHO, + connect_args={"check_same_thread": False}, +) + + +def get_session(): + with Session(engine) as session: + yield session diff --git a/api/db/init_db.py b/app/db/init_db.py similarity index 65% rename from api/db/init_db.py rename to app/db/init_db.py index ea77cb6..fc17147 100644 --- a/api/db/init_db.py +++ b/app/db/init_db.py @@ -1,9 +1,15 @@ -import json import datetime -from sqlmodel import SQLModel, Session, select -from api.db.database import engine -from api.db import models -from api.db.models import Template +import logging + +from sqlmodel import Session, SQLModel, select + +from app.core.config import DEFAULT_TEMPLATE_DIR +from app.db.database import engine + +from app.models import FormSubmission, Template # noqa: F401 + +logger = logging.getLogger(__name__) + def seed_db(): with Session(engine) as session: @@ -14,9 +20,9 @@ def seed_db(): except Exception: # Table might not exist yet if called at a weird time results = None - + if not results: - print("Seeding database with default template...") + logger.info("Seeding database with default template...") fields = { "Employee's name": "string", "Employee's job title": "string", @@ -24,24 +30,26 @@ def seed_db(): "Employee's phone number": "string", "Employee's email": "string", "Signature": "string", - "Date": "string" + "Date": "string", } - + # Using ID 2 as agreed to avoid any ID 1 corruption default_template = Template( id=2, name="Manual Test Template", fields=fields, - pdf_path="src/inputs/file_template_manual.pdf", - created_at=datetime.datetime.now() + pdf_path=f"{DEFAULT_TEMPLATE_DIR}/file_template_manual.pdf", + created_at=datetime.datetime.now(), ) session.add(default_template) session.commit() - print("Database seeded successfully.") + logger.info("Database seeded successfully.") + def init_db(): SQLModel.metadata.create_all(engine) seed_db() + if __name__ == "__main__": - init_db() \ No newline at end of file + init_db() diff --git a/api/db/repositories.py b/app/db/repositories.py similarity index 93% rename from api/db/repositories.py rename to app/db/repositories.py index a9ac3cf..ebb80de 100644 --- a/api/db/repositories.py +++ b/app/db/repositories.py @@ -1,5 +1,5 @@ from sqlmodel import Session, select -from api.db.models import Template, FormSubmission +from app.models import Template, FormSubmission # Templates def create_template(session: Session, template: Template) -> Template: diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..3d3bdad --- /dev/null +++ b/app/main.py @@ -0,0 +1,34 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.api.router import api_router +from app.core import config +from app.core.errors.handlers import register_exception_handlers +from app.core.lifespan import lifespan +from app.core.logging import setup_logging + + +def create_app() -> FastAPI: + setup_logging() + + app = FastAPI( + title=config.APP_TITLE, + version=config.APP_VERSION, + lifespan=lifespan, + ) + + app.add_middleware( + CORSMiddleware, + allow_origins=config.ALLOWED_ORIGINS, + allow_credentials=False, + allow_methods=["*"], + allow_headers=["*"], + ) + + register_exception_handlers(app) + app.include_router(api_router) + + return app + + +app = create_app() diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..f46e17d --- /dev/null +++ b/app/models/__init__.py @@ -0,0 +1,5 @@ +"""ORM models. Import from here: `from app.models import Template`.""" + +from app.models.models import FormSubmission, Template + +__all__ = ["Template", "FormSubmission"] diff --git a/api/db/models.py b/app/models/models.py similarity index 100% rename from api/db/models.py rename to app/models/models.py diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/controller.py b/app/services/controller.py similarity index 87% rename from src/controller.py rename to app/services/controller.py index 0e19290..49c10e4 100644 --- a/src/controller.py +++ b/app/services/controller.py @@ -1,4 +1,4 @@ -from src.file_manipulator import FileManipulator +from app.services.file_manipulator import FileManipulator class Controller: def __init__(self): diff --git a/src/file_manipulator.py b/app/services/file_manipulator.py similarity index 96% rename from src/file_manipulator.py rename to app/services/file_manipulator.py index 8d1f3a0..357356e 100644 --- a/src/file_manipulator.py +++ b/app/services/file_manipulator.py @@ -1,6 +1,6 @@ import os -from src.filler import Filler -from src.llm import LLM +from app.services.filler import Filler +from app.services.llm import LLM class FileManipulator: diff --git a/src/filler.py b/app/services/filler.py similarity index 97% rename from src/filler.py rename to app/services/filler.py index 7f738c2..aec9756 100644 --- a/src/filler.py +++ b/app/services/filler.py @@ -1,5 +1,5 @@ from pdfrw import PdfReader, PdfWriter -from src.llm import LLM +from app.services.llm import LLM from datetime import datetime diff --git a/src/inputs/input.txt b/app/services/inputs/input.txt similarity index 100% rename from src/inputs/input.txt rename to app/services/inputs/input.txt diff --git a/src/llm.py b/app/services/llm.py similarity index 94% rename from src/llm.py rename to app/services/llm.py index 053b883..2998318 100644 --- a/src/llm.py +++ b/app/services/llm.py @@ -3,6 +3,8 @@ import requests from requests.exceptions import Timeout, RequestException +from app.core.config import OLLAMA_HOST, OLLAMA_MODEL + class LLM: def __init__(self, transcript_text: str=None, target_fields: list=None, json_dict: dict=None, model: str=None): @@ -31,9 +33,8 @@ def main_loop(self): total_fields = len(self._target_fields) for i, (field, field_type) in enumerate(self._target_fields.items(), 1): prompt = self.build_prompt(field, field_type if isinstance(field_type, str) else "string") - ollama_host = os.getenv("OLLAMA_HOST", "http://localhost:11434").rstrip("/") - ollama_url = f"{ollama_host}/api/generate" - ollama_model = self._model or os.getenv("OLLAMA_MODEL", "qwen2.5:1.5b") + ollama_url = f"{OLLAMA_HOST}/api/generate" + ollama_model = self._model or OLLAMA_MODEL payload = { "model": ollama_model, diff --git a/src/outputs/test_output_1.json b/app/services/outputs/test_output_1.json similarity index 100% rename from src/outputs/test_output_1.json rename to app/services/outputs/test_output_1.json diff --git a/src/prompt.txt b/app/services/prompt.txt similarity index 100% rename from src/prompt.txt rename to app/services/prompt.txt diff --git a/src/test/example_template.json b/app/services/test/example_template.json similarity index 100% rename from src/test/example_template.json rename to app/services/test/example_template.json diff --git a/src/test/test_output_1.json b/app/services/test/test_output_1.json similarity index 100% rename from src/test/test_output_1.json rename to app/services/test/test_output_1.json diff --git a/examples/pipeline_demo.py b/examples/pipeline_demo.py new file mode 100644 index 0000000..dbad305 --- /dev/null +++ b/examples/pipeline_demo.py @@ -0,0 +1,27 @@ +"""Manual, end-to-end demo of the fill pipeline (not part of the package). + +Run from the repo root with a real PDF path. This is a developer convenience +for exercising the services layer without the API; it is not imported anywhere. +""" + +from commonforms import prepare_form +from pypdf import PdfReader + +from app.services.controller import Controller + +if __name__ == "__main__": + file = "./data/inputs/file.pdf" # update to a real input PDF + user_input = ( + "Hi. The employee's name is John Doe. His job title is managing director. " + "His department supervisor is Jane Doe. His phone number is 123456. " + "His email is jdoe@ucsc.edu. The signature is , and the date is 01/02/2005" + ) + prepared_pdf = "temp_outfile.pdf" + prepare_form(file, prepared_pdf) + + reader = PdfReader(prepared_pdf) + fields = reader.get_fields() + num_fields = len(fields) if fields else 0 + + controller = Controller() + controller.fill_form(user_input, fields, file) diff --git a/src/main.py b/src/main.py deleted file mode 100644 index 630d262..0000000 --- a/src/main.py +++ /dev/null @@ -1,42 +0,0 @@ -import os -os.environ["CUDA_VISIBLE_DEVICES"] = "" - -# Monkey patch rfdetr to force CPU usage on Mac Silicon / Docker -try: - import rfdetr.detr - original_ensure = rfdetr.detr._ensure_model_on_device - def patched_ensure(model_ctx): - model_ctx.device = "cpu" - original_ensure(model_ctx) - rfdetr.detr._ensure_model_on_device = patched_ensure -except ImportError: - pass - -from commonforms import prepare_form -from pypdf import PdfReader -from controller import Controller - -if __name__ == "__main__": - file = "./src/inputs/file.pdf" - user_input = "Hi. The employee's name is John Doe. His job title is managing director. His department supervisor is Jane Doe. His phone number is 123456. His email is jdoe@ucsc.edu. The signature is , and the date is 01/02/2005" - fields = [ - "Employee's name", - "Employee's job title", - "Employee's department supervisor", - "Employee's phone number", - "Employee's email", - "Signature", - "Date", - ] - prepared_pdf = "temp_outfile.pdf" - prepare_form(file, prepared_pdf) - - reader = PdfReader(prepared_pdf) - fields = reader.get_fields() - if fields: - num_fields = len(fields) - else: - num_fields = 0 - - controller = Controller() - controller.fill_form(user_input, fields, file) diff --git a/tests/conftest.py b/tests/conftest.py index 5f1eb3f..56dea60 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,9 +12,9 @@ from sqlalchemy.pool import StaticPool from sqlmodel import SQLModel, Session, create_engine -from api.main import app -from api.deps import get_db -from api.db.models import Template, FormSubmission # noqa: F401 — registers tables +from app.main import app +from app.api.deps import get_db +from app.models import Template, FormSubmission # noqa: F401 — registers tables # --------------------------------------------------------------------------- # In-memory database @@ -85,8 +85,8 @@ def pdf_upload(pdf_bytes): @pytest.fixture def mock_controller(): """Patch Controller so create_template / fill_form don't touch the FS or LLM.""" - with patch("api.routes.templates.Controller") as tpl_cls, \ - patch("api.routes.forms.Controller") as form_cls: + with patch("app.api.routes.templates.Controller") as tpl_cls, \ + patch("app.api.routes.forms.Controller") as form_cls: tpl_instance = MagicMock() tpl_instance.create_template.return_value = "src/inputs/test_template.pdf" tpl_cls.return_value = tpl_instance diff --git a/tests/test_api.py b/tests/test_api.py index 89a9b25..66696b9 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -7,7 +7,7 @@ import pytest from sqlmodel import select -from api.db.models import Template, FormSubmission +from app.models import Template, FormSubmission # ═══════════════════════════════════════════════════════════════════════════ diff --git a/src/test/test_model.py b/tests/test_model.py similarity index 100% rename from src/test/test_model.py rename to tests/test_model.py From f4ee6fe909c6a0c45b9e17f9938cdf6a611b169f Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Sat, 30 May 2026 23:34:39 +0530 Subject: [PATCH 02/15] Updated the change of code structure layer in docker, makefile and workflows --- .github/workflows/release.yml | 2 +- Dockerfile | 4 ++-- Makefile | 4 +--- docker-compose.yml | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6d1f83..9ddeaf3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,7 +43,7 @@ jobs: working-directory: . shell: bash run: | - pyinstaller --name api-backend --onefile api/main.py + pyinstaller --name api-backend --onefile app/main.py mkdir -p frontend/bin cp dist/api-backend* frontend/bin/ diff --git a/Dockerfile b/Dockerfile index 282eb25..b7b00ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,11 +18,11 @@ RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY . . -# All imports use api.*, src.* which require the root to be on the path +# All imports use the app.* package, which requires the root on the path ENV PYTHONPATH=/app # Expose FastAPI port EXPOSE 8000 # Start the FastAPI server (not tail -f /dev/null which does nothing) -CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/Makefile b/Makefile index 026a721..3f15533 100644 --- a/Makefile +++ b/Makefile @@ -67,10 +67,8 @@ shell: # Start the FastAPI server inside the running container run: - docker compose exec app uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload + docker compose exec app uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload -exec: - docker compose exec app python3 src/main.py pull-model: docker compose exec ollama ollama pull $(OLLAMA_MODEL) diff --git a/docker-compose.yml b/docker-compose.yml index a9e8e6e..45ad034 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,7 +49,7 @@ services: condition: service_healthy whisper: condition: service_started - command: /bin/sh -c "python3 -m api.db.init_db && python3 -m uvicorn api.main:app --host 0.0.0.0 --port 8000" + command: /bin/sh -c "python3 -m app.db.init_db && python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8000" volumes: - .:/app # Persist the SQLite DB (~/.fireform) across container rebuilds so created From e260df9e86dc2d1502c8872830e8515a47cbf07d Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Tue, 2 Jun 2026 23:59:24 +0530 Subject: [PATCH 03/15] fix: lint target src/ -> app/ and remove stale electron release workflow --- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 59 ----------------------------------- 2 files changed, 1 insertion(+), 60 deletions(-) delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 93d41fb..1652a2b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -26,4 +26,4 @@ jobs: - name: Run linter run: | - ruff check src/ --output-format=github \ No newline at end of file + ruff check app/ --output-format=github \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 9ddeaf3..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Release Electron App - -on: - push: - tags: ["v*"] - -jobs: - build: - strategy: - matrix: - os: [macos-latest, ubuntu-latest, windows-latest] - runs-on: ${{ matrix.os }} - defaults: - run: - working-directory: frontend - - permissions: - contents: write - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install Python dependencies - working-directory: . - shell: bash - run: | - python -m pip install --upgrade pip - if [ "$RUNNER_OS" == "macOS" ]; then - pip install pyinstaller - pip install -r requirements.txt - else - pip install torch --index-url https://download.pytorch.org/whl/cpu - pip install pyinstaller - pip install -r requirements.txt - fi - - - name: Build Python backend with PyInstaller - working-directory: . - shell: bash - run: | - pyinstaller --name api-backend --onefile app/main.py - mkdir -p frontend/bin - cp dist/api-backend* frontend/bin/ - - - uses: actions/setup-node@v4 - with: - node-version: 20 - - - run: npm ci - - - name: Build & publish - run: npx electron-builder --publish always - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From fa7188f2d048f97fc30e2f8d41919887e5440d3a Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Wed, 3 Jun 2026 00:05:22 +0530 Subject: [PATCH 04/15] fix: explicit re-export of templates and forms in routes __init__. Fixes lint errors --- app/api/routes/__init__.py | 2 ++ app/api/schemas/common.py | 2 +- tests/test_api.py | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/api/routes/__init__.py b/app/api/routes/__init__.py index e8fe8f4..2264aee 100644 --- a/app/api/routes/__init__.py +++ b/app/api/routes/__init__.py @@ -1 +1,3 @@ from . import templates, forms + +__all__ = ["templates", "forms"] diff --git a/app/api/schemas/common.py b/app/api/schemas/common.py index 578cd2c..8d20a24 100644 --- a/app/api/schemas/common.py +++ b/app/api/schemas/common.py @@ -1,5 +1,5 @@ from pydantic import BaseModel -from typing import Any, Optional +from typing import Any class SuccessResponse(BaseModel): success: bool = True diff --git a/tests/test_api.py b/tests/test_api.py index 66696b9..33b2c62 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -4,7 +4,6 @@ All heavy dependencies (LLM, commonforms, filesystem) are mocked via conftest. """ -import pytest from sqlmodel import select from app.models import Template, FormSubmission From 93a5b3ac1630e1cc66664ae9eee58886bd438e69 Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Wed, 3 Jun 2026 00:16:29 +0530 Subject: [PATCH 05/15] chore: remove frontend directory, as it's migrated to fireform-frontend repo --- frontend/app.js | 1362 ------------ frontend/electron.js | 64 - frontend/index.html | 165 -- frontend/package-lock.json | 4025 ------------------------------------ frontend/package.json | 44 - frontend/preload.js | 3 - frontend/styles.css | 658 ------ 7 files changed, 6321 deletions(-) delete mode 100644 frontend/app.js delete mode 100644 frontend/electron.js delete mode 100644 frontend/index.html delete mode 100644 frontend/package-lock.json delete mode 100644 frontend/package.json delete mode 100644 frontend/preload.js delete mode 100644 frontend/styles.css diff --git a/frontend/app.js b/frontend/app.js deleted file mode 100644 index 4fd15ad..0000000 --- a/frontend/app.js +++ /dev/null @@ -1,1362 +0,0 @@ -const STORAGE_TEMPLATES_KEY = "fireform.templates.v1"; -const STORAGE_LAST_OUTPUT_KEY = "fireform.lastOutputPath.v1"; -// Where uploaded template PDFs are copied. Fixed for now; longer term this -// should be user-configurable behind a Settings button (see note below). -const DEFAULT_TEMPLATE_DIRECTORY = "src/inputs"; -const API_BASE_URL = "http://127.0.0.1:8000"; - -// UI label <-> stored type-string mapping. The stored values stay backward -// compatible with the existing default "string" type. -const FIELD_TYPES = [ - { label: "Text", value: "string" }, - { label: "Long Text", value: "long_text" }, - { label: "Number", value: "number" }, - { label: "Date", value: "date" }, - { label: "Time", value: "time" }, - { label: "Email", value: "email" }, - { label: "Phone", value: "phone" }, - { label: "Signature", value: "signature" }, - { label: "Checkbox", value: "checkbox" }, - { label: "List", value: "list" }, -]; -const TYPE_VALUE_TO_LABEL = Object.fromEntries(FIELD_TYPES.map((t) => [t.value, t.label])); -const DEFAULT_FIELD_ROWS = [{ name: "", type: "string" }]; - -const elements = { - tabs: Array.from(document.querySelectorAll(".tab")), - panels: Array.from(document.querySelectorAll(".panel")), - templateForm: document.getElementById("templateForm"), - templateName: document.getElementById("templateName"), - templatePdfFile: document.getElementById("templatePdfFile"), - pdfDropZone: document.getElementById("pdfDropZone"), - selectedFileMeta: document.getElementById("selectedFileMeta"), - changePdfBtn: document.getElementById("changePdfBtn"), - makeFillableBtn: document.getElementById("makeFillableBtn"), - makeFillableHelpBtn: document.getElementById("makeFillableHelpBtn"), - makeFillableHelp: document.getElementById("makeFillableHelp"), - fieldsBuilder: document.getElementById("fieldsBuilder"), - fieldCountBadge: document.getElementById("fieldCountBadge"), - addFieldBtn: document.getElementById("addFieldBtn"), - templateFormMessage: document.getElementById("templateFormMessage"), - templateFormResponse: document.getElementById("templateFormResponse"), - fillForm: document.getElementById("fillForm"), - fillModel: document.getElementById("fillModel"), - fillTemplateTiles: document.getElementById("fillTemplateTiles"), - fillSelectionHint: document.getElementById("fillSelectionHint"), - fillSubmitBtn: document.getElementById("fillSubmitBtn"), - inputText: document.getElementById("inputText"), - sttControls: document.getElementById("sttControls"), - sttRecordBtn: document.getElementById("sttRecordBtn"), - sttPauseBtn: document.getElementById("sttPauseBtn"), - sttStopBtn: document.getElementById("sttStopBtn"), - sttStatus: document.getElementById("sttStatus"), - fillFormMessage: document.getElementById("fillFormMessage"), - fillFormResponse: document.getElementById("fillFormResponse"), - templatesEmpty: document.getElementById("templatesEmpty"), - templatesList: document.getElementById("templatesList"), - localPdfFile: document.getElementById("localPdfFile"), - serverPdfPath: document.getElementById("serverPdfPath"), - previewPathBtn: document.getElementById("previewPathBtn"), - previewStatus: document.getElementById("previewStatus"), - pdfFrame: document.getElementById("pdfFrame"), -}; - -let templates = loadTemplates(); -let activeObjectUrl = null; -let selectedTemplateFile = null; -// Field rows are scratch state for building one template — they start empty -// each session and are not persisted. -let fieldRows = DEFAULT_FIELD_ROWS.map((row) => ({ ...row })); -let dragSourceIndex = null; -let uploadedPath = null; -let uploadedFieldCount = null; -// Template ids currently selected in the Fill Form tab (multi-select). -let selectedFillIds = new Set(); - -// Speech-to-text recording state. The MediaRecorder captures compressed audio -// in the renderer; on stop we POST it straight to /forms/transcribe (the local -// Whisper service handles decoding). -let mediaRecorder = null; -let recordedChunks = []; -let recordingStream = null; - -waitForBackend().then(initialize); - -async function waitForBackend() { - const loadingScreen = document.getElementById("loadingScreen"); - let isReady = false; - - while (!isReady) { - try { - const response = await fetch(`${API_BASE_URL}/templates`); - if (response.ok) { - isReady = true; - } - } catch (e) { - // Ignore error and try again - } - - if (!isReady) { - await new Promise(r => setTimeout(r, 500)); - } - } - - if (loadingScreen) { - loadingScreen.classList.add("hidden"); - } -} - -async function initialize() { - bindEvents(); - renderFieldRows(); - renderTemplates(); - renderFillTemplates(); - restorePreviewState(); - updateSelectedFileMeta(); - loadModels(); - await refreshTemplatesFromApi(); -} - -function bindEvents() { - elements.tabs.forEach((tab) => { - tab.addEventListener("click", () => activateSection(tab.dataset.target)); - }); - - elements.templateForm.addEventListener("submit", handleTemplateSubmit); - elements.templatePdfFile.addEventListener("change", handleTemplateFileInput); - elements.pdfDropZone.addEventListener("click", () => elements.templatePdfFile.click()); - elements.pdfDropZone.addEventListener("keydown", handleDropZoneKeyDown); - elements.changePdfBtn.addEventListener("click", () => elements.templatePdfFile.click()); - elements.addFieldBtn.addEventListener("click", handleAddFieldClick); - elements.makeFillableBtn.addEventListener("click", handleMakeFillableClick); - elements.makeFillableHelpBtn.addEventListener("click", toggleMakeFillableHelp); - bindDropZoneDragEvents(); - elements.fillForm.addEventListener("submit", handleFillSubmit); - elements.fillTemplateTiles.addEventListener("click", handleTileClick); - elements.fillTemplateTiles.addEventListener("keydown", handleTileKeydown); - elements.sttRecordBtn.addEventListener("click", startRecording); - elements.sttPauseBtn.addEventListener("click", togglePauseRecording); - elements.sttStopBtn.addEventListener("click", stopRecording); - elements.templatesList.addEventListener("click", handleTemplateActionClick); - elements.localPdfFile.addEventListener("change", handleLocalFilePreview); - elements.previewPathBtn.addEventListener("click", () => - previewFromPath(elements.serverPdfPath.value, { switchToPreview: true }) - ); -} - -function activateSection(targetId) { - switchSection(targetId); -} - -async function refreshTemplatesFromApi() { - try { - const response = await fetch(`${API_BASE_URL}/templates`); - const body = await parseJsonResponse(response); - if (!response.ok) { - throw new Error(extractErrorMessage(body, response.status)); - } - - if (Array.isArray(body)) { - templates = body.map((template) => ({ - id: template.id, - name: template.name || "", - pdf_path: template.pdf_path || "", - fields: template.fields || {}, - })); - saveTemplates(); - // Drop selections for templates that no longer exist. - const liveIds = new Set(templates.map((t) => Number(t.id))); - selectedFillIds.forEach((id) => { if (!liveIds.has(id)) selectedFillIds.delete(id); }); - renderTemplates(); - renderFillTemplates(); - } - } catch (error) { - setStatus( - elements.templateFormMessage, - `Could not refresh templates from API: ${error.message}`, - "error" - ); - } -} - -function bindDropZoneDragEvents() { - ["dragenter", "dragover"].forEach((eventName) => { - elements.pdfDropZone.addEventListener(eventName, (event) => { - event.preventDefault(); - event.stopPropagation(); - elements.pdfDropZone.classList.add("active"); - }); - }); - - ["dragleave", "dragend", "drop"].forEach((eventName) => { - elements.pdfDropZone.addEventListener(eventName, (event) => { - event.preventDefault(); - event.stopPropagation(); - elements.pdfDropZone.classList.remove("active"); - }); - }); - - elements.pdfDropZone.addEventListener("drop", (event) => { - const file = event.dataTransfer?.files?.[0]; - setSelectedTemplateFile(file); - }); -} - -function handleDropZoneKeyDown(event) { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - elements.templatePdfFile.click(); - } -} - -function handleTemplateFileInput(event) { - const file = event.target.files && event.target.files[0]; - setSelectedTemplateFile(file); -} - -function setSelectedTemplateFile(file) { - if (!file) { - return; - } - - if (!isPdfFile(file)) { - selectedTemplateFile = null; - uploadedPath = null; - uploadedFieldCount = null; - setMakeFillableButtonState(); - renderFieldCountBadge(); - setStatus(elements.templateFormMessage, "Please select a PDF file.", "error"); - updateSelectedFileMeta(); - return; - } - - selectedTemplateFile = file; - uploadedPath = null; - uploadedFieldCount = null; - setMakeFillableButtonState(); - renderFieldCountBadge(); - clearJson(elements.templateFormResponse); - setStatus(elements.templateFormMessage, ""); - updateSelectedFileMeta(); - // Eager upload so the user gets a live field-count comparison while building rows. - uploadSelectedFileSilently(); -} - -async function uploadSelectedFileSilently() { - if (!selectedTemplateFile) return; - const directory = DEFAULT_TEMPLATE_DIRECTORY; - - const fileAtUploadStart = selectedTemplateFile; - try { - const upload = await uploadTemplatePdf(fileAtUploadStart, directory); - // Guard against the user picking a different file mid-upload. - if (fileAtUploadStart !== selectedTemplateFile) return; - uploadedPath = upload.pdf_path; - uploadedFieldCount = - typeof upload.field_count === "number" ? upload.field_count : null; - maybeSeedFieldRows(upload.fields); - renderFieldCountBadge(); - } catch (_error) { - // Silent failure — the explicit Create / Make Fillable paths surface errors. - } -} - -// Auto-add a row per field the PDF already defines — same as clicking "+ Add -// Field" for each — filling in its description and type so the user can edit. -// If the list already has rows the user typed, warn before replacing them. -function maybeSeedFieldRows(fields) { - if (!Array.isArray(fields) || !fields.length) return; - syncFieldRowsFromDom(); - - if (fieldRows.some((row) => row.name.trim())) { - const replace = window.confirm( - `This PDF has ${fields.length} fillable field${fields.length === 1 ? "" : "s"}.\n\n` + - "Replace your current form fields with them? Your existing entries will be lost." - ); - if (!replace) { - setStatus(elements.templateFormMessage, "Kept your existing form fields.", "info"); - return; - } - } - - fieldRows = fields.map((f) => ({ - name: f.description || f.name || "", - type: normalizeFieldType(f.type), - })); - renderFieldRows(); - setStatus( - elements.templateFormMessage, - `Loaded ${fieldRows.length} field${fieldRows.length === 1 ? "" : "s"} from the PDF — edit the descriptions as needed.`, - "info" - ); -} - -function setMakeFillableButtonState() { - if (!elements.makeFillableBtn) return; - elements.makeFillableBtn.disabled = !selectedTemplateFile; - elements.makeFillableBtn.textContent = "Make this PDF fillable"; -} - -function renderFieldCountBadge() { - const badge = elements.fieldCountBadge; - if (!badge) return; - - if (!selectedTemplateFile || uploadedFieldCount === null) { - badge.classList.add("hidden"); - badge.classList.remove("match", "mismatch"); - badge.textContent = ""; - return; - } - - const expected = uploadedFieldCount; - const actual = fieldRows.length; - const noun = (n) => `${n} fillable field${n === 1 ? "" : "s"}`; - const rowNoun = (n) => `${n} row${n === 1 ? "" : "s"}`; - - badge.classList.remove("hidden", "match", "mismatch"); - if (expected === actual) { - badge.classList.add("match"); - badge.textContent = `PDF has ${noun(expected)} — your ${rowNoun(actual)} match.`; - } else { - badge.classList.add("mismatch"); - badge.textContent = `PDF has ${noun(expected)} — you have ${rowNoun(actual)}.`; - } -} - -function isPdfFile(file) { - const name = String(file?.name || "").toLowerCase(); - return name.endsWith(".pdf"); -} - -function updateSelectedFileMeta() { - // Once a file is chosen, swap the drop zone for a compact "change" control. - const hasFile = !!selectedTemplateFile; - elements.pdfDropZone.classList.toggle("hidden", hasFile); - elements.changePdfBtn.classList.toggle("hidden", !hasFile); - - if (!hasFile) { - elements.selectedFileMeta.textContent = "No PDF selected."; - return; - } - - const destinationPath = `${DEFAULT_TEMPLATE_DIRECTORY}/${selectedTemplateFile.name}`; - - elements.selectedFileMeta.textContent = `Selected: ${selectedTemplateFile.name} (${formatBytes( - selectedTemplateFile.size - )}) - destination: ${destinationPath}`; -} - -function formatBytes(bytes) { - if (!Number.isFinite(bytes) || bytes <= 0) { - return "0 B"; - } - - const units = ["B", "KB", "MB", "GB"]; - let value = bytes; - let unitIndex = 0; - - while (value >= 1024 && unitIndex < units.length - 1) { - value /= 1024; - unitIndex += 1; - } - - return `${value.toFixed(value >= 10 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`; -} - -function switchSection(targetId) { - elements.panels.forEach((panel) => { - panel.classList.toggle("hidden", panel.id !== targetId); - }); - elements.tabs.forEach((tab) => { - tab.classList.toggle("active", tab.dataset.target === targetId); - }); -} - -function setStatus(target, message, type = "info") { - target.textContent = message || ""; - target.className = "status"; - if (type) { - target.classList.add(type); - } -} - -function showJson(preElement, payload) { - preElement.textContent = JSON.stringify(payload, null, 2); - preElement.classList.remove("hidden"); -} - -function clearJson(preElement) { - preElement.textContent = ""; - preElement.classList.add("hidden"); -} - -function collectFieldRows() { - syncFieldRowsFromDom(); - - if (fieldRows.length === 0) { - return { error: "Add at least one field before creating the template." }; - } - - const dict = {}; - const seen = new Set(); - for (const row of fieldRows) { - const name = row.name.trim(); - if (!name) { - return { error: "Every field needs a name." }; - } - const key = name.toLowerCase(); - if (seen.has(key)) { - return { error: `Field names must be unique ("${name}" appears more than once).` }; - } - seen.add(key); - dict[name] = row.type || "string"; - } - return { value: dict }; -} - -async function handleTemplateSubmit(event) { - event.preventDefault(); - clearJson(elements.templateFormResponse); - setStatus(elements.templateFormMessage, ""); - - const name = elements.templateName.value.trim(); - const templateDirectory = DEFAULT_TEMPLATE_DIRECTORY; - const collected = collectFieldRows(); - - if (!name || !selectedTemplateFile) { - setStatus( - elements.templateFormMessage, - "Name and PDF file are required.", - "error" - ); - return; - } - - if (collected.error) { - setStatus(elements.templateFormMessage, collected.error, "error"); - return; - } - - try { - let activePdfPath = uploadedPath; - if (!activePdfPath) { - setStatus(elements.templateFormMessage, "Copying PDF into project directory...", "info"); - const upload = await uploadTemplatePdf(selectedTemplateFile, templateDirectory); - activePdfPath = upload.pdf_path; - uploadedPath = upload.pdf_path; - } - - const payload = { - name, - pdf_path: activePdfPath, - fields: collected.value, - }; - - setStatus(elements.templateFormMessage, "Creating template...", "info"); - const response = await fetch(`${API_BASE_URL}/templates/create`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - const body = await parseJsonResponse(response); - if (!response.ok) { - throw new Error(extractErrorMessage(body, response.status)); - } - - upsertTemplate(body); - if (body.id != null) { - selectedFillIds.add(Number(body.id)); - } - await refreshTemplatesFromApi(); - elements.serverPdfPath.value = body.pdf_path || ""; - - const expected = body.field_count; - const actual = Object.keys(collected.value).length; - let mismatchNote = ""; - let statusLevel = "success"; - if (typeof expected === "number" && expected !== actual) { - mismatchNote = ` Heads up — the PDF has ${expected} fillable field${expected === 1 ? "" : "s"}, but you added ${actual} row${actual === 1 ? "" : "s"}. Fills may be incomplete or misaligned.`; - statusLevel = "error"; - } - - setStatus( - elements.templateFormMessage, - `Template created (id: ${body.id}). PDF saved at ${activePdfPath}.${mismatchNote}`, - statusLevel - ); - showJson(elements.templateFormResponse, body); - uploadedPath = null; - uploadedFieldCount = null; - setMakeFillableButtonState(); - renderFieldCountBadge(); - } catch (error) { - setStatus(elements.templateFormMessage, error.message, "error"); - } -} - -async function uploadTemplatePdf(file, directory) { - const formData = new FormData(); - formData.append("file", file, file.name); - formData.append("directory", directory); - - const response = await fetch(`${API_BASE_URL}/templates/upload`, { - method: "POST", - body: formData, - }); - - const body = await parseJsonResponse(response); - if (!response.ok) { - throw new Error(extractErrorMessage(body, response.status)); - } - - return body; -} - -// ───────────────────────── Fill Form: model + template tiles ────────────── - -// "1 field" / "3 forms" — keeps the count-and-label logic in one place. -function pluralize(count, noun) { - return `${count} ${noun}${count === 1 ? "" : "s"}`; -} - -// Look up a template by id (ids may arrive as strings from dataset attributes). -function findTemplate(id) { - return templates.find((template) => Number(template.id) === Number(id)); -} - -// Populate the model picker from the local Ollama models the API reports. -async function loadModels() { - const select = elements.fillModel; - try { - const response = await fetch(`${API_BASE_URL}/forms/models`); - const body = await parseJsonResponse(response); - if (!response.ok) { - throw new Error(extractErrorMessage(body, response.status)); - } - - select.innerHTML = ""; - const models = body.models || []; - models.forEach((name) => { - const isDefault = name === body.default; - const option = document.createElement("option"); - option.value = name; - option.textContent = isDefault ? `${name} (default)` : name; - option.selected = isDefault; - select.append(option); - }); - } catch (_error) { - // Ollama unreachable — leave one placeholder so the picker isn't empty. - if (!select.options.length) { - const option = document.createElement("option"); - option.value = ""; - option.textContent = "(default model)"; - select.append(option); - } - } -} - -// Build one selectable tile. Whether it's selected is shown purely through the -// tile's highlighted styling (.selected) — there's no separate checkbox. -function createTemplateTile(template) { - const id = Number(template.id); - const selected = selectedFillIds.has(id); - - const tile = document.createElement("div"); - tile.className = selected ? "template-tile selected" : "template-tile"; - tile.dataset.templateId = String(id); - // Behaves like a toggle button for keyboard and screen-reader users. - tile.setAttribute("role", "button"); - tile.setAttribute("tabindex", "0"); - tile.setAttribute("aria-pressed", String(selected)); - - const title = document.createElement("span"); - title.className = "tile-title"; - title.textContent = template.name || "Untitled"; - - const fieldCount = template.fields ? Object.keys(template.fields).length : 0; - const meta = document.createElement("span"); - meta.className = "tile-meta"; - meta.textContent = pluralize(fieldCount, "field"); - - const body = document.createElement("div"); - body.className = "tile-body"; - body.append(title, meta); - - // Preview must not toggle selection, so it carries its own id and the click - // handler stops the event from bubbling up to the tile. - const previewButton = document.createElement("button"); - previewButton.type = "button"; - previewButton.className = "tile-preview-btn"; - previewButton.dataset.previewId = String(id); - previewButton.textContent = "Preview"; - - tile.append(body, previewButton); - return tile; -} - -function renderFillTemplates() { - const container = elements.fillTemplateTiles; - container.innerHTML = ""; - - if (!templates.length) { - const empty = document.createElement("p"); - empty.className = "empty-state"; - empty.textContent = "No templates yet — create one in the Create Template tab."; - container.append(empty); - updateFillButtonState(); - return; - } - - templates.forEach((template) => container.append(createTemplateTile(template))); - updateFillButtonState(); -} - -function handleTileClick(event) { - // A click on the Preview button previews the PDF without toggling selection. - const previewButton = event.target.closest(".tile-preview-btn"); - if (previewButton) { - event.stopPropagation(); - const template = findTemplate(previewButton.dataset.previewId); - if (template) { - elements.serverPdfPath.value = template.pdf_path || ""; - previewFromPath(template.pdf_path || "", { switchToPreview: true }); - } - return; - } - - // A click anywhere else on the tile toggles it on/off for filling. - const tile = event.target.closest(".template-tile"); - if (tile) { - toggleFillSelection(Number(tile.dataset.templateId)); - } -} - -function handleTileKeydown(event) { - // Enter/Space activate the focused tile, matching its role="button". - if (event.key !== "Enter" && event.key !== " ") { - return; - } - const tile = event.target.closest(".template-tile"); - if (tile) { - event.preventDefault(); - toggleFillSelection(Number(tile.dataset.templateId)); - } -} - -function toggleFillSelection(id) { - if (selectedFillIds.has(id)) { - selectedFillIds.delete(id); - } else { - selectedFillIds.add(id); - } - renderFillTemplates(); -} - -function updateFillButtonState() { - const count = selectedFillIds.size; - const nothingSelected = count === 0; - - // Greyed out (but still clickable) until at least one form is chosen. - elements.fillSubmitBtn.classList.toggle("is-disabled", nothingSelected); - elements.fillSubmitBtn.textContent = count > 1 ? `Fill ${count} Forms` : "Fill Form"; - - elements.fillSelectionHint.classList.remove("error"); - elements.fillSelectionHint.textContent = nothingSelected - ? "Select one or more forms to fill." - : `${pluralize(count, "form")} selected.`; -} - -// A human-readable label for a template, used in the success/error summary. -function templateLabel(id) { - const template = findTemplate(id); - return template && template.name ? template.name : `id ${id}`; -} - -// Fill a single template and return its submission. Throws on failure so the -// caller can note which form failed and still continue with the others. -async function fillOneTemplate(id, inputText, model) { - const response = await fetch(`${API_BASE_URL}/forms/fill`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ template_id: id, input_text: inputText, model }), - }); - const body = await parseJsonResponse(response); - if (!response.ok) { - throw new Error(extractErrorMessage(body, response.status)); - } - return body; -} - -// Summarize "N filled, M failed" into the status line, choosing the right tone: -// all-good = success, some failed but some worked = info, nothing worked = error. -function reportFillOutcome(results, errors) { - const parts = []; - if (results.length) parts.push(`${results.length} filled`); - if (errors.length) parts.push(`${errors.length} failed`); - - let level = "success"; - if (errors.length) { - level = results.length ? "info" : "error"; - } - - const detail = errors.length ? ` ${errors.join("; ")}` : ""; - setStatus(elements.fillFormMessage, `${parts.join(", ")}.${detail}`, level); -} - -async function handleFillSubmit(event) { - event.preventDefault(); - clearJson(elements.fillFormResponse); - setStatus(elements.fillFormMessage, ""); - - const ids = Array.from(selectedFillIds); - if (!ids.length) { - // The button looks disabled but stays clickable, so prompt the user here. - elements.fillSelectionHint.classList.add("error"); - elements.fillSelectionHint.textContent = "Select at least one form to fill."; - setStatus(elements.fillFormMessage, "Select at least one form to fill.", "error"); - return; - } - - const inputText = elements.inputText.value.trim(); - if (!inputText) { - setStatus(elements.fillFormMessage, "Input text is required.", "error"); - return; - } - - // An empty picker value means "let the server use its default model". - const model = elements.fillModel.value || undefined; - setStatus(elements.fillFormMessage, `Filling ${pluralize(ids.length, "form")}…`, "info"); - - // Fill each selected form independently so one failure doesn't stop the rest. - const results = []; - const errors = []; - for (const id of ids) { - try { - results.push(await fillOneTemplate(id, inputText, model)); - } catch (error) { - errors.push(`${templateLabel(id)}: ${error.message}`); - } - } - - const lastResult = results[results.length - 1]; - if (lastResult) { - showJson(elements.fillFormResponse, results.length === 1 ? lastResult : results); - if (lastResult.output_pdf_path) { - localStorage.setItem(STORAGE_LAST_OUTPUT_KEY, lastResult.output_pdf_path); - elements.serverPdfPath.value = lastResult.output_pdf_path; - } - } - - reportFillOutcome(results, errors); - - // Preview the most recently filled PDF. - if (lastResult && lastResult.output_pdf_path) { - await previewFromPath(lastResult.output_pdf_path, { switchToPreview: true }); - } -} - -// ───────────────────────── Speech-to-text (local Whisper) ───────────────── - -function setSttStatus(message) { - if (elements.sttStatus) { - elements.sttStatus.textContent = message || ""; - } -} - -async function startRecording() { - if (mediaRecorder) { - return; - } - if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { - setSttStatus("Microphone capture is not available in this environment."); - return; - } - - try { - recordingStream = await navigator.mediaDevices.getUserMedia({ audio: true }); - } catch (error) { - setSttStatus("Microphone permission denied."); - return; - } - - recordedChunks = []; - mediaRecorder = new MediaRecorder(recordingStream); - mediaRecorder.addEventListener("dataavailable", (event) => { - if (event.data && event.data.size > 0) { - recordedChunks.push(event.data); - } - }); - mediaRecorder.addEventListener("stop", handleRecordingStop); - mediaRecorder.start(); - - elements.sttControls.classList.add("is-recording"); - elements.sttControls.classList.remove("is-paused"); - elements.sttRecordBtn.disabled = true; - elements.sttPauseBtn.disabled = false; - elements.sttStopBtn.disabled = false; - elements.sttPauseBtn.textContent = "Pause"; - setSttStatus("Recording…"); -} - -function togglePauseRecording() { - if (!mediaRecorder) { - return; - } - if (mediaRecorder.state === "recording") { - mediaRecorder.pause(); - elements.sttControls.classList.add("is-paused"); - elements.sttControls.classList.remove("is-recording"); - elements.sttPauseBtn.textContent = "Resume"; - setSttStatus("Paused."); - } else if (mediaRecorder.state === "paused") { - mediaRecorder.resume(); - elements.sttControls.classList.add("is-recording"); - elements.sttControls.classList.remove("is-paused"); - elements.sttPauseBtn.textContent = "Pause"; - setSttStatus("Recording…"); - } -} - -function stopRecording() { - if (!mediaRecorder) { - return; - } - // Lock the controls while we finalize capture and transcribe. - elements.sttPauseBtn.disabled = true; - elements.sttStopBtn.disabled = true; - setSttStatus("Finishing capture…"); - mediaRecorder.stop(); -} - -async function handleRecordingStop() { - elements.sttControls.classList.remove("is-recording", "is-paused"); - stopRecordingStream(); - - const chunks = recordedChunks; - const recorder = mediaRecorder; - recordedChunks = []; - mediaRecorder = null; - - const blob = new Blob(chunks, { type: (recorder && recorder.mimeType) || "audio/webm" }); - if (!blob.size) { - resetSttControls(); - setSttStatus("Nothing was recorded."); - return; - } - - try { - setSttStatus("Transcribing…"); - const text = await transcribeAudio(blob); - appendTranscribedText(text); - setSttStatus(text ? "Transcription added." : "No speech detected."); - } catch (error) { - setSttStatus(`Transcription failed: ${error.message}`); - } finally { - resetSttControls(); - } -} - -function resetSttControls() { - elements.sttRecordBtn.disabled = false; - elements.sttPauseBtn.disabled = true; - elements.sttStopBtn.disabled = true; - elements.sttPauseBtn.textContent = "Pause"; - elements.sttControls.classList.remove("is-recording", "is-paused"); -} - -function stopRecordingStream() { - if (recordingStream) { - recordingStream.getTracks().forEach((track) => track.stop()); - recordingStream = null; - } -} - -function appendTranscribedText(text) { - if (!text) { - return; - } - const existing = elements.inputText.value.trim(); - elements.inputText.value = existing ? `${existing} ${text}` : text; - // Let any listeners (and the required-field check) see the new value. - elements.inputText.dispatchEvent(new Event("input")); -} - -// "audio/webm;codecs=opus" -> "webm". Just gives the upload a sensible filename; -// the server decodes by content, not extension. -function audioExtension(mimeType) { - const subtype = (mimeType || "").split("/")[1] || ""; - const withoutCodecs = subtype.split(";")[0].trim(); - return withoutCodecs || "webm"; -} - -// The Whisper ASR service decodes audio with ffmpeg, so we post the recording -// as-is (typically webm/opus) — no client-side transcoding needed. -async function transcribeAudio(blob) { - const formData = new FormData(); - formData.append("audio", blob, `recording.${audioExtension(blob.type)}`); - - const response = await fetch(`${API_BASE_URL}/forms/transcribe`, { - method: "POST", - body: formData, - }); - const body = await parseJsonResponse(response); - if (!response.ok) { - throw new Error(extractErrorMessage(body, response.status)); - } - return (body.text || "").trim(); -} - -function handleTemplateActionClick(event) { - const button = event.target.closest("button[data-action]"); - if (!button) { - return; - } - - const id = Number(button.dataset.templateId); - const template = templates.find((item) => Number(item.id) === id); - if (!template) { - return; - } - - if (button.dataset.action === "preview") { - elements.serverPdfPath.value = template.pdf_path || ""; - previewFromPath(template.pdf_path || "", { switchToPreview: true }); - return; - } - - if (button.dataset.action === "use-fill") { - selectedFillIds.add(Number(template.id)); - renderFillTemplates(); - activateSection("fillFormSection"); - setStatus( - elements.fillFormMessage, - `"${template.name || "Template"}" selected for filling.`, - "info" - ); - } -} - -function handleLocalFilePreview(event) { - const file = event.target.files && event.target.files[0]; - if (!file) { - return; - } - - if (activeObjectUrl) { - URL.revokeObjectURL(activeObjectUrl); - } - - activeObjectUrl = URL.createObjectURL(file); - elements.pdfFrame.src = activeObjectUrl; - switchSection("pdfPreviewerSection"); - setStatus(elements.previewStatus, `Previewing local file: ${file.name}`, "success"); -} - -function resolvePreviewCandidates(pathInput) { - const raw = String(pathInput || "").trim(); - if (!raw) { - return []; - } - - if (/^https?:\/\//i.test(raw)) { - return [raw]; - } - - return [`${API_BASE_URL}/templates/preview?path=${encodeURIComponent(raw)}`]; -} - -async function previewFromPath(pathInput, options = {}) { - if (options.switchToPreview) { - switchSection("pdfPreviewerSection"); - } - - const raw = String(pathInput || "").trim(); - if (!raw) { - setStatus(elements.previewStatus, "Enter a PDF path or URL first.", "error"); - return false; - } - - const candidates = resolvePreviewCandidates(raw); - if (!candidates.length) { - setStatus(elements.previewStatus, "Unable to parse preview path.", "error"); - return false; - } - - setStatus(elements.previewStatus, "Attempting to preview path...", "info"); - let lastReason = "unknown error"; - - for (const candidate of candidates) { - try { - const response = await fetch(candidate, { method: "HEAD" }); - if (response.ok || response.status === 405) { - elements.pdfFrame.src = candidate; - setStatus(elements.previewStatus, `Previewing path: ${candidate}`, "success"); - return true; - } - lastReason = `${response.status} ${response.statusText}`.trim(); - } catch (error) { - lastReason = error.message; - } - } - - const likelyServerLocal = - !/^https?:\/\//i.test(raw) && !raw.startsWith("/"); - - if (likelyServerLocal) { - setStatus( - elements.previewStatus, - `Could not preview "${raw}". It looks like a server-local path and may not be web-accessible.`, - "error" - ); - } else { - setStatus( - elements.previewStatus, - `Could not preview path. Last error: ${lastReason}`, - "error" - ); - } - - return false; -} - -function renderTemplates() { - elements.templatesList.innerHTML = ""; - - if (!templates.length) { - elements.templatesEmpty.classList.remove("hidden"); - return; - } - - elements.templatesEmpty.classList.add("hidden"); - templates.forEach((template) => { - const card = document.createElement("article"); - card.className = "template-card"; - - const title = document.createElement("h3"); - title.textContent = `${template.name || "Untitled"} (id: ${template.id ?? "n/a"})`; - - const path = document.createElement("p"); - path.className = "template-meta"; - path.textContent = `pdf_path: ${template.pdf_path || ""}`; - - const fields = buildFieldsTable(template.fields || {}); - - const actions = document.createElement("div"); - actions.className = "card-actions"; - - const previewButton = document.createElement("button"); - previewButton.type = "button"; - previewButton.dataset.action = "preview"; - previewButton.dataset.templateId = String(template.id); - previewButton.textContent = "Preview This Template"; - - const useFillButton = document.createElement("button"); - useFillButton.type = "button"; - useFillButton.dataset.action = "use-fill"; - useFillButton.dataset.templateId = String(template.id); - useFillButton.textContent = "Use in Fill Form"; - - actions.append(previewButton, useFillButton); - card.append(title, path, fields, actions); - elements.templatesList.append(card); - }); -} - -function buildFieldsTable(fieldsDict) { - const table = document.createElement("table"); - table.className = "fields-table"; - - const thead = document.createElement("thead"); - thead.innerHTML = "FieldType"; - table.appendChild(thead); - - const tbody = document.createElement("tbody"); - const entries = Object.entries(fieldsDict || {}); - if (!entries.length) { - const row = document.createElement("tr"); - const cell = document.createElement("td"); - cell.colSpan = 2; - cell.textContent = "No fields."; - row.appendChild(cell); - tbody.appendChild(row); - } else { - for (const [name, type] of entries) { - const row = document.createElement("tr"); - const nameCell = document.createElement("td"); - nameCell.textContent = name; - const typeCell = document.createElement("td"); - typeCell.textContent = TYPE_VALUE_TO_LABEL[type] || "Text"; - row.append(nameCell, typeCell); - tbody.appendChild(row); - } - } - table.appendChild(tbody); - return table; -} - -function normalizeFieldType(value) { - return TYPE_VALUE_TO_LABEL[value] ? value : "string"; -} - -function syncFieldRowsFromDom() { - const rowEls = Array.from(elements.fieldsBuilder.querySelectorAll(".field-row")); - fieldRows = rowEls.map((rowEl) => ({ - name: rowEl.querySelector(".field-name").value, - type: rowEl.querySelector(".field-type").value, - })); -} - -function renderFieldRows() { - const fragment = document.createDocumentFragment(); - fieldRows.forEach((row, index) => { - fragment.appendChild(buildFieldRow(row, index)); - }); - elements.fieldsBuilder.innerHTML = ""; - elements.fieldsBuilder.appendChild(fragment); - renderFieldCountBadge(); -} - -function buildFieldRow(row, index) { - const rowEl = document.createElement("div"); - rowEl.className = "field-row"; - rowEl.draggable = true; - rowEl.dataset.index = String(index); - - const handle = document.createElement("span"); - handle.className = "field-drag-handle"; - handle.setAttribute("aria-hidden", "true"); - handle.textContent = "⋮⋮"; // two-column dots — reads as a grip handle - - const nameInput = document.createElement("input"); - nameInput.type = "text"; - nameInput.className = "field-name"; - nameInput.placeholder = "Give description here"; - nameInput.value = row.name || ""; - nameInput.addEventListener("input", () => { - syncFieldRowsFromDom(); - }); - - const typeSelect = document.createElement("select"); - typeSelect.className = "field-type"; - FIELD_TYPES.forEach((t) => { - const opt = document.createElement("option"); - opt.value = t.value; - opt.textContent = t.label; - typeSelect.appendChild(opt); - }); - typeSelect.value = normalizeFieldType(row.type); - typeSelect.addEventListener("change", () => { - syncFieldRowsFromDom(); - }); - - const deleteBtn = document.createElement("button"); - deleteBtn.type = "button"; - deleteBtn.className = "field-delete-btn"; - deleteBtn.setAttribute("aria-label", "Remove field"); - deleteBtn.textContent = "✕"; // ✕ - deleteBtn.addEventListener("click", () => { - syncFieldRowsFromDom(); - const rowIndex = Number(rowEl.dataset.index); - fieldRows.splice(rowIndex, 1); - renderFieldRows(); - }); - - rowEl.addEventListener("dragstart", handleRowDragStart); - rowEl.addEventListener("dragover", handleRowDragOver); - rowEl.addEventListener("dragleave", handleRowDragLeave); - rowEl.addEventListener("drop", handleRowDrop); - rowEl.addEventListener("dragend", handleRowDragEnd); - - rowEl.append(handle, nameInput, typeSelect, deleteBtn); - return rowEl; -} - -function toggleMakeFillableHelp() { - const willShow = elements.makeFillableHelp.classList.contains("hidden"); - elements.makeFillableHelp.classList.toggle("hidden", !willShow); - elements.makeFillableHelpBtn.setAttribute("aria-expanded", String(willShow)); -} - -async function handleMakeFillableClick() { - if (!selectedTemplateFile) { - setStatus(elements.templateFormMessage, "Select a PDF first.", "error"); - return; - } - - const templateDirectory = DEFAULT_TEMPLATE_DIRECTORY; - - elements.makeFillableBtn.disabled = true; - const previousLabel = elements.makeFillableBtn.textContent; - elements.makeFillableBtn.textContent = "Working..."; - setStatus( - elements.templateFormMessage, - "Uploading PDF and running fillable-field detection (this can take a minute)...", - "info" - ); - - try { - if (!uploadedPath) { - const upload = await uploadTemplatePdf(selectedTemplateFile, templateDirectory); - uploadedPath = upload.pdf_path; - } - - const response = await fetch(`${API_BASE_URL}/templates/make-fillable`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ pdf_path: uploadedPath }), - }); - const body = await parseJsonResponse(response); - if (!response.ok) { - throw new Error(extractErrorMessage(body, response.status)); - } - - uploadedPath = body.pdf_path; - const count = typeof body.field_count === "number" ? body.field_count : null; - uploadedFieldCount = count; - renderFieldCountBadge(); - setStatus( - elements.templateFormMessage, - count !== null - ? `Fillable PDF created — ${count} field${count === 1 ? "" : "s"} detected.` - : "Fillable PDF created.", - "success" - ); - elements.makeFillableBtn.textContent = "Re-detect fields"; - elements.makeFillableBtn.disabled = false; - } catch (error) { - setStatus(elements.templateFormMessage, error.message, "error"); - elements.makeFillableBtn.textContent = previousLabel; - elements.makeFillableBtn.disabled = false; - } -} - -function handleAddFieldClick() { - syncFieldRowsFromDom(); - fieldRows.push({ name: "", type: "string" }); - renderFieldRows(); - const rows = elements.fieldsBuilder.querySelectorAll(".field-row .field-name"); - if (rows.length) { - rows[rows.length - 1].focus(); - } -} - -function handleRowDragStart(event) { - const rowEl = event.currentTarget; - dragSourceIndex = Number(rowEl.dataset.index); - rowEl.classList.add("is-dragging"); - if (event.dataTransfer) { - event.dataTransfer.effectAllowed = "move"; - event.dataTransfer.setData("text/plain", String(dragSourceIndex)); - } -} - -function handleRowDragOver(event) { - event.preventDefault(); - if (event.dataTransfer) { - event.dataTransfer.dropEffect = "move"; - } - event.currentTarget.classList.add("drag-over"); -} - -function handleRowDragLeave(event) { - event.currentTarget.classList.remove("drag-over"); -} - -function handleRowDrop(event) { - event.preventDefault(); - const rowEl = event.currentTarget; - rowEl.classList.remove("drag-over"); - const targetIndex = Number(rowEl.dataset.index); - if (dragSourceIndex === null || dragSourceIndex === targetIndex) { - return; - } - syncFieldRowsFromDom(); - const [moved] = fieldRows.splice(dragSourceIndex, 1); - fieldRows.splice(targetIndex, 0, moved); - dragSourceIndex = null; - renderFieldRows(); -} - -function handleRowDragEnd(event) { - event.currentTarget.classList.remove("is-dragging"); - elements.fieldsBuilder - .querySelectorAll(".field-row.drag-over") - .forEach((el) => el.classList.remove("drag-over")); - dragSourceIndex = null; -} - -function loadTemplates() { - try { - const raw = localStorage.getItem(STORAGE_TEMPLATES_KEY); - if (!raw) { - return []; - } - const parsed = JSON.parse(raw); - return Array.isArray(parsed) ? parsed : []; - } catch (_error) { - return []; - } -} - -function saveTemplates() { - localStorage.setItem(STORAGE_TEMPLATES_KEY, JSON.stringify(templates)); -} - -function upsertTemplate(template) { - const normalized = { - id: template.id, - name: template.name || "", - pdf_path: template.pdf_path || "", - fields: template.fields || {}, - }; - - const index = templates.findIndex((item) => Number(item.id) === Number(template.id)); - if (index >= 0) { - templates[index] = normalized; - } else { - templates.unshift(normalized); - } - - saveTemplates(); -} - -function restorePreviewState() { - const lastPath = localStorage.getItem(STORAGE_LAST_OUTPUT_KEY); - if (lastPath) { - elements.serverPdfPath.value = lastPath; - } -} - -async function parseJsonResponse(response) { - const text = await response.text(); - if (!text) { - return {}; - } - try { - return JSON.parse(text); - } catch (_error) { - return { raw: text }; - } -} - -function extractErrorMessage(responseBody, statusCode) { - if (responseBody && typeof responseBody === "object") { - if (typeof responseBody.error === "string") { - return responseBody.error; - } - if (Array.isArray(responseBody.detail)) { - const first = responseBody.detail[0]; - if (first && typeof first.msg === "string") { - return first.msg; - } - } - if (typeof responseBody.detail === "string") { - return responseBody.detail; - } - if (typeof responseBody.raw === "string") { - return responseBody.raw; - } - } - return `Request failed with status ${statusCode}.`; -} diff --git a/frontend/electron.js b/frontend/electron.js deleted file mode 100644 index 9fc7e19..0000000 --- a/frontend/electron.js +++ /dev/null @@ -1,64 +0,0 @@ -const { app, BrowserWindow } = require("electron"); -const path = require("path"); -const { spawn } = require("child_process"); - -let backendProcess = null; - -function startBackend() { - if (app.isPackaged) { - const ext = process.platform === 'win32' ? '.exe' : ''; - const backendPath = path.join(process.resourcesPath, "bin", `api-backend${ext}`); - - backendProcess = spawn(backendPath, [], { - stdio: 'ignore' - }); - - backendProcess.on('error', (err) => { - console.error('Failed to start backend process.', err); - }); - } else { - console.log("Running in development mode. Assuming backend is running via Docker Desktop."); - } -} - -function createWindow() { - const win = new BrowserWindow({ - width: 1100, - height: 800, - webPreferences: { - preload: path.join(__dirname, "preload.js"), - }, - }); - - // Allow microphone capture for the local speech-to-text recorder. Audio is - // only ever sent to the local Whisper service — nothing leaves the machine. - win.webContents.session.setPermissionRequestHandler( - (webContents, permission, callback) => { - callback(permission === "media"); - } - ); - - win.loadFile("index.html"); - if (!app.isPackaged) { - win.webContents.openDevTools(); - } -} - -app.whenReady().then(() => { - startBackend(); - createWindow(); -}); - -app.on("will-quit", () => { - if (backendProcess) { - backendProcess.kill(); - } -}); - -app.on("window-all-closed", () => { - if (process.platform !== "darwin") app.quit(); -}); - -app.on("activate", () => { - if (BrowserWindow.getAllWindows().length === 0) createWindow(); -}); diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index d525015..0000000 --- a/frontend/index.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - FireForm - - - -
-
-

Starting up...

-

Initializing FireForm backend

-
-
-
-

FireForm

-

- Create templates, fill forms, and preview PDFs from one place. -

-
- - - -
-

Create Template

-
- - - - - -
- Drag and drop a PDF here - or click to select a file -
-

No PDF selected.

- - -
- - -
- - - -

- What information should be filled in? Add one row per field. Fields are filled - into your PDF in the order shown — drag to reorder. -

- -
- - - -
-

- -
- - - - - - -
- - - - diff --git a/frontend/package-lock.json b/frontend/package-lock.json deleted file mode 100644 index 86ed680..0000000 --- a/frontend/package-lock.json +++ /dev/null @@ -1,4025 +0,0 @@ -{ - "name": "fireform", - "version": "1.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "fireform", - "version": "1.1.0", - "devDependencies": { - "electron": "^35.0.0", - "electron-builder": "^26.0.0" - } - }, - "node_modules/@develar/schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.0", - "ajv-keywords": "^3.4.1" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/@electron/asar": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", - "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^5.0.0", - "glob": "^7.1.6", - "minimatch": "^3.0.4" - }, - "bin": { - "asar": "bin/asar.js" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/@electron/asar/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@electron/asar/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@electron/asar/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@electron/fuses": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", - "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.1", - "fs-extra": "^9.0.1", - "minimist": "^1.2.5" - }, - "bin": { - "electron-fuses": "dist/bin.js" - } - }, - "node_modules/@electron/fuses/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@electron/fuses/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/fuses/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@electron/get": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", - "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", - "got": "^11.8.5", - "progress": "^2.0.3", - "semver": "^6.2.0", - "sumchecker": "^3.0.1" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "global-agent": "^3.0.0" - } - }, - "node_modules/@electron/notarize": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", - "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "fs-extra": "^9.0.1", - "promise-retry": "^2.0.1" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@electron/notarize/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@electron/notarize/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/notarize/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@electron/osx-sign": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", - "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "compare-version": "^0.1.2", - "debug": "^4.3.4", - "fs-extra": "^10.0.0", - "isbinaryfile": "^4.0.8", - "minimist": "^1.2.6", - "plist": "^3.0.5" - }, - "bin": { - "electron-osx-flat": "bin/electron-osx-flat.js", - "electron-osx-sign": "bin/electron-osx-sign.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@electron/osx-sign/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/@electron/osx-sign/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/osx-sign/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@electron/rebuild": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.4.tgz", - "integrity": "sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@malept/cross-spawn-promise": "^2.0.0", - "debug": "^4.1.1", - "node-abi": "^4.2.0", - "node-api-version": "^0.2.1", - "node-gyp": "^12.2.0", - "read-binary-file-arch": "^1.0.6" - }, - "bin": { - "electron-rebuild": "lib/cli.js" - }, - "engines": { - "node": ">=22.12.0" - } - }, - "node_modules/@electron/universal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", - "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@electron/asar": "^3.3.1", - "@malept/cross-spawn-promise": "^2.0.0", - "debug": "^4.3.1", - "dir-compare": "^4.2.0", - "fs-extra": "^11.1.1", - "minimatch": "^9.0.3", - "plist": "^3.1.0" - }, - "engines": { - "node": ">=16.4" - } - }, - "node_modules/@electron/universal/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@electron/universal/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/universal/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/universal/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@electron/universal/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@electron/windows-sign": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", - "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "cross-dirname": "^0.1.0", - "debug": "^4.3.4", - "fs-extra": "^11.1.1", - "minimist": "^1.2.8", - "postject": "^1.0.0-alpha.6" - }, - "bin": { - "electron-windows-sign": "bin/electron-windows-sign.js" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/windows-sign/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/windows-sign/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/windows-sign/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@malept/cross-spawn-promise": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", - "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/malept" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" - } - ], - "license": "Apache-2.0", - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/@malept/flatpak-bundler": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", - "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "fs-extra": "^9.0.0", - "lodash": "^4.17.15", - "tmp-promise": "^3.0.2" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@malept/flatpak-bundler/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "node_modules/@types/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/fs-extra": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", - "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", - "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/plist": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", - "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*", - "xmlbuilder": ">=11.0.1" - } - }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/verror": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", - "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.13", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", - "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/7zip-bin": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", - "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", - "dev": true, - "license": "MIT" - }, - "node_modules/abbrev": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", - "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/app-builder-bin": { - "version": "5.0.0-alpha.12", - "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz", - "integrity": "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/app-builder-lib": { - "version": "26.8.1", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.8.1.tgz", - "integrity": "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@develar/schema-utils": "~2.6.5", - "@electron/asar": "3.4.1", - "@electron/fuses": "^1.8.0", - "@electron/get": "^3.0.0", - "@electron/notarize": "2.5.0", - "@electron/osx-sign": "1.3.3", - "@electron/rebuild": "^4.0.3", - "@electron/universal": "2.0.3", - "@malept/flatpak-bundler": "^0.4.0", - "@types/fs-extra": "9.0.13", - "async-exit-hook": "^2.0.1", - "builder-util": "26.8.1", - "builder-util-runtime": "9.5.1", - "chromium-pickle-js": "^0.2.0", - "ci-info": "4.3.1", - "debug": "^4.3.4", - "dotenv": "^16.4.5", - "dotenv-expand": "^11.0.6", - "ejs": "^3.1.8", - "electron-publish": "26.8.1", - "fs-extra": "^10.1.0", - "hosted-git-info": "^4.1.0", - "isbinaryfile": "^5.0.0", - "jiti": "^2.4.2", - "js-yaml": "^4.1.0", - "json5": "^2.2.3", - "lazy-val": "^1.0.5", - "minimatch": "^10.0.3", - "plist": "3.1.0", - "proper-lockfile": "^4.1.2", - "resedit": "^1.7.0", - "semver": "~7.7.3", - "tar": "^7.5.7", - "temp-file": "^3.4.0", - "tiny-async-pool": "1.3.0", - "which": "^5.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "dmg-builder": "26.8.1", - "electron-builder-squirrel-windows": "26.8.1" - } - }, - "node_modules/app-builder-lib/node_modules/@electron/get": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", - "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", - "got": "^11.8.5", - "progress": "^2.0.3", - "semver": "^6.2.0", - "sumchecker": "^3.0.1" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "global-agent": "^3.0.0" - } - }, - "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/app-builder-lib/node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/app-builder-lib/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/app-builder-lib/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/async-exit-hook": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", - "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/boolean": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/builder-util": { - "version": "26.8.1", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.8.1.tgz", - "integrity": "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/debug": "^4.1.6", - "7zip-bin": "~5.2.0", - "app-builder-bin": "5.0.0-alpha.12", - "builder-util-runtime": "9.5.1", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.6", - "debug": "^4.3.4", - "fs-extra": "^10.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "js-yaml": "^4.1.0", - "sanitize-filename": "^1.6.3", - "source-map-support": "^0.5.19", - "stat-mode": "^1.0.0", - "temp-file": "^3.4.0", - "tiny-async-pool": "1.3.0" - } - }, - "node_modules/builder-util-runtime": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz", - "integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "sax": "^1.2.4" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/builder-util/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/builder-util/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/builder-util/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/chromium-pickle-js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", - "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/compare-version": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", - "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "buffer": "^5.1.0" - } - }, - "node_modules/cross-dirname": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", - "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cross-spawn/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/dir-compare": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", - "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimatch": "^3.0.5", - "p-limit": "^3.1.0 " - } - }, - "node_modules/dir-compare/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/dir-compare/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/dir-compare/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/dmg-builder": { - "version": "26.8.1", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.8.1.tgz", - "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "app-builder-lib": "26.8.1", - "builder-util": "26.8.1", - "fs-extra": "^10.1.0", - "iconv-lite": "^0.6.2", - "js-yaml": "^4.1.0" - }, - "optionalDependencies": { - "dmg-license": "^1.0.11" - } - }, - "node_modules/dmg-builder/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dmg-builder/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/dmg-builder/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/dmg-license": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", - "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "@types/plist": "^3.0.1", - "@types/verror": "^1.10.3", - "ajv": "^6.10.0", - "crc": "^3.8.0", - "iconv-corefoundation": "^1.1.7", - "plist": "^3.0.4", - "smart-buffer": "^4.0.2", - "verror": "^1.10.0" - }, - "bin": { - "dmg-license": "bin/dmg-license.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dotenv-expand": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", - "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron": { - "version": "35.7.5", - "resolved": "https://registry.npmjs.org/electron/-/electron-35.7.5.tgz", - "integrity": "sha512-dnL+JvLraKZl7iusXTVTGYs10TKfzUi30uEDTqsmTm0guN9V2tbOjTzyIZbh9n3ygUjgEYyo+igAwMRXIi3IPw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@electron/get": "^2.0.0", - "@types/node": "^22.7.7", - "extract-zip": "^2.0.1" - }, - "bin": { - "electron": "cli.js" - }, - "engines": { - "node": ">= 12.20.55" - } - }, - "node_modules/electron-builder": { - "version": "26.8.1", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.8.1.tgz", - "integrity": "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "app-builder-lib": "26.8.1", - "builder-util": "26.8.1", - "builder-util-runtime": "9.5.1", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "dmg-builder": "26.8.1", - "fs-extra": "^10.1.0", - "lazy-val": "^1.0.5", - "simple-update-notifier": "2.0.0", - "yargs": "^17.6.2" - }, - "bin": { - "electron-builder": "cli.js", - "install-app-deps": "install-app-deps.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/electron-builder-squirrel-windows": { - "version": "26.8.1", - "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.8.1.tgz", - "integrity": "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "app-builder-lib": "26.8.1", - "builder-util": "26.8.1", - "electron-winstaller": "5.4.0" - } - }, - "node_modules/electron-builder/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-builder/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/electron-builder/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/electron-publish": { - "version": "26.8.1", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.8.1.tgz", - "integrity": "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/fs-extra": "^9.0.11", - "builder-util": "26.8.1", - "builder-util-runtime": "9.5.1", - "chalk": "^4.1.2", - "form-data": "^4.0.5", - "fs-extra": "^10.1.0", - "lazy-val": "^1.0.5", - "mime": "^2.5.2" - } - }, - "node_modules/electron-publish/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-publish/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/electron-publish/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/electron-winstaller": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", - "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@electron/asar": "^3.2.1", - "debug": "^4.1.1", - "fs-extra": "^7.0.1", - "lodash": "^4.17.21", - "temp": "^0.9.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "optionalDependencies": { - "@electron/windows-sign": "^1.1.2" - } - }, - "node_modules/electron-winstaller/node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extsprintf": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", - "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "optional": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/filelist": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", - "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/global-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", - "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", - "semver": "^7.3.2", - "serialize-error": "^7.0.1" - }, - "engines": { - "node": ">=10.0" - } - }, - "node_modules/global-agent/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/iconv-corefoundation": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", - "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "cli-truncate": "^2.1.0", - "node-addon-api": "^1.6.3" - }, - "engines": { - "node": "^8.11.2 || >=10" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/isbinaryfile": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", - "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/jake": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", - "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.6", - "filelist": "^1.0.4", - "picocolors": "^1.1.1" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC", - "optional": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/lazy-val": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", - "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/matcher": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", - "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "escape-string-regexp": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-abi": { - "version": "4.29.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.29.0.tgz", - "integrity": "sha512-bGc7hHz6lrdpMqH3XqfiHc5PKzEhjgUj6OLpTXynkLi9JZKyMByI/tdpm4Liu6O2BjtE1lakBWXjOQS1EnSQLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.6.3" - }, - "engines": { - "node": ">=22.12.0" - } - }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/node-api-version": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", - "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - } - }, - "node_modules/node-api-version/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", - "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "nopt": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "tar": "^7.5.4", - "tinyglobby": "^0.2.12", - "undici": "^6.25.0", - "which": "^6.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/nopt": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", - "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "^4.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pe-library": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", - "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jet2jet" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/plist": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", - "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xmldom/xmldom": "^0.8.8", - "base64-js": "^1.5.1", - "xmlbuilder": "^15.1.1" - }, - "engines": { - "node": ">=10.4.0" - } - }, - "node_modules/postject": { - "version": "1.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", - "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "commander": "^9.4.0" - }, - "bin": { - "postject": "dist/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/postject/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/proc-log": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-binary-file-arch": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", - "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "bin": { - "read-binary-file-arch": "cli.js" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resedit": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", - "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pe-library": "^0.4.1" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jet2jet" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/roarr": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", - "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "detect-node": "^2.0.4", - "globalthis": "^1.0.1", - "json-stringify-safe": "^5.0.1", - "semver-compare": "^1.0.0", - "sprintf-js": "^1.1.2" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/sanitize-filename": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", - "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", - "dev": true, - "license": "WTFPL OR ISC", - "dependencies": { - "truncate-utf8-bytes": "^1.0.0" - } - }, - "node_modules/sax": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", - "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "type-fest": "^0.13.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/stat-mode": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", - "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sumchecker": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", - "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.1.0" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", - "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/temp": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", - "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "mkdirp": "^0.5.1", - "rimraf": "~2.6.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/temp-file": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", - "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-exit-hook": "^2.0.1", - "fs-extra": "^10.0.0" - } - }, - "node_modules/temp-file/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/temp-file/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/temp-file/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/tiny-async-pool": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", - "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^5.5.0" - } - }, - "node_modules/tiny-async-pool/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.14" - } - }, - "node_modules/tmp-promise": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", - "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tmp": "^0.2.0" - } - }, - "node_modules/truncate-utf8-bytes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", - "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", - "dev": true, - "license": "WTFPL", - "dependencies": { - "utf8-byte-length": "^1.0.1" - } - }, - "node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", - "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utf8-byte-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", - "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", - "dev": true, - "license": "(WTFPL OR MIT)" - }, - "node_modules/verror": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", - "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index 468e34c..0000000 --- a/frontend/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "fireform", - "version": "1.1.0", - "description": "FireForm — report once, file everywhere", - "repository": "fireform-core/FireForm", - "main": "electron.js", - "scripts": { - "start": "electron .", - "dist": "electron-builder --publish never" - }, - "devDependencies": { - "electron": "^35.0.0", - "electron-builder": "^26.0.0" - }, - "build": { - "appId": "com.fireform.app", - "productName": "FireForm", - "files": [ - "index.html", - "app.js", - "styles.css", - "electron.js", - "preload.js" - ], - "extraResources": [ - { - "from": "bin", - "to": "bin", - "filter": [ - "**/*" - ] - } - ], - "mac": { - "target": "dmg" - }, - "win": { - "target": "nsis" - }, - "linux": { - "target": "AppImage" - } - } -} diff --git a/frontend/preload.js b/frontend/preload.js deleted file mode 100644 index 4e99198..0000000 --- a/frontend/preload.js +++ /dev/null @@ -1,3 +0,0 @@ -// Preload script — placeholder for future Node.js bridge APIs. -// Use contextBridge.exposeInMainWorld() here to safely expose -// Node functionality to the renderer process when needed. diff --git a/frontend/styles.css b/frontend/styles.css deleted file mode 100644 index c5b4a69..0000000 --- a/frontend/styles.css +++ /dev/null @@ -1,658 +0,0 @@ -:root { - --bg: linear-gradient(145deg, #f7f4ec 0%, #eef4f7 100%); - --panel: #ffffff; - --panel-border: #d7dde3; - --text: #1f2a36; - --muted: #576779; - --primary: #125a86; - --primary-strong: #0b4568; - --success: #1a7f4b; - --error: #a43434; - --radius: 14px; - --shadow: 0 10px 28px rgba(16, 44, 70, 0.09); -} - -* { - box-sizing: border-box; -} - -body { - margin: 0; - min-height: 100vh; - font-family: "Avenir Next", "Trebuchet MS", "Segoe UI", sans-serif; - color: var(--text); - background: var(--bg); -} - -.app-shell { - max-width: 980px; - margin: 0 auto; - padding: 24px 18px 48px; -} - -.app-header { - margin-bottom: 16px; -} - -.eyebrow { - margin: 0 0 8px; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--muted); - font-size: 0.78rem; -} - -h1 { - margin: 0; - font-size: clamp(1.8rem, 3vw, 2.5rem); - line-height: 1.15; -} - -h2 { - margin: 0 0 14px; - font-size: 1.25rem; -} - -.subtitle { - margin: 8px 0 0; - color: var(--muted); -} - -.card { - background: var(--panel); - border: 1px solid var(--panel-border); - border-radius: var(--radius); - box-shadow: var(--shadow); - padding: 18px; -} - -.api-config { - display: grid; - gap: 8px; - margin-bottom: 14px; -} - -.tabs { - display: flex; - gap: 8px; - margin-bottom: 14px; - flex-wrap: wrap; -} - -.tab { - border: 1px solid var(--panel-border); - background: #f9fbfc; - border-radius: 999px; - padding: 8px 14px; - cursor: pointer; - font-weight: 600; - color: var(--text); -} - -.tab.active { - border-color: var(--primary); - color: #fff; - background: var(--primary); -} - -.panel { - display: grid; - gap: 14px; -} - -.stacked-form { - display: grid; - gap: 10px; -} - -.dropzone { - border: 2px dashed #9ab1c7; - border-radius: 12px; - padding: 20px 14px; - display: grid; - gap: 4px; - text-align: center; - cursor: pointer; - background: #f7fbff; - color: #2f4a63; - transition: border-color 0.2s ease, background-color 0.2s ease; -} - -.dropzone strong { - font-size: 1.02rem; - color: #1f3a53; -} - -.dropzone span { - font-size: 0.92rem; - color: #4e657b; -} - -.dropzone:hover, -.dropzone:focus-visible, -.dropzone.active { - border-color: var(--primary); - background: #eef7ff; - outline: none; -} - -label { - font-weight: 600; -} - -input, -textarea, -select, -button { - font: inherit; -} - -input, -textarea, -select { - width: 100%; - border: 1px solid #bcc8d6; - border-radius: 10px; - padding: 10px 12px; - background: #fff; - color: var(--text); -} - -select { - appearance: none; - -webkit-appearance: none; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: right 12px center; - padding-right: 32px; -} - -input:focus, -textarea:focus, -select:focus { - outline: 2px solid var(--primary); - outline-offset: 1px; - border-color: var(--primary); -} - -textarea { - resize: vertical; -} - -button { - border: 0; - border-radius: 10px; - background: var(--primary); - color: #fff; - cursor: pointer; - padding: 10px 14px; - font-weight: 700; -} - -button:hover { - background: var(--primary-strong); -} - -.helper { - margin: 0; - color: var(--muted); - font-size: 0.95rem; -} - -.status { - margin: 0; - min-height: 1.4em; - color: var(--muted); -} - -.status.success { - color: var(--success); -} - -.status.error { - color: var(--error); -} - -.json-output { - margin: 0; - white-space: pre-wrap; - word-break: break-word; - border-radius: 10px; - border: 1px solid #d7dde3; - background: #f7fafc; - padding: 10px 12px; - max-height: 280px; - overflow: auto; - font-family: "IBM Plex Mono", "Menlo", "Consolas", monospace; - font-size: 0.88rem; -} - -.hidden { - display: none; -} - -.secondary-btn { - background: #f1f5f9; - color: var(--primary-strong); - border: 1px solid var(--panel-border); - font-weight: 600; - padding: 8px 14px; - justify-self: start; -} - -.secondary-btn:hover { - background: #e5ecf3; -} - -.stt-controls { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 8px; -} - -.stt-btn { - display: inline-flex; - align-items: center; - gap: 7px; - padding: 8px 14px; -} - -.stt-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.stt-dot { - width: 9px; - height: 9px; - border-radius: 50%; - background: #c2ccd6; - flex-shrink: 0; -} - -.stt-controls.is-recording .stt-dot { - background: var(--error); - animation: stt-pulse 1.1s ease-in-out infinite; -} - -.stt-controls.is-paused .stt-dot { - background: var(--primary); -} - -@keyframes stt-pulse { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.35; transform: scale(0.78); } -} - -.stt-status { - color: var(--muted); - font-size: 0.92rem; - min-height: 1.2em; -} - -/* Fill Form: model picker, template tiles, view toggle */ -.helper.error { - color: var(--error); - font-weight: 600; -} - -.template-tiles { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); - gap: 14px; -} - -.template-tile { - position: relative; - display: flex; - flex-direction: column; - gap: 6px; - min-height: 112px; - border: 1px solid var(--panel-border); - border-radius: 12px; - background: #fbfcfe; - padding: 16px; - cursor: pointer; - transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease; -} - -.template-tile:hover { - border-color: #9ab1c7; -} - -.template-tile:focus-visible { - outline: 2px solid var(--primary); - outline-offset: 1px; -} - -.template-tile.selected { - border-color: var(--primary); - background: #eef7ff; - box-shadow: inset 0 0 0 1px var(--primary); -} - -.tile-body { - display: flex; - flex-direction: column; - gap: 4px; - min-width: 0; -} - -.tile-title { - font-weight: 700; - color: var(--text); - /* Uniform tiles: clamp long titles to two lines, then ellipsis. */ - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - word-break: break-word; -} - -.tile-meta { - font-size: 0.85rem; - color: var(--muted); -} - -.tile-preview-btn { - margin-top: auto; /* pin to the bottom so every tile's button lines up */ - align-self: flex-start; - background: transparent; - color: var(--primary-strong); - border: 1px solid var(--panel-border); - border-radius: 8px; - padding: 5px 10px; - font-size: 0.82rem; - font-weight: 600; -} - -.tile-preview-btn:hover { - background: #e5ecf3; -} - -/* Greyed-but-clickable: looks disabled, still fires click to prompt the user. */ -button.is-disabled, -button.is-disabled:hover { - background: #c2ccd6; - cursor: not-allowed; -} - -.help-trigger { - background: #e5ecf3; - color: var(--primary-strong); - border: 1px solid #c8d2dd; - border-radius: 50%; - width: 24px; - height: 24px; - padding: 0; - font-weight: 700; - font-size: 0.9rem; - line-height: 1; - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - flex-shrink: 0; -} - -.help-trigger:hover { - background: #d8e2eb; -} - -.help-trigger[aria-expanded="true"] { - background: var(--primary); - color: #fff; - border-color: var(--primary); -} - -.field-count-badge { - margin: 0; - padding: 6px 10px; - border-radius: 8px; - background: #f1f5f9; - border: 1px solid var(--panel-border); - color: var(--muted); - font-size: 0.92rem; - display: inline-block; - justify-self: start; -} - -.field-count-badge.hidden { - display: none; -} - -.field-count-badge.match { - background: #e8f3ec; - border-color: #b9d8c4; - color: var(--success); -} - -.field-count-badge.mismatch { - background: #fbe9e9; - border-color: #f3d3d3; - color: var(--error); -} - -.fields-builder { - display: grid; - gap: 8px; -} - -.field-row { - display: grid; - grid-template-columns: 24px 1fr 170px 36px; - gap: 10px; - align-items: center; - padding: 8px 10px; - border: 1px solid var(--panel-border); - border-radius: 12px; - background: #fbfcfe; -} - -.field-row.is-dragging { - opacity: 0.4; -} - -.field-row.drag-over { - border-color: var(--primary); - background: #eef7ff; -} - -.field-drag-handle { - cursor: grab; - color: var(--muted); - text-align: center; - user-select: none; - font-size: 1rem; - line-height: 1; -} - -.field-drag-handle:active { - cursor: grabbing; -} - -.field-delete-btn { - background: transparent; - color: var(--muted); - border: 1px solid transparent; - padding: 0; - font-size: 1rem; - font-weight: 600; - line-height: 1; - border-radius: 8px; - width: 32px; - height: 32px; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.field-delete-btn:hover { - background: #fbe9e9; - color: var(--error); - border-color: #f3d3d3; -} - -.fields-table { - width: 100%; - border-collapse: collapse; - border: 1px solid var(--panel-border); - border-radius: 10px; - overflow: hidden; - background: #fff; - font-size: 0.92rem; -} - -.fields-table th, -.fields-table td { - text-align: left; - padding: 8px 10px; - border-bottom: 1px solid var(--panel-border); -} - -.fields-table tr:last-child td { - border-bottom: 0; -} - -.fields-table th { - background: #f1f5f9; - font-weight: 600; - color: var(--muted); -} - -@media (max-width: 540px) { - .field-row { - grid-template-columns: 22px 1fr 36px; - grid-template-areas: - "handle name delete" - ". select select"; - } - - .field-drag-handle { grid-area: handle; } - .field-row input[type="text"] { grid-area: name; } - .field-row select { grid-area: select; } - .field-delete-btn { grid-area: delete; } -} - -.divider { - width: 100%; - border: 0; - border-top: 1px solid #d7dde3; - margin: 4px 0; -} - -.template-list { - display: grid; - gap: 12px; -} - -.template-card { - border: 1px solid #d7dde3; - border-radius: 12px; - padding: 14px; - background: #fbfcfe; -} - -.template-meta { - margin: 0; -} - -.card-actions { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 10px; -} - -.card-actions button { - padding: 8px 12px; - font-weight: 600; -} - -.empty-state { - padding: 14px; - border-radius: 10px; - border: 1px dashed #bcc8d6; - color: var(--muted); - background: #f8fafb; -} - -.preview-controls { - display: grid; - gap: 8px; -} - -.inline-actions { - display: grid; - grid-template-columns: 1fr auto; - gap: 8px; - align-items: center; -} - -#pdfFrame { - width: 100%; - min-height: 560px; - border: 1px solid #d7dde3; - border-radius: 10px; - background: #fff; -} - -@media (max-width: 720px) { - .app-shell { - padding: 16px 12px 28px; - } - - .card { - padding: 14px; - } - - .inline-actions { - grid-template-columns: 1fr; - } - - #pdfFrame { - min-height: 420px; - } -} - -.loading-screen { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background: var(--bg); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - z-index: 9999; - transition: opacity 0.5s ease, visibility 0.5s ease; -} - -.loading-screen.hidden { - opacity: 0; - visibility: hidden; - pointer-events: none; -} - -.loading-screen h2 { - margin-top: 24px; - color: var(--primary); -} - -.spinner { - width: 50px; - height: 50px; - border: 5px solid rgba(18, 90, 134, 0.2); - border-top-color: var(--primary); - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} From e26a0fff8ab4a981ab5f0d0440cb9cb54411d70c Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Wed, 3 Jun 2026 00:37:22 +0530 Subject: [PATCH 06/15] chore: move scripts to scripts/ directory, fixes #517 --- container-init.sh => scripts/container-init.sh | 0 setup-dockers-env.sh => scripts/setup-dockers-env.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename container-init.sh => scripts/container-init.sh (100%) rename setup-dockers-env.sh => scripts/setup-dockers-env.sh (100%) diff --git a/container-init.sh b/scripts/container-init.sh similarity index 100% rename from container-init.sh rename to scripts/container-init.sh diff --git a/setup-dockers-env.sh b/scripts/setup-dockers-env.sh similarity index 100% rename from setup-dockers-env.sh rename to scripts/setup-dockers-env.sh From b6527b7ea109b6360a10bbb11bc7f022f5c9db2c Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Thu, 4 Jun 2026 00:57:31 +0530 Subject: [PATCH 07/15] chore: created docker file and compose for development envirnment --- docker/dev/Dockerfile | 26 +++++++++++++ docker/dev/compose.yml | 83 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 docker/dev/Dockerfile create mode 100644 docker/dev/compose.yml diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile new file mode 100644 index 0000000..ad4bfd4 --- /dev/null +++ b/docker/dev/Dockerfile @@ -0,0 +1,26 @@ +# syntax=docker/dockerfile:1 +FROM python:3.11-slim + +WORKDIR /app + +# Use apt cache mount to speed up system package installation across builds +RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt \ + apt-get update && apt-get install -y \ + curl \ + libgl1 \ + libglib2.0-0 \ + libxcb1 + +COPY requirements.txt . + +# Use pip cache mount so it remembers downloaded wheels +RUN --mount=type=cache,target=/root/.cache/pip \ + pip install -r requirements.txt + +ENV PYTHONPATH=/app + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/docker/dev/compose.yml b/docker/dev/compose.yml new file mode 100644 index 0000000..5b7c2f0 --- /dev/null +++ b/docker/dev/compose.yml @@ -0,0 +1,83 @@ +services: + ollama: + image: ollama/ollama:latest + container_name: fireform-ollama + ports: + - "127.0.0.1:11434:11434" + volumes: + - ollama_data:/root/.ollama + networks: + - fireform-network + healthcheck: + test: ["CMD-SHELL", "ollama list || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + whisper: + image: onerahmet/openai-whisper-asr-webservice:latest + container_name: fireform-whisper + environment: + - ASR_ENGINE=faster_whisper + - ASR_MODEL=${WHISPER_MODEL:-small.en} + - ASR_MODEL_PATH=/data/whisper + volumes: + - whisper_models:/data/whisper + ports: + - "127.0.0.1:9000:9000" + networks: + - fireform-network + healthcheck: + test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:9000/docs')\" || exit 1"] + interval: 15s + timeout: 5s + retries: 5 + start_period: 60s + + app: + build: + context: ../.. + dockerfile: docker/dev/Dockerfile + container_name: fireform-app + depends_on: + ollama: + condition: service_healthy + whisper: + condition: service_started + command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] + volumes: + - ../..:/app + - fireform_db:/data/db + - fireform_uploads:/data/uploads + ports: + - "${APP_PORT:-8000}:8000" + environment: + - PYTHONUNBUFFERED=1 + - CUDA_VISIBLE_DEVICES= + - PYTHONPATH=/app + - OLLAMA_HOST=${OLLAMA_HOST:-http://ollama:11434} + - OLLAMA_TIMEOUT=${OLLAMA_TIMEOUT:-300} + - OLLAMA_MODEL=${OLLAMA_MODEL:-qwen2.5:1.5b} + - WHISPER_HOST=${WHISPER_HOST:-http://whisper:9000} + - FIREFORM_DB_PATH=/data/db/fireform.db + - FIREFORM_DATA_DIR=/data/uploads + - FIREFORM_TEMPLATE_DIR=/data/uploads + - FIREFORM_DB_ECHO=${FIREFORM_DB_ECHO:-true} + - FRONTEND_ORIGINS=${FRONTEND_ORIGINS:-http://localhost:5173,http://127.0.0.1:5173} + networks: + - fireform-network + +volumes: + ollama_data: + driver: local + whisper_models: + driver: local + fireform_db: + driver: local + fireform_uploads: + driver: local + +networks: + fireform-network: + driver: bridge From 79a1ce05ef82009893de5e3a30e059354db6502d Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Thu, 4 Jun 2026 01:10:06 +0530 Subject: [PATCH 08/15] chore: docker file and docker compose for prod envirnment --- docker/prod/Dockerfile | 37 +++++++++++++++++ docker/prod/compose.yml | 90 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 docker/prod/Dockerfile create mode 100644 docker/prod/compose.yml diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile new file mode 100644 index 0000000..8ad8dea --- /dev/null +++ b/docker/prod/Dockerfile @@ -0,0 +1,37 @@ +FROM python:3.11-slim AS builder + +WORKDIR /build + +COPY requirements.txt . +RUN pip install --no-cache-dir --prefix=/install -r requirements.txt + + +FROM python:3.11-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + curl \ + libgl1 \ + libglib2.0-0 \ + libxcb1 \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /install /usr/local + +# Copy only app code, not data/ temp/ tests/ docs/ etc. +COPY app/ ./app/ +COPY requirements.txt . + +ENV PYTHONPATH=/app + +# Data dirs created here; actual storage comes from mounted volumes at runtime. +RUN mkdir -p /data/db /data/uploads && chmod +x /entrypoint.sh + +EXPOSE 8000 + +CMD ["gunicorn", "app.main:app", \ + "--worker-class", "uvicorn.workers.UvicornWorker", \ + "--bind", "0.0.0.0:8000", \ + "--access-logfile", "-", \ + "--error-logfile", "-"] diff --git a/docker/prod/compose.yml b/docker/prod/compose.yml new file mode 100644 index 0000000..edd2fa4 --- /dev/null +++ b/docker/prod/compose.yml @@ -0,0 +1,90 @@ +services: + ollama: + image: ollama/ollama:latest + container_name: fireform-ollama + ports: + - "127.0.0.1:11434:11434" + volumes: + - ollama_data:/root/.ollama + networks: + - fireform-network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "ollama list || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + whisper: + image: onerahmet/openai-whisper-asr-webservice:latest + container_name: fireform-whisper + environment: + - ASR_ENGINE=faster_whisper + - ASR_MODEL=${WHISPER_MODEL} + - ASR_MODEL_PATH=/data/whisper + volumes: + - whisper_models:/data/whisper + ports: + - "127.0.0.1:9000:9000" + networks: + - fireform-network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:9000/docs')\" || exit 1"] + interval: 15s + timeout: 5s + retries: 5 + start_period: 60s + + app: + build: + context: ../.. + dockerfile: docker/prod/Dockerfile + container_name: fireform-app + depends_on: + ollama: + condition: service_healthy + whisper: + condition: service_started + command: ["gunicorn", "app.main:app", + "--worker-class", "uvicorn.workers.UvicornWorker", + "--bind", "0.0.0.0:8000", + "--access-logfile", "-", + "--error-logfile", "-"] + volumes: + - fireform_db:/data/db + - fireform_uploads:/data/uploads + ports: + - "${APP_PORT}:8000" + environment: + - PYTHONUNBUFFERED=1 + - CUDA_VISIBLE_DEVICES= + - PYTHONPATH=/app + - OLLAMA_HOST=${OLLAMA_HOST} + - OLLAMA_TIMEOUT=${OLLAMA_TIMEOUT} + - OLLAMA_MODEL=${OLLAMA_MODEL} + - WHISPER_HOST=${WHISPER_HOST} + - FIREFORM_DB_PATH=/data/db/fireform.db + - FIREFORM_DATA_DIR=/data/uploads + - FIREFORM_TEMPLATE_DIR=/data/uploads + - FIREFORM_DB_ECHO=false + - FRONTEND_ORIGINS=${FRONTEND_ORIGINS} + - WEB_CONCURRENCY=${GUNICORN_WORKERS} + networks: + - fireform-network + restart: unless-stopped + +volumes: + ollama_data: + driver: local + whisper_models: + driver: local + fireform_db: + driver: local + fireform_uploads: + driver: local + +networks: + fireform-network: + driver: bridge From 0bc991a5cade6d7cdd085d7211b2728049057209 Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Thu, 4 Jun 2026 15:43:36 +0530 Subject: [PATCH 09/15] Updated Makefile: added make init command to run a project initilization setup. added init-env, select ollama model scripts. Updated docker compose path proposed in #526 and #527 --- Makefile | 102 +++++++++++++++++++++----------------- scripts/check-deps.sh | 51 +++++++++++++++++++ scripts/init-env.sh | 35 +++++++++++++ scripts/select-model.sh | 107 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+), 44 deletions(-) create mode 100755 scripts/check-deps.sh create mode 100755 scripts/init-env.sh create mode 100755 scripts/select-model.sh diff --git a/Makefile b/Makefile index 3f15533..13ea366 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ -.PHONY: help build up down logs shell exec pull-model test clean fireform logs-app logs-ollama logs-frontend super-clean +.PHONY: help init fireform build up down logs logs-app logs-ollama shell pull-model test clean super-clean -# The extraction model pulled into Ollama and used by src/llm.py. Override with -# `make pull-model OLLAMA_MODEL=...`. A 1.5B model keeps per-field fills fast. -OLLAMA_MODEL ?= qwen2.5:1.5b +COMPOSE = docker compose -f docker/dev/compose.yml --env-file docker/.env.dev +ENV_DEV = docker/.env.dev + +# Read OLLAMA_MODEL from .env.dev at runtime; fall back to default if file absent. +OLLAMA_MODEL = $(shell grep -E '^OLLAMA_MODEL=' $(ENV_DEV) 2>/dev/null | cut -d= -f2 | tr -d '[:space:]' || echo qwen2.5:1.5b) help: @printf '%s\n' \ @@ -13,72 +15,84 @@ help: '/_/ /_//_/ \___/ /_/ \____/_/ /_/ /_/ /_/ ' \ '' @echo "" - @echo "Fireform Development Commands" + @echo "FireForm Development Commands" @echo "==============================" - @echo "make fireform - Build and start containers, then open a shell" + @echo "make init - First-time setup: check deps, create .env.dev, pick model" + @echo "make fireform - Build images, start containers, pull Ollama model" @echo "make build - Build Docker images" - @echo "make up - Start all containers" + @echo "make up - Start all containers (detached)" @echo "make down - Stop all containers" - @echo "make logs - View container logs" - @echo "make logs-app - View API container logs" - @echo "make logs-frontend - View frontend container logs" - @echo "make logs-ollama - View Ollama container logs" - @echo "make shell - Open Python shell in app container" - @echo "make exec - Execute Python script in container" - @echo "make pull-model - Pull the extraction model ($(OLLAMA_MODEL)) into Ollama" - @echo "make test - Run tests" - @echo "make clean - Remove containers" - @echo "make super-clean - [CAUTION] Use carefully. Cleans up ALL stopped containers, networks, build cache..." - -# Fix #382 — pull-model is now part of the main setup flow -# The extraction model is pulled automatically before you need it -fireform: build up pull-model + @echo "make logs - Stream all container logs" + @echo "make logs-app - Stream app container logs" + @echo "make logs-ollama - Stream Ollama container logs" + @echo "make shell - Open shell in running app container" + @echo "make pull-model - Pull Ollama model from .env.dev ($(OLLAMA_MODEL))" + @echo "make test - Run test suite" + @echo "make clean - Stop containers (preserves volumes)" + @echo "make super-clean - [CAUTION] Stop containers, delete volumes, prune Docker" + +init: + @chmod +x scripts/check-deps.sh scripts/init-env.sh scripts/select-model.sh + @sh scripts/check-deps.sh + @sh scripts/init-env.sh + @sh scripts/select-model.sh + @printf "Build containers and pull model now? [y/N] "; \ + read answer; \ + case "$$answer" in \ + [yY]*) $(MAKE) fireform ;; \ + *) echo "Run 'make fireform' when ready." ;; \ + esac + +fireform: build up + @printf "Waiting for Ollama to be ready..." + @until $(COMPOSE) exec -T ollama ollama list > /dev/null 2>&1; do \ + printf '.'; sleep 2; \ + done + @echo " ready." + @if $(COMPOSE) exec -T ollama ollama list 2>/dev/null | grep -q "^$(OLLAMA_MODEL)"; then \ + echo " Model $(OLLAMA_MODEL) already pulled."; \ + else \ + echo " Pulling $(OLLAMA_MODEL)..."; \ + $(COMPOSE) exec -T ollama ollama pull $(OLLAMA_MODEL); \ + fi @echo "" - @echo "✅ FireForm is ready!" - @echo " Frontend: http://localhost:5173" + @echo "FireForm is ready!" @echo " API: http://localhost:8000" @echo " API Docs: http://localhost:8000/docs" @echo "" @echo "Run 'make logs' to view live logs, 'make down' to stop." build: - docker compose build + @$(COMPOSE) build --progress=quiet up: - docker compose up -d + @$(COMPOSE) up -d down: - docker compose down + @$(COMPOSE) down --remove-orphans logs: - docker compose logs -f + @$(COMPOSE) logs -f logs-app: - docker compose logs -f app + @$(COMPOSE) logs -f app logs-ollama: - docker compose logs -f ollama - -logs-frontend: - docker compose logs -f frontend + @$(COMPOSE) logs -f ollama shell: - docker compose exec app /bin/bash - -# Start the FastAPI server inside the running container -run: - docker compose exec app uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload - + @$(COMPOSE) exec app /bin/sh pull-model: - docker compose exec ollama ollama pull $(OLLAMA_MODEL) + @$(COMPOSE) exec -T ollama ollama pull $(OLLAMA_MODEL) -# Fix — correct test directory (was src/test/ which doesn't exist) test: - docker compose exec app python3 -m pytest tests/ -v + @$(COMPOSE) exec -T app python3 -m pytest tests/ -v clean: - docker compose down -v + @$(COMPOSE) down + super-clean: - docker compose down -v - docker system prune + @echo "WARNING: this will delete all volumes (database, uploads, model weights)." + @$(COMPOSE) down -v + @docker system prune -f diff --git a/scripts/check-deps.sh b/scripts/check-deps.sh new file mode 100755 index 0000000..3c4484e --- /dev/null +++ b/scripts/check-deps.sh @@ -0,0 +1,51 @@ +#!/bin/sh +set -e + +PASS=0 +FAIL=1 +errors=0 + +check() { + label="$1" + shift + if "$@" > /dev/null 2>&1; then + echo " [ok] $label" + else + echo " [!!] $label" + errors=$((errors + 1)) + fi +} + +echo "" +echo "Checking dependencies..." +echo "========================" + +# Docker daemon running +check "Docker daemon is running" docker info + +# docker compose v2 (plugin form, not legacy docker-compose) +check "docker compose v2 available" docker compose version + +# Minimum Docker version: 24 (BuildKit cache mounts stable) +DOCKER_VERSION=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1) +if [ -n "$DOCKER_VERSION" ] && [ "$DOCKER_VERSION" -ge 24 ] 2>/dev/null; then + echo " [ok] Docker version >= 24 (found $(docker version --format '{{.Server.Version}}' 2>/dev/null))" +else + echo " [!!] Docker version >= 24 required (found $(docker version --format '{{.Server.Version}}' 2>/dev/null || echo 'unknown'))" + echo " BuildKit cache mounts in docker/dev/Dockerfile require Docker 24+." + errors=$((errors + 1)) +fi + +echo "" + +if [ "$errors" -gt 0 ]; then + echo "$errors check(s) failed. Fix the above before continuing." + echo "" + echo " Install Docker: https://docs.docker.com/get-docker/" + echo " Upgrade Docker Desktop: https://docs.docker.com/desktop/release-notes/" + echo "" + exit 1 +fi + +echo "All checks passed." +echo "" diff --git a/scripts/init-env.sh b/scripts/init-env.sh new file mode 100755 index 0000000..c2c0804 --- /dev/null +++ b/scripts/init-env.sh @@ -0,0 +1,35 @@ +#!/bin/sh +set -e + +ENV_EXAMPLE="docker/.env.example" +ENV_DEV="docker/.env.dev" + +echo "" +echo "Setting up environment..." +echo "=========================" + +if [ ! -f "$ENV_EXAMPLE" ]; then + echo "Error: $ENV_EXAMPLE not found. Are you running from the repo root?" + exit 1 +fi + +if [ -f "$ENV_DEV" ]; then + printf " %s already exists. Overwrite? [y/N] " "$ENV_DEV" + read -r answer + case "$answer" in + [yY]*) + cp "$ENV_EXAMPLE" "$ENV_DEV" + echo " Overwritten." + ;; + *) + echo " Kept existing $ENV_DEV." + ;; + esac +else + cp "$ENV_EXAMPLE" "$ENV_DEV" + echo " Created $ENV_DEV from $ENV_EXAMPLE." +fi + +echo "" +echo " Review docker/.env.dev and adjust values if needed before running 'make fireform'." +echo "" diff --git a/scripts/select-model.sh b/scripts/select-model.sh new file mode 100755 index 0000000..4b3c9d3 --- /dev/null +++ b/scripts/select-model.sh @@ -0,0 +1,107 @@ +#!/bin/sh +set -e + +ENV_DEV="docker/.env.dev" +COMPOSE="docker compose -f docker/dev/compose.yml --env-file $ENV_DEV" + +# model name | approx size +MODELS="qwen2.5:1.5b|~1GB qwen2.5:3b|~2GB qwen2.5:7b|~4GB llama3.2:3b|~2GB mistral:7b|~4GB" + +current_model="" +if [ -f "$ENV_DEV" ]; then + current_model=$(grep -E '^OLLAMA_MODEL=' "$ENV_DEV" | cut -d= -f2 | tr -d '[:space:]') +fi + +echo "" +echo "Select Ollama model" +echo "===================" +[ -n "$current_model" ] && echo " Current: $current_model" +echo "" + +i=1 +for entry in $MODELS; do + name=$(echo "$entry" | cut -d'|' -f1) + size=$(echo "$entry" | cut -d'|' -f2) + if [ "$name" = "${current_model}" ]; then + echo " $i) $name $size [current]" + else + echo " $i) $name $size" + fi + i=$((i + 1)) +done +echo " $i) Enter custom model name" +i=$((i + 1)) +echo " $i) Keep current" +echo "" +printf "Choice [1-$i]: " +read -r choice + +total=$(echo "$MODELS" | wc -w | tr -d '[:space:]') +keep_choice=$((total + 2)) +custom_choice=$((total + 1)) + +if [ "$choice" = "$keep_choice" ] || [ -z "$choice" ]; then + echo " Keeping current model: ${current_model:-qwen2.5:1.5b}" + echo "" + exit 0 +fi + +if [ "$choice" = "$custom_choice" ]; then + printf " Enter model name (e.g. phi3:mini): " + read -r selected + if [ -z "$selected" ]; then + echo " No model entered. Keeping current." + echo "" + exit 0 + fi + selected_size="unknown size" +else + # Validate numeric choice in range + if ! echo "$choice" | grep -qE '^[0-9]+$' || [ "$choice" -lt 1 ] || [ "$choice" -gt "$total" ]; then + echo " Invalid choice. Keeping current model." + echo "" + exit 0 + fi + i=1 + for entry in $MODELS; do + if [ "$i" = "$choice" ]; then + selected=$(echo "$entry" | cut -d'|' -f1) + selected_size=$(echo "$entry" | cut -d'|' -f2) + fi + i=$((i + 1)) + done +fi + +# Patch OLLAMA_MODEL in .env.dev +tmp=$(mktemp) +sed "s|^OLLAMA_MODEL=.*|OLLAMA_MODEL=$selected|" "$ENV_DEV" > "$tmp" +mv "$tmp" "$ENV_DEV" +echo "" +echo " OLLAMA_MODEL set to: $selected" + +# If container is running, check and optionally pull immediately. +# If not running, the caller (make init) handles the pull prompt. +if $COMPOSE ps ollama 2>/dev/null | grep -q "running"; then + if $COMPOSE exec -T ollama ollama list 2>/dev/null | grep -q "^$selected"; then + echo " Model already pulled. Nothing to download." + echo "" + exit 0 + fi + + echo "" + printf " Model not yet downloaded ($selected_size). Pull now? [y/N] " + read -r pull_answer + case "$pull_answer" in + [yY]*) + echo "" + $COMPOSE exec -T ollama ollama pull "$selected" + echo "" + echo " Model ready." + ;; + *) + echo " Skipped. Run 'make pull-model' when ready." + ;; + esac +fi + +echo "" From e2e066265130f51d93eec4e8491b9bf1b9f48257 Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Sat, 6 Jun 2026 03:44:31 +0530 Subject: [PATCH 10/15] remove unwanted html files under docs/ which is already migrated to fireform-core/fireform-website --- docs/dpg.html | 203 ------------------- docs/index.html | 176 ----------------- docs/privacy.html | 97 --------- docs/styles.css | 488 ---------------------------------------------- docs/terms.html | 152 --------------- 5 files changed, 1116 deletions(-) delete mode 100644 docs/dpg.html delete mode 100644 docs/index.html delete mode 100644 docs/privacy.html delete mode 100644 docs/styles.css delete mode 100644 docs/terms.html diff --git a/docs/dpg.html b/docs/dpg.html deleted file mode 100644 index 47371bc..0000000 --- a/docs/dpg.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - FireForm - Digital Public Good & SDG Relevance - - - - - - - -
-
-
-
- - - -
-
-
-
Digital Public Good
-

SDG Relevance

-

- FireForm is proud to be an open-source Digital Public Good, dedicated to advancing the United Nations' Sustainable Development Goals (SDGs). By drastically reducing the administrative burden on first responders, we enable emergency services to operate more efficiently, safely, and transparently. -

-
-
-
- -
-
-

Relevant Sustainable Development Goals

- -
- -
-
🏢
-

SDG 16: Peace, Justice and Strong Institutions

-

Target 16.6: Develop effective, accountable and transparent institutions at all levels.

-

How FireForm contributes: By unifying and digitizing the reporting structure for firefighters and other emergency responders, FireForm builds stronger, more accountable local institutions. Emergency services can seamlessly share critical incident data across county lines, sheriff departments, and EMS, ensuring transparency and highly effective public service administration without the overhead of repetitive paperwork.

-
- -
-
🏙️
-

SDG 11: Sustainable Cities and Communities

-

Target 11.b: By 2020, substantially increase the number of cities and human settlements adopting and implementing integrated policies and plans towards inclusion, resource efficiency, mitigation and adaptation to climate change, resilience to disasters...

-

How FireForm contributes: FireForm directly enhances a city's resilience to disasters by giving first responders hours of their time back. Instead of doing administrative work, firefighters can focus on training, disaster mitigation, and responding to emergencies, ultimately creating safer and more resilient communities.

-
- -
-
💡
-

SDG 9: Industry, Innovation and Infrastructure

-

Target 9.4: By 2030, upgrade infrastructure and retrofit industries to make them sustainable, with increased resource-use efficiency and greater adoption of clean and environmentally sound technologies and industrial processes...

-

How FireForm contributes: FireForm modernizes the outdated infrastructure of first responder reporting. By leveraging open-source AI locally, it serves as an innovative digital infrastructure that makes emergency management more resource-efficient and environmentally sound (eliminating physical paperwork and reducing digital redundancies).

-
- -
- -
-

Clear Ownership & Accountability

-
-

- In accordance with the Digital Public Goods Alliance requirements for Open Software, the ownership and accountability of the FireForm project and all its produced assets are clearly defined. -

-

- Accountable Entity: The intellectual property and copyright of the software code and content are owned by the original core creators: Juan Álvarez Sánchez, Manuel Carriedo Garrido, Vincent Harkins, Marc Vergés, and Jan Sans. -

-

- This ownership is publicly documented and verifiable across our project's assets: -

-
    -
  • Software License: Detailed in our public MIT License.
  • -
  • Source Repository: Explicitly stated in our GitHub README page.
  • -
  • Public Website: Documented here on our official project website.
  • -
-
-
- -
-

Platform Independence

-
-

- In accordance with the Digital Public Goods Alliance requirements for Open Software and Open AI Systems, FireForm guarantees platform independence. We do not rely on mandatory proprietary dependencies or closed components that would restrict our MIT License. -

-

- Core Dependencies: -

-
    -
  • Frontend: React, Electron, Node.js. (Declared in frontend/package.json)
  • -
  • Backend: Python, FastAPI, SQLite. (Declared in requirements.txt)
  • -
  • AI System (Optional): Ollama running open-weight models like Mistral. All inference runs locally. Note: The AI features are optional and not core to the main functionality of FireForm (which operates as a digital form and template manager). The AI extraction can be disabled in the application settings. Furthermore, this local Ollama dependency can be swapped with any other LLM service.
  • -
-

- Dependency Graph (SBOM): All components and their versions in our software supply chain are automatically tracked by our source repository. You can view our full dependency graph and SBOM directly on our GitHub repository. If any proprietary components are ever integrated, they will be strictly optional and replaceable with open alternatives without modifying the core solution. -

-
-
- -
-

Mechanism for Extracting Data

-
-

- Digital public goods must ensure that data and content can be extracted or imported in a non-proprietary format. FireForm embraces this requirement at the core of its architecture: -

-
    -
  • Standard Format (JSON): All data extracted by the LLM—whether non-PII or PII—is formatted and exported natively as a non-proprietary JSON object. This makes it instantly compatible with any modern software or data pipeline without vendor lock-in.
  • -
  • Open Storage (SQLite): Metadata, templates, and local configuration are stored using SQLite, an open, serverless database engine. The data is saved locally in a fireform.db file, which can be easily extracted, backed up, and read by hundreds of open-source tools.
  • -
  • API Access: Our local Python FastAPI backend exposes these JSON objects and database entries, allowing automated systems to securely extract the data.
  • -
-

- By standardizing on JSON and SQLite, FireForm guarantees that emergency departments retain full ownership and accessibility of their data at all times. -

-
-
- -
-

Privacy & Applicable Laws

-
-

- FireForm is designed from the ground up with a privacy-first, local-only architecture. Because emergency incident reports frequently contain sensitive Personally Identifiable Information (PII), we guarantee that no data ever leaves the user's device. -

-

- By eliminating cloud servers and third-party APIs, FireForm enables first responder organizations to easily comply with the world's most stringent data protection laws, including: -

-
    -
  • HIPAA (Health Insurance Portability and Accountability Act): Ensures EMS medical data remains entirely offline and secure.
  • -
  • CCPA (California Consumer Privacy Act): Ensures no consumer data is shared or sold.
  • -
  • GDPR (General Data Protection Regulation): Enforces strict data minimization and localizes data subjects' rights.
  • -
-

- For full details on our consent management procedures, data minimization practices, and how the solution was designed to comply with HIPAA, CCPA, and GDPR, please read our official Privacy Policy. -

-
-
- -
-

Do No Harm by Design

-
-

- FireForm is committed to anticipating and preventing harm by design, strictly adhering to the DPGA's framework: -

-
    -
  • 9A. Data Privacy & Security: Because our solution handles sensitive incident data (PII), we mitigate risk by strictly operating offline. Data is never exposed to public networks, avoiding data breaches. (See our Privacy Policy).
  • -
  • 9B. Inappropriate & Illegal Content: FireForm is an enterprise productivity tool for emergency responders, not a social platform. It does not host public user-generated content, completely mitigating the risk of distributing illegal or misleading public content.
  • -
  • 9C. Protection from Harassment: There is no public user-to-user interaction within the app. For our open-source contributor community, we strictly enforce our Code of Conduct to protect against harassment and abuse.
  • -
-
-
- -
-

Standards & Best Practices

-
-

- FireForm strictly aligns with globally recognized standards and best practices curated by the DPGA to ensure high-quality, interoperable, and sustainable software: -

-

Adhered Standards:

-
    -
  • JSON & UTF-8 (Data Interchange): All incident data extracted by our AI system is structured purely as JSON with UTF-8 encoding.
  • -
  • OpenAPI & REST (Data Exchange): Our Python backend is built with FastAPI, which automatically generates interactive OpenAPI specifications and follows RESTful architectural patterns.
  • -
-

Adhered Best Practices:

-
    -
  • Community: We maintain a welcoming environment through our Code of Conduct and clear Contribution Guidelines.
  • -
  • Lifecycle Management: All codebase modifications use Git for Change Management. We track progress with Tagged Releases and strictly adhere to Semantic Versioning.
  • -
  • Interoperability & Architecture: We prioritize Open Standards and File Formats, modularized Programmatic APIs, and strict Dependency Management.
  • -
-
-
- -
-

Public Communications & Mission

-

- Our mission is to build software that protects the people who protect us. FireForm was originally conceived and won 1st place at the Reboot the Earth hackathon, hosted by the UN and UC Santa Cruz, specifically targeting solutions for a better, more sustainable future. -

-

- We believe that modern technology like Local LLMs should be leveraged as public goods, accessible to any department regardless of budget, to help them better serve their communities. -

-
-
-
- -
-
-

FireForm is a Digital Public Good. Licensed under MIT.

-
-
- - diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index d01e95e..0000000 --- a/docs/index.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - FireForm - Report Once, File Everywhere - - - - - - - -
-
-
-
- - - -
-
-
-
Reboot the Earth Hackathon Winner
-

Report Once,
File Everywhere.

-

- An open-source, AI-powered system built to solve administrative overhead for first responders. Save hundreds of hours by eliminating redundant paperwork. -

- - -
- -
-

Digital Public Good

-
-
-

FireForm is proudly recognized as a Digital Public Good (DPG). We are committed to advancing the United Nations' Sustainable Development Goals (SDGs).

-
- Learn about our SDG Relevance → -
-
- -
-

Current Features

-
-
-
📝
-

Smart PDF Conversion

-

Automatically turn any non-fillable PDF into a fillable, standardized template.

-
-
-
💾
-

Local Template Database

-

Store your templates in a local DB. Recover past templates and quickly generate new forms.

-
-
-
🎙️
-

Voice or Text Input

-

Provide input via text or voice transcription. A single input drives everything.

-
-
-
🤖
-

Automated LLM Filling

-

Our local LLM extracts relevant information and automatically populates the documents.

-
-
-
- -
-

Future Roadmap

-
-
-
-
-
🌍
-

External Data APIs

-

Connect to APIs to automatically fetch climate, location, and other external data.

-
-
-
-
-
-
📊
-

Insightful Dashboard

-

Visualize your reports with comprehensive graphics and statistics.

-
-
-
-
-
-
🧠
-

ML Field Detection

-

Machine learning models for advanced, automatic form field detection.

-
-
-
-
-
-
📑
-

Advanced PDF Features

-

More powerful PDF manipulation, merging, and management tools.

-
-
-
-
- -
-

About the Project

-
-
-

FireForm was born at a Silicon Valley hackathon organized by the United Nations, University of California, and CalFire.

-

After winning first place, the project was awarded a 6-month mentorship by Salesforce. We are also proud to be part of Google Summer of Code, welcoming new collaborators to scale our impact.

-
- -
-
-
-
- - - - diff --git a/docs/privacy.html b/docs/privacy.html deleted file mode 100644 index 461a370..0000000 --- a/docs/privacy.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - FireForm - Privacy Policy - - - - - - - -
-
-
-
- - - -
-
-
-

Privacy Policy

-

- FireForm is built on the principle of privacy by design. Our architecture guarantees that your data, including Personally Identifiable Information (PII), never leaves your device. -

-
-
-
- -
-
- -
-

1. Local-First Architecture

-

- FireForm is a completely offline, local-first application. All processing, including voice transcription and AI-based information extraction, is performed on your local hardware using local Large Language Models (LLMs) via Ollama. -

-

- We do not use cloud APIs, we do not have central servers that collect your data, and we do not track telemetry. Therefore, we do not collect, process, or share any PII with third parties. -

-
- -
-

2. Data Collection and Usage

-

- The only data collected by FireForm is the information you explicitly input (voice memos, text, and PDF templates) to generate incident reports. This data is stored locally on your device in a local SQLite database and standard JSON files. You maintain full ownership and control over this data, and can delete it or export it at any time. -

-

- Because all data remains on your device, consent management and data subject requests are fully within the control of the user or the deploying organization (e.g., the fire department). -

-
- -
-

3. Compliance with Applicable Laws

-

- By guaranteeing that data never leaves the host machine, FireForm enables deploying organizations to easily comply with the strictest data protection and privacy laws worldwide. We specifically support compliance with: -

-
    -
  • Health Insurance Portability and Accountability Act (HIPAA): By ensuring EMS and healthcare-related incident data remains entirely offline and secure on the local device.
  • -
  • California Consumer Privacy Act (CCPA) / CPRA: No consumer data is shared or sold. The local architecture defaults to maximum privacy.
  • -
  • General Data Protection Regulation (GDPR): Complete data minimization and localized processing ensures that no unauthorized cross-border data transfers occur, and data subjects' rights are easily managed locally.
  • -
-
- -
-

4. Contact Us

-

- If you have any questions about this Privacy Policy or how FireForm handles data, please contact the core maintainers via our GitHub Repository. -

-
- -
-
- - - - diff --git a/docs/styles.css b/docs/styles.css deleted file mode 100644 index 4f3dae7..0000000 --- a/docs/styles.css +++ /dev/null @@ -1,488 +0,0 @@ -:root { - --bg-dark: #0f0a0a; - --bg-card: rgba(20, 10, 10, 0.6); - --bg-card-hover: rgba(40, 15, 15, 0.8); - --text-primary: #ffffff; - --text-secondary: #ffb8b8; - --accent-red: #ff3b30; - --accent-orange: #ff9500; - --border-color: rgba(255, 59, 48, 0.2); - --glow-color: rgba(255, 59, 48, 0.4); - --font-main: 'Inter', system-ui, -apple-system, sans-serif; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - background-color: var(--bg-dark); - color: var(--text-primary); - font-family: var(--font-main); - line-height: 1.6; - overflow-x: hidden; - min-height: 100vh; - display: flex; - flex-direction: column; -} - -.container { - width: 100%; - max-width: 1200px; - margin: 0 auto; - padding: 0 2rem; -} - -/* Background Effects */ -.background-effects { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - z-index: -1; - overflow: hidden; - pointer-events: none; -} - -.glow { - position: absolute; - width: 60vw; - height: 60vw; - border-radius: 50%; - filter: blur(100px); - opacity: 0.15; - animation: pulse 10s ease-in-out infinite alternate; -} - -.red-glow { - top: -20%; - right: -10%; - background: var(--accent-red); -} - -.orange-glow { - bottom: -20%; - left: -10%; - background: var(--accent-orange); - animation-delay: -5s; -} - -@keyframes pulse { - 0% { transform: scale(1) translate(0, 0); opacity: 0.15; } - 100% { transform: scale(1.1) translate(-5%, 5%); opacity: 0.25; } -} - -/* Navigation */ -.navbar { - padding: 1.5rem 0; - border-bottom: 1px solid var(--border-color); - background: rgba(15, 10, 10, 0.8); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - position: sticky; - top: 0; - z-index: 100; -} - -.navbar .container { - display: flex; - justify-content: space-between; - align-items: center; -} - -.logo { - font-size: 1.5rem; - font-weight: 800; - letter-spacing: -0.5px; - display: flex; - align-items: center; - gap: 0.5rem; -} - -.fire-icon { - font-size: 1.8rem; - filter: drop-shadow(0 0 8px var(--accent-orange)); -} - -.github-link { - color: var(--text-primary); - text-decoration: none; - font-weight: 600; - padding: 0.5rem 1rem; - border: 1px solid var(--border-color); - border-radius: 8px; - transition: all 0.3s ease; -} - -.github-link:hover { - background: var(--border-color); - box-shadow: 0 0 15px var(--glow-color); -} - -/* Hero Section */ -.hero { - flex: 1; - display: flex; - align-items: center; - padding: 6rem 0; -} - -.hero-content { - text-align: center; - max-width: 800px; - margin: 0 auto; -} - -.badge { - display: inline-block; - padding: 0.4rem 1rem; - background: rgba(255, 149, 0, 0.1); - border: 1px solid var(--accent-orange); - color: var(--accent-orange); - border-radius: 20px; - font-size: 0.9rem; - font-weight: 600; - margin-bottom: 1.5rem; - box-shadow: 0 0 15px rgba(255, 149, 0, 0.2); -} - -.hero-title { - font-size: 4rem; - font-weight: 800; - line-height: 1.1; - margin-bottom: 1.5rem; - letter-spacing: -1px; -} - -.gradient-text { - background: linear-gradient(135deg, var(--accent-red), var(--accent-orange)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.hero-subtitle { - font-size: 1.25rem; - color: var(--text-secondary); - margin-bottom: 3rem; - font-weight: 300; -} - -/* Download Section */ -.download-section { - background: var(--bg-card); - border: 1px solid var(--border-color); - border-radius: 24px; - padding: 3rem 2rem; - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - box-shadow: 0 20px 40px rgba(0,0,0,0.4), inset 0 0 0 1px rgba(255,255,255,0.05); -} - -.download-heading { - font-size: 1.5rem; - margin-bottom: 2rem; - font-weight: 600; -} - -.button-group { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 1.5rem; - margin-bottom: 2rem; -} - -.btn { - display: flex; - align-items: center; - gap: 1rem; - padding: 1.25rem; - border-radius: 16px; - text-decoration: none; - transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); - background: rgba(25, 15, 15, 0.8); - border: 1px solid var(--border-color); - position: relative; - overflow: hidden; -} - -.btn::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient(135deg, rgba(255,59,48,0.1), rgba(255,149,0,0.1)); - opacity: 0; - transition: opacity 0.3s ease; -} - -.btn:hover { - transform: translateY(-5px) scale(1.02); - border-color: var(--accent-orange); - box-shadow: 0 15px 30px rgba(255, 59, 48, 0.2); -} - -.btn:hover::before { - opacity: 1; -} - -.os-icon { - font-size: 2rem; - z-index: 1; -} - -.btn-text { - display: flex; - flex-direction: column; - align-items: flex-start; - z-index: 1; -} - -.os-name { - color: var(--text-primary); - font-weight: 600; - font-size: 1.1rem; -} - -.file-type { - color: var(--text-secondary); - font-size: 0.85rem; -} - -.view-all-link { - color: var(--accent-orange); - text-decoration: none; - font-size: 0.95rem; - font-weight: 600; - transition: color 0.2s ease; -} - -.view-all-link:hover { - color: var(--text-primary); -} - -/* Features Sections */ -.features-section { - margin-top: 5rem; -} - -.section-heading { - text-align: center; - font-size: 2.2rem; - margin-bottom: 2.5rem; - color: var(--text-primary); - font-weight: 800; -} - -.section-heading::after { - content: ''; - display: block; - width: 60px; - height: 4px; - background: linear-gradient(135deg, var(--accent-red), var(--accent-orange)); - margin: 1rem auto 0; - border-radius: 2px; -} - -.features-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 2rem; -} - -/* Timeline Styles */ -.timeline { - position: relative; - max-width: 800px; - margin: 0 auto; - padding: 2rem 0; -} -.timeline::before { - content: ''; - position: absolute; - left: 50%; - top: 0; - bottom: 0; - width: 2px; - background: linear-gradient(to bottom, var(--accent-red), var(--accent-orange)); - transform: translateX(-50%); -} -.timeline-item { - position: relative; - width: 50%; - padding: 1.5rem 3rem; -} -.timeline-item:nth-child(odd) { - left: 0; - text-align: right; -} -.timeline-item:nth-child(even) { - left: 50%; - text-align: left; -} -.timeline-dot { - position: absolute; - top: 50%; - width: 20px; - height: 20px; - background: var(--bg-dark); - border: 4px solid var(--accent-orange); - border-radius: 50%; - transform: translateY(-50%); - box-shadow: 0 0 10px var(--accent-orange); - z-index: 2; -} -.timeline-item:nth-child(odd) .timeline-dot { - right: -10px; -} -.timeline-item:nth-child(even) .timeline-dot { - left: -10px; -} -.timeline-content { - background: rgba(15, 10, 10, 0.4); - border: 1px dashed rgba(255, 149, 0, 0.3); - padding: 1.5rem; - border-radius: 16px; - transition: all 0.3s ease; -} -.timeline-item:hover .timeline-content { - background: rgba(255, 149, 0, 0.05); - border-style: solid; - border-color: var(--accent-orange); - transform: translateY(-5px); -} - -.feature-card { - background: rgba(15, 10, 10, 0.4); - border: 1px solid rgba(255, 255, 255, 0.05); - padding: 2rem; - border-radius: 16px; - text-align: left; - transition: transform 0.3s ease, border-color 0.3s ease; -} - -.feature-card:hover { - transform: translateY(-5px); - border-color: var(--border-color); -} - -.feature-icon { - font-size: 2.5rem; - margin-bottom: 1rem; -} - -.feature-card h3 { - margin-bottom: 0.5rem; - font-size: 1.2rem; -} - -.feature-card p { - color: var(--text-secondary); - font-size: 0.95rem; -} - -/* About Section Styles */ -.about-section { - margin-top: 5rem; - padding-bottom: 3rem; -} -.about-card { - background: linear-gradient(135deg, rgba(20, 10, 10, 0.8), rgba(40, 15, 15, 0.6)); - border: 1px solid var(--border-color); - border-radius: 24px; - padding: 3rem; - display: flex; - flex-direction: column; - gap: 2rem; - box-shadow: 0 10px 30px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(255,59,48,0.1); -} -.about-content { - font-size: 1.1rem; - color: var(--text-secondary); -} -.about-content p { - margin-bottom: 1rem; -} -.about-content strong { - color: var(--text-primary); -} -.contact-links { - margin-top: 1rem; - padding-top: 2rem; - border-top: 1px solid rgba(255, 255, 255, 0.05); -} -.contact-links h3 { - margin-bottom: 1rem; - font-size: 1.2rem; -} -.social-badges { - display: flex; - gap: 1rem; - flex-wrap: wrap; -} -.badge-link { - display: inline-flex; - align-items: center; - padding: 0.5rem 1rem; - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); - color: var(--text-primary); - text-decoration: none; - border-radius: 8px; - font-size: 0.95rem; - transition: all 0.2s ease; -} -.badge-link:hover { - background: rgba(255, 59, 48, 0.1); - border-color: var(--accent-red); - transform: translateY(-2px); -} - -/* Footer */ -footer { - text-align: center; - padding: 2rem 0; - border-top: 1px solid rgba(255, 255, 255, 0.05); - color: rgba(255, 255, 255, 0.4); - font-size: 0.9rem; -} - -/* Responsive */ -@media (max-width: 768px) { - .hero-title { - font-size: 2.8rem; - } - - .hero { - padding: 3rem 0; - } - - .download-section { - padding: 2rem 1.5rem; - } - - .timeline::before { - left: 20px; - } - .timeline-item { - width: 100%; - padding-left: 50px; - padding-right: 0; - } - .timeline-item:nth-child(odd), .timeline-item:nth-child(even) { - left: 0; - text-align: left; - } - .timeline-item:nth-child(odd) .timeline-dot, .timeline-item:nth-child(even) .timeline-dot { - left: 10px; - right: auto; - } - .about-card { - padding: 2rem 1.5rem; - } -} diff --git a/docs/terms.html b/docs/terms.html deleted file mode 100644 index 557c127..0000000 --- a/docs/terms.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - FireForm - Terms of Service - - - - - - - -
-
-
-
- - - -
-
-
-

Terms of Service

-

- By downloading, installing, or using FireForm you agree to the following terms. Please read them carefully. If you do not agree, do not use the software. -

-

- Last updated: May 2026 -

-
-
-
- -
-
- -
-

1. Acceptance of Terms

-

- These Terms of Service ("Terms") govern your access to and use of the FireForm software application, source code, documentation, and related materials (collectively, the "Software"). By using the Software you confirm that you are at least 18 years old (or the age of majority in your jurisdiction) and have the legal authority to accept these Terms on behalf of yourself or the organization you represent. -

-
- -
-

2. Open-Source License

-

- FireForm is released under the MIT License. You are free to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software subject to the conditions stated in the LICENSE file included with every distribution and available in our GitHub repository. -

-

- Nothing in these Terms restricts rights granted to you by the MIT License; they are intended solely to clarify responsibilities and limitations not addressed by that license. -

-
- -
-

3. Permitted Use

-

You may use FireForm for any lawful purpose, including:

-
    -
  • Internal use by emergency services, public safety agencies, and fire departments.
  • -
  • Research, academic, and non-commercial projects.
  • -
  • Commercial deployments, provided you comply with the MIT License attribution requirements.
  • -
  • Developing derivative works or integrations, subject to the MIT License terms.
  • -
-
- -
-

4. Prohibited Use

-

You must not use FireForm to:

-
    -
  • Violate any applicable law or regulation, including but not limited to laws governing data protection, privacy, and public safety reporting.
  • -
  • Intentionally generate false, fraudulent, or misleading incident reports.
  • -
  • Infringe the intellectual property rights of any third party.
  • -
  • Introduce malware, exploits, or other harmful code into the Software or its dependencies.
  • -
  • Use the Software in a manner that endangers the health or safety of any person.
  • -
-
- -
-

5. No Warranty

-

- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THE FIREFORM CONTRIBUTORS DO NOT WARRANT THAT THE SOFTWARE WILL BE ERROR-FREE, UNINTERRUPTED, SECURE, OR THAT ANY DEFECTS WILL BE CORRECTED. USE OF THE SOFTWARE FOR CRITICAL PUBLIC-SAFETY OPERATIONS IS ENTIRELY AT YOUR OWN RISK AND YOUR ORGANIZATION'S DISCRETION. -

-
- -
-

6. Limitation of Liability

-

- TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL THE FIREFORM CONTRIBUTORS, MAINTAINERS, SPONSORS, OR AFFILIATED ORGANIZATIONS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES — INCLUDING LOSS OF DATA, REVENUE, GOODWILL, OR LIFE — ARISING OUT OF OR IN CONNECTION WITH THE USE OR INABILITY TO USE THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. IN JURISDICTIONS THAT DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR LIMITATIONS OF LIABILITY, OUR LIABILITY IS LIMITED TO THE FULLEST EXTENT PERMITTED BY LAW. -

-
- -
-

7. User-Generated Content and Data

-

- FireForm processes data exclusively on your local device. You retain full ownership of all incident reports, templates, voice recordings, and any other data you create or import. We do not access, store, or transmit your data. -

-

- You are solely responsible for the accuracy and legality of the data you enter into FireForm and for complying with any reporting obligations imposed by your jurisdiction or regulatory authority. -

-
- -
-

8. Third-Party Components

-

- FireForm integrates with third-party software, including Ollama for local LLM inference and Whisper for voice transcription. Your use of those components is governed by their respective licenses and terms. We make no representations or warranties regarding third-party software and are not responsible for any issues arising from their use. -

-
- -
-

9. Contributions

-

- Contributions to FireForm (pull requests, issues, documentation, etc.) are subject to our Contributing Guidelines and Code of Conduct. By submitting a contribution you confirm that you have the right to license it under the MIT License and that you grant the FireForm project a perpetual, worldwide, royalty-free license to use, reproduce, and distribute your contribution. -

-
- -
-

10. Changes to These Terms

-

- We may update these Terms from time to time. When we do, we will revise the "Last updated" date at the top of this page and, for material changes, post a notice in our GitHub repository. Continued use of FireForm after changes are posted constitutes your acceptance of the revised Terms. -

-
- -
-

11. Contact

-

- If you have questions about these Terms, please reach out to the core maintainers via our GitHub Repository. We welcome feedback from deploying organizations and community members. -

-
- -
-
- - - - From a318ff976ab668e8cc0b6792c35fc4e2e93aafb5 Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Sun, 7 Jun 2026 20:33:01 +0530 Subject: [PATCH 11/15] added entrypoint, readme and updated docker file and compose --- .gitignore | 3 ++- docker/.env.example | 27 +++++++++++++++++++++++++++ docker/README.md | 31 +++++++++++++++++++++++++++++++ docker/dev/compose.yml | 1 + docker/entrypoint.sh | 10 ++++++++++ docker/prod/Dockerfile | 2 ++ 6 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 docker/.env.example create mode 100644 docker/README.md create mode 100755 docker/entrypoint.sh diff --git a/.gitignore b/.gitignore index 268a0ff..1515bf3 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ frontend/release/ # Local Claude Code instructions CLAUDE.md -*temp/ \ No newline at end of file +*temp/ +*.env* \ No newline at end of file diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 0000000..c4321ac --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,27 @@ +# Copy this file to .env.dev or .env.prod and fill in values. +# Never commit .env.dev or .env.prod — they are gitignored. + +# --- App ------------------------------------------------------------------ +APP_PORT=8000 + +# --- Ollama --------------------------------------------------------------- +# Ollama runs in Docker with port mapped to host. App runs on host. +# Override to point at an external Ollama instance (e.g. a GPU server). +OLLAMA_HOST=http://localhost:11434 +OLLAMA_MODEL=qwen2.5:1.5b +OLLAMA_TIMEOUT=300 + +# --- Whisper -------------------------------------------------------------- +# Whisper runs in Docker with port mapped to host. App runs on host. +# Override to point at an external Whisper endpoint. +WHISPER_HOST=http://localhost:9000 +WHISPER_MODEL=small.en + +# --- Gunicorn (prod only) ------------------------------------------------- +GUNICORN_WORKERS=2 + +# --- CORS ----------------------------------------------------------------- +# Comma-separated list of allowed frontend origins. +# Dev: http://localhost:5173,http://127.0.0.1:5173 +# Prod: https://your-domain.com +FRONTEND_ORIGINS=http://localhost:5173,http://127.0.0.1:5173 diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..e238c64 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,31 @@ +# Docker + +``` +docker/ + dev/ + Dockerfile # uvicorn --reload, source-mounted for hot reload + compose.yml + prod/ + Dockerfile # multi-stage build, gunicorn, no source mount + compose.yml + .env.example # template — copy to .env.dev or .env.prod + .env.dev # gitignored, dev values + .env.prod # gitignored, prod values + entrypoint.sh # runs DB migrations then exec's the server command + README.md +``` + +## Env vars + +See `.env.example` for the full list with descriptions. + +## Volumes + +`docker compose down` never removes volumes. Use `docker compose down -v` only to intentionally wipe all data. + +| Volume | Path in container | Purpose | +| ------------------ | ---------------------- | ----------------------------------- | +| `fireform_db` | `/data/db/fireform.db` | SQLite database | +| `fireform_uploads` | `/data/uploads` | Uploaded templates + generated PDFs | +| `ollama_data` | `/root/.ollama` | Ollama model weights | +| `whisper_models` | `/data/whisper` | Whisper model cache | diff --git a/docker/dev/compose.yml b/docker/dev/compose.yml index 5b7c2f0..cdb94cc 100644 --- a/docker/dev/compose.yml +++ b/docker/dev/compose.yml @@ -45,6 +45,7 @@ services: condition: service_healthy whisper: condition: service_started + entrypoint: ["sh", "docker/entrypoint.sh"] command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] volumes: - ../..:/app diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..84a8142 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -e + +# Ensure data directories exist (volumes may be empty on first run) +mkdir -p /data/db /data/uploads + +# Run DB migrations / init before starting the server +python3 -m app.db.init_db + +exec "$@" diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 8ad8dea..f97d6dd 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -22,6 +22,7 @@ COPY --from=builder /install /usr/local # Copy only app code, not data/ temp/ tests/ docs/ etc. COPY app/ ./app/ COPY requirements.txt . +COPY docker/entrypoint.sh /entrypoint.sh ENV PYTHONPATH=/app @@ -30,6 +31,7 @@ RUN mkdir -p /data/db /data/uploads && chmod +x /entrypoint.sh EXPOSE 8000 +ENTRYPOINT ["/entrypoint.sh"] CMD ["gunicorn", "app.main:app", \ "--worker-class", "uvicorn.workers.UvicornWorker", \ "--bind", "0.0.0.0:8000", \ From eb20a9570b70559ad6dd92f665378ab53a8b4ad2 Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Sun, 7 Jun 2026 20:44:30 +0530 Subject: [PATCH 12/15] fixed tests, replaced api -> app.api following our current code structure --- tests/test_api.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 33b2c62..e98d16f 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -125,7 +125,7 @@ def test_upload_pdf(self, client, pdf_upload, tmp_path, monkeypatch): # Point the upload directory inside tmp_path (which is inside the project # for the path-safety check — we monkeypatch the check). monkeypatch.setattr( - "api.routes.templates.PROJECT_ROOT", + "app.api.routes.templates.PROJECT_ROOT", tmp_path, ) resp = client.post( @@ -234,7 +234,7 @@ def fake_post(url, params=None, files=None, timeout=None): captured["files"] = files return fake_response - monkeypatch.setattr("api.routes.forms.requests.post", fake_post) + monkeypatch.setattr("app.api.routes.forms.requests.post", fake_post) audio = ("audio", ("recording.wav", io.BytesIO(b"RIFFfake"), "audio/wav")) resp = client.post("/forms/transcribe", files=[audio]) @@ -252,7 +252,7 @@ def test_list_models(self, client, monkeypatch): fake_response = MagicMock() fake_response.json.return_value = {"models": [{"name": "qwen2.5:3b"}, {"name": "mistral:latest"}]} fake_response.raise_for_status.return_value = None - monkeypatch.setattr("api.routes.forms.requests.get", lambda *a, **k: fake_response) + monkeypatch.setattr("app.api.routes.forms.requests.get", lambda *a, **k: fake_response) monkeypatch.setenv("OLLAMA_MODEL", "qwen2.5:1.5b") resp = client.get("/forms/models") @@ -269,7 +269,7 @@ def test_list_models_ollama_down(self, client, monkeypatch): def boom(*a, **k): raise requests.exceptions.ConnectionError("down") - monkeypatch.setattr("api.routes.forms.requests.get", boom) + monkeypatch.setattr("app.api.routes.forms.requests.get", boom) monkeypatch.setenv("OLLAMA_MODEL", "qwen2.5:1.5b") resp = client.get("/forms/models") @@ -296,7 +296,7 @@ def test_transcribe_service_unavailable(self, client, monkeypatch): def fake_post(*args, **kwargs): raise requests.exceptions.ConnectionError("no service") - monkeypatch.setattr("api.routes.forms.requests.post", fake_post) + monkeypatch.setattr("app.api.routes.forms.requests.post", fake_post) audio = ("audio", ("recording.wav", io.BytesIO(b"data"), "audio/wav")) resp = client.post("/forms/transcribe", files=[audio]) @@ -315,7 +315,7 @@ class TestE2EPipeline: def test_full_flow(self, client, mock_controller, pdf_upload, tmp_path, monkeypatch, db): # -- Step 1: Upload a PDF -- - monkeypatch.setattr("api.routes.templates.PROJECT_ROOT", tmp_path) + monkeypatch.setattr("app.api.routes.templates.PROJECT_ROOT", tmp_path) upload_resp = client.post( "/templates/upload", files=[pdf_upload], From 0529cfd90f7abc39b6a9006674091375af1b13cf Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Thu, 11 Jun 2026 23:43:17 +0530 Subject: [PATCH 13/15] added setup docs, updated contributing.md --- CONTRIBUTING.md | 23 +++--------- docs/SETUP.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 docs/SETUP.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 154daf8..7d297fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,25 +39,12 @@ If you have a great idea for FireForm, we'd love to hear it! Please open an issu 4. Make sure your code lints. 5. Issue that pull request! -## 🛠️ Local Development Setup - -FireForm uses Docker and Docker Compose for the backend to ensure a consistent environment. - -### Prerequisites +**Issues are not formally assigned.** You are free to pick any open issue, work on it, and raise a PR directly. If multiple PRs address the same issue, the first one that actually fixes it generally gets preference. To avoid duplicating someone else's work, coordinate with other contributors on our [Discord](https://discord.gg/nBv5b6kF68) before starting. -- [Docker](https://docs.docker.com/get-docker/) -- [Docker Compose](https://docs.docker.com/compose/install/) -- `make` (optional, but recommended) -- [Node.js](https://nodejs.org/) 20+ (only needed for the desktop app) +## 💬 Community -### Desktop App Development +Join our Discord server to ask questions, discuss issues, and coordinate work with other contributors: https://discord.gg/nBv5b6kF68 -The frontend is a vanilla HTML/CSS/JS app wrapped in Electron. To run it locally: - -```bash -cd frontend -npm install # one-time setup -npm start # launches the Electron desktop window -``` +## 🛠️ Local Development Setup -The backend (API + Ollama) must be running separately via Docker — see `make fireform`. +See the [Setup Guide](docs/SETUP.md) for the full walkthrough: prerequisites, running the backend with Docker, testing endpoints via Swagger UI, day-to-day commands, and troubleshooting. diff --git a/docs/SETUP.md b/docs/SETUP.md new file mode 100644 index 0000000..216bc24 --- /dev/null +++ b/docs/SETUP.md @@ -0,0 +1,98 @@ +# Setup Guide + +This guide gets the FireForm backend running locally with Docker. It assumes you are comfortable with git and a terminal, but new to this project. + +## Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) **24 or newer**, with the Docker daemon running +- Docker Compose v2 (bundled with Docker Desktop; verify with `docker compose version`) +- `make` +- ~3 GB of free disk space (Docker images + LLM model weights) + +## Setup + +### 1. Clone the repository + +```bash +git clone https://github.com/fireform-core/FireForm.git +cd FireForm +``` + +### 2. Run first-time setup + +```bash +make init +``` + +This will: + +1. Check that Docker meets the requirements above +2. Create `docker/.env.dev` from `docker/.env.example` (gitignored; defaults work out of the box) +3. Prompt you to pick an Ollama model (the default, `qwen2.5:1.5b`, is the smallest and fine for development) +4. Offer to build and start everything answer `y`, or run `make fireform` later + +### 3. Build and start (if you skipped it in step 2) + +```bash +make fireform +``` + +This builds the Docker images, starts the containers, waits for Ollama, and pulls the LLM model. The first run takes several minutes (image build + model download); later runs are fast. + +When it finishes you'll see: + +``` +FireForm is ready! + API: http://localhost:8000 + API Docs: http://localhost:8000/docs +``` + +### 4. Verify it works + +Open **http://localhost:8000/docs** in your browser. This is the interactive Swagger UI you can explore and test every API endpoint directly from there (expand an endpoint, click _Try it out_, then _Execute_). + +## Day-to-day commands + +| Command | What it does | +| ------------ | ------------------------------------------------------------------------ | +| `make up` | Start containers | +| `make down` | Stop containers (data is preserved) | +| `make logs` | Stream all container logs (`make logs-app` / `make logs-ollama` for one) | +| `make shell` | Open a shell inside the app container | +| `make test` | Run the test suite | +| `make help` | List all commands | + +The dev container mounts the source code, so code changes reload automatically — no rebuild needed. Rebuild (`make build`) only when dependencies in `requirements.txt` or the Dockerfile change. + +## Frontend (optional) + +The desktop/web frontend lives in a separate repository: + +```bash +git clone https://github.com/fireform-core/fireform-frontend.git +``` + +Follow the README in that repository to run it. The backend from this guide must be running for the frontend to work. + +## Troubleshooting + +**`make init` fails dependency checks** +Docker isn't running or is too old. Start Docker Desktop (or the Docker daemon) and confirm `docker version` reports 24+. + +**Port 8000 already in use** +Another process is bound to the port. Either stop it, or change `APP_PORT` in `docker/.env.dev` and run `make down && make up`. + +**Model pull is slow or times out** +The first `make fireform` downloads the LLM weights (~1 GB for the default model). On a slow connection just wait, or re-run `make pull-model` it resumes safely. + +**Containers start but the API doesn't respond** +Check `make logs-app` for the actual error. The entrypoint runs database migrations on startup, so the API takes a few seconds after the container starts. + +**Want a clean slate** +`make super-clean` stops everything and **deletes all volumes** database, uploads, and downloaded model weights. Only use it when you intend to wipe all local data. + +## Where to go next + +- **Join our [Discord](https://discord.gg/nBv5b6kF68)** — ask questions and coordinate with other contributors +- [CONTRIBUTING.md](../CONTRIBUTING.md) - how to contribute +- [docker/README.md](../docker/README.md) - Docker layout, env vars, and volumes in detail From c131ee14d479a806c8037d9c8ab5e20d0cb5d795 Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Fri, 12 Jun 2026 03:13:11 +0530 Subject: [PATCH 14/15] added project structure --- CONTRIBUTING.md | 2 + docs/{SETUP.md => 1. SETUP.md} | 1 + docs/2. PROJECT_STRUCTURE.md | 87 ++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) rename docs/{SETUP.md => 1. SETUP.md} (97%) create mode 100644 docs/2. PROJECT_STRUCTURE.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d297fb..6cf0be7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,3 +48,5 @@ Join our Discord server to ask questions, discuss issues, and coordinate work wi ## 🛠️ Local Development Setup See the [Setup Guide](docs/SETUP.md) for the full walkthrough: prerequisites, running the backend with Docker, testing endpoints via Swagger UI, day-to-day commands, and troubleshooting. + +Before writing code, read the [Project Structure](docs/PROJECT_STRUCTURE.md) guide — it explains how the codebase is organized and where new code should go. diff --git a/docs/SETUP.md b/docs/1. SETUP.md similarity index 97% rename from docs/SETUP.md rename to docs/1. SETUP.md index 216bc24..5b4de34 100644 --- a/docs/SETUP.md +++ b/docs/1. SETUP.md @@ -95,4 +95,5 @@ Check `make logs-app` for the actual error. The entrypoint runs database migrati - **Join our [Discord](https://discord.gg/nBv5b6kF68)** — ask questions and coordinate with other contributors - [CONTRIBUTING.md](../CONTRIBUTING.md) - how to contribute +- [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md) - how the codebase is organized and where new code goes - [docker/README.md](../docker/README.md) - Docker layout, env vars, and volumes in detail diff --git a/docs/2. PROJECT_STRUCTURE.md b/docs/2. PROJECT_STRUCTURE.md new file mode 100644 index 0000000..cee9c40 --- /dev/null +++ b/docs/2. PROJECT_STRUCTURE.md @@ -0,0 +1,87 @@ +# Project Structure + +This document explains how the FireForm repository is organized and, when you add new code, where it should go. + +## Top-level layout + +``` +FireForm/ +├── app/ # Backend source code (FastAPI application) +├── tests/ # Test suite (pytest) +├── docker/ # Dockerfiles, compose files, env templates +├── scripts/ # Shell scripts +├── docs/ # Project documentation +├── examples/ # Standalone demo scripts +├── data/ # Local runtime data (gitignored contents) +├── Makefile # Entry point for all dev commands (`make help`) +└── requirements.txt # Python dependencies +``` + +| You want to… | Go to | +| -------------------------------- | ------------------------------------------------------- | +| Change backend behavior | `app/` | +| Add or fix tests | `tests/` | +| Change container setup, env vars | `docker/` (see [docker/README.md](../docker/README.md)) | +| Change setup/bootstrap scripts | `scripts/` | +| Add documentation | `docs/` | +| Add dev commands | `Makefile` | + +## Inside `app/` + +The backend follows a layered structure: **routes → services → repositories → database**. Requests enter through the API layer, business logic lives in services, and all database access goes through repositories. + +``` +app/ +├── main.py # App factory: creates FastAPI app, wires middleware and routers +├── api/ # HTTP layer - request/response handling only, no business logic +│ ├── router.py # Aggregates all route modules into one router; main.py mounts this +│ ├── deps.py # Shared FastAPI dependencies (e.g. DB session injection) +│ ├── routes/ # One module per feature (forms.py, templates.py, …) +│ └── schemas/ # Pydantic request/response models, one module per feature +├── core/ # App-wide infrastructure +│ ├── config.py # All settings and env var reading nothing else reads os.environ +│ ├── lifespan.py # Startup/shutdown logic (DB init, etc.) +│ ├── logging.py # Logging configuration +│ └── errors/ # Custom exception classes (base.py) and handlers (handlers.py) +├── services/ # Business logic — LLM extraction, PDF filling, orchestration +├── db/ # Database engine/session (database.py) and repositories.py +├── models/ # SQLAlchemy ORM models +└── utils/ # Small generic helpers with no business logic +``` + +### Where does my new code go? + +**Adding a new API endpoint:** + +1. `app/api/schemas/<...>.py` - Pydantic models for the request and response bodies +2. `app/api/routes/<...>.py` - the route handlers; keep them thin, delegate to a service +3. `app/api/router.py` - register the new router (one `include_router` line) +4. Business logic goes in `app/services/`, not in the route handler + +**Adding business logic:** `app/services/`. A service should not know about HTTP (no FastAPI imports) it takes plain data in and returns plain data, so it can be tested and reused independently. + +**Adding a database table:** define the ORM model in `app/models/models.py`, and add its query/persistence functions to `app/db/repositories.py`. Services call repositories; routes never touch the database directly. + +**Adding a setting or env var:** declare it in `app/core/config.py` and document it in `docker/.env.example`. Code elsewhere imports from `config`, never reads `os.environ` itself. + +**Adding a custom error:** subclass in `app/core/errors/base.py`; map it to an HTTP response in `app/core/errors/handlers.py`. + +## Tests + +Tests live in `tests/`, run with `make test` (pytest inside the app container). `conftest.py` holds shared fixtures. Name files `test_.py` and mirror what you're testing: API endpoint tests alongside `test_api.py`, model/DB tests alongside `test_model.py`. +Note: Tests will be undergoing many changes hence the docs can be outdated, Please raise issue if tests or docs are outdated. + +## Docker & scripts + +- `docker/dev/` - development image (hot reload, source mounted) and its compose file. This is what `make up` runs. +- `docker/prod/` - production image (multi-stage build, gunicorn, no source mount). +- `docker/.env.example` - template for env vars; copied to `.env.dev` by `make init`. +- `scripts/` - shell scripts invoked by `make init` (dependency checks, env file creation, model selection) and by container startup. Not meant to be run directly. + +Full details: [docker/README.md](../docker/README.md). + +## Everything else + +- `examples/` - runnable demo scripts showing the pipeline end to end; not imported by the app. +- `data/` - runtime working data on your machine; contents are not part of the codebase. +- `src/`, `temp/` - scratch/legacy directories slated for cleanup; don't add new code here. From 6ea00393f865393b2a3aee3aa5c93942a17e5fc0 Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Fri, 12 Jun 2026 19:17:47 +0530 Subject: [PATCH 15/15] Updated renamed file name --- CONTRIBUTING.md | 4 ++-- docs/1. SETUP.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6cf0be7..d30e9b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,6 +47,6 @@ Join our Discord server to ask questions, discuss issues, and coordinate work wi ## 🛠️ Local Development Setup -See the [Setup Guide](docs/SETUP.md) for the full walkthrough: prerequisites, running the backend with Docker, testing endpoints via Swagger UI, day-to-day commands, and troubleshooting. +See the [Setup Guide](docs/1.%20SETUP.md) for the full walkthrough: prerequisites, running the backend with Docker, testing endpoints via Swagger UI, day-to-day commands, and troubleshooting. -Before writing code, read the [Project Structure](docs/PROJECT_STRUCTURE.md) guide — it explains how the codebase is organized and where new code should go. +Before writing code, read the [Project Structure](docs/2.%20PROJECT_STRUCTURE.md) guide — it explains how the codebase is organized and where new code should go. diff --git a/docs/1. SETUP.md b/docs/1. SETUP.md index 5b4de34..30d2aee 100644 --- a/docs/1. SETUP.md +++ b/docs/1. SETUP.md @@ -95,5 +95,5 @@ Check `make logs-app` for the actual error. The entrypoint runs database migrati - **Join our [Discord](https://discord.gg/nBv5b6kF68)** — ask questions and coordinate with other contributors - [CONTRIBUTING.md](../CONTRIBUTING.md) - how to contribute -- [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md) - how the codebase is organized and where new code goes +- [PROJECT_STRUCTURE.md](2.%20PROJECT_STRUCTURE.md) - how the codebase is organized and where new code goes - [docker/README.md](../docker/README.md) - Docker layout, env vars, and volumes in detail