windowsweep 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -133,7 +133,7 @@ answered by a script, so `--elevate` does not belong in an unattended context.
133
133
  - - **Protected paths are refused regardless of flags**: every drive root, fifteen declared roots (Windows, System32, SysWOW64, both Program Files folders, ProgramData, `C:\Users` with Default and Public, the profile root, and the AppData Roaming, Local and LocalLow folders), 66 protected subtrees, 50 path patterns and 13 file names. Two paths are declared exceptions - `%LOCALAPPDATA%\Android\Sdk\.temp` and `.downloadIntermediates` - and there are no others. No flag bypasses any of it.
134
134
  - **Junctions and symlinks are removed as links, never followed.**
135
135
  - **`--dry-run` and `--scan` write nothing** but the log and the report.
136
- - **Section numbers are a public contract.** 0-29 in the 1.4.0 source; a section may be retired as a no-op, and a number is
136
+ - **Section numbers are a public contract.** 0-29 in the current source; a section may be retired as a no-op, and a number is
137
137
  never reused. Read `--list --json` rather than hard-coding the set.
138
138
  - **Zero runtime dependencies.** The package is PowerShell plus a Node launcher that uses only built-in
139
139
  modules.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,41 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [1.4.2] - 2026-11-05
13
+
14
+ ### Added
15
+
16
+ - **Desktop tours.** Each main screen has **Tour this page** in the header. Its steps name the controls on that
17
+ screen, skip any that are hidden in your current state, and close before a run starts. **Walk through
18
+ windowsweep** shows the path from scan to report in seven screens captured from 1.4.1. Neither starts a
19
+ cleanup or spends a real-run claim.
20
+
21
+ ### Changed
22
+
23
+ - **The desktop app installs its updates.** When the update check finds a newer build, the app downloads it in the
24
+ background and installs it when you close the window, so the next start is the new version. **Install and
25
+ restart** still installs at once. A run in progress is never interrupted for an update.
26
+ - **An elevated run's log reaches the window as it is written.** The app watches the run folder instead of
27
+ re-reading it every 0.7 seconds.
28
+
29
+ ### Fixed
30
+
31
+ - **Release notices follow the newest record.** A notice the app stored no longer outranks a newer, lower release
32
+ record, so a corrected release takes effect on the next check, and a record at or below the running version
33
+ clears the notice.
34
+ - The command-line tool is unchanged in this release. It moves to 1.4.2 so the CLI and the desktop app stay on one
35
+ version.
36
+
37
+ ## [1.4.1] - 2026-11-03
38
+
39
+ ### Fixed
40
+
41
+ - The desktop app now reads published release records on start, resume and reconnect. An urgent release blocks an older build immediately; a normal release is announced immediately and required from the next start. Its Update button uses the signed updater.
42
+ - Removed the desktop updater's six-hour network timer. Release broadcasts and lifecycle events trigger the checks.
43
+ - Corrected the package's release notes and documentation for section 19's Recycle Bin rule and section 26's permanent uninstall action.
44
+
45
+ ## [1.4.0] - 2026-11-03
46
+
12
47
  ### Added
13
48
 
14
49
  - **Section 26 - uninstall the programs you pick.** It lists the installed programs - never Windows parts,
@@ -46,7 +81,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
46
81
  any age) or old (untouched for `--days`, at any size) - each file once, with its reason, age and the timestamps
47
82
  that age came from. A folder is refused as a root when it is a drive root, your profile folder, inside Windows,
48
83
  Program Files, ProgramData or AppData, a link, or protected. Removal stays yours: exact files only, never by
49
- `--yes`, the Recycle Bin by default - and a file that changed after the list was made, or is now reached through a
84
+ `--yes`, always the Recycle Bin - and a file that changed after the list was made, or is now reached through a
50
85
  link, is refused. File names and paths stay on your machine. `--json` candidates for section 19 add `reason`
51
86
  (`large`/`old`/`both`), `age_basis` (`write` or `write+access`), `modified`, `root` and `selectable` (with
52
87
  `skip_reason` and `index` 0 for a file that cannot be picked); the summary adds `personal_roots`.
package/README.md CHANGED
@@ -18,10 +18,11 @@
18
18
 
