@inerrata-corporation/errata 2.0.0-dev.54 → 2.0.0-dev.56

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.
package/README.txt CHANGED
@@ -2,36 +2,38 @@ errata - local-first observation engine for coding agents
2
2
 
3
3
  Requires Node.js 22 or newer on PATH (https://nodejs.org).
4
4
 
5
- Install (Windows, no admin rights required):
5
+ INSTALL
6
6
 
7
- .\install.ps1
7
+ npm i -g @inerrata-corporation/errata
8
8
 
9
- This copies errata.mjs + the tree-sitter runtime to %LOCALAPPDATA%\errata
10
- and adds that directory to your user PATH. A small errata.cmd shim runs the
11
- bundle under your system Node.
9
+ That's the whole install: npm drops the bundle + tree-sitter runtime and puts
10
+ the `errata` command on your PATH. Works on Windows, macOS, and Linux. (Alpha
11
+ dogfooders track the dev channel: `npm i -g @inerrata-corporation/errata@dev`.)
12
12
 
13
- It also registers a Scheduled Task (\errata\ai.errata.daemon) that auto-starts
14
- the daemon (`errata dash`) at every logon, launched hidden via a small .vbs so
15
- no console window flashes. uninstall.ps1 removes it. Note: this runs the
16
- installed copy under %LOCALAPPDATA%\errata - re-run install.ps1 after rebuilding
17
- to keep the autostarted daemon current.
13
+ Windows autostart (optional): run install.ps1 (shipped alongside this file) to
14
+ register a hidden logon Scheduled Task that keeps `errata dash` running across
15
+ reboots. It installs via the SAME `npm i -g` and points the task at the
16
+ npm-managed bundle, so a later `errata update` is picked up automatically -
17
+ install and update never diverge. uninstall.ps1 removes the task + the install.
18
18
 
19
- macOS / Linux:
19
+ USE
20
20
 
21
- errata.mjs carries a `#!/usr/bin/env node` shebang. Put the dist/ folder
22
- somewhere stable, then symlink the bundle onto your PATH:
21
+ cd /path/to/your/project
22
+ errata init # auto-detects the profile + installs harness hooks
23
+ errata start # observe this repo (or 'errata dash' to watch all projects)
24
+ errata status # verify the whole chain (daemon, cloud, consent, version)
23
25
 
24
- chmod +x errata.mjs
25
- ln -s "$PWD/errata.mjs" ~/.local/bin/errata
26
+ Dashboard: http://127.0.0.1:7891
26
27
 
27
- The resources/wasm/ folder must stay next to errata.mjs.
28
+ UPDATE
28
29
 
29
- Use:
30
+ errata update # pull the newest build on your channel (npm i -g)
31
+ errata update --channel latest # switch channels; the choice is persisted
32
+ errata update --check # just report whether you're behind
30
33
 
31
- cd C:\path\to\your\project
32
- errata init # auto-detects profile + installs harness hooks
33
- errata start # observe this repo
34
- errata status # verify the whole chain (daemon, cloud, consent)
34
+ CLOUD (optional - errata works fully local without it)
35
+
36
+ errata login # browser OAuth (loopback); '--device' for the code flow
35
37
 
36
38
  Model caveat (Claude Code + Claude Fable 5): the harness treats Fable 5's
37
39
  mid-turn assistant text (written between tool calls) as ephemeral - it reaches
@@ -41,16 +43,10 @@ tells the agent to restate mid-turn tags in its final message (which always
41
43
  survives), so expect reduced mid-turn capture on Fable sessions; Opus/Sonnet
42
44
  sessions are fully captured. (Verified with a 3-model matrix, 2026-07-02.)
43
45
 
44
- Open the dashboard: http://127.0.0.1:7891
45
-
46
- Point at a cloud (defaults to the public instance):
47
-
48
- errata init --cloud https://your-instance --token <api-key>
49
- errata login # or browser device-code flow
50
-
51
- Per-workspace state lives at .errata/ inside each project root and is
52
- preserved on uninstall - delete manually if you want it gone.
46
+ Per-workspace state lives at .errata/ inside each project root and is preserved
47
+ on uninstall - delete manually if you want it gone.
53
48
 
54
- Uninstall (Windows):
49
+ UNINSTALL
55
50
 
56
- .\uninstall.ps1
51
+ .\uninstall.ps1 # Windows (removes the autostart task too)
52
+ npm uninstall -g @inerrata-corporation/errata # any platform
package/errata.mjs CHANGED
@@ -20896,6 +20896,8 @@ var init_client = __esm({
20896
20896
  refreshSkewSec;
20897
20897
  fetchFn;
20898
20898
  timeoutMs;
20899
+ daemonVersion;
20900
+ daemonChannel;
20899
20901
  constructor(opts) {
20900
20902
  this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
20901
20903
  if (opts.apiKey !== void 0) this.apiKey = opts.apiKey;
@@ -20907,6 +20909,8 @@ var init_client = __esm({
20907
20909
  this.refreshSkewSec = opts.refreshSkewSec ?? 60;
20908
20910
  this.fetchFn = opts.fetchFn ?? fetch;
20909
20911
  this.timeoutMs = opts.timeoutMs ?? 15e3;
20912
+ this.daemonVersion = opts.daemonVersion;
20913
+ this.daemonChannel = opts.daemonChannel;
20910
20914
  }
20911
20915
  /**
20912
20916
  * The bearer token to send: a fresh OAuth access token (refreshing it first if
@@ -21204,6 +21208,8 @@ var init_client = __esm({
21204
21208
  async getSkills(context = "daemon skill sync", seed) {
21205
21209
  const qs = new URLSearchParams({ context });
21206
21210
  if (seed && seed.length > 0) qs.set("seed", seed.join(","));
21211
+ if (this.daemonVersion) qs.set("daemonVersion", this.daemonVersion);
21212
+ if (this.daemonChannel) qs.set("daemonChannel", this.daemonChannel);
21207
21213
  return this.json("GET", `/v2/skills?${qs.toString()}`);
21208
21214
  }
21209
21215
  /** CL-induce: request a skill for a locally-detected community's cloud-
@@ -21751,6 +21757,7 @@ function oauthClientId() {
21751
21757
  function authedCloudClient(cfg, opts = {}) {
21752
21758
  const baseUrl = normalizeCloudUrl(cfg.cloudUrl);
21753
21759
  const fetchFn = guardCloudFetch(baseUrl, opts.fetchFn ?? fetch);
21760
+ const versionOpts = opts.daemonVersion ? { daemonVersion: opts.daemonVersion, daemonChannel: cfg.updateChannel } : {};
21754
21761
  if (cfg.accessToken) {
21755
21762
  const tokenEndpoint = cfg.tokenEndpoint;
21756
21763
  return new CloudClient({
@@ -21758,6 +21765,7 @@ function authedCloudClient(cfg, opts = {}) {
21758
21765
  accessToken: cfg.accessToken,
21759
21766
  ...cfg.refreshToken ? { refreshToken: cfg.refreshToken } : {},
21760
21767
  fetchFn,
21768
+ ...versionOpts,
21761
21769
  ...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {},
21762
21770
  ...tokenEndpoint && cfg.refreshToken ? {
21763
21771
  refreshFn: (rt) => refreshAccessToken(opts.fetchFn ?? fetch, tokenEndpoint, {
@@ -21778,6 +21786,7 @@ function authedCloudClient(cfg, opts = {}) {
21778
21786
  baseUrl,
21779
21787
  ...cfg.apiKey ? { apiKey: cfg.apiKey } : {},
21780
21788
  fetchFn,
21789
+ ...versionOpts,
21781
21790
  ...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}
21782
21791
  });
21783
21792
  }
@@ -42355,10 +42364,11 @@ function readSkillManifest(manifestPath) {
42355
42364
  }
42356
42365
  async function syncSkills(paths, client, seed) {
42357
42366
  const res = await client.getSkills(void 0, seed);
42367
+ const staleDaemon = res.staleDaemon && res.latestDaemonVersion ? { latest: res.latestDaemonVersion } : void 0;
42358
42368
  mkdirSync5(paths.skillsDir, { recursive: true });
42359
42369
  if (res.skills.length === 0) {
42360
42370
  const existing = readdirSync6(paths.skillsDir).filter((f) => f.endsWith(".md"));
42361
- if (existing.length > 0) return { written: 0, pruned: 0 };
42371
+ if (existing.length > 0) return { written: 0, pruned: 0, ...staleDaemon ? { staleDaemon } : {} };
42362
42372
  }
42363
42373
  const rows = [];
42364
42374
  const keep = /* @__PURE__ */ new Set();
@@ -42390,7 +42400,7 @@ async function syncSkills(paths, client, seed) {
42390
42400
  JSON.stringify({ generatedAt: Date.now(), skills: rows }, null, 2),
42391
42401
  "utf8"
42392
42402
  );
42393
- return { written: rows.length, pruned };
42403
+ return { written: rows.length, pruned, ...staleDaemon ? { staleDaemon } : {} };
42394
42404
  }
42395
42405
 
42396
42406
  // src/working-file-state.ts
@@ -42810,7 +42820,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
42810
42820
  }
42811
42821
 
42812
42822
  // src/engine.ts
42813
- var DAEMON_VERSION = true ? "2.0.0-dev.54" : "2.0.0-alpha.0";
42823
+ var DAEMON_VERSION = true ? "2.0.0-dev.56" : "2.0.0-alpha.0";
42814
42824
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
42815
42825
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
42816
42826
  var GIT_OP_MUTE_MS = 4e3;
@@ -44991,7 +45001,7 @@ async function startMultiDaemon(opts = {}) {
44991
45001
  initialCfg.tokenEndpoint = null;
44992
45002
  initialCfg.oauthClientId = null;
44993
45003
  }
44994
- const cloud = authedCloudClient(initialCfg, { timeoutMs: CLOUD_TIMEOUT_MS });
45004
+ const cloud = authedCloudClient(initialCfg, { timeoutMs: CLOUD_TIMEOUT_MS, daemonVersion: DAEMON_VERSION });
44995
45005
  const cloudNow = () => {
44996
45006
  const c = loadConfig();
44997
45007
  const baseUrl2 = normalizeCloudUrl(opts.cloudUrl ?? c.cloudUrl);
@@ -45003,7 +45013,7 @@ async function startMultiDaemon(opts = {}) {
45003
45013
  current.tokenEndpoint = null;
45004
45014
  current.oauthClientId = null;
45005
45015
  }
45006
- return authedCloudClient(current, { timeoutMs: CLOUD_TIMEOUT_MS });
45016
+ return authedCloudClient(current, { timeoutMs: CLOUD_TIMEOUT_MS, daemonVersion: DAEMON_VERSION });
45007
45017
  };
45008
45018
  const sharedDbPath = sharedStorePath();
45009
45019
  ensureParent(sharedDbPath);
@@ -45022,6 +45032,7 @@ async function startMultiDaemon(opts = {}) {
45022
45032
  for (const r of records) r.engine.markContextDirty();
45023
45033
  }
45024
45034
  }) : null;
45035
+ let serverPending = null;
45025
45036
  const boundaryListeners = [];
45026
45037
  let boundaryFlushing = false;
45027
45038
  const fireBoundary = () => {
@@ -45040,7 +45051,7 @@ async function startMultiDaemon(opts = {}) {
45040
45051
  apiKey: opts.apiKey,
45041
45052
  reviewUrl: () => `${baseUrl}/ws/${id}/review`,
45042
45053
  sharedStore,
45043
- pendingUpdate: () => updatePoller?.current() ?? null,
45054
+ pendingUpdate: () => serverPending ?? updatePoller?.current() ?? null,
45044
45055
  ...opts.skipWatchers ? { skipWatchers: true } : {}
45045
45056
  });
45046
45057
  const webApp = buildWebUi({
@@ -45615,10 +45626,17 @@ async function startMultiDaemon(opts = {}) {
45615
45626
  async syncSkillsAll() {
45616
45627
  let written = 0;
45617
45628
  let pruned2 = 0;
45629
+ let stale;
45618
45630
  for (const r of records) {
45619
45631
  const s = await r.engine.fetchSkills();
45620
45632
  written += s.written;
45621
45633
  pruned2 += s.pruned;
45634
+ if (s.staleDaemon) stale = s.staleDaemon;
45635
+ }
45636
+ const next = stale ? { current: DAEMON_VERSION, latest: stale.latest, channel: loadConfig().updateChannel } : null;
45637
+ if ((next?.latest ?? null) !== (serverPending?.latest ?? null)) {
45638
+ serverPending = next;
45639
+ for (const rec of records) rec.engine.markContextDirty();
45622
45640
  }
45623
45641
  return { written, pruned: pruned2 };
45624
45642
  },
@@ -46014,6 +46032,9 @@ var cmd = argv[0] ?? "help";
46014
46032
  var rest = argv.slice(1);
46015
46033
  var NIGHTLY_INTERVAL_MS = 10 * 6e4;
46016
46034
  async function main() {
46035
+ if (cmd !== "help" && cmd !== "--help" && cmd !== "-h" && (rest.includes("--help") || rest.includes("-h"))) {
46036
+ return printHelp();
46037
+ }
46017
46038
  switch (cmd) {
46018
46039
  case "init":
46019
46040
  return cmdInit();
package/install.ps1 CHANGED
@@ -1,15 +1,22 @@
1
- # errata Windows installer / patcher (Node-bundle distribution).
2
- # Idempotent: re-running upgrades an existing install in place.
1
+ # errata Windows installer + logon autostart (no admin rights required).
3
2
  #
4
- # Unlike the old SEA build (a 90 MB self-contained errata.exe), this ships the
5
- # CLI as a single bundled errata.mjs run under the system Node so Node 22+ must
6
- # be on PATH. Everything else (tree-sitter WASM) rides along under resources/.
3
+ # The ONE install path is `npm i -g @inerrata-corporation/errata` - the SAME
4
+ # mechanism `errata update` uses, so an update actually takes effect. (The old
5
+ # installer copied a bundle to %LOCALAPPDATA%\errata while `errata update`
6
+ # installed into npm's global prefix - two diverging locations, so the autostart
7
+ # task kept relaunching the STALE copy and updates silently never landed. This
8
+ # unifies them: install + update share one npm-managed location.)
9
+ #
10
+ # Requires Node 22+ on PATH (npm ships with it).
11
+ #
12
+ # Usage: .\install.ps1 # dev channel (the alpha default)
13
+ # .\install.ps1 -Channel latest
14
+
15
+ param([ValidateSet('dev', 'latest')][string]$Channel = 'dev')
7
16
 
8
17
  $ErrorActionPreference = "Stop"
9
- $ScriptRoot = $PSScriptRoot
10
- if (-not $ScriptRoot) { $ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path }
11
18
 
12
- # errata runs on the system Node fail loudly if it's missing rather than after install.
19
+ # errata runs on the system Node - fail loudly if it's missing rather than after install.
13
20
  $node = Get-Command node -ErrorAction SilentlyContinue
14
21
  if (-not $node) {
15
22
  Write-Host "[X] Node.js not found on PATH. Install Node 22+ from https://nodejs.org and re-run."
@@ -21,41 +28,55 @@ if ([int]$nodeMajor -lt 22) {
21
28
  exit 1
22
29
  }
23
30
 
24
- $Target = Join-Path $env:LOCALAPPDATA "errata"
25
- $IsPatch = Test-Path (Join-Path $Target "errata.mjs")
26
-
27
- if ($IsPatch) {
28
- Write-Host "patching existing errata install at $Target"
29
- } else {
30
- Write-Host "installing errata to $Target (Node $nodeMajor)"
31
+ $Pkg = "@inerrata-corporation/errata"
32
+ Write-Host "installing $Pkg@$Channel via npm (Node $nodeMajor)..."
33
+ & npm install -g "$Pkg@$Channel"
34
+ if ($LASTEXITCODE -ne 0) {
35
+ Write-Host "[X] npm install failed (exit $LASTEXITCODE). Is npm on PATH?"
36
+ exit 1
31
37
  }
32
38
 
33
- if (-not (Test-Path $Target)) {
34
- New-Item -ItemType Directory -Path $Target | Out-Null
39
+ # Migrate off the OLD %LOCALAPPDATA%\errata bundle install if present: delete its
40
+ # copied bundle and drop it from PATH, so the npm-generated `errata` shim (which
41
+ # `errata update` keeps current) is the one that wins instead of a stale shadow.
42
+ $LegacyDir = Join-Path $env:LOCALAPPDATA "errata"
43
+ $LegacyMjs = Join-Path $LegacyDir "errata.mjs"
44
+ if (Test-Path $LegacyMjs) {
45
+ Write-Host "migrating off the legacy %LOCALAPPDATA%\errata bundle install..."
46
+ foreach ($f in @("errata.mjs", "errata.cmd", "pass-worker.mjs", "consolidate-worker.mjs", "resources")) {
47
+ $p = Join-Path $LegacyDir $f
48
+ if (Test-Path $p) { Remove-Item -Path $p -Recurse -Force -ErrorAction SilentlyContinue }
49
+ }
50
+ $UserPath = [Environment]::GetEnvironmentVariable("Path", "User")
51
+ if ($UserPath -and ($UserPath.Split(";") -contains $LegacyDir)) {
52
+ $NewPath = (($UserPath.Split(";") | Where-Object { $_ -ne $LegacyDir }) -join ";").Trim(";")
53
+ [Environment]::SetEnvironmentVariable("Path", $NewPath, "User")
54
+ Write-Host "removed the stale $LegacyDir entry from user PATH"
55
+ }
35
56
  }
36
- Copy-Item -Path (Join-Path $ScriptRoot "errata.mjs") -Destination $Target -Force
37
- Copy-Item -Path (Join-Path $ScriptRoot "errata.cmd") -Destination $Target -Force
38
- $ResSrc = Join-Path $ScriptRoot "resources"
39
- $ResDst = Join-Path $Target "resources"
40
- if (Test-Path $ResDst) { Remove-Item $ResDst -Recurse -Force }
41
- Copy-Item -Path $ResSrc -Destination $Target -Recurse -Force
42
57
 
43
- $UserPath = [Environment]::GetEnvironmentVariable("Path", "User")
44
- $PathParts = if ($UserPath) { $UserPath.Split(";") } else { @() }
45
- if ($PathParts -notcontains $Target) {
46
- $NewPath = if ($UserPath) { "$UserPath;$Target" } else { $Target }
47
- [Environment]::SetEnvironmentVariable("Path", $NewPath, "User")
48
- Write-Host "added $Target to user PATH"
49
- Write-Host " (open a new terminal for PATH changes to take effect)"
50
- } else {
51
- Write-Host "PATH already contains $Target"
58
+ # Resolve the npm-installed bundle so autostart points at the SAME file `errata
59
+ # update` (npm i -g) replaces IN PLACE - this is what makes an update take effect.
60
+ $globalRoot = (& npm root -g).Trim()
61
+ $Mjs = Join-Path (Join-Path (Join-Path $globalRoot "@inerrata-corporation") "errata") "errata.mjs"
62
+ if (-not (Test-Path $Mjs)) {
63
+ Write-Host "[X] installed, but could not locate the bundle at $Mjs - autostart not registered."
64
+ Write-Host " You can still run 'errata dash' manually."
65
+ exit 1
52
66
  }
53
67
 
68
+ # Persist the update channel so `errata update` tracks the one you installed.
69
+ # (No-op display-wise; the config default is already 'dev', so this only matters
70
+ # when you install -Channel latest.)
71
+ try { & node "$Mjs" update --channel $Channel --check | Out-Null } catch { }
72
+
54
73
  # Auto-start the daemon at logon via a Scheduled Task. We launch through a hidden
55
- # .vbs shim because node.exe is a console app running it from the task directly
74
+ # .vbs shim because node.exe is a console app - running it from the task directly
56
75
  # flashes a window every logon; WScript's Run(..., 0, False) starts it windowless.
57
76
  # Task name/path mirror uninstall.ps1 (\errata\ai.errata.daemon) so the two stay
58
- # symmetric. Best-effort: a failure here must not fail the install.
77
+ # symmetric. The launcher points at the npm bundle above, so a later `errata
78
+ # update` is picked up on next logon with no re-install. Best-effort.
79
+ if (-not (Test-Path $LegacyDir)) { New-Item -ItemType Directory -Path $LegacyDir | Out-Null }
59
80
  $VbsTemplate = @'
60
81
  ' errata daemon autostart launcher (written by install.ps1) - runs `errata dash` fully hidden.
61
82
  Set sh = CreateObject("WScript.Shell")
@@ -63,8 +84,8 @@ sh.CurrentDirectory = "__CWD__"
63
84
  sh.Run """__NODE__"" ""__MJS__"" dash", 0, False
64
85
  '@
65
86
  try {
66
- $Launcher = Join-Path $Target "errata-autostart.vbs"
67
- $Vbs = $VbsTemplate.Replace("__CWD__", $env:USERPROFILE).Replace("__NODE__", $node.Source).Replace("__MJS__", (Join-Path $Target "errata.mjs"))
87
+ $Launcher = Join-Path $LegacyDir "errata-autostart.vbs"
88
+ $Vbs = $VbsTemplate.Replace("__CWD__", $env:USERPROFILE).Replace("__NODE__", $node.Source).Replace("__MJS__", $Mjs)
68
89
  Set-Content -Path $Launcher -Value $Vbs -Encoding ASCII
69
90
 
70
91
  $action = New-ScheduledTaskAction -Execute "wscript.exe" -Argument ('"{0}"' -f $Launcher)
@@ -72,19 +93,19 @@ try {
72
93
  $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Seconds 0) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
73
94
  $principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Limited
74
95
  Register-ScheduledTask -TaskName "ai.errata.daemon" -TaskPath "\errata\" -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Description "Auto-start the errata dash daemon at logon." -Force | Out-Null
75
- Write-Host "registered autostart task \errata\ai.errata.daemon (starts at logon)"
96
+ Write-Host "registered autostart task \errata\ai.errata.daemon (starts at logon, runs the npm-installed bundle)"
76
97
  } catch {
77
98
  Write-Host "[!] could not register autostart task: $($_.Exception.Message)"
78
99
  Write-Host " (install still succeeded - start the daemon manually with 'errata dash')"
79
100
  }
80
101
 
81
102
  Write-Host ""
82
- if ($IsPatch) { Write-Host "[OK] errata patched." } else { Write-Host "[OK] errata installed." }
103
+ Write-Host "[OK] errata installed (channel: $Channel)."
83
104
  Write-Host ""
84
105
  Write-Host "Next steps:"
85
- Write-Host " 1. Open a new terminal so PATH refreshes."
106
+ Write-Host " 1. Open a new terminal so PATH refreshes (npm's global bin holds the 'errata' command)."
86
107
  Write-Host " 2. cd into the workspace you want to observe."
87
108
  Write-Host " 3. errata init"
88
109
  Write-Host " 4. errata start (or 'errata dash' to watch all projects)"
89
110
  Write-Host ""
90
- Write-Host "The daemon will also auto-start at every logon from now on."
111
+ Write-Host "Update anytime with: errata update (autostart picks it up on next logon)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.0-dev.54",
3
+ "version": "2.0.0-dev.56",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/uninstall.ps1 CHANGED
@@ -1,29 +1,35 @@
1
1
  # errata uninstaller.
2
2
  $ErrorActionPreference = "Stop"
3
- $Target = Join-Path $env:LOCALAPPDATA "errata"
4
-
5
- if (-not (Test-Path $Target)) {
6
- Write-Host "errata is not installed at $Target."
7
- exit 0
8
- }
9
3
 
10
- Write-Host "removing $Target"
11
- Remove-Item -Path $Target -Recurse -Force
12
-
13
- $UserPath = [Environment]::GetEnvironmentVariable("Path", "User")
14
- if ($UserPath) {
15
- $PathParts = $UserPath.Split(";") | Where-Object { $_ -ne $Target }
16
- $NewPath = ($PathParts -join ";").Trim(";")
17
- [Environment]::SetEnvironmentVariable("Path", $NewPath, "User")
18
- Write-Host "removed $Target from user PATH"
4
+ # Remove the npm-global install (the binary + bundle + npm's PATH shim).
5
+ $node = Get-Command node -ErrorAction SilentlyContinue
6
+ if ($node) {
7
+ Write-Host "removing @inerrata-corporation/errata (npm global)..."
8
+ & npm uninstall -g "@inerrata-corporation/errata" 2>$null | Out-Null
19
9
  }
20
10
 
11
+ # Remove the logon autostart task.
21
12
  $Existing = Get-ScheduledTask -TaskName "ai.errata.daemon" -TaskPath "\errata\" -ErrorAction SilentlyContinue
22
13
  if ($Existing) {
23
14
  Unregister-ScheduledTask -TaskName "ai.errata.daemon" -TaskPath "\errata\" -Confirm:$false
24
15
  Write-Host "removed scheduled task errata\ai.errata.daemon"
25
16
  }
26
17
 
18
+ # Remove %LOCALAPPDATA%\errata - the autostart launcher lives here, and (for
19
+ # machines upgraded from the pre-npm installer) so did the legacy bundle. Also
20
+ # drop the legacy dir from user PATH if a pre-npm install left it there.
21
+ $Target = Join-Path $env:LOCALAPPDATA "errata"
22
+ if (Test-Path $Target) {
23
+ Remove-Item -Path $Target -Recurse -Force
24
+ Write-Host "removed $Target"
25
+ }
26
+ $UserPath = [Environment]::GetEnvironmentVariable("Path", "User")
27
+ if ($UserPath -and ($UserPath.Split(";") -contains $Target)) {
28
+ $NewPath = (($UserPath.Split(";") | Where-Object { $_ -ne $Target }) -join ";").Trim(";")
29
+ [Environment]::SetEnvironmentVariable("Path", $NewPath, "User")
30
+ Write-Host "removed the legacy $Target entry from user PATH"
31
+ }
32
+
27
33
  Write-Host ""
28
34
  Write-Host "[OK] errata uninstalled."
29
35
  Write-Host "Per-workspace .errata/ folders are left in place - delete them manually if desired."