v1.5.0

iOfficeAI/OfficeCLIv1.5.0Aug 12, 2026by MadsLorentzen

Upgrading?

Key Highlights

  • New 'Cross-scan' workflow to pivot results and rate hits with confidence scores.
  • Added Tinder and Andele Mandele username modules.
  • Added `impersonate_request_async` for non-blocking email module execution.
  • Parallelized profile photo downloading for faster PDF reports.

New Features

  • Cross-scan workflow.
  • Tinder username module.
  • Andele Mandele username module.
  • New email modules (asafeer, tatacliq, couplejoy, lovenudge).
  • Async request handling for email modules.
  • PDF improvements (parallel download).

Full Release Notes

Two themes this release: **your fork now finds out what upstream did**, and **the security guard watches the one key in `settings.json` it used to ignore**.

## Highlights

### Commit-level upstream triage for forks (#305, #320)
`tools/upstream_triage.py` walks the commits your fork is behind and sorts them into "worth reviewing" vs "probably skip". Cherry-picks you already ported drop off on their own (matched by `git patch-id`, no bookkeeping), commits touching files you removed are set aside, and a flat `.github/upstream-wontport.txt` keeps declined SHAs from resurfacing. It ships with a weekly `.github/workflows/upstream-watch.yml` that posts the report to a rolling issue in *your own fork* - guarded to no-op on the upstream template (pinned by a test), `GITHUB_TOKEN` only, and report-only by design: it prints ready-to-run `git cherry-pick` lines but never merges or opens a PR, because on a fork "applies cleanly" isn't "correct". It's the commit-history companion to `check_upstream_updates.py`'s version stamps; SETUP.md 8 now introduces the two side by side. Thanks @anjolok1997 - from design discussion (#305) to shipped PR (#320).

### `.claude/settings.json` hooks are now held to a reviewed allowlist (#313)
The security guard read `permissions.allow` and nothing else, so a `hooks` block in the same file passed silently. A hook is strictly more dangerous than a pre-approved permission: it runs unconditionally when its event fires, with no prompt and no model decision in between - the exact vector the Shai-Hulud worm's August 2026 wave used, planting a `SessionStart` hook in `.claude/settings.json`. `ALLOWED_HOOKS` ships empty, the check runs before the permissions shape guards so a malformed block can't skip it, and unrecognised hook layouts fail closed. Thanks @Haseeb-1698.

### One tracker status vocabulary, defined once (#298, #299)
The tracker CSV's `status` column had six independent restatements with inconsistent spellings, and two real bugs to show for it: `/html-report` silently dropped `no response`/`offer declined` rows from the rejection-rate denominator, and `/gmail-sync` kept chasing applications that were already closed. `/outcome` (the only writer) now owns the canonical set; every reader references it and accepts both spellings on read. Diagnosis @jakob1379, implementation @oscarbol09.

### `/apply` archives the posting while it still holds it (#306, #307)
The full posting text now lands in `documents/applications/<company>_<role>/job_posting.md` at draft time - never a re-fetch of a URL the spec itself expects to die, and pasted-text postings finally have a recovery path. Diagnosis and implementation @jakob1379.

## Also in this release

- **CI discovers portal CLIs dynamically** (#310) - a portal added with `/add-portal` gets its typecheck and tests run by CI automatically, on this repo and on any fork, no workflow edit. Thanks @ayobamiseun.
- **`/upskill` reports gitignored at the path the skill actually writes them** (#317) - the old rule was rooted; the report (your skill gaps, measured against named employers) could land unignored one directory deeper. New rule pins the report-file prefix at both depths, guard-enforced. Thanks @ayobamiseun.
- **Phantom `evaluated` status dropped from `seen_jobs.json`'s documented vocabulary** (#315, #316) - it never had a writer or a reader. Thanks @ayobamiseun.
- **`/add-portal` credential spec** (#304) - if a portal is only reachable through a paid fetching service, the command now surfaces the billing *before* scaffolding, requires the token env-only (`<SERVICE>_API_TOKEN`), and keeps the whole path explicitly subordinate to the robots/ToS decision. Spec only; no shipped portal needs a credential. Thanks @Haseeb-1698.
- **`/add-portal`'s fetching contract line now states the honest-UA posture** - generated skills default to `Mozilla/5.0 (compatible; <portal>-cli/1.0)`, the convention every shipped CLI follows.

## Updating a personalized fork

Versioned files touched since v1.4.0:

| File | v1.4.0 | v1.5.0 |
|---|---|---|
| `job-application-assistant/SKILL.md` | 1.3.0 | 1.3.2 |

Run `python3 tools/check_upstream_updates.py` for per-file diffs - and from this release, `python3 tools/upstream_triage.py` for the commit-level view.

Three things to check after pulling:

1. **Status spellings** - if your personalized `/outcome` writes `no response` or `offer declined` (space forms), switch the write path to `no_response`/`offer_declined`. Existing rows keep working; every reader now accepts both forms on read.
2. **`upstream-watch.yml` arrives in-tree** - it no-ops on the upstream template and only runs on your fork if you enable Actions, so activation stays a deliberate act. Don't want it? Delete the file or add its SHA to `.github/upstream-wontport.txt`.
3. **New required gitignore rule** - `**/upskill/report-*.md` is now in `REQUIRED_IGNORE_RULES`; if your fork trimmed `.gitignore`, `security_guards.py` will name what's missing.

## Thanks

@anjolok1997, @Haseeb-1698, @ayobamiseun, @jakob1379, @oscarbol09 - five contributors, and every item above started as a community issue, discussion, or PR.

Related Repositories