19
19
  > [!IMPORTANT]
20
20
  > **This tool deletes files.** Most of what it removes are caches that rebuild themselves; personal files it
21
- > lists go to the Recycle Bin; and two sections - emptying the Recycle Bin, clearing event logs - are
22
- > permanent, with no undo of any kind. Those two sit behind `--i-understand-deep`, along with the hibernation
21
+ > lists go to the Recycle Bin; emptying the Recycle Bin and clearing event logs have no undo. Those two sit
22
+ > behind `--i-understand-deep`, along with the hibernation
23
23
  > file and disk-image compaction. Start with `npx windowsweep --scan`, which deletes nothing, then
24
24
  > `npx windowsweep --dry-run --all --yes`, which shows exactly what a real run would remove.
25
+ > Uninstalling a program you select in section 26 is permanent too.
25
26
  > **Windows only** - npm refuses to install it elsewhere.
26
27
 
27
28
  What it reclaims is the disk space that quietly disappears on a Windows machine: package-manager and build caches,
@@ -35,14 +36,14 @@ It is the Windows member of a family with [linux-cleanup](https://github.com/aon
35
36
 
36
37
  | | |
37
38
  |---|---|
38
- | **Version** | `1.4.0` |
39
+ | **Version** | `1.4.2` |
39
40
  | **License** | MIT |
40
41
  | **Node** | `>=14` (launcher only) |
41
42
  | **Runtime** | Windows PowerShell 5.1 (built in) or PowerShell 7 |
42
43
  | **Platforms** | Windows 10 (1809+) and Windows 11 |
43
44
  | **Install size** | ~167 kB packed · ~560 kB unpacked · 53 files · no dependencies |
44
45
  | **Undo** | Recycle Bin for personal files; none for caches (they regenerate) |
45
- | **Status** | Stable · 1.4.0, released 2026-11-03 |
46
+ | **Status** | Stable · 1.4.2, released 2026-11-05 |
46
47
 
47
48
  <a id="table-of-contents"></a>
48
49
  ## 🧭 Table of Contents&nbsp;[#](#table-of-contents)
@@ -201,7 +202,7 @@ section with `a` run / `s` skip / `q` quit and a running total.
201
202
 
202
203
  ### The sections
203
204
 
204
- This table describes version 1.4.0.
205
+ This table describes version 1.4.2.
205
206
 
206
207
  **Tier** says what happens to the data: *report* deletes nothing, *rebuilds* comes back on its own, *slow*
207
208
  comes back but costs minutes, *Recycle Bin* is recoverable until you empty it (`--list` shows this tier as
@@ -458,8 +459,10 @@ More: [FAQ](https://windowsweep-docs.aoneahsan.com/faq).
458
459
  <a id="changelog"></a>
459
460
  ## 🔄 Changelog&nbsp;[#](#changelog)
460
461
 
461
- Latest release: **`1.4.0`** - scan Downloads and folders you add for large or old files, then choose individual
462
- files for the Recycle Bin. The new uninstall and leftover-folder sections also require explicit selection.
462
+ Latest release: **`1.4.2`** - the desktop app adds page tours and a seven-step walkthrough, and installs a
463
+ found update when you close it. The 1.4 scanner searches Downloads and folders you add for large or old files;
464
+ you choose individual files for the Recycle Bin. The uninstall and leftover-folder sections require explicit
465
+ selection.
463
466
  Full history: [CHANGELOG.md](https://github.com/aoneahsan/windowsweep/blob/main/CHANGELOG.md).
464
467
 
465
468
  <a id="contributing"></a>
package/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.0
1
+ 1.4.2
package/lib/actions.ps1 CHANGED
@@ -107,7 +107,7 @@ function Test-KnownCacheLeaf {
107
107
  }
108
108
 
109
109
  function Add-CacheLeafExceptions {
110
- <# .SYNOPSIS D79 (1.4.0): lift the protected lists (lib/safety.ps1) for EXACTLY these folders, each of which must
110
+ <# .SYNOPSIS D79 (P9): lift the protected lists (lib/safety.ps1) for EXACTLY these folders, each of which must
111
111
  carry a declared cache folder's name - never a profile, an app root or a protected root. Returns the count. #>
112
112
  param([string[]] $Paths)
113
113
  $pr = $Script:WS_PROTECT; $n = 0
package/lib/constants.ps1 CHANGED
@@ -4,7 +4,7 @@
4
4
  # Single source of truth for the version is package.json: the Node launcher exports it as
5
5
  # WINDOWSWEEP_VERSION. A direct checkout falls back to this literal; the version:check script
6
6
  # asserts the two never drift.
7
- $Script:WS_VERSION_FALLBACK = '1.4.0'
7
+ $Script:WS_VERSION_FALLBACK = '1.4.2'
8
8
 
9
9
  $Script:WS_NAME = 'windowsweep'
10
10
  $Script:WS_TAGLINE = 'Windows cleanup CLI: dry-run first, personal files chosen row by row, zero install via npx.'
package/lib/fs.ps1 CHANGED
@@ -233,7 +233,7 @@ function Get-DirectoryBytes {
233
233
  function Get-IdleDays {
234
234
  <# .SYNOPSIS Days since the path (or anything inside it) was last touched. 999999 when absent.
235
235
  Last-access counts unless Test-LastAccessLive found it carries no signal here (D82). #>
236
- # -WriteOnly (1.4.0): write/creation time only - for declared scratch roots, where a scanner's partial read
236
+ # -WriteOnly (P9): write/creation time only - for declared scratch roots, where a scanner's partial read
237
237
  # (67-85% of a month-old test profile's files on the P52, 2026-11-03) must not count as use.
238
238
  param([string] $Path, [switch] $WriteOnly)
239
239
  $info = Get-ItemInfo $Path
package/lib/safety.ps1 CHANGED
@@ -26,7 +26,7 @@ function Initialize-Safety {
26
26
 
27
27
  $exact = New-Object System.Collections.Generic.HashSet[string]([StringComparer]::OrdinalIgnoreCase)
28
28
  foreach ($d in [IO.DriveInfo]::GetDrives()) { $null = $exact.Add($d.Name.TrimEnd('\')); $null = $exact.Add($d.Name) }
29
- # 1.4.0: the Common Files and ProgramData\Microsoft roots - section 26 reads InstallLocation from the registry,
29
+ # P9: the Common Files and ProgramData\Microsoft roots - section 26 reads InstallLocation from the registry,
30
30
  # and an installer that registered one of these as its own folder must never get it offered as a leftover.
31
31
  foreach ($p in @($SR, "$SR\System32", "$SR\SysWOW64", $PF, $PF86, $PD, "$SD\Users", "$SD\Users\Default", "$SD\Users\Public", $env:PUBLIC, $U, "$U\AppData", $A, $L, $LL,
32
32
  "$PF\Common Files", "$PF86\Common Files", "$PD\Microsoft")) {
@@ -1,5 +1,5 @@
1
1
  # android_avd.ps1 - section 4: Android emulators (AVDs) and what they leave. Slow to recreate, so a per-AVD idle
2
- # gate and never in --all. 1.4.0: every AVD root (also ANDROID_AVD_HOME), pointer .ini files whose AVD is gone
2
+ # gate and never in --all. P9: every AVD root (also ANDROID_AVD_HOME), pointer .ini files whose AVD is gone
3
3
  # (the P52's pointed into a drive that no longer existed, 2026-11-02) and system images no remaining AVD uses -
4
4
  # removed with the SDK's own sdkmanager, the emulator too once no AVD is left. ASCII only.
5
5
 
@@ -1,5 +1,5 @@
1
1
  # browsers.ps1 - section 7: browser caches for every profile. Profile data is never touched.
2
- # 1.4.0: --browser-profile-roots / browserProfileRoots name folders that hold Chromium user-data directories of
2
+ # P9: --browser-profile-roots / browserProfileRoots name folders that hold Chromium user-data directories of
3
3
  # their own - automation and test profiles (the P52 kept 2.0 GB of cache in ~\.local\share\ahsan-automation\profiles,
4
4
  # 2026-11-02). Only the declared cache folders inside them are cleared (D79), and a profile a running browser
5
5
  # holds through --user-data-dir is skipped. ASCII only.
@@ -1,4 +1,4 @@
1
- # data_scanner.ps1 - section 19 (1.4.0 data scanner): large or old personal files. Downloads by default; folders a
1
+ # data_scanner.ps1 - section 19 (P9 data scanner): large or old personal files. Downloads by default; folders a
2
2
  # person adds with --personal-roots / personalRoots (owner, 2026-11-03). Discovery is read-only and lists each file
3
3
  # once with its reason (large: >= --large-file-mb at any age; old: idle --days at any size; both), its age and the
4
4
  # timestamps that age came from (D82). Removal stays interactive: exact paths only, never by --yes, Recycle Bin by
@@ -59,7 +59,7 @@ function ConvertFrom-PrefetchName {
59
59
  }
60
60
 
61
61
  function Get-PrefetchLastRun {
62
- <# .SYNOPSIS 1.4.0: program file name -> newest run (UTC) from Windows\Prefetch, which Windows rewrites on every
62
+ <# .SYNOPSIS P9: program file name -> newest run (UTC) from Windows\Prefetch, which Windows rewrites on every
63
63
  run. Readable only when elevated; empty otherwise or when prefetch is off. #>
64
64
  $map = @{}
65
65
  if (-not $Script:WS.IsAdmin) { return $map }
@@ -78,7 +78,7 @@ function Get-InstalledProgramRows {
78
78
  Publisher = [string]$props.Publisher; InstallDate = [string]$props.InstallDate
79
79
  EstimatedSizeKb = [long]0 + $props.EstimatedSize; InstallLocation = [string]$props.InstallLocation
80
80
  UninstallString = [string]$props.UninstallString; SystemComponent = [int]0 + $props.SystemComponent
81
- # Section 26 (1.4.0) reads these; sections 23 and 24 ignore them.
81
+ # Section 26 (P9) reads these; sections 23 and 24 ignore them.
82
82
  KeyPath = [string]$k.Name; KeyName = [string]$k.PSChildName; Machine = ([string]$k.Name).StartsWith('HKEY_LOCAL_MACHINE', [StringComparison]::OrdinalIgnoreCase)
83
83
  QuietUninstallString = [string]$props.QuietUninstallString; WindowsInstaller = [int]0 + $props.WindowsInstaller
84
84
  ParentKeyName = [string]$props.ParentKeyName; ReleaseType = [string]$props.ReleaseType
@@ -91,7 +91,7 @@ function Get-InstalledProgramRows {
91
91
  function Get-InstalledProgramIndex {
92
92
  <# .SYNOPSIS Everything that says "a program owns this name": uninstall entries, program folders, Store
93
93
  package names and running processes. RegistryCount drives the fail-closed gate in section 23. #>
94
- # -IgnorePaths (1.4.0, sections 26 and 27): folders being judged as leftovers, whose own names must not
94
+ # -IgnorePaths (P9, sections 26 and 27): folders being judged as leftovers, whose own names must not
95
95
  # count as a claim on themselves.
96
96
  param([string[]] $IgnorePaths = @())
97
97
  $P = $Script:P
@@ -64,9 +64,9 @@ function Add-JsonCandidate {
64
64
  [System.Collections.IDictionary] $Extra = $null)
65
65
  if (-not $Script:WS.JsonMode) { return }
66
66
  $c = [ordered]@{ section = $Section; index = $Index; path = $Path; bytes = $Bytes; idle_days = $IdleDays; project = $Project }
67
- # 1.4.0: an optional display name - section 26's candidates are programs whose path is a registry key.
67
+ # P9: an optional display name - section 26's candidates are programs whose path is a registry key.
68
68
  if ($Label) { $c.label = $Label }
69
- # 1.4.0: section-specific fields added after the shared ones (section 19's data scanner: reason, age basis, ...).
69
+ # P9: section-specific fields added after the shared ones (section 19's data scanner: reason, age basis, ...).
70
70
  if ($null -ne $Extra) { foreach ($k in $Extra.Keys) { $c[$k] = $Extra[$k] } }
71
71
  $Script:WS.Candidates += $c
72
72
  }
@@ -234,7 +234,7 @@ function Get-JsonSummary {
234
234
  refusals = @($ws.Refusals); excluded = @($ws.Excluded)
235
235
  # D82: the volumes where idle age used write/creation time because last-access was found live.
236
236
  access_live_volumes = @(Get-AccessLiveVolumes)
237
- # 1.4.0: the folders section 19 scanned or refused ({path, accepted, reason}); empty unless it ran.
237
+ # P9: the folders section 19 scanned or refused ({path, accepted, reason}); empty unless it ran.
238
238
  personal_roots = @($ws.PersonalRootsReport)
239
239
  log_file = $ws.LogFile; report_file = $ws.ReportFile
240
240
  }
@@ -1,4 +1,4 @@
1
- # self_test_1_4.ps1 - self-test group [19]: the 1.4.0 behaviour found on a real machine (P9, 2026-11-02).
1
+ # self_test_1_4.ps1 - self-test group [19]: the P9 behaviour found on a real machine (P9, 2026-11-02).
2
2
  # Called by Invoke-SelfTestExtra with its fixture root; verdict lines only. ASCII only.
3
3
 
4
4
  function New-AgedFiles {
@@ -24,7 +24,7 @@ function Invoke-SelfTest14 {
24
24
  $root = Join-Path $Fixture 'g19'
25
25
  Reset-AccessVerdicts
26
26
  try {
27
- Write-Section '[19] 1.4.0: idle basis when last-access is live (D82), one-line --json, orphan claims'
27
+ Write-Section '[19] P9: idle basis when last-access is live (D82), one-line --json, orphan claims'
28
28
 
29
29
  # 19a - the verdict rule itself
30
30
  $checks++
@@ -123,7 +123,7 @@ function Invoke-SelfTest21 {
123
123
  $savedDry = $ws.DryRun
124
124
  $key = 'HKCU:\Software\windowsweep-selftest-' + [guid]::NewGuid().ToString('N')
125
125
  try {
126
- Write-Section '[21] 1.4.0: the Chrome on-device AI policy (section 29)'
126
+ Write-Section '[21] P9: the Chrome on-device AI policy (section 29)'
127
127
  # 21a - a dry-run prints the change and writes nothing
128
128
  $checks++
129
129
  $ws.DryRun = $true
@@ -156,7 +156,7 @@ function Invoke-SelfTest20 {
156
156
  $root = Join-Path $Fixture 'g20'
157
157
  $savedExclude = $Script:WS_EXCLUDE
158
158
  try {
159
- Write-Section '[20] 1.4.0: superseded releases (section 28) and narrow exceptions (D79)'
159
+ Write-Section '[20] P9: superseded releases (section 28) and narrow exceptions (D79)'
160
160
  $rel = Join-Path $root 'codex\releases'
161
161
  foreach ($v in '0.1.0-x86_64-pc-windows-msvc', '0.2.0-x86_64-pc-windows-msvc', '0.3.0-x86_64-pc-windows-msvc', '0.4.0-x86_64-pc-windows-msvc', 'notes') { New-Item -ItemType Directory -Force -Path (Join-Path $rel $v) | Out-Null }
162
162
  $link = Join-Path $root 'codex\current'
@@ -1,4 +1,4 @@
1
- # self_test_1_4b.ps1 - self-test groups [22]-[23] for 1.4.0: uninstall programs (section 26) and the leftover
1
+ # self_test_1_4b.ps1 - self-test groups [22]-[23] for P9: uninstall programs (section 26) and the leftover
2
2
  # finder (sections 26 and 27). Fixtures and pure logic only; nothing is uninstalled or recycled. ASCII only.
3
3
 
4
4
  function New-ProgramRow {
@@ -26,7 +26,7 @@ function Invoke-SelfTest22 {
26
26
  $root = Join-Path $Fixture 'g22'
27
27
  $saved = @{ Queue = $ws.SelectQueue; Paths = $ws.SelectPaths; Interactive = $ws.Interactive; DryRun = $ws.DryRun }
28
28
  try {
29
- Write-Section '[22] 1.4.0: uninstall programs (section 26) and the folders they leave'
29
+ Write-Section '[22] P9: uninstall programs (section 26) and the folders they leave'
30
30
  # 22a - command lines exactly as the P52's uninstall entries wrote them (2026-11-02)
31
31
  $checks++
32
32
  $cases = @(
@@ -178,14 +178,14 @@ function Invoke-SelfTest26 {
178
178
  }
179
179
 
180
180
  function Invoke-SelfTest24 {
181
- <# .SYNOPSIS Group [24]: the 1.4.0 extensions to sections 4, 7, 10 and 24. #>
181
+ <# .SYNOPSIS Group [24]: the P9 extensions to sections 4, 7, 10 and 24. #>
182
182
  param([string] $Fixture)
183
183
  $checks = 0; $fails = 0
184
184
  $root = Join-Path $Fixture 'g24'
185
185
  $pr = $Script:WS_PROTECT
186
186
  $savedEx = @($pr.Exceptions); $savedExP = @($pr.ExceptionPrefixes)
187
187
  try {
188
- Write-Section '[24] 1.4.0: emulator leftovers (4), declared profiles (7), declared temp roots (10), last run (24)'
188
+ Write-Section '[24] P9: emulator leftovers (4), declared profiles (7), declared temp roots (10), last run (24)'
189
189
  # 24a - an AVD, its pointer, and a pointer to a folder that is gone (the P52's pointed into a missing drive)
190
190
  $checks++
191
191
  $avdRoot = Join-Path $root 'avd'
@@ -283,7 +283,7 @@ function Invoke-SelfTest25 {
283
283
  $savedExclude = $Script:WS_EXCLUDE
284
284
  $saved = @{ Json = $ws.JsonMode; Cands = $ws.Candidates; Permanent = $ws.Permanent }
285
285
  try {
286
- Write-Section '[25] 1.4.0: the data scanner (section 19) - large or old, refusals, the change check'
286
+ Write-Section '[25] P9: the data scanner (section 19) - large or old, refusals, the change check'
287
287
  $d = Join-Path $root 'data'
288
288
  New-Item -ItemType Directory -Force -Path (Join-Path $d 'sub') | Out-Null
289
289
  $old = [datetime]::UtcNow.AddDays(-400)
@@ -361,7 +361,7 @@ function Invoke-SelfTest23 {
361
361
  $root = Join-Path $Fixture 'g23'
362
362
  $savedExclude = $Script:WS_EXCLUDE
363
363
  try {
364
- Write-Section '[23] 1.4.0: leftover program folders (section 27)'
364
+ Write-Section '[23] P9: leftover program folders (section 27)'
365
365
  # 23a - registered commands as services, drivers, tasks and Run entries write them
366
366
  $checks++
367
367
  $SR = $Script:P.SR
@@ -354,7 +354,7 @@ function Invoke-SelfTestExtra {
354
354
  $contract = Invoke-SelfTestContract -Fixture $fx
355
355
  $checks += $contract.Checks; $fails += $contract.Fails
356
356
 
357
- # [19] the 1.4.0 behaviour found on a real machine lives in modules/self_test_1_4.ps1 (P9).
357
+ # [19] the P9 behaviour found on a real machine lives in modules/self_test_1_4.ps1 (P9).
358
358
  $g19 = Invoke-SelfTest14 -Fixture $fx
359
359
  $checks += $g19.Checks; $fails += $g19.Fails
360
360
  $g20 = Invoke-SelfTest20 -Fixture $fx
@@ -68,7 +68,7 @@ function Get-TempRoots {
68
68
  }
69
69
 
70
70
  function Get-DeclaredTempRoots {
71
- <# .SYNOPSIS 1.4.0: --temp-roots / tempRoots - folders a person uses as scratch space (C:\t on the P52, where
71
+ <# .SYNOPSIS P9: --temp-roots / tempRoots - folders a person uses as scratch space (C:\t on the P52, where
72
72
  test runs left one browser profile per run). Existing ones, once each, never a Windows temp root twice. #>
73
73
  $known = @(Get-TempRoots)
74
74
  $out = @()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windowsweep",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "Windows cleanup CLI: dry-run first, personal files chosen row by row, zero install via npx.",
5
5
  "bin": {
6
6
  "windowsweep": "bin/windowsweep.js"