ananyajoshi commited on
Commit
607beb5
·
verified ·
1 Parent(s): f9a06ca

Clean deployment: add PDF user guide, drop desktop/dev-only files

Browse files
.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
- ## Running locally
 
 
 
49
 
50
- This same code also runs as a local desktop app where you point it at a folder
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"