pi-updater 0.2.8 → 0.2.9

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/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.2.9 - 2026-03-16
4
+
5
+ - Keep startup checks cache-first and non-blocking.
6
+ - Add a one-time background live check per run.
7
+ - Show update prompt in the same session when the background check finds a newer version.
8
+ - Respect `PI_SKIP_VERSION_CHECK` and `PI_OFFLINE` for automatic checks.
9
+ - Avoid duplicate automatic prompts for the same version in one run.
10
+ - `/update` now warns and exits early when `PI_OFFLINE` is set.
package/README.md CHANGED
@@ -1,8 +1,11 @@
1
1
  # pi-updater
2
2
 
3
- A Codex-style auto-updater for Pi.
3
+ A lightweight, Codex-style auto-updater for pi with fast, cache-first startup checks.
4
4
 
5
- > **Note:** Currently supports npm installations only.
5
+ - npm: https://www.npmjs.com/package/pi-updater
6
+ - repo: https://github.com/tonze/pi-updater
7
+
8
+ > **Note:** Automatic installation currently supports npm-based pi installs only.
6
9
 
7
10
  <img width="800" height="482" alt="Screenshot 2026-02-28 at 09 01 37" src="https://github.com/user-attachments/assets/89df2dad-8d91-464b-b3cb-dfd15bce1c06" />
8
11
 
@@ -13,9 +16,18 @@ A Codex-style auto-updater for Pi.
13
16
  - **Skip** — dismiss until next session
14
17
  - **Skip this version** — don't ask again until a newer version appears
15
18
 
16
- **`/update`:** manually check for updates (always fetches fresh from npm)
19
+ **In the background (once per run):** performs one live npm check and can show the prompt in the same session when a new release is detected.
20
+
21
+ **`/update`:** manually check for updates (always fetches fresh from npm, unless `PI_OFFLINE` is set).
22
+
23
+ ## How version checks work
24
+
25
+ pi-updater uses a cache-first approach to keep startup fast:
17
26
 
18
- Version checks are cached. Latest version is fetched in the background on startup.
27
+ 1. On startup, cached version data is checked instantly.
28
+ 2. One background live fetch refreshes the cache.
29
+ 3. If the background fetch finds a newer version, pi-updater can prompt in the same session.
30
+ 4. Automatic checks are skipped when `PI_SKIP_VERSION_CHECK` or `PI_OFFLINE` is set.
19
31
 
20
32
  ## Install
21
33
 
@@ -33,6 +45,20 @@ pi install git:github.com/tonze/pi-updater
33
45
 
34
46
  Use `/update` inside pi to manually check for updates and install them.
35
47
 
48
+ ## Environment flags
49
+
50
+ Disable automatic version checks:
51
+
52
+ ```bash
53
+ export PI_SKIP_VERSION_CHECK=1
54
+ ```
55
+
56
+ Or run in offline mode (also disables automatic checks):
57
+
58
+ ```bash
59
+ export PI_OFFLINE=1
60
+ ```
61
+
36
62
  ## Updating this package
37
63
 
