Spaces:
Sleeping
Sleeping
Clean deployment: add PDF user guide, drop desktop/dev-only files
Browse files- .env.example +0 -54
- .gitignore +0 -39
- DEVELOPER.md +0 -252
- README.md +5 -4
- Upwork-Proposal-Strategist-Guide.pdf +0 -0
- desktop_app.py +0 -271
- docs/FIRST_TIME_SETUP_GUIDE.md +0 -377
- docs/QUICK_START_GUIDE.md +0 -65
- pyproject.toml +0 -37
- scripts/ensure_runtime_mac.sh +0 -123
- scripts/package.sh +0 -88
- scripts/prepare_bundle.sh +0 -53
- scripts/run_mac.sh +0 -64
.env.example
DELETED
|
@@ -1,54 +0,0 @@
|
|
| 1 |
-
# Copy this file to .env, or just configure everything from the app's
|
| 2 |
-
# Setup page — the app writes these values to .env for you. Never commit .env.
|
| 3 |
-
|
| 4 |
-
# --------------------------------------------------
|
| 5 |
-
# Main LLM provider — "openai", "anthropic", "groq", or "gemini"
|
| 6 |
-
# --------------------------------------------------
|
| 7 |
-
LLM_PROVIDER=openai
|
| 8 |
-
|
| 9 |
-
# OpenAI (used when LLM_PROVIDER=openai)
|
| 10 |
-
# Suggested models: gpt-4.1, gpt-4.1-mini, gpt-4o, gpt-4o-mini (4o/4.1 = vision)
|
| 11 |
-
OPENAI_API_KEY=
|
| 12 |
-
OPENAI_MODEL=gpt-4.1
|
| 13 |
-
|
| 14 |
-
# Anthropic (used when LLM_PROVIDER=anthropic; leave blank otherwise)
|
| 15 |
-
# Suggested models: claude-sonnet-4-6, claude-3-5-sonnet-latest, claude-3-5-haiku-latest
|
| 16 |
-
ANTHROPIC_API_KEY=
|
| 17 |
-
ANTHROPIC_MODEL=
|
| 18 |
-
|
| 19 |
-
# Groq (used when LLM_PROVIDER=groq; leave blank otherwise). Text-only here.
|
| 20 |
-
# Suggested models: llama-3.3-70b-versatile, llama-3.1-8b-instant, mixtral-8x7b-32768, gemma2-9b-it
|
| 21 |
-
GROQ_API_KEY=
|
| 22 |
-
GROQ_MODEL=llama-3.3-70b-versatile
|
| 23 |
-
|
| 24 |
-
# Gemini (used when LLM_PROVIDER=gemini; leave blank otherwise)
|
| 25 |
-
# Suggested models: gemini-1.5-pro, gemini-1.5-flash, gemini-2.0-flash, gemini-2.0-flash-lite
|
| 26 |
-
GEMINI_API_KEY=
|
| 27 |
-
GEMINI_MODEL=gemini-1.5-pro
|
| 28 |
-
|
| 29 |
-
# Vision model for reading job screenshots. VISION_PROVIDER may be "openai",
|
| 30 |
-
# "anthropic", "gemini", or "same_as_llm" (reuse LLM_PROVIDER when it supports
|
| 31 |
-
# images). Groq is NOT a valid vision provider — it is text-only in this app.
|
| 32 |
-
# VISION_MODEL must be a vision-capable model.
|
| 33 |
-
# Suggested vision models — OpenAI: gpt-4o, gpt-4.1; Anthropic: claude-sonnet-4-6,
|
| 34 |
-
# claude-3-5-sonnet-latest; Gemini: gemini-1.5-pro, gemini-1.5-flash, gemini-2.0-flash.
|
| 35 |
-
VISION_PROVIDER=openai
|
| 36 |
-
VISION_MODEL=gpt-4o
|
| 37 |
-
|
| 38 |
-
# --------------------------------------------------
|
| 39 |
-
# Safety / fallback controls — keep false for production-like behavior.
|
| 40 |
-
# When false, the main UI hides provider/model labels, task names, and raw
|
| 41 |
-
# API errors; only short, safe, actionable messages are shown.
|
| 42 |
-
# --------------------------------------------------
|
| 43 |
-
ALLOW_PROVIDER_FALLBACK=false
|
| 44 |
-
ALLOW_LOCAL_PLACEHOLDERS=false
|
| 45 |
-
SHOW_DEBUG_PANEL=false
|
| 46 |
-
|
| 47 |
-
# --------------------------------------------------
|
| 48 |
-
# Proposal context limits and logging. The full dossier is never sent; only
|
| 49 |
-
# a small relevance-filtered subset is.
|
| 50 |
-
# --------------------------------------------------
|
| 51 |
-
MAX_PROPOSAL_CONTEXT_CHARS=15000
|
| 52 |
-
MAX_PROPOSAL_EVIDENCE_POINTS=20
|
| 53 |
-
PROPOSAL_MAX_OUTPUT_TOKENS=700
|
| 54 |
-
LOG_RETENTION_DAYS=7
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
DELETED
|
@@ -1,39 +0,0 @@
|
|
| 1 |
-
.env
|
| 2 |
-
.env.*
|
| 3 |
-
!.env.example
|
| 4 |
-
.env.tmp
|
| 5 |
-
.venv
|
| 6 |
-
venv/
|
| 7 |
-
__pycache__/
|
| 8 |
-
*.pyc
|
| 9 |
-
.pytest_cache/
|
| 10 |
-
.mypy_cache/
|
| 11 |
-
.ruff_cache/
|
| 12 |
-
logs/
|
| 13 |
-
*.db
|
| 14 |
-
*.sqlite
|
| 15 |
-
.DS_Store
|
| 16 |
-
|
| 17 |
-
# Mac virtual environment — created on first run, machine-specific
|
| 18 |
-
runtime_mac/
|
| 19 |
-
|
| 20 |
-
# The bundled Python lives in runtime/ and IS committed, so a git download runs
|
| 21 |
-
# directly (no separate staging step). Only the per-machine, first-run-generated
|
| 22 |
-
# bits are kept out of git:
|
| 23 |
-
runtime/Lib/
|
| 24 |
-
runtime/Scripts/
|
| 25 |
-
runtime/.deps_installed
|
| 26 |
-
runtime/python-embed.zip
|
| 27 |
-
|
| 28 |
-
# Packaging / build outputs
|
| 29 |
-
build/
|
| 30 |
-
dist/
|
| 31 |
-
.venv-build/
|
| 32 |
-
*.egg-info/
|
| 33 |
-
|
| 34 |
-
# Claude Code local settings (machine-specific)
|
| 35 |
-
.claude/settings.local.json
|
| 36 |
-
|
| 37 |
-
# Personal sample dossier files (PII) — only the template is committed
|
| 38 |
-
sample_dossier/*
|
| 39 |
-
!sample_dossier/TEMPLATE_dossier.json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEVELOPER.md
DELETED
|
@@ -1,252 +0,0 @@
|
|
| 1 |
-
# Developer & Packaging Guide
|
| 2 |
-
|
| 3 |
-
Everything technical lives here. **End users need none of it** — they double-click
|
| 4 |
-
`Start Upwork Proposal Strategist` (see [README.md](README.md)). This document
|
| 5 |
-
covers how the browser-app design works, how to run from source, and how to
|
| 6 |
-
build/distribute.
|
| 7 |
-
|
| 8 |
-
---
|
| 9 |
-
|
| 10 |
-
## 1. How it works (architecture)
|
| 11 |
-
|
| 12 |
-
The goal: a non-technical Windows user double-clicks **one** thing and the app
|
| 13 |
-
opens in their **web browser**, with **nothing pre-installed** — no Python,
|
| 14 |
-
conda, pip, PYTHONPATH, or env vars, ever.
|
| 15 |
-
|
| 16 |
-
Python travels *inside* the folder — `runtime\` (the embeddable Python) is
|
| 17 |
-
**committed to the repo**, so every checkout, "Download ZIP", and packaged zip
|
| 18 |
-
already has it. The client never downloads an interpreter — only the app's
|
| 19 |
-
libraries, from PyPI, on the first run.
|
| 20 |
-
|
| 21 |
-
```
|
| 22 |
-
Start Upwork Proposal Strategist.bat ← user double-clicks (console = "running" light)
|
| 23 |
-
└─ scripts\run.bat ← sets UPS_PACKAGED=1, then…
|
| 24 |
-
├─ scripts\ensure_runtime.bat ← FIRST RUN ONLY:
|
| 25 |
-
│ 1. (Python is already in runtime\ — bundled, not downloaded)
|
| 26 |
-
│ 2. enable site-packages in python311._pth
|
| 27 |
-
│ 3. install pip from the bundled get-pip.py
|
| 28 |
-
│ 4. pip install -r requirements.txt (from PyPI; internet)
|
| 29 |
-
│ 5. write runtime\.deps_installed ← success marker (LAST)
|
| 30 |
-
└─ runtime\python.exe desktop_app.py ← runs in the FOREGROUND (this console)
|
| 31 |
-
├─ picks a free 127.0.0.1 port; starts Streamlit headless, in-process
|
| 32 |
-
├─ a daemon thread waits for /_stcore/health, then opens the browser
|
| 33 |
-
└─ closing the console window stops the server (one process; no orphan)
|
| 34 |
-
```
|
| 35 |
-
|
| 36 |
-
Key properties:
|
| 37 |
-
|
| 38 |
-
- **Bundled interpreter, libraries on first run.** `runtime\python.exe` ships
|
| 39 |
-
inside the folder; `ensure_runtime.bat` only installs the Python libraries
|
| 40 |
-
(once). There is **no "download an interpreter from the web" step** — that
|
| 41 |
-
download-then-execute pattern is what antivirus/SmartScreen flags. The old
|
| 42 |
-
`pywebview` / `pythonnet` / WebView2 stack is gone: the UI is just the browser.
|
| 43 |
-
- **Idempotent + self-healing setup.** `ensure_runtime.bat` writes its success
|
| 44 |
-
marker (`runtime\.deps_installed`) **only after every step succeeds**, so an
|
| 45 |
-
interrupted/failed first run leaves no marker and simply resumes on the next
|
| 46 |
-
launch. A completed setup is an instant no-op.
|
| 47 |
-
- **Single process, clean shutdown.** `desktop_app.py` *is* the Streamlit server
|
| 48 |
-
(its stable CLI is driven in-process) and runs in the foreground. Closing the
|
| 49 |
-
console window stops it — nothing to taskkill, no orphaned `python.exe`.
|
| 50 |
-
- **No PYTHONPATH.** The project is a proper installable package
|
| 51 |
-
(`pyproject.toml`), and the launcher (`desktop_app.py`) puts the project root
|
| 52 |
-
on `sys.path` before handing off to Streamlit.
|
| 53 |
-
- **Writable state is outside the folder.** The API-key `.env` and logs live in
|
| 54 |
-
`%APPDATA%\UpworkProposalStrategist\`, so the install folder can be read-only
|
| 55 |
-
and can be moved/renamed without losing settings. See `app/paths.py`.
|
| 56 |
-
- **Move-safe.** All paths are resolved at runtime from file locations and
|
| 57 |
-
`%APPDATA%` — never baked in — so moving the whole folder still works.
|
| 58 |
-
|
| 59 |
-
### Which runtime: embeddable Python (not a venv)
|
| 60 |
-
|
| 61 |
-
I used **python.org's embeddable Python 3.11 zip**, not an auto-created venv.
|
| 62 |
-
Why: the embeddable build has **zero dependence on any system Python** (a venv
|
| 63 |
-
needs a base interpreter to create it), it's a single self-contained folder, and
|
| 64 |
-
it's the lightest way to get "nothing pre-installed." The one quirk it requires —
|
| 65 |
-
enabling `site` / `Lib\site-packages` via the `._pth` file so pip-installed
|
| 66 |
-
packages import — is handled deterministically in `ensure_runtime.bat`.
|
| 67 |
-
|
| 68 |
-
This interpreter is **committed** under `runtime\`. It was created by
|
| 69 |
-
`scripts\prepare_bundle.sh` (which just downloads + unzips the Windows build and
|
| 70 |
-
`get-pip.py` on your Mac; nothing is executed on macOS) — re-run that only to
|
| 71 |
-
rebuild or bump it.
|
| 72 |
-
|
| 73 |
-
> Internet is needed on the **first** run only, to fetch the dependency wheels
|
| 74 |
-
> from PyPI. Python itself is bundled. See [Offline / air-gapped
|
| 75 |
-
> install](#4-offline--air-gapped-install) to remove the first-run download too.
|
| 76 |
-
|
| 77 |
-
### The path/runtime/state helpers (load-bearing)
|
| 78 |
-
|
| 79 |
-
| File | Role |
|
| 80 |
-
|------|------|
|
| 81 |
-
| `app/paths.py` | The ONE path helper. `resource_base()` (bundled resources, MEIPASS-aware), `state_dir()`/`user_state_dir()` (writable `%APPDATA%`), `is_packaged()`/`is_frozen()`. |
|
| 82 |
-
| `app/config.py` | Derives `PROJECT_ROOT`, `STATE_DIR`, `ENV_PATH`, `LOG_DIR` from `app.paths`. |
|
| 83 |
-
| `desktop_app.py` | The launcher: free port, in-process headless Streamlit, health-gated browser open, foreground serve. Sets `UPS_PACKAGED=1`. |
|
| 84 |
-
| `scripts\prepare_bundle.sh` | Dev/**macOS/Linux**: (re)create `runtime\` (embeddable Python + `get-pip.py`). Rarely needed — `runtime\` is committed. |
|
| 85 |
-
| `scripts\prepare_bundle.bat` | Dev/**Windows**: same, to rebuild a deleted runtime (downloads via PowerShell). |
|
| 86 |
-
| `scripts\ensure_runtime.bat` | First-run: enable site-packages, install pip, pip-install the libraries. |
|
| 87 |
-
| `scripts\run.bat` | Set `UPS_PACKAGED=1`, ensure runtime, then run in the foreground (normal or `debug`). |
|
| 88 |
-
| `Start Upwork Proposal Strategist.bat` | Primary launcher (opens the browser; the console is the run indicator). |
|
| 89 |
-
| `Start Upwork Proposal Strategist (Debug).bat` | Same app, with full logs for troubleshooting. |
|
| 90 |
-
|
| 91 |
-
`UPS_PACKAGED=1` is how a non-frozen embeddable run still gets per-user state:
|
| 92 |
-
`run.bat` and `desktop_app.py` set it; `app.paths.is_packaged()` honours it
|
| 93 |
-
(alongside `sys.frozen`). Dev `streamlit run` leaves it unset → state stays in
|
| 94 |
-
the repo root, so tests and local dev are unaffected.
|
| 95 |
-
|
| 96 |
-
---
|
| 97 |
-
|
| 98 |
-
## 2. Run from source (developers)
|
| 99 |
-
|
| 100 |
-
No PYTHONPATH needed. Pick either launch style.
|
| 101 |
-
|
| 102 |
-
```bash
|
| 103 |
-
# Install the libraries.
|
| 104 |
-
pip install -r requirements.txt
|
| 105 |
-
pip install -e . # optional: `import app...` from anywhere + the
|
| 106 |
-
# `upwork-strategist` console command
|
| 107 |
-
|
| 108 |
-
# A) Exactly what the packaged app runs (opens your default browser):
|
| 109 |
-
python desktop_app.py
|
| 110 |
-
# ...sets UPS_PACKAGED, so state goes to %APPDATA% (or
|
| 111 |
-
# ~/.upwork_proposal_strategist on macOS/Linux). Force dev state with:
|
| 112 |
-
# UPS_PACKAGED=0 python desktop_app.py (mac/linux) set UPS_PACKAGED=0 (win)
|
| 113 |
-
|
| 114 |
-
# B) Plain Streamlit (state stays in the repo root):
|
| 115 |
-
python -m streamlit run app/main.py
|
| 116 |
-
```
|
| 117 |
-
|
| 118 |
-
There is no longer a separate "native window" vs "browser" mode — both open in
|
| 119 |
-
the browser. `pywebview` and `pythonnet` were removed.
|
| 120 |
-
|
| 121 |
-
### Tests
|
| 122 |
-
|
| 123 |
-
```bash
|
| 124 |
-
pytest -q
|
| 125 |
-
```
|
| 126 |
-
|
| 127 |
-
The suite runs in dev mode (state in the repo root) and needs only
|
| 128 |
-
`requirements.txt` — no desktop/GUI packages.
|
| 129 |
-
|
| 130 |
-
---
|
| 131 |
-
|
| 132 |
-
## 3. Distributing the folder
|
| 133 |
-
|
| 134 |
-
**One command builds a clean, ready-to-send zip:**
|
| 135 |
-
|
| 136 |
-
```bash
|
| 137 |
-
bash scripts/package.sh
|
| 138 |
-
```
|
| 139 |
-
|
| 140 |
-
It stages the bundled Python, copies the project while **excluding your secrets
|
| 141 |
-
and dev junk** (`.env` / API key, `.git`, `.vscode`, `.claude`, caches, `logs`,
|
| 142 |
-
`tests`, your personal `sample_dossier` files), writes
|
| 143 |
-
`dist/UpworkProposalStrategist.zip`, and **aborts if a `.env` ever lands inside**.
|
| 144 |
-
Re-run it after any change.
|
| 145 |
-
|
| 146 |
-
**Send `dist/UpworkProposalStrategist.zip`.** The user unzips and double-clicks
|
| 147 |
-
`Start Upwork Proposal Strategist`. Their first run installs the libraries from
|
| 148 |
-
PyPI (internet needed once), then the app opens in their browser.
|
| 149 |
-
|
| 150 |
-
> **The GitHub repo runs directly.** `runtime\` (the bundled Python) is
|
| 151 |
-
> **committed**, so a `git clone` / "Download ZIP" already has `python.exe` —
|
| 152 |
-
> double-click `Start Upwork Proposal Strategist` and it works (the first run
|
| 153 |
-
> installs the libraries from PyPI; internet needed once). Only the first-run
|
| 154 |
-
> *output* (`runtime\Lib`, `runtime\Scripts`, the `.deps_installed` marker) is
|
| 155 |
-
> git-ignored.
|
| 156 |
-
|
| 157 |
-
> Helper: `scripts/prepare_bundle.sh` (macOS/Linux) and `scripts/prepare_bundle.bat`
|
| 158 |
-
> (Windows) (re)create `runtime\` from the pinned embeddable Python + `get-pip.py`.
|
| 159 |
-
> You normally don't need them — the runtime is committed. Use them only to rebuild
|
| 160 |
-
> a deleted/corrupted runtime or to bump the Python version. `package.sh` also calls
|
| 161 |
-
> the `.sh` one (it's a no-op when the runtime is already present).
|
| 162 |
-
|
| 163 |
-
> **Client vs repo:** both run, but the packaged `UpworkProposalStrategist.zip`
|
| 164 |
-
> (from `package.sh`) is the cleaner hand-off for a non-technical client — it
|
| 165 |
-
> strips `tests/`, `.git`, and dev files and gives a tidy folder name. The raw
|
| 166 |
-
> GitHub download works too; it just also carries the dev files.
|
| 167 |
-
|
| 168 |
-
To give it a real icon, create a Windows shortcut to
|
| 169 |
-
`Start Upwork Proposal Strategist.bat`, set its icon to `build_assets\app.ico`,
|
| 170 |
-
and place that shortcut on the Desktop.
|
| 171 |
-
|
| 172 |
-
If antivirus/policy blocks the launcher, the user can double-click
|
| 173 |
-
`scripts\run.bat` directly (same behaviour).
|
| 174 |
-
|
| 175 |
-
> **Why this is friendlier to antivirus than the old build:** the previous
|
| 176 |
-
> version used a hidden-launch `.vbs` *and* downloaded a Python interpreter at
|
| 177 |
-
> runtime via PowerShell — both classic malware-heuristic / ASR triggers. This
|
| 178 |
-
> version ships a plain `.bat`, bundles Python (no interpreter download), and
|
| 179 |
-
> only lets `pip` fetch library wheels from PyPI (ordinary, not flagged the same
|
| 180 |
-
> way). Note: unsigned code can still trip SmartScreen ("More info → Run
|
| 181 |
-
> anyway"); the only real cure for that is code-signing, which is out of scope
|
| 182 |
-
> here.
|
| 183 |
-
|
| 184 |
-
---
|
| 185 |
-
|
| 186 |
-
## 4. Offline / air-gapped install
|
| 187 |
-
|
| 188 |
-
The default already **bundles Python**, so the only thing a client downloads is
|
| 189 |
-
the library wheels on first run. To make the folder **fully offline** (no
|
| 190 |
-
internet even on the first run):
|
| 191 |
-
|
| 192 |
-
1. **Bundle wheels:** on a machine with internet,
|
| 193 |
-
`pip download -r requirements.txt -d wheelhouse\` (from a Mac, add
|
| 194 |
-
`--platform win_amd64 --python-version 311 --only-binary=:all:` to fetch the
|
| 195 |
-
Windows wheels), ship the `wheelhouse\` folder, and change the install line in
|
| 196 |
-
`scripts\ensure_runtime.bat` to:
|
| 197 |
-
`"%PYEXE%" -m pip install --no-index --find-links "%ROOT%\wheelhouse" -r "%REQ%"`
|
| 198 |
-
2. Python and `get-pip.py` are already committed under `runtime\`, so nothing
|
| 199 |
-
else is needed.
|
| 200 |
-
|
| 201 |
-
---
|
| 202 |
-
|
| 203 |
-
## 5. Design notes
|
| 204 |
-
|
| 205 |
-
- **Browser, not a native window.** The app opens in the default browser instead
|
| 206 |
-
of a pywebview/WebView2 window; `pywebview` + `pythonnet` were removed (fewer
|
| 207 |
-
moving parts, and `pythonnet`'s .NET interop was itself an extra antivirus
|
| 208 |
-
trigger). `127.0.0.1` is a browser "secure context", so clipboard paste on the
|
| 209 |
-
Job Screenshot screen still works; the file-upload control is the always-on
|
| 210 |
-
fallback. The console window is both the "running" indicator and the
|
| 211 |
-
clean-shutdown mechanism — close it and the single server process stops.
|
| 212 |
-
Streamlit binds to `127.0.0.1` **only** (verified with `lsof`: `TCP
|
| 213 |
-
127.0.0.1:<port> (LISTEN)`), so no network-facing port is ever exposed. A
|
| 214 |
-
first-run Windows Defender Firewall prompt for `python.exe` is therefore
|
| 215 |
-
harmless — a loopback listener works whether the user clicks **Allow access**
|
| 216 |
-
or **Cancel**. There is no clean non-admin way to suppress the prompt, so the
|
| 217 |
-
user docs just reassure (click Allow). Don't change the bind to `0.0.0.0`.
|
| 218 |
-
- **PYTHONPATH dependence** was only in the *launch invocation* — the package is
|
| 219 |
-
well-formed (`__init__.py` throughout, absolute `app.x` imports). Fixed by
|
| 220 |
-
`pyproject.toml` (installable package) + the launcher's `sys.path` injection.
|
| 221 |
-
- **conda** appeared only in docs; no code dependence.
|
| 222 |
-
- **Writable state** previously redirected to `%APPDATA%` only when
|
| 223 |
-
`sys.frozen`; the embeddable run isn't frozen, so it would have written into
|
| 224 |
-
the folder. Fixed with the `UPS_PACKAGED` packaged-mode flag.
|
| 225 |
-
- **Runtime decision:** embeddable Python (reasoning in §1).
|
| 226 |
-
|
| 227 |
-
---
|
| 228 |
-
|
| 229 |
-
## 6. Verification checklist
|
| 230 |
-
|
| 231 |
-
Items marked ✅ were verified on this (macOS) dev box; items marked 🪟 are
|
| 232 |
-
Windows-only and must be checked on a clean Windows machine (the `.bat`
|
| 233 |
-
bootstrap and the bundled `python.exe` can't run on macOS).
|
| 234 |
-
|
| 235 |
-
| # | Check | Status |
|
| 236 |
-
|---|-------|--------|
|
| 237 |
-
| 1 | Fresh Windows, no Python: double-click → one-time library install w/ feedback → browser opens | 🪟 |
|
| 238 |
-
| 2 | Second double-click: no reinstall, opens in seconds (marker fast-path) | 🪟 |
|
| 239 |
-
| 3 | Runs with NO manual PYTHONPATH (imports as a package) | ✅ `pip install -e .` + import from `/tmp` |
|
| 240 |
-
| 4 | Spaces in path, and moving the whole folder, still work | ✅ paths resolved from file locations; 🪟 end-to-end |
|
| 241 |
-
| 5 | Setup saves API config to `%APPDATA%\...` and persists across restarts/moves | ✅ bootstrap + packaged-gate tests |
|
| 242 |
-
| 6 | Bundled data files load in the relocated/bundled runtime | ✅ no path-based data reads; resources via `resource_base()` |
|
| 243 |
-
| 7 | Clipboard paste + dossier picker work in the browser | 🪟 (paste = 127.0.0.1 secure context; upload fallback ✅) |
|
| 244 |
-
| 8 | Console stays open as the run indicator; debug launcher shows logs | 🪟 (`UPS_DEBUG=1` raises the log level) |
|
| 245 |
-
| 9 | Closing the console leaves no orphan `python.exe` | ✅ single process; 🪟 end-to-end |
|
| 246 |
-
| 10 | Interrupted first-run setup self-heals next double-click | 🪟 (marker written last) |
|
| 247 |
-
| 11 | `prepare_bundle.sh` stages `runtime\` (python.exe + get-pip.py) | ✅ runs on macOS |
|
| 248 |
-
| 12 | `SHOW_DEBUG_PANEL` still toggles | ✅ unchanged; tests pass |
|
| 249 |
-
|
| 250 |
-
To verify the Windows-only items, run `build`-free on a clean VM: stage the
|
| 251 |
-
bundle, copy the folder, double-click the launcher, and walk the checklist. Use
|
| 252 |
-
`Start Upwork Proposal Strategist (Debug).bat` to see logs if anything fails.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -45,11 +45,12 @@ The app walks you through five steps in the left sidebar:
|
|
| 45 |
> ⚠️ The **Job Screenshot** step needs a vision-capable provider — use
|
| 46 |
> **OpenAI**, **Anthropic**, or **Gemini**. **Groq cannot read images.**
|
| 47 |
|
| 48 |
-
##
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
|
| 51 |
-
on your machine instead of uploading. See **README_DESKTOP.md** and
|
| 52 |
-
**DEVELOPER.md** for that path.
|
| 53 |
|
| 54 |
```bash
|
| 55 |
pip install -r requirements.txt
|
|
|
|
| 45 |
> ⚠️ The **Job Screenshot** step needs a vision-capable provider — use
|
| 46 |
> **OpenAI**, **Anthropic**, or **Gemini**. **Groq cannot read images.**
|
| 47 |
|
| 48 |
+
## Step-by-step user guide
|
| 49 |
+
|
| 50 |
+
A complete, click-by-click walkthrough is included in this repo:
|
| 51 |
+
**[Upwork-Proposal-Strategist-Guide.pdf](Upwork-Proposal-Strategist-Guide.pdf)**.
|
| 52 |
|
| 53 |
+
## Running locally
|
|
|
|
|
|
|
| 54 |
|
| 55 |
```bash
|
| 56 |
pip install -r requirements.txt
|
Upwork-Proposal-Strategist-Guide.pdf
ADDED
|
Binary file (70 kB). View file
|
|
|
desktop_app.py
DELETED
|
@@ -1,271 +0,0 @@
|
|
| 1 |
-
"""Browser launcher for the Upwork Proposal Strategist.
|
| 2 |
-
|
| 3 |
-
What this does
|
| 4 |
-
--------------
|
| 5 |
-
The app is a local Streamlit server. This launcher starts that server and opens
|
| 6 |
-
it in the user's **default web browser** — there is no native window and no
|
| 7 |
-
extra GUI runtime (no pywebview / WebView2 / .NET). It runs in the **foreground**,
|
| 8 |
-
so the console window it launches from is the "app is running" indicator:
|
| 9 |
-
closing that window stops the server cleanly.
|
| 10 |
-
|
| 11 |
-
Flow
|
| 12 |
-
----
|
| 13 |
-
1. Declare packaged-desktop mode (``UPS_PACKAGED=1``) so writable state — the
|
| 14 |
-
API-key ``.env`` and rotating logs — goes to ``%APPDATA%`` instead of the
|
| 15 |
-
(possibly read-only / relocatable) install folder. See :mod:`app.paths`.
|
| 16 |
-
2. Create the per-user ``.env`` from the bundled template on first run.
|
| 17 |
-
3. Pick a FREE local port (never hard-code 8501, so two copies don't collide).
|
| 18 |
-
4. On a background thread, wait for the server's health endpoint and then open
|
| 19 |
-
the browser at the local URL — opening only once it actually answers avoids a
|
| 20 |
-
"can't connect" tab on the slow first launch.
|
| 21 |
-
5. Run Streamlit **headless**, bound to 127.0.0.1, in the foreground. It serves
|
| 22 |
-
until the console window is closed (or Ctrl+C), which cleanly stops it.
|
| 23 |
-
|
| 24 |
-
Design notes
|
| 25 |
-
------------
|
| 26 |
-
* ONE process. The launcher *is* the Streamlit server: its stable CLI is driven
|
| 27 |
-
in-process by :func:`run_streamlit_cli`. There is no child process, GUI library,
|
| 28 |
-
or re-exec, so closing the console is the clean, orphan-free shutdown — nothing
|
| 29 |
-
to taskkill.
|
| 30 |
-
* 127.0.0.1 is a browser "secure context", so clipboard paste on the Job
|
| 31 |
-
Screenshot screen keeps working; the file-upload control is always available
|
| 32 |
-
too.
|
| 33 |
-
* Writable state is handled by :mod:`app.config`, which redirects to a per-user
|
| 34 |
-
location when packaged — see DEVELOPER.md.
|
| 35 |
-
"""
|
| 36 |
-
|
| 37 |
-
from __future__ import annotations
|
| 38 |
-
|
| 39 |
-
import os
|
| 40 |
-
import shutil
|
| 41 |
-
import socket
|
| 42 |
-
import sys
|
| 43 |
-
import threading
|
| 44 |
-
import time
|
| 45 |
-
import urllib.request
|
| 46 |
-
import webbrowser
|
| 47 |
-
from pathlib import Path
|
| 48 |
-
|
| 49 |
-
# Put the project root (this file's own directory, which holds the `app`
|
| 50 |
-
# package) on sys.path BEFORE importing app. A normal Python auto-prepends the
|
| 51 |
-
# script's directory, but the Windows embeddable Python uses a ._pth file that
|
| 52 |
-
# does NOT — so without this the very first `import app` dies with
|
| 53 |
-
# "ModuleNotFoundError: No module named 'app'". This is a no-op under a normal
|
| 54 |
-
# dev Python (the directory is already there). Must run before any `from app…`.
|
| 55 |
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 56 |
-
|
| 57 |
-
# The one shared path helper (stdlib-only, no import cycle).
|
| 58 |
-
from app.paths import PACKAGED_ENV_FLAG, resource_base
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
# Mirrors app.config.APP_TITLE. Kept as a literal so the launcher can print the
|
| 62 |
-
# banner without importing the (heavier) app package first.
|
| 63 |
-
APP_TITLE = "Upwork Proposal Strategist"
|
| 64 |
-
|
| 65 |
-
# How long to wait for the Streamlit server to answer before opening the browser
|
| 66 |
-
# anyway. The first run imports a lot (and antivirus can slow it), so this is
|
| 67 |
-
# deliberately generous.
|
| 68 |
-
BOOT_TIMEOUT = 90.0
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
def _main_script() -> Path:
|
| 72 |
-
"""Absolute path to the Streamlit entry script Streamlit will execute."""
|
| 73 |
-
return resource_base() / "app" / "main.py"
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
# ---------------------------------------------------------------------------
|
| 77 |
-
# Streamlit server (driven in-process via its own stable CLI)
|
| 78 |
-
# ---------------------------------------------------------------------------
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
def run_streamlit_cli(port: int) -> None:
|
| 82 |
-
"""Run the Streamlit server (headless) on ``port``. Blocks until stopped.
|
| 83 |
-
|
| 84 |
-
Points Streamlit at the bundled ``app/main.py`` and pins every server option
|
| 85 |
-
on the command line so behavior never depends on a config file being found.
|
| 86 |
-
Working directory is the resource base so ``import app...`` resolves and the
|
| 87 |
-
bundled ``.streamlit/config.toml`` (theme, toolbar) is still picked up.
|
| 88 |
-
"""
|
| 89 |
-
base = resource_base()
|
| 90 |
-
# CWD so Streamlit finds .streamlit/config.toml; sys.path so `import app`
|
| 91 |
-
# works when Streamlit execs app/main.py.
|
| 92 |
-
os.chdir(str(base))
|
| 93 |
-
if str(base) not in sys.path:
|
| 94 |
-
sys.path.insert(0, str(base))
|
| 95 |
-
|
| 96 |
-
# Quiet console on a normal run; full logs when launched via the debug .bat.
|
| 97 |
-
log_level = "debug" if os.environ.get("UPS_DEBUG") == "1" else "error"
|
| 98 |
-
sys.argv = [
|
| 99 |
-
"streamlit",
|
| 100 |
-
"run",
|
| 101 |
-
str(_main_script()),
|
| 102 |
-
"--server.address=127.0.0.1",
|
| 103 |
-
f"--server.port={port}",
|
| 104 |
-
"--server.headless=true", # we open the browser ourselves
|
| 105 |
-
"--browser.gatherUsageStats=false", # local-first: no telemetry call
|
| 106 |
-
"--server.fileWatcherType=none", # packaged app: nothing to hot-reload
|
| 107 |
-
"--server.runOnSave=false",
|
| 108 |
-
# Local single-user desktop context: these clear the 403 upload error
|
| 109 |
-
# local proxies otherwise trigger on st.file_uploader. Mirrors the
|
| 110 |
-
# bundled .streamlit/config.toml.
|
| 111 |
-
"--server.enableCORS=false",
|
| 112 |
-
"--server.enableXsrfProtection=false",
|
| 113 |
-
"--server.maxUploadSize=20",
|
| 114 |
-
f"--logger.level={log_level}",
|
| 115 |
-
]
|
| 116 |
-
# Imported here (not at module top) so the cost is only paid in the process
|
| 117 |
-
# that actually serves, and import errors surface in the console.
|
| 118 |
-
from streamlit.web import cli as stcli
|
| 119 |
-
|
| 120 |
-
sys.exit(stcli.main())
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
# ---------------------------------------------------------------------------
|
| 124 |
-
# Port / health / browser
|
| 125 |
-
# ---------------------------------------------------------------------------
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
def pick_free_port() -> int:
|
| 129 |
-
"""Return an OS-assigned free TCP port on the loopback interface.
|
| 130 |
-
|
| 131 |
-
Binding to port 0 lets the kernel hand us a port nothing else holds, so a
|
| 132 |
-
second instance never collides with the first. The socket is closed
|
| 133 |
-
immediately; Streamlit re-binds the same port a moment later.
|
| 134 |
-
"""
|
| 135 |
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
| 136 |
-
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 137 |
-
sock.bind(("127.0.0.1", 0))
|
| 138 |
-
return int(sock.getsockname()[1])
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
def _health_ok(port: int) -> bool:
|
| 142 |
-
"""True if the Streamlit server answers its health endpoint with HTTP 200."""
|
| 143 |
-
# /_stcore/health is current; /healthz covers older Streamlit just in case.
|
| 144 |
-
for path in ("/_stcore/health", "/healthz"):
|
| 145 |
-
try:
|
| 146 |
-
with urllib.request.urlopen(
|
| 147 |
-
f"http://127.0.0.1:{port}{path}", timeout=2
|
| 148 |
-
) as resp:
|
| 149 |
-
if resp.status == 200:
|
| 150 |
-
return True
|
| 151 |
-
except Exception: # noqa: BLE001 - not up yet / endpoint absent → try next
|
| 152 |
-
continue
|
| 153 |
-
return False
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
def _page_ok(port: int) -> bool:
|
| 157 |
-
"""True if the root page itself serves real HTML (not just the health ping).
|
| 158 |
-
|
| 159 |
-
The health endpoint flips to 200 the instant the server process is up —
|
| 160 |
-
a split second BEFORE Streamlit can actually render the page. Opening the
|
| 161 |
-
browser in that gap leaves a blank tab that needs a manual refresh. We
|
| 162 |
-
additionally confirm the root URL returns HTML so the page is genuinely
|
| 163 |
-
ready before we open it.
|
| 164 |
-
"""
|
| 165 |
-
try:
|
| 166 |
-
with urllib.request.urlopen(
|
| 167 |
-
f"http://127.0.0.1:{port}/", timeout=3
|
| 168 |
-
) as resp:
|
| 169 |
-
if resp.status != 200:
|
| 170 |
-
return False
|
| 171 |
-
body = resp.read(4096).decode("utf-8", "ignore").lower()
|
| 172 |
-
# Streamlit's shell always contains these markers once it serves.
|
| 173 |
-
return ("streamlit" in body) or ("<div id=\"root\"" in body) or ("<title" in body)
|
| 174 |
-
except Exception: # noqa: BLE001 - not serving the page yet
|
| 175 |
-
return False
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
def _open_browser_when_ready(
|
| 179 |
-
port: int, url: str, timeout: float = BOOT_TIMEOUT
|
| 180 |
-
) -> None:
|
| 181 |
-
"""Wait for the server to be healthy, then open the default browser once.
|
| 182 |
-
|
| 183 |
-
Runs on a daemon thread so the main thread is free to serve. If the server
|
| 184 |
-
never comes up within ``timeout`` we still open the browser once, so the
|
| 185 |
-
user sees the address (and any browser-side error) rather than nothing.
|
| 186 |
-
"""
|
| 187 |
-
deadline = time.monotonic() + timeout
|
| 188 |
-
ready = False
|
| 189 |
-
while time.monotonic() < deadline:
|
| 190 |
-
# Require BOTH the health ping AND the root page to serve real HTML,
|
| 191 |
-
# so the browser never opens onto a blank, not-yet-rendered shell.
|
| 192 |
-
if _health_ok(port) and _page_ok(port):
|
| 193 |
-
ready = True
|
| 194 |
-
break
|
| 195 |
-
time.sleep(0.4)
|
| 196 |
-
# A short settle delay after the page first serves — Streamlit needs a
|
| 197 |
-
# moment more before the websocket-driven first render is reliably ready.
|
| 198 |
-
if ready:
|
| 199 |
-
time.sleep(1.2)
|
| 200 |
-
try:
|
| 201 |
-
webbrowser.open(url)
|
| 202 |
-
except Exception: # noqa: BLE001 - never crash the server if no browser opens
|
| 203 |
-
pass
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
# ---------------------------------------------------------------------------
|
| 207 |
-
# First-run setup
|
| 208 |
-
# ---------------------------------------------------------------------------
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
def _bootstrap_env() -> None:
|
| 212 |
-
"""Create the per-user ``.env`` from the bundled template on first run.
|
| 213 |
-
|
| 214 |
-
The write target comes from :data:`app.config.ENV_PATH`, which already
|
| 215 |
-
redirects to ``%APPDATA%\\UpworkProposalStrategist`` when packaged, so the
|
| 216 |
-
Setup screen and this bootstrap always agree on one location.
|
| 217 |
-
"""
|
| 218 |
-
try:
|
| 219 |
-
from app.config import ENV_PATH # packaged-aware target
|
| 220 |
-
except Exception: # noqa: BLE001 - never block launch on config import
|
| 221 |
-
return
|
| 222 |
-
if ENV_PATH.exists():
|
| 223 |
-
return
|
| 224 |
-
try:
|
| 225 |
-
ENV_PATH.parent.mkdir(parents=True, exist_ok=True)
|
| 226 |
-
template = resource_base() / ".env.example"
|
| 227 |
-
if template.is_file():
|
| 228 |
-
shutil.copyfile(template, ENV_PATH)
|
| 229 |
-
else:
|
| 230 |
-
ENV_PATH.write_text("LLM_PROVIDER=openai\n", encoding="utf-8")
|
| 231 |
-
except OSError: # pragma: no cover - best effort; Setup can still write it
|
| 232 |
-
pass
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
# ---------------------------------------------------------------------------
|
| 236 |
-
# Entry point
|
| 237 |
-
# ---------------------------------------------------------------------------
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
def main() -> None:
|
| 241 |
-
# Declare packaged-desktop mode (unless explicitly overridden) so app.config
|
| 242 |
-
# redirects writable state — the API-key .env and logs — to %APPDATA%
|
| 243 |
-
# instead of the read-only / relocatable install folder. A developer can
|
| 244 |
-
# force dev behaviour with UPS_PACKAGED=0.
|
| 245 |
-
os.environ.setdefault(PACKAGED_ENV_FLAG, "1")
|
| 246 |
-
|
| 247 |
-
_bootstrap_env()
|
| 248 |
-
port = pick_free_port()
|
| 249 |
-
url = f"http://127.0.0.1:{port}"
|
| 250 |
-
|
| 251 |
-
# Print the address up front (with logger.level=error the console stays
|
| 252 |
-
# readable) so the user can open it manually if their browser doesn't.
|
| 253 |
-
print(
|
| 254 |
-
f"\n{APP_TITLE} is starting…\n"
|
| 255 |
-
f"If your browser does not open automatically, go to: {url}\n",
|
| 256 |
-
flush=True,
|
| 257 |
-
)
|
| 258 |
-
|
| 259 |
-
# Open the browser from a daemon thread once the server answers; meanwhile
|
| 260 |
-
# the main thread serves below.
|
| 261 |
-
threading.Thread(
|
| 262 |
-
target=_open_browser_when_ready, args=(port, url), daemon=True
|
| 263 |
-
).start()
|
| 264 |
-
|
| 265 |
-
# Serve in the foreground. Closing the console window (or Ctrl+C) stops the
|
| 266 |
-
# server — single process, so there is no child to orphan.
|
| 267 |
-
run_streamlit_cli(port)
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
if __name__ == "__main__":
|
| 271 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/FIRST_TIME_SETUP_GUIDE.md
DELETED
|
@@ -1,377 +0,0 @@
|
|
| 1 |
-
# First Time Setup Guide
|
| 2 |
-
|
| 3 |
-
This guide is for people who have **never** done anything like this before. That's okay. Just follow the steps in order. Do one, then go to the next.
|
| 4 |
-
|
| 5 |
-
The main way to use this tool is the **Windows app**: you **double-click one file** and it opens in your **web browser**. The very first time, it sets itself up automatically (one time, a few minutes, needs internet). **No terminal. No installing anything yourself. No setup commands.**
|
| 6 |
-
|
| 7 |
-
> 💡 **Tip:** Do not skip steps. Do them top to bottom: 1, 2, 3…
|
| 8 |
-
|
| 9 |
-
> ℹ️ **On a Mac, or a developer?** This guide is for the Windows app. Running from the source code is covered in [DEVELOPER.md](../DEVELOPER.md); the in-app steps (3–8) are the same.
|
| 10 |
-
|
| 11 |
-
---
|
| 12 |
-
|
| 13 |
-
## How to read this guide
|
| 14 |
-
|
| 15 |
-
- A **button** or thing you click is shown in **bold**.
|
| 16 |
-
- After each step, the guide tells you **what you should see**.
|
| 17 |
-
- 📷 means a picture can be added there later.
|
| 18 |
-
|
| 19 |
-
---
|
| 20 |
-
|
| 21 |
-
## Table of Contents
|
| 22 |
-
|
| 23 |
-
- [What This Tool Does](#what-this-tool-does)
|
| 24 |
-
- [Things You Need Before Starting](#things-you-need-before-starting)
|
| 25 |
-
- [Step 1: Open the App (first time)](#step-1-open-the-app-first-time)
|
| 26 |
-
- [Step 2: Opening It Again](#step-2-opening-it-again)
|
| 27 |
-
- [Step 3: Connect Your AI Service](#step-3-connect-your-ai-service)
|
| 28 |
-
- [Step 4: Run the API Check](#step-4-run-the-api-check)
|
| 29 |
-
- [Step 5: Add Your Dossier](#step-5-add-your-dossier)
|
| 30 |
-
- [Step 6: Add the Job Screenshot](#step-6-add-the-job-screenshot)
|
| 31 |
-
- [Step 7: Read the Analysis](#step-7-read-the-analysis)
|
| 32 |
-
- [Step 8: Generate Your Proposal](#step-8-generate-your-proposal)
|
| 33 |
-
- [Common Problems and Fixes](#common-problems-and-fixes)
|
| 34 |
-
- [Safe Practices](#safe-practices)
|
| 35 |
-
- [Mac, or Running From Source](#mac-or-running-from-source)
|
| 36 |
-
|
| 37 |
-
---
|
| 38 |
-
|
| 39 |
-
## What This Tool Does
|
| 40 |
-
|
| 41 |
-
This tool is the **Upwork Proposal Strategist**.
|
| 42 |
-
|
| 43 |
-
On Upwork, people send a message called a **proposal** to win freelance jobs. This tool helps you write a good proposal fast.
|
| 44 |
-
|
| 45 |
-
Here's what it does:
|
| 46 |
-
|
| 47 |
-
1. You give it a folder with your work files (resume, past projects, and so on).
|
| 48 |
-
2. You give it a **picture** of an Upwork job.
|
| 49 |
-
3. It compares the job to your files.
|
| 50 |
-
4. It gives you a clear **verdict**: 🟢 Apply Confidently · 🟡 Proceed With Caution · 🔴 Do Not Proceed.
|
| 51 |
-
5. It writes a first draft of your proposal, using only true facts from your files.
|
| 52 |
-
|
| 53 |
-
> 💡 **Tip:** Everything runs on your own computer. Your files stay with you.
|
| 54 |
-
|
| 55 |
-
---
|
| 56 |
-
|
| 57 |
-
## Things You Need Before Starting
|
| 58 |
-
|
| 59 |
-
A short checklist. The guide shows you how to get each one.
|
| 60 |
-
|
| 61 |
-
- [ ] **A Windows 10 or 11 computer.**
|
| 62 |
-
- [ ] **The app folder** — usually a ZIP file someone sent you, named something like `UpworkProposalStrategist.zip`.
|
| 63 |
-
- [ ] **Internet** (Wi-Fi or cable) — needed the first time so the app can set itself up.
|
| 64 |
-
- [ ] **An API key** — a secret password for an AI service. You get one in **Step 3**.
|
| 65 |
-
|
| 66 |
-
> ℹ️ **Note:** You do **not** need to install Python or open a terminal. The app sets up everything it needs by itself, the first time you open it.
|
| 67 |
-
|
| 68 |
-
---
|
| 69 |
-
|
| 70 |
-
## Step 1: Open the App (first time)
|
| 71 |
-
|
| 72 |
-
**Do this:**
|
| 73 |
-
|
| 74 |
-
1. If you were sent a **ZIP**, right-click it → **Extract All…** → **Extract**. You now have a folder (for example `UpworkProposalStrategist`).
|
| 75 |
-
2. Open that folder and **double-click** the file named **`Start Upwork Proposal Strategist`**.
|
| 76 |
-
3. Windows may show a blue box (**"Windows protected your PC"**). This is normal for a new app that isn't signed yet.
|
| 77 |
-
- Click **More info**, then **Run anyway**.
|
| 78 |
-
4. A small **black setup window** appears and says it's doing a **one-time setup**. It works for a few minutes. **Leave it alone until it finishes** — it's downloading and installing everything the app needs, just this once.
|
| 79 |
-
|
| 80 |
-
**You should see:** After setup, the app opens in your **web browser** on the **Setup** page. The small black window stays open behind it — that's normal; leave it open while you use the app.
|
| 81 |
-
|
| 82 |
-
> ✅ **Success:** The app is open in your browser. Continue to **Step 3**.
|
| 83 |
-
|
| 84 |
-
> 💡 **Tip:** The first time is the slow time (a few minutes, needs internet). Every launch after that takes only a few seconds.
|
| 85 |
-
|
| 86 |
-
> 🔒 **If Windows asks about the firewall:** A box may appear saying *"Windows Defender Firewall has blocked some features of python."* Click **Allow access** (you can leave **Public networks** unchecked). It's safe — the app only talks to **your own computer**. Even if you click **Cancel**, the app still works.
|
| 87 |
-
|
| 88 |
-
> ℹ️ **Note:** Five pages run down the **left side**: **Setup**, **Dossier**, **Job Screenshot**, **Analysis**, **Proposal**. A lock (🔒) is normal — each unlocks when you finish the one before it.
|
| 89 |
-
|
| 90 |
-
> ⚠️ **If the browser doesn't open on its own:** the black window shows a web address like `http://127.0.0.1:…` — type that into your browser. See [Common Problems → App won't open](#common-problems-and-fixes).
|
| 91 |
-
|
| 92 |
-
> 📷 *Screenshot placeholder: the "one-time setup" window, then the app on Setup.*
|
| 93 |
-
|
| 94 |
-
---
|
| 95 |
-
|
| 96 |
-
## Step 2: Opening It Again
|
| 97 |
-
|
| 98 |
-
Next time you want to use the tool:
|
| 99 |
-
|
| 100 |
-
- Open the app folder and **double-click `Start Upwork Proposal Strategist`** again.
|
| 101 |
-
- No setup this time — it opens in your browser in a few seconds.
|
| 102 |
-
|
| 103 |
-
> 💡 **Tip:** To make it easier later, right-click **`Start Upwork Proposal Strategist`** → **Send to → Desktop (create shortcut)**. Then it's one click from your Desktop.
|
| 104 |
-
|
| 105 |
-
> ✅ **Success:** The app opens in your browser. Your AI key from last time is remembered, so you can usually go straight to **Step 5**.
|
| 106 |
-
|
| 107 |
-
---
|
| 108 |
-
|
| 109 |
-
## Step 3: Connect Your AI Service
|
| 110 |
-
|
| 111 |
-
Now you tell the app which AI service to use and give it your secret key. This is on the **Setup** page. You only do this **once** — the app remembers it next time.
|
| 112 |
-
|
| 113 |
-
### Pick an AI service
|
| 114 |
-
|
| 115 |
-
You only need **one**:
|
| 116 |
-
|
| 117 |
-
- **OpenAI** — popular and easy. A good first choice. Can read pictures.
|
| 118 |
-
- **Anthropic** — makes the "Claude" AI. Can read pictures.
|
| 119 |
-
- **Gemini** — Google's AI. Can read pictures.
|
| 120 |
-
- **Groq** — fast and cheap, but **cannot read pictures** (so the screenshot step won't work).
|
| 121 |
-
|
| 122 |
-
> ✅ **Recommended:** Pick **OpenAI**, **Anthropic**, or **Gemini** so screenshots work.
|
| 123 |
-
|
| 124 |
-
### Get a key
|
| 125 |
-
|
| 126 |
-
1. Open the website for your service:
|
| 127 |
-
- **OpenAI:** `https://platform.openai.com/api-keys`
|
| 128 |
-
- **Anthropic:** `https://console.anthropic.com/`
|
| 129 |
-
- **Gemini:** `https://aistudio.google.com/app/apikey`
|
| 130 |
-
- **Groq:** `https://console.groq.com/keys`
|
| 131 |
-
2. Sign up or log in.
|
| 132 |
-
3. Click a button like **Create new secret key**.
|
| 133 |
-
4. **Copy** the key (a long line of letters and numbers).
|
| 134 |
-
|
| 135 |
-
> ⚠️ **Warning:** The key is shown only once — copy it right away. These services can cost money based on use; check their pricing.
|
| 136 |
-
|
| 137 |
-
### Fill in the Setup page
|
| 138 |
-
|
| 139 |
-
The service names are lowercase: `openai`, `anthropic`, `groq`, `gemini`.
|
| 140 |
-
|
| 141 |
-
1. In **LLM Provider**, pick your service.
|
| 142 |
-
2. In **LLM Model**, leave the one already filled in.
|
| 143 |
-
3. Paste your key into the **API Key** box. ("Paste" = press **Ctrl + V**.)
|
| 144 |
-
4. In **Vision Provider**, pick **`same_as_llm`** (this reuses your choice, so you only need one key).
|
| 145 |
-
5. Click **Save Configuration**.
|
| 146 |
-
|
| 147 |
-
**You should see:** A message like `Configuration saved. Run API Check to continue.`
|
| 148 |
-
|
| 149 |
-
> ⚠️ **Warning:** Do not pick **Groq** for **Vision Provider** — it cannot read pictures.
|
| 150 |
-
|
| 151 |
-
> 📷 *Screenshot placeholder: the Setup page with the fields filled in.*
|
| 152 |
-
|
| 153 |
-
---
|
| 154 |
-
|
| 155 |
-
## Step 4: Run the API Check
|
| 156 |
-
|
| 157 |
-
This tests your key.
|
| 158 |
-
|
| 159 |
-
**Do this:** Click the **Run API Check** button.
|
| 160 |
-
|
| 161 |
-
**You should see (good):** A green message: `AI service is ready.` A **Continue to Dossier** button appears.
|
| 162 |
-
|
| 163 |
-
> ✅ **Success:** You see `AI service is ready.` Click **Continue to Dossier**.
|
| 164 |
-
|
| 165 |
-
**You should see (problem):** A red message, like `The API key or model could not be validated.`
|
| 166 |
-
|
| 167 |
-
If you see a red message, check these:
|
| 168 |
-
|
| 169 |
-
- The key was copied with no extra spaces.
|
| 170 |
-
- The **LLM Provider** matches your key (an OpenAI key needs OpenAI).
|
| 171 |
-
- Your internet works, and your account has credit.
|
| 172 |
-
|
| 173 |
-
Then click **Save Configuration** again, and **Run API Check** again.
|
| 174 |
-
|
| 175 |
-
---
|
| 176 |
-
|
| 177 |
-
## Step 5: Add Your Dossier
|
| 178 |
-
|
| 179 |
-
A **dossier** is just your folder of work files. The app reads it as proof of what you can do.
|
| 180 |
-
|
| 181 |
-
### Put files in one folder
|
| 182 |
-
|
| 183 |
-
Make one folder and put your work files in it. The app can read these types:
|
| 184 |
-
|
| 185 |
-
- PDF (`.pdf`), Word (`.docx`), Text (`.txt`), Markdown (`.md`)
|
| 186 |
-
- Data (`.json`, `.csv`)
|
| 187 |
-
- Images (`.png`, `.jpg`, `.jpeg`)
|
| 188 |
-
|
| 189 |
-
Good things to add: resume / CV, portfolio, testimonials or reviews, case studies, certifications, work samples.
|
| 190 |
-
|
| 191 |
-
> 💡 **Tip:** To try it the first time, use the folder named `sample_dossier` that came with the project.
|
| 192 |
-
|
| 193 |
-
> ⚠️ **Warning:** Do not put private files (passwords, bank files) in this folder.
|
| 194 |
-
|
| 195 |
-
### Get the folder's path
|
| 196 |
-
|
| 197 |
-
A **path** is the full address of a folder. In **File Explorer**, hold **Shift**, right-click the folder, and choose **Copy as path**.
|
| 198 |
-
|
| 199 |
-
> 💡 **Tip:** Folders with spaces in the name (like `My Work Files`) are fine.
|
| 200 |
-
|
| 201 |
-
### Do it in the app
|
| 202 |
-
|
| 203 |
-
On the **Dossier** page:
|
| 204 |
-
|
| 205 |
-
1. Paste the path into the **Dossier folder path** box (**Ctrl + V**).
|
| 206 |
-
2. Click **Continue to Job Screenshot**.
|
| 207 |
-
|
| 208 |
-
The app reads your files behind the scenes and moves you to the next page.
|
| 209 |
-
|
| 210 |
-
> ✅ **Success:** You land on the **Job Screenshot** page.
|
| 211 |
-
|
| 212 |
-
> ⚠️ **Warning:** If you see "Couldn't read that folder", the path was wrong or the folder had no readable files. Re-copy the path and try again.
|
| 213 |
-
|
| 214 |
-
> 📷 *Screenshot placeholder: the Dossier page.*
|
| 215 |
-
|
| 216 |
-
---
|
| 217 |
-
|
| 218 |
-
## Step 6: Add the Job Screenshot
|
| 219 |
-
|
| 220 |
-
A **screenshot** is a picture of your screen. Take a picture of the Upwork job you want.
|
| 221 |
-
|
| 222 |
-
### Take the picture
|
| 223 |
-
|
| 224 |
-
- Press **Windows + Shift + S**. Drag a box around the whole job post.
|
| 225 |
-
- The picture is now copied, ready to paste.
|
| 226 |
-
|
| 227 |
-
> 💡 **Tip:** Capture the whole job post — title, description, skills, and budget.
|
| 228 |
-
|
| 229 |
-
### Add it in the app
|
| 230 |
-
|
| 231 |
-
On the **Job Screenshot** page, you have **two easy ways**:
|
| 232 |
-
|
| 233 |
-
- **Paste it:** click **📋 Paste screenshot from clipboard** (works right after the step above). *— easiest*
|
| 234 |
-
- **Or upload a file:** click **Browse files** and pick a saved picture. Types: **PNG, JPG, JPEG, WEBP**, each **20 MB or smaller**.
|
| 235 |
-
|
| 236 |
-
Then:
|
| 237 |
-
|
| 238 |
-
1. Check it says a screenshot is ready.
|
| 239 |
-
2. Click **Analyze Opportunity**.
|
| 240 |
-
|
| 241 |
-
The app reads the job from your picture and goes straight to the **Analysis** page.
|
| 242 |
-
|
| 243 |
-
> ✅ **Success:** The app reads the picture and shows the Analysis.
|
| 244 |
-
|
| 245 |
-
> ⚠️ **Warning:** If you picked **Groq**, this step won't work. Go back to **Setup** and pick OpenAI, Anthropic, or Gemini.
|
| 246 |
-
|
| 247 |
-
> 💡 **Tip:** If the **📋 Paste** button doesn't pick up your screenshot, save it as a PNG/JPG and use **Browse files** instead — that always works.
|
| 248 |
-
|
| 249 |
-
> 📷 *Screenshot placeholder: the Job Screenshot page.*
|
| 250 |
-
|
| 251 |
-
---
|
| 252 |
-
|
| 253 |
-
## Step 7: Read the Analysis
|
| 254 |
-
|
| 255 |
-
The **Analysis** page compares the job to your files and shows a clear verdict.
|
| 256 |
-
|
| 257 |
-
**You should see:**
|
| 258 |
-
|
| 259 |
-
1. A **Verdict** — one of:
|
| 260 |
-
- 🟢 **Apply Confidently** — a good match; go for it.
|
| 261 |
-
- 🟡 **Proceed With Caution** — possible, but check the concerns first.
|
| 262 |
-
- 🔴 **Do Not Proceed** — likely not worth your connects.
|
| 263 |
-
2. A few **short reasons** for the verdict.
|
| 264 |
-
3. **Key strengths** — why you fit.
|
| 265 |
-
4. **Concerns** — what may count against you.
|
| 266 |
-
5. Sometimes a **Heads up** card naming job details that weren't visible in the picture.
|
| 267 |
-
|
| 268 |
-
**Do this:** Read the results. When ready, click **Continue to Proposal**.
|
| 269 |
-
|
| 270 |
-
> 💡 **Tip:** Click **Re-run Analysis** at the top if you want a fresh result.
|
| 271 |
-
|
| 272 |
-
> 📷 *Screenshot placeholder: the Analysis page with the verdict.*
|
| 273 |
-
|
| 274 |
-
---
|
| 275 |
-
|
| 276 |
-
## Step 8: Generate Your Proposal
|
| 277 |
-
|
| 278 |
-
The **Proposal** page writes your proposal draft, using only facts from your files.
|
| 279 |
-
|
| 280 |
-
**Do this:**
|
| 281 |
-
|
| 282 |
-
1. Click **Generate Proposal**.
|
| 283 |
-
2. Wait a moment for the draft to appear in the box.
|
| 284 |
-
3. Click **Copy Proposal** (to copy) or **Download as .txt** (to save). Paste it into Upwork.
|
| 285 |
-
|
| 286 |
-
> ℹ️ **Note:** If the verdict was 🔴 **Do Not Proceed**, the button is off. To force a draft anyway, first tick **Yes, generate the proposal anyway**.
|
| 287 |
-
|
| 288 |
-
> 💡 **Tip:** This is a first draft. Read it, add your own voice, and fix anything before you send it.
|
| 289 |
-
|
| 290 |
-
> ✅ **Success:** Your proposal draft is ready.
|
| 291 |
-
|
| 292 |
-
> 📷 *Screenshot placeholder: the Proposal page with a finished draft.*
|
| 293 |
-
|
| 294 |
-
---
|
| 295 |
-
|
| 296 |
-
## When You're Done
|
| 297 |
-
|
| 298 |
-
- Close the small **black status window** that opened with the app (the one that says it's running). That quits the app — there's nothing else to stop. You can also close the browser tab.
|
| 299 |
-
- Your AI settings are saved, so next time you only do Steps 5–8.
|
| 300 |
-
|
| 301 |
-
> 💡 **Tip:** Next time, use the shorter [QUICK_START_GUIDE.md](QUICK_START_GUIDE.md) in this same `docs` folder.
|
| 302 |
-
|
| 303 |
-
---
|
| 304 |
-
|
| 305 |
-
## Common Problems and Fixes
|
| 306 |
-
|
| 307 |
-
### Browser didn't open on its own
|
| 308 |
-
|
| 309 |
-
- The small **black window** shows a web address like `http://127.0.0.1:8xxx`. Open your browser (Chrome, Edge, Firefox…) and type that address into the bar.
|
| 310 |
-
- The **first** launch is just slow (it's installing — give it a few minutes, and make sure you have internet that first time). Later launches take only seconds.
|
| 311 |
-
- If you closed the black window, the app stops. Double-click **`Start Upwork Proposal Strategist`** again to reopen it.
|
| 312 |
-
|
| 313 |
-
### "Windows protected your PC"
|
| 314 |
-
|
| 315 |
-
- Expected for a new app that isn't signed yet. Click **More info → Run anyway**. It is safe.
|
| 316 |
-
|
| 317 |
-
### A "Windows Firewall" box about "python"
|
| 318 |
-
|
| 319 |
-
- Normal on the first run. To show its pages in your browser, the app runs a tiny server on **your own computer** — Windows just wants you to okay it. Click **Allow access** (you can leave **Public networks** unchecked).
|
| 320 |
-
- It's safe: nothing is opened to the internet. The server only listens on your own machine (`127.0.0.1`), so even if you click **Cancel**, the app keeps working.
|
| 321 |
-
|
| 322 |
-
### Double-clicking does nothing / the launcher is blocked
|
| 323 |
-
|
| 324 |
-
- If antivirus blocked the launcher, open the app folder, go into the **`scripts`** folder, and double-click **`run.bat`** instead — it does exactly the same thing.
|
| 325 |
-
- If files seem to be missing, your antivirus may have removed them. Re-download the folder, **allow** it when prompted, and fully **Extract All** before opening.
|
| 326 |
-
|
| 327 |
-
### API not working
|
| 328 |
-
|
| 329 |
-
- Re-copy your key (watch for extra spaces).
|
| 330 |
-
- Make sure the **LLM Provider** matches your key.
|
| 331 |
-
- Check your internet and your account credit.
|
| 332 |
-
- Click **Save Configuration**, then **Run API Check** again.
|
| 333 |
-
|
| 334 |
-
### Screenshot not working
|
| 335 |
-
|
| 336 |
-
- Use a **PNG, JPG, JPEG, or WEBP** picture, **20 MB or smaller**.
|
| 337 |
-
- **Groq cannot read pictures.** On **Setup**, set **Vision Provider** to OpenAI, Anthropic, or Gemini.
|
| 338 |
-
- If the **📋 Paste** button doesn't catch your screenshot, save it as a file and use **Browse files**.
|
| 339 |
-
|
| 340 |
-
### I need to change or remove my saved key
|
| 341 |
-
|
| 342 |
-
- Go to **Setup**. Type a new key and **Save Configuration**, or click **Clear Credentials** to wipe the saved key.
|
| 343 |
-
- Your key is stored privately in `%APPDATA%\UpworkProposalStrategist\.env`.
|
| 344 |
-
|
| 345 |
-
### Start over completely
|
| 346 |
-
|
| 347 |
-
- Close the app. Then open **File Explorer**, click the address bar at the top, type `%APPDATA%\UpworkProposalStrategist` and press **Enter** — it opens the app's private folder. Delete that folder (this removes your saved key and logs). Open the app again to set up fresh.
|
| 348 |
-
|
| 349 |
-
---
|
| 350 |
-
|
| 351 |
-
## Safe Practices
|
| 352 |
-
|
| 353 |
-
> ⚠️ **Warning:** Follow these to stay safe.
|
| 354 |
-
|
| 355 |
-
### Never share your API key
|
| 356 |
-
|
| 357 |
-
- It's a secret password. Anyone with it can spend your money.
|
| 358 |
-
- Never put it in chats, emails, or screenshots.
|
| 359 |
-
- If it leaks, delete it on the provider's website and make a new one.
|
| 360 |
-
|
| 361 |
-
### Never share the `.env` file
|
| 362 |
-
|
| 363 |
-
- The app saves your key in a hidden file named `.env` (in `%APPDATA%\UpworkProposalStrategist\` for the installed app).
|
| 364 |
-
- Never send this file to anyone or put it online.
|
| 365 |
-
|
| 366 |
-
### Keep backup copies
|
| 367 |
-
|
| 368 |
-
- Keep a spare copy of your **dossier folder**.
|
| 369 |
-
- Save any proposal drafts you like.
|
| 370 |
-
|
| 371 |
-
---
|
| 372 |
-
|
| 373 |
-
## Mac, or Running From Source
|
| 374 |
-
|
| 375 |
-
This guide is for the **Windows app**. If you're on a **Mac/Linux**, or you're a developer who wants to run the project's source code, see **[DEVELOPER.md](../DEVELOPER.md)**. It explains how to start the app (no conda and no PYTHONPATH needed). Once the app is open in your browser, the in-app steps **3–8 above are identical**.
|
| 376 |
-
|
| 377 |
-
> ℹ️ **Note (for whoever hands this out):** To package it so end users just double-click, see [DEVELOPER.md](../DEVELOPER.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/QUICK_START_GUIDE.md
DELETED
|
@@ -1,65 +0,0 @@
|
|
| 1 |
-
# Quick Start Guide
|
| 2 |
-
|
| 3 |
-
**Use this after your first time** (see `FIRST_TIME_SETUP_GUIDE.md`). It's the short version for using the tool again. If something doesn't work, open the full guide and check **Common Problems and Fixes**.
|
| 4 |
-
|
| 5 |
-
> 💡 **Tip:** There's no terminal and no setup to repeat — your AI key is already saved.
|
| 6 |
-
|
| 7 |
-
---
|
| 8 |
-
|
| 9 |
-
## Open the app
|
| 10 |
-
|
| 11 |
-
- Open the app folder and **double-click `Start Upwork Proposal Strategist`** (or its Desktop shortcut, if you made one).
|
| 12 |
-
- A small status window opens, then the app appears in your **web browser** after a few seconds. Leave that status window open while you use the app.
|
| 13 |
-
|
| 14 |
-
> 🔒 **Tip:** If Windows ever shows a firewall box about **"python"**, click **Allow access** — it's safe (the app only uses your own computer). Clicking Cancel works too.
|
| 15 |
-
|
| 16 |
-
> ℹ️ **Note:** The five pages are on the **left**: **Setup**, **Dossier**, **Job Screenshot**, **Analysis**, **Proposal**. They unlock in order (🔒 is normal).
|
| 17 |
-
|
| 18 |
-
---
|
| 19 |
-
|
| 20 |
-
## The 4 steps
|
| 21 |
-
|
| 22 |
-
### 1. Setup (only if needed)
|
| 23 |
-
|
| 24 |
-
Your AI key is saved from last time, so you can usually skip straight to step 2.
|
| 25 |
-
|
| 26 |
-
- If a page is locked, go to **Setup** and click **Run API Check**. Wait for `AI service is ready.`, then **Continue to Dossier**.
|
| 27 |
-
- Changing services or key? Update the fields, click **Save Configuration**, then **Run API Check**.
|
| 28 |
-
|
| 29 |
-
### 2. Dossier
|
| 30 |
-
|
| 31 |
-
- On **Dossier**, paste your work-files folder path into **Dossier folder path**.
|
| 32 |
-
- Click **Continue to Job Screenshot**.
|
| 33 |
-
|
| 34 |
-
> 💡 **Tip:** Get the path in File Explorer: hold **Shift**, right-click the folder → **Copy as path**.
|
| 35 |
-
|
| 36 |
-
### 3. Job Screenshot
|
| 37 |
-
|
| 38 |
-
- Take a screenshot of the Upwork job: **Windows + Shift + S**, drag a box around the whole post.
|
| 39 |
-
- On **Job Screenshot**, click **📋 Paste screenshot from clipboard** (or **Browse files** for a saved PNG/JPG).
|
| 40 |
-
- Click **Analyze Opportunity**. The app jumps to the Analysis.
|
| 41 |
-
|
| 42 |
-
> ⚠️ **Warning:** The **Groq** provider can't read screenshots. Use **OpenAI**, **Anthropic**, or **Gemini** as your **Vision Provider**.
|
| 43 |
-
|
| 44 |
-
### 4. Analysis → Proposal
|
| 45 |
-
|
| 46 |
-
- On **Analysis**, read the verdict — 🟢 **Apply Confidently** · 🟡 **Proceed With Caution** · 🔴 **Do Not Proceed** — plus your strengths and concerns. Click **Continue to Proposal**.
|
| 47 |
-
- On **Proposal**, click **Generate Proposal**, then **Copy Proposal** or **Download as .txt**.
|
| 48 |
-
|
| 49 |
-
> ℹ️ **Note:** If the verdict is 🔴 **Do Not Proceed**, tick **Yes, generate the proposal anyway** to enable the button.
|
| 50 |
-
|
| 51 |
-
> ✅ **Success:** You have a finished proposal draft. Review it, add your own voice, and send it on Upwork.
|
| 52 |
-
|
| 53 |
-
---
|
| 54 |
-
|
| 55 |
-
## When you're done
|
| 56 |
-
|
| 57 |
-
- Close the small **status window** that opened with the app (the one that says it's running). That quits the app. You can also close the browser tab.
|
| 58 |
-
|
| 59 |
-
> 💡 **Tip:** Never share your API key or your `.env` file. See **Safe Practices** in `FIRST_TIME_SETUP_GUIDE.md`.
|
| 60 |
-
|
| 61 |
-
---
|
| 62 |
-
|
| 63 |
-
## Mac, or running from source?
|
| 64 |
-
|
| 65 |
-
This is for the Windows app. To run from the source code (Mac/Linux or developers), see **[../DEVELOPER.md](../DEVELOPER.md)** — no conda or PYTHONPATH needed. The 4 steps above are the same once the app is open in your browser.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pyproject.toml
DELETED
|
@@ -1,37 +0,0 @@
|
|
| 1 |
-
# Packaging metadata for the Upwork Proposal Strategist.
|
| 2 |
-
#
|
| 3 |
-
# Purpose: make the project a PROPER importable package so the app runs with
|
| 4 |
-
# NO manual PYTHONPATH. After `pip install -e .` the `app` package and the
|
| 5 |
-
# `desktop_app` launcher import from anywhere.
|
| 6 |
-
#
|
| 7 |
-
# Dependencies are intentionally NOT duplicated here. The pinned, single source
|
| 8 |
-
# of truth is requirements.txt, which is what the first-run bootstrap installs.
|
| 9 |
-
# Declaring them again here would invite drift. The app runs in the browser, so
|
| 10 |
-
# there is no native-window dependency stack to mirror.
|
| 11 |
-
|
| 12 |
-
[build-system]
|
| 13 |
-
requires = ["setuptools>=68"]
|
| 14 |
-
build-backend = "setuptools.build_meta"
|
| 15 |
-
|
| 16 |
-
[project]
|
| 17 |
-
name = "upwork-proposal-strategist"
|
| 18 |
-
version = "1.0.0"
|
| 19 |
-
description = "Local-first desktop app that scores Upwork jobs against your dossier and drafts grounded proposals."
|
| 20 |
-
readme = "README.md"
|
| 21 |
-
requires-python = ">=3.11"
|
| 22 |
-
dependencies = [] # see requirements.txt (pinned, single source of truth)
|
| 23 |
-
|
| 24 |
-
[project.scripts]
|
| 25 |
-
# Lets a developer launch the app (browser) with a bare `upwork-strategist`.
|
| 26 |
-
upwork-strategist = "desktop_app:main"
|
| 27 |
-
|
| 28 |
-
[tool.setuptools]
|
| 29 |
-
# desktop_app.py lives at the repo root as a single module (the launcher).
|
| 30 |
-
py-modules = ["desktop_app"]
|
| 31 |
-
|
| 32 |
-
[tool.setuptools.packages.find]
|
| 33 |
-
# Ship the application package (and its subpackages); leave tests out.
|
| 34 |
-
include = ["app*"]
|
| 35 |
-
|
| 36 |
-
[tool.pytest.ini_options]
|
| 37 |
-
testpaths = ["tests"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/ensure_runtime_mac.sh
DELETED
|
@@ -1,123 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env bash
|
| 2 |
-
# ===========================================================================
|
| 3 |
-
# ensure_runtime_mac.sh — first-run setup for macOS.
|
| 4 |
-
#
|
| 5 |
-
# What it does:
|
| 6 |
-
# 1. Verifies Python 3.9+ is available on the system.
|
| 7 |
-
# 2. Creates a local virtual environment at runtime_mac/venv/ (once).
|
| 8 |
-
# 3. Installs the app's libraries from requirements.txt (once).
|
| 9 |
-
# 4. Writes a success marker (runtime_mac/.deps_installed) so subsequent
|
| 10 |
-
# launches skip all of this — instant no-op.
|
| 11 |
-
#
|
| 12 |
-
# Idempotent: safe to run multiple times. A partial/failed run leaves no
|
| 13 |
-
# marker, so it resumes cleanly on the next launch.
|
| 14 |
-
#
|
| 15 |
-
# Called by run_mac.sh. Never run this directly.
|
| 16 |
-
# ===========================================================================
|
| 17 |
-
|
| 18 |
-
set -euo pipefail
|
| 19 |
-
|
| 20 |
-
# Resolve the project root (this script lives in <root>/scripts/).
|
| 21 |
-
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
| 22 |
-
ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
|
| 23 |
-
|
| 24 |
-
VENV="$ROOT/runtime_mac/venv"
|
| 25 |
-
MARKER="$ROOT/runtime_mac/.deps_installed"
|
| 26 |
-
REQ="$ROOT/requirements.txt"
|
| 27 |
-
PYEXE="$VENV/bin/python"
|
| 28 |
-
|
| 29 |
-
# ── Fast path: already set up ─────────────────────────────────────────────
|
| 30 |
-
if [ -f "$MARKER" ]; then
|
| 31 |
-
exit 0
|
| 32 |
-
fi
|
| 33 |
-
|
| 34 |
-
# ── Find Python 3.9+ ──────────────────────────────────────────────────────
|
| 35 |
-
find_python() {
|
| 36 |
-
for cmd in python3.12 python3.11 python3.10 python3.9 python3; do
|
| 37 |
-
if command -v "$cmd" &>/dev/null; then
|
| 38 |
-
ver=$("$cmd" -c "import sys; print(sys.version_info >= (3,9))" 2>/dev/null)
|
| 39 |
-
if [ "$ver" = "True" ]; then
|
| 40 |
-
echo "$cmd"
|
| 41 |
-
return 0
|
| 42 |
-
fi
|
| 43 |
-
fi
|
| 44 |
-
done
|
| 45 |
-
return 1
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
if ! PYTHON=$(find_python); then
|
| 49 |
-
echo ""
|
| 50 |
-
echo "============================================================"
|
| 51 |
-
echo " Python 3.9 or newer is required but was not found."
|
| 52 |
-
echo ""
|
| 53 |
-
echo " To install Python on your Mac:"
|
| 54 |
-
echo " 1. Go to https://www.python.org/downloads/"
|
| 55 |
-
echo " 2. Download the latest macOS installer"
|
| 56 |
-
echo " 3. Run the installer, then launch this app again."
|
| 57 |
-
echo ""
|
| 58 |
-
echo " (Alternatively, if you have Homebrew: brew install python3)"
|
| 59 |
-
echo "============================================================"
|
| 60 |
-
echo ""
|
| 61 |
-
exit 1
|
| 62 |
-
fi
|
| 63 |
-
|
| 64 |
-
echo ""
|
| 65 |
-
echo "============================================================"
|
| 66 |
-
echo " Upwork Proposal Strategist - one-time setup"
|
| 67 |
-
echo ""
|
| 68 |
-
echo " This runs ONCE and downloads the app's libraries"
|
| 69 |
-
echo " (about 150 MB). It usually takes 2-5 minutes the first"
|
| 70 |
-
echo " time, depending on your internet speed."
|
| 71 |
-
echo ""
|
| 72 |
-
echo " >> Progress is shown below. This is NOT stuck - please"
|
| 73 |
-
echo " wait until you see 'Setup complete.'"
|
| 74 |
-
echo "============================================================"
|
| 75 |
-
echo ""
|
| 76 |
-
|
| 77 |
-
_fail() {
|
| 78 |
-
echo ""
|
| 79 |
-
echo "============================================================"
|
| 80 |
-
echo " SETUP DID NOT COMPLETE"
|
| 81 |
-
echo " $1"
|
| 82 |
-
echo ""
|
| 83 |
-
echo " Check your internet connection and try launching again -"
|
| 84 |
-
echo " setup resumes safely from where it stopped."
|
| 85 |
-
echo " (For full details, use 'Troubleshoot (Mac).command'.)"
|
| 86 |
-
echo "============================================================"
|
| 87 |
-
echo ""
|
| 88 |
-
exit 1
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
using_python_version="$("$PYTHON" -c 'import sys;print(".".join(map(str,sys.version_info[:3])))' 2>/dev/null)"
|
| 92 |
-
echo "[setup] Using Python $using_python_version ($PYTHON)"
|
| 93 |
-
|
| 94 |
-
# ── Create the virtual environment ────────────────────────────────────────
|
| 95 |
-
mkdir -p "$ROOT/runtime_mac"
|
| 96 |
-
echo "[setup] Creating an isolated Python environment..."
|
| 97 |
-
# Explicit failure handling: with 'set -e', a bare failing command would exit
|
| 98 |
-
# before our friendly message — 'if !' keeps the message visible.
|
| 99 |
-
if ! "$PYTHON" -m venv "$VENV"; then
|
| 100 |
-
_fail "Could not create the Python environment (python3 -m venv failed)."
|
| 101 |
-
fi
|
| 102 |
-
if [ ! -x "$PYEXE" ]; then
|
| 103 |
-
_fail "The Python environment is missing its interpreter at $PYEXE."
|
| 104 |
-
fi
|
| 105 |
-
|
| 106 |
-
# ── Upgrade pip inside the venv ────────────────────────────────────────────
|
| 107 |
-
echo "[setup] Preparing the installer (pip)..."
|
| 108 |
-
if ! "$PYEXE" -m pip install --upgrade pip --disable-pip-version-check; then
|
| 109 |
-
_fail "Could not prepare pip (the installer)."
|
| 110 |
-
fi
|
| 111 |
-
|
| 112 |
-
# ── Install dependencies (progress shown — NOT silent) ─────────────────────
|
| 113 |
-
echo "[setup] Downloading and installing the app libraries now..."
|
| 114 |
-
echo "[setup] (This is the slow part. Watch the progress below.)"
|
| 115 |
-
if ! "$PYEXE" -m pip install -r "$REQ" --disable-pip-version-check; then
|
| 116 |
-
_fail "Could not download/install the app libraries."
|
| 117 |
-
fi
|
| 118 |
-
|
| 119 |
-
# ── Write success marker (LAST, so a failed run safely retries) ───────────
|
| 120 |
-
echo "installed $(date)" > "$MARKER"
|
| 121 |
-
echo ""
|
| 122 |
-
echo "[setup] Setup complete. Starting the app..."
|
| 123 |
-
echo ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/package.sh
DELETED
|
@@ -1,88 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env bash
|
| 2 |
-
# ===========================================================================
|
| 3 |
-
# package.sh - build a clean, ready-to-send client ZIP in ONE command.
|
| 4 |
-
#
|
| 5 |
-
# Does everything for you, so nothing has to be excluded by hand:
|
| 6 |
-
# 1. stages the bundled Windows Python into runtime\ (via prepare_bundle.sh),
|
| 7 |
-
# 2. copies the project into a clean folder, EXCLUDING your secrets and
|
| 8 |
-
# dev-machine junk (.env / API key, .git, .vscode, .claude, caches, logs,
|
| 9 |
-
# tests, your personal sample_dossier files…),
|
| 10 |
-
# 3. zips it to dist/UpworkProposalStrategist.zip,
|
| 11 |
-
# 4. verifies the zip contains NO .env before declaring success.
|
| 12 |
-
#
|
| 13 |
-
# Send dist/UpworkProposalStrategist.zip as-is.
|
| 14 |
-
#
|
| 15 |
-
# Requirements: rsync, zip, curl, unzip (all ship with macOS).
|
| 16 |
-
# Usage: bash scripts/package.sh
|
| 17 |
-
# ===========================================================================
|
| 18 |
-
set -euo pipefail
|
| 19 |
-
|
| 20 |
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 21 |
-
ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
| 22 |
-
|
| 23 |
-
NAME="UpworkProposalStrategist" # client-facing folder + zip name
|
| 24 |
-
DIST="${ROOT}/dist"
|
| 25 |
-
STAGE="${DIST}/${NAME}"
|
| 26 |
-
ZIP="${DIST}/${NAME}.zip"
|
| 27 |
-
|
| 28 |
-
# --- 1. Make sure the bundled runtime is staged ----------------------------
|
| 29 |
-
bash "${SCRIPT_DIR}/prepare_bundle.sh"
|
| 30 |
-
|
| 31 |
-
# --- 2. Clean staging copy (exclude secrets + dev junk) --------------------
|
| 32 |
-
echo
|
| 33 |
-
echo "[package] Building a clean copy (excluding your .env / API key and dev files)..."
|
| 34 |
-
rm -rf "${STAGE}"
|
| 35 |
-
mkdir -p "${STAGE}"
|
| 36 |
-
|
| 37 |
-
rsync -a \
|
| 38 |
-
--exclude '.git/' \
|
| 39 |
-
--exclude '.gitignore' \
|
| 40 |
-
--exclude '.env' \
|
| 41 |
-
--exclude '.env.tmp' \
|
| 42 |
-
--exclude '.env.local' \
|
| 43 |
-
--exclude '.env.production' \
|
| 44 |
-
--exclude '.vscode/' \
|
| 45 |
-
--exclude '.claude/' \
|
| 46 |
-
--exclude '.DS_Store' \
|
| 47 |
-
--exclude '__pycache__/' \
|
| 48 |
-
--exclude '*.pyc' \
|
| 49 |
-
--exclude '.pytest_cache/' \
|
| 50 |
-
--exclude '.mypy_cache/' \
|
| 51 |
-
--exclude '.ruff_cache/' \
|
| 52 |
-
--exclude 'logs/' \
|
| 53 |
-
--exclude 'dist/' \
|
| 54 |
-
--exclude 'tests/' \
|
| 55 |
-
--exclude 'sample_dossier/' \
|
| 56 |
-
--exclude '.python-embed.zip' \
|
| 57 |
-
"${ROOT}/" "${STAGE}/"
|
| 58 |
-
|
| 59 |
-
# Re-add the two things we DO want that live under excluded patterns/paths:
|
| 60 |
-
# - .env.example: the template the app copies to create the user's .env.
|
| 61 |
-
# - the dossier TEMPLATE only (never the user's personal sample files).
|
| 62 |
-
cp "${ROOT}/.env.example" "${STAGE}/.env.example"
|
| 63 |
-
mkdir -p "${STAGE}/sample_dossier"
|
| 64 |
-
cp "${ROOT}/sample_dossier/TEMPLATE_dossier.json" "${STAGE}/sample_dossier/TEMPLATE_dossier.json"
|
| 65 |
-
|
| 66 |
-
# Safety net: never ship a real .env even if one ever slipped through.
|
| 67 |
-
rm -f "${STAGE}/.env"
|
| 68 |
-
|
| 69 |
-
# --- 3. Zip it -------------------------------------------------------------
|
| 70 |
-
echo "[package] Zipping..."
|
| 71 |
-
rm -f "${ZIP}"
|
| 72 |
-
( cd "${DIST}" && zip -r -q -X "${NAME}.zip" "${NAME}" )
|
| 73 |
-
rm -rf "${STAGE}"
|
| 74 |
-
|
| 75 |
-
SIZE="$(du -h "${ZIP}" | cut -f1 | tr -d ' ')"
|
| 76 |
-
|
| 77 |
-
# --- 4. Verify no secret leaked into the zip -------------------------------
|
| 78 |
-
if unzip -l "${ZIP}" | grep -Eq '/\.env$'; then
|
| 79 |
-
echo "[package] !! WARNING: a .env file is inside the zip - DO NOT SEND IT." >&2
|
| 80 |
-
exit 1
|
| 81 |
-
fi
|
| 82 |
-
|
| 83 |
-
echo
|
| 84 |
-
echo "[package] Done -> dist/${NAME}.zip (${SIZE})"
|
| 85 |
-
echo "[package] Verified: no .env (API key) inside. Safe to send."
|
| 86 |
-
echo
|
| 87 |
-
echo "Send dist/${NAME}.zip to your client. They unzip it and double-click"
|
| 88 |
-
echo "\"Start Upwork Proposal Strategist\". Re-run this script after any change."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/prepare_bundle.sh
DELETED
|
@@ -1,53 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env bash
|
| 2 |
-
# ===========================================================================
|
| 3 |
-
# prepare_bundle.sh - (re)create the bundled Windows runtime in runtime/.
|
| 4 |
-
#
|
| 5 |
-
# You normally DON'T need this: runtime/ (the embeddable Windows Python +
|
| 6 |
-
# get-pip.py) is committed to the repo, so a fresh checkout already has it. Use
|
| 7 |
-
# this only to rebuild a deleted/corrupted runtime, or to bump the pinned Python
|
| 8 |
-
# version (edit PYVER, delete runtime/, then re-run).
|
| 9 |
-
#
|
| 10 |
-
# It downloads + unzips the embeddable Windows Python and get-pip.py into
|
| 11 |
-
# runtime/. The files are Windows binaries - we only stage them here, never
|
| 12 |
-
# execute them on macOS/Linux. The client never downloads an interpreter (the
|
| 13 |
-
# part antivirus/SmartScreen flags); their first run only pip-installs the
|
| 14 |
-
# libraries from PyPI (internet needed once).
|
| 15 |
-
#
|
| 16 |
-
# Requirements: curl and unzip (both ship with macOS).
|
| 17 |
-
# Usage: bash scripts/prepare_bundle.sh
|
| 18 |
-
# ===========================================================================
|
| 19 |
-
set -euo pipefail
|
| 20 |
-
|
| 21 |
-
PYVER="3.11.9" # keep in step with scripts/ensure_runtime.bat (python311._pth)
|
| 22 |
-
PYZIP="python-${PYVER}-embed-amd64.zip"
|
| 23 |
-
PYURL="https://www.python.org/ftp/python/${PYVER}/${PYZIP}"
|
| 24 |
-
GETPIP_URL="https://bootstrap.pypa.io/get-pip.py"
|
| 25 |
-
|
| 26 |
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 27 |
-
ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
| 28 |
-
RT="${ROOT}/runtime"
|
| 29 |
-
|
| 30 |
-
mkdir -p "${RT}"
|
| 31 |
-
|
| 32 |
-
# --- 1. Embeddable Python -> runtime/ --------------------------------------
|
| 33 |
-
if [ -f "${RT}/python.exe" ]; then
|
| 34 |
-
echo "[prepare] runtime/python.exe already present - skipping Python download."
|
| 35 |
-
else
|
| 36 |
-
echo "[prepare] Downloading embeddable Python ${PYVER} (Windows)..."
|
| 37 |
-
TMPZIP="${RT}/.python-embed.zip"
|
| 38 |
-
curl -fSL "${PYURL}" -o "${TMPZIP}"
|
| 39 |
-
echo "[prepare] Extracting into runtime/ ..."
|
| 40 |
-
unzip -o -q "${TMPZIP}" -d "${RT}"
|
| 41 |
-
rm -f "${TMPZIP}"
|
| 42 |
-
fi
|
| 43 |
-
|
| 44 |
-
# --- 2. get-pip.py -> runtime/ ---------------------------------------------
|
| 45 |
-
if [ -f "${RT}/get-pip.py" ]; then
|
| 46 |
-
echo "[prepare] runtime/get-pip.py already present - skipping."
|
| 47 |
-
else
|
| 48 |
-
echo "[prepare] Downloading get-pip.py..."
|
| 49 |
-
curl -fSL "${GETPIP_URL}" -o "${RT}/get-pip.py"
|
| 50 |
-
fi
|
| 51 |
-
|
| 52 |
-
echo "[prepare] runtime/ staged ($(ls -1 "${RT}" | wc -l | tr -d ' ') files, incl. python.exe + get-pip.py)."
|
| 53 |
-
echo "[prepare] To build a clean, ready-to-send zip, run: bash scripts/package.sh"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/run_mac.sh
DELETED
|
@@ -1,64 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env bash
|
| 2 |
-
# ===========================================================================
|
| 3 |
-
# run_mac.sh — launch the Upwork Proposal Strategist on macOS.
|
| 4 |
-
#
|
| 5 |
-
# Usage:
|
| 6 |
-
# ./scripts/run_mac.sh normal mode (clean output)
|
| 7 |
-
# ./scripts/run_mac.sh debug debug mode (full Streamlit logs)
|
| 8 |
-
#
|
| 9 |
-
# Called by the .command launchers in the project root.
|
| 10 |
-
# ===========================================================================
|
| 11 |
-
|
| 12 |
-
set -uo pipefail
|
| 13 |
-
|
| 14 |
-
# Resolve the project root (this script lives in <root>/scripts/).
|
| 15 |
-
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
| 16 |
-
ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
|
| 17 |
-
|
| 18 |
-
VENV="$ROOT/runtime_mac/venv"
|
| 19 |
-
PYEXE="$VENV/bin/python"
|
| 20 |
-
|
| 21 |
-
cd "$ROOT"
|
| 22 |
-
|
| 23 |
-
# Sanity check: confirm the folder was fully extracted.
|
| 24 |
-
if [ ! -f "desktop_app.py" ]; then
|
| 25 |
-
echo ""
|
| 26 |
-
echo "============================================================"
|
| 27 |
-
echo " Core files are missing — this folder looks incomplete."
|
| 28 |
-
echo ""
|
| 29 |
-
echo " Please make sure you extracted the FULL zip folder"
|
| 30 |
-
echo " (right-click → 'Open With Archive Utility' on Mac),"
|
| 31 |
-
echo " then launch the app from the extracted folder."
|
| 32 |
-
echo "============================================================"
|
| 33 |
-
echo ""
|
| 34 |
-
exit 1
|
| 35 |
-
fi
|
| 36 |
-
|
| 37 |
-
# Tell the app this is a packaged desktop run.
|
| 38 |
-
export UPS_PACKAGED=1
|
| 39 |
-
|
| 40 |
-
# Debug mode raises the log level.
|
| 41 |
-
if [ "${1:-}" = "debug" ]; then
|
| 42 |
-
export UPS_DEBUG=1
|
| 43 |
-
fi
|
| 44 |
-
|
| 45 |
-
# First-run setup (no-op once installed).
|
| 46 |
-
bash "$SCRIPT_DIR/ensure_runtime_mac.sh"
|
| 47 |
-
if [ $? -ne 0 ]; then
|
| 48 |
-
echo ""
|
| 49 |
-
echo "Setup did not complete. Please try launching again."
|
| 50 |
-
exit 1
|
| 51 |
-
fi
|
| 52 |
-
|
| 53 |
-
echo ""
|
| 54 |
-
echo " ============================================================"
|
| 55 |
-
echo " Upwork Proposal Strategist is running."
|
| 56 |
-
echo " Your web browser will open in a few seconds."
|
| 57 |
-
echo ""
|
| 58 |
-
echo " >> KEEP THIS WINDOW OPEN while you use the app."
|
| 59 |
-
echo " >> To quit the app, close this window."
|
| 60 |
-
echo " ============================================================"
|
| 61 |
-
echo ""
|
| 62 |
-
|
| 63 |
-
# Run the app. This call blocks until the window is closed.
|
| 64 |
-
"$PYEXE" "desktop_app.py"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|