38
64
  ```bash
package/index.ts CHANGED
@@ -11,9 +11,13 @@ const PACKAGE_NAME = "@mariozechner/pi-coding-agent";
11
11
  const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
12
12
  const CACHE_FILE = join(homedir(), ".pi", "agent", "update-cache.json");
13
13
 
14
+ const ENV_SKIP_VERSION_CHECK = "PI_SKIP_VERSION_CHECK";
15
+ const ENV_OFFLINE = "PI_OFFLINE";
16
+
14
17
  interface VersionCache {
15
18
  latestVersion: string;
16
19
  dismissedVersion?: string;
20
+ checkedAt?: string;
17
21
  }
18
22
 
19
23
  function readCache(): VersionCache | undefined {
@@ -48,6 +52,27 @@ function isNewer(latest: string, current: string): boolean {
48
52
  return l[2] > c[2];
49
53
  }
50
54
 
55
+ function isEnvSet(name: string): boolean {
56
+ return Boolean(process.env[name]);
57
+ }
58
+
59
+ function shouldSkipAutoChecks(): boolean {
60
+ return isEnvSet(ENV_SKIP_VERSION_CHECK) || isEnvSet(ENV_OFFLINE);
61
+ }
62
+
63
+ function isOffline(): boolean {
64
+ return isEnvSet(ENV_OFFLINE);
65
+ }
66
+
67
+ function saveLatestToCache(latest: string) {
68
+ const prev = readCache();
69
+ writeCache({
70
+ latestVersion: latest,
71
+ dismissedVersion: prev?.dismissedVersion,
72
+ checkedAt: new Date().toISOString(),
73
+ });
74
+ }
75
+
51
76
  async function fetchLatestVersion(): Promise<string | undefined> {
52
77
  try {
53
78
  const res = await fetch(REGISTRY_URL, {
@@ -60,33 +85,30 @@ async function fetchLatestVersion(): Promise<string | undefined> {
60
85
  }
61
86
  }
62
87
 
63
- /**
64
- * Returns the cached latest version if an upgrade is available and not dismissed.
65
- * Always kicks off a background fetch to refresh the cache for the next run.
66
- */
67
- function getUpgradeVersion(): string | undefined {
88
+ /** Returns a cached upgrade if available and not dismissed. */
89
+ function getCachedUpgradeVersion(): string | undefined {
68
90
  const cache = readCache();
69
-
70
- void fetchLatestVersion().then((latest) => {
71
- if (!latest) return;
72
- // Re-read cache to avoid overwriting a dismissal that happened during the fetch
73
- writeCache({
74
- latestVersion: latest,
75
- dismissedVersion: readCache()?.dismissedVersion,
76
- });
77
- });
78
-
79
91
  if (!cache) return undefined;
80
92
  if (!isNewer(cache.latestVersion, VERSION)) return undefined;
81
93
  if (cache.dismissedVersion === cache.latestVersion) return undefined;
82
94
  return cache.latestVersion;
83
95
  }
84
96
 
97
+ /** Fetch latest from npm and refresh cache. */
98
+ async function refreshLatestVersionInCache(): Promise<string | undefined> {
99
+ const latest = await fetchLatestVersion();
100
+ if (!latest) return undefined;
101
+ saveLatestToCache(latest);
102
+ return latest;
103
+ }
104
+
85
105
  function dismissVersion(version: string) {
86
106
  const cache = readCache();
87
- if (!cache) return;
88
- cache.dismissedVersion = version;
89
- writeCache(cache);
107
+ writeCache({
108
+ latestVersion: cache?.latestVersion ?? version,
109
+ dismissedVersion: version,
110
+ checkedAt: cache?.checkedAt,
111
+ });
90
112
  }
91
113
 
92
114
  function getInstallCommand(version: string): { program: string; args: string[] } {
@@ -101,6 +123,10 @@ function fmtCmd(cmd: { program: string; args: string[] }): string {
101
123
  }
102
124
 
103
125
  export default function (pi: ExtensionAPI) {
126
+ let promptOpen = false;
127
+ const promptedVersions = new Set<string>();
128
+ let liveCheckStarted = false;
129
+
104
130
  async function doInstall(
105
131
  ctx: ExtensionContext,
106
132
  latest: string,
@@ -159,21 +185,64 @@ export default function (pi: ExtensionAPI) {
159
185
  await doInstall(ctx, latest, cmd);
160
186
  }
161
187
 
162
- pi.on("session_start", async (_event, ctx) => {
188
+ function canAutoPromptVersion(latest: string): boolean {
189
+ if (!isNewer(latest, VERSION)) return false;
190
+ if (promptedVersions.has(latest)) return false;
191
+ if (readCache()?.dismissedVersion === latest) return false;
192
+ return true;
193
+ }
194
+
195
+ async function maybeShowAutoPrompt(ctx: ExtensionContext, latest: string) {
163
196
  if (!ctx.hasUI) return;
164
- const latest = getUpgradeVersion();
165
- if (latest) void showUpdatePrompt(ctx, latest);
197
+ if (promptOpen) return;
198
+ if (!canAutoPromptVersion(latest)) return;
199
+
200
+ promptOpen = true;
201
+ promptedVersions.add(latest);
202
+ try {
203
+ await showUpdatePrompt(ctx, latest);
204
+ } finally {
205
+ promptOpen = false;
206
+ }
207
+ }
208
+
209
+ function runAutoChecks(ctx: ExtensionContext) {
210
+ if (!ctx.hasUI) return;
211
+ if (shouldSkipAutoChecks()) return;
212
+
213
+ const cached = getCachedUpgradeVersion();
214
+ if (cached) void maybeShowAutoPrompt(ctx, cached);
215
+
216
+ if (liveCheckStarted) return;
217
+ liveCheckStarted = true;
218
+
219
+ void refreshLatestVersionInCache()
220
+ .then((latest) => {
221
+ if (!latest) return;
222
+ void maybeShowAutoPrompt(ctx, latest);
223
+ })
224
+ .catch(() => {});
225
+ }
226
+
227
+ pi.on("session_start", async (_event, ctx) => {
228
+ runAutoChecks(ctx);
166
229
  });
167
230
 
168
231
  pi.on("session_switch", async (_event, ctx) => {
169
- if (!ctx.hasUI) return;
170
- const latest = getUpgradeVersion();
171
- if (latest) void showUpdatePrompt(ctx, latest);
232
+ runAutoChecks(ctx);
172
233
  });
173
234
 
174
235
  pi.registerCommand("update", {
175
236
  description: "Check for pi updates and install",
176
237
  handler: async (_args, ctx) => {
238
+ if (isOffline()) {
239
+ ctx.ui.notify(
240
+ "PI_OFFLINE is set. Disable it to check for updates.",
241
+ "warning",
242
+ );
243
+ return;
244
+ }
245
+
177
246
  const latest = await ctx.ui.custom<string | null>(
178
247
  (tui, theme, _kb, done) => {
179
248
  const loader = new BorderedLoader(
@@ -194,16 +263,14 @@ export default function (pi: ExtensionAPI) {
194
263
  return;
195
264
  }
196
265
 
197
- writeCache({
198
- latestVersion: latest,
199
- dismissedVersion: readCache()?.dismissedVersion,
200
- });
266
+ saveLatestToCache(latest);
201
267
 
202
268
  if (!isNewer(latest, VERSION)) {
203
269
  ctx.ui.notify(`Already on latest version (${VERSION}).`, "info");
204
270
  return;
205
271
  }
206
272
 
273
+ promptedVersions.add(latest);
207
274
  await showUpdatePrompt(ctx, latest);
208
275
  },
209
276
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-updater",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Codex-style auto-updater for pi. Checks for new versions on startup and prompts to install.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,7 +22,8 @@
22
22
  },
23
23
  "files": [
24
24
  "index.ts",
25
- "README.md"
25
+ "README.md",
26
+ "CHANGELOG.md"
26
27
  ],
27
28
  "peerDependencies": {
28
29
  "@mariozechner/pi-coding-agent": "*"