@pi-unipi/updater 0.1.1
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.md +71 -0
- package/index.ts +6 -0
- package/package.json +53 -0
- package/skills/configure-updater/SKILL.md +65 -0
- package/src/cache.ts +67 -0
- package/src/changelog.ts +141 -0
- package/src/checker.ts +84 -0
- package/src/commands.ts +83 -0
- package/src/index.ts +178 -0
- package/src/installer.ts +74 -0
- package/src/markdown.ts +173 -0
- package/src/readme.ts +139 -0
- package/src/settings.ts +98 -0
- package/src/tui/changelog-overlay.ts +256 -0
- package/src/tui/readme-overlay.ts +236 -0
- package/src/tui/settings-overlay.ts +191 -0
- package/src/tui/update-overlay.ts +261 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @pi-unipi/updater
|
|
2
|
+
|
|
3
|
+
Auto-updater, changelog browser, and readme browser for the Unipi extension suite.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Auto-updater** — Periodically checks npm registry for new versions, shows update prompt with changelog diff
|
|
8
|
+
- **Changelog browser** — TUI overlay listing all versions with dates and status labels (✓ Current, ↑ New)
|
|
9
|
+
- **Readme browser** — TUI overlay listing all packages with versions, opens rendered README content
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
| Command | Description |
|
|
14
|
+
|---------|-------------|
|
|
15
|
+
| `/unipi:readme [package]` | Browse package README files. No arg opens list, with arg opens directly. |
|
|
16
|
+
| `/unipi:changelog` | Browse CHANGELOG.md with version list and detail view |
|
|
17
|
+
| `/unipi:updater-settings` | Configure check interval and auto-update mode |
|
|
18
|
+
|
|
19
|
+
## Configuration
|
|
20
|
+
|
|
21
|
+
Config stored at `~/.unipi/config/updater/config.json`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"checkIntervalMs": 3600000,
|
|
26
|
+
"autoUpdate": "notify"
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Options
|
|
31
|
+
|
|
32
|
+
| Option | Values | Default |
|
|
33
|
+
|--------|--------|---------|
|
|
34
|
+
| `checkIntervalMs` | `1800000` (30min), `3600000` (1h), `21600000` (6h), `86400000` (1d) | `3600000` (1h) |
|
|
35
|
+
| `autoUpdate` | `disabled`, `notify`, `auto` | `notify` |
|
|
36
|
+
|
|
37
|
+
### Auto-update modes
|
|
38
|
+
|
|
39
|
+
- **disabled** — No update checks on session start
|
|
40
|
+
- **notify** — Show update overlay with changelog diff, user chooses [Y] Update or [n] Skip
|
|
41
|
+
- **auto** — Show countdown overlay, auto-install after 5 seconds unless cancelled
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
1. On session start, checks npm registry for `@pi-unipi/unipi` latest version
|
|
46
|
+
2. Compares with installed version from `package.json`
|
|
47
|
+
3. If newer version found and not previously skipped, shows update overlay
|
|
48
|
+
4. User can view changelog diff, update with `[Y]`, or skip with `[n]`
|
|
49
|
+
5. Skipped versions are cached — only re-prompted when an even newer version appears
|
|
50
|
+
6. Update installs via `pi install npm:@pi-unipi/unipi`
|
|
51
|
+
|
|
52
|
+
## TUI Overlays
|
|
53
|
+
|
|
54
|
+
All overlays use keyboard navigation:
|
|
55
|
+
- `j`/`k` or ↑/↓ — navigate
|
|
56
|
+
- `Enter` — select/open
|
|
57
|
+
- `q`/`Esc` — back/close
|
|
58
|
+
- `g`/`G` — jump to top/bottom
|
|
59
|
+
- `Space` — cycle options (settings overlay)
|
|
60
|
+
- `h`/`l` or ←/→ — cycle options (settings overlay)
|
|
61
|
+
|
|
62
|
+
## Cache
|
|
63
|
+
|
|
64
|
+
Last-check cache stored at `~/.unipi/cache/updater/last-check.json`:
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"lastCheck": "2026-05-01T12:00:00.000Z",
|
|
68
|
+
"latestVersion": "0.1.16",
|
|
69
|
+
"skippedVersion": "0.1.16"
|
|
70
|
+
}
|
|
71
|
+
```
|
package/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pi-unipi/updater",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Auto-updater, changelog browser, and readme browser for Unipi — checks npm registry, renders CHANGELOG.md and README.md files in TUI overlays",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Neuron Mr White",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Neuron-Mr-White/unipi.git",
|
|
11
|
+
"directory": "packages/updater"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pi-package",
|
|
15
|
+
"pi-extension",
|
|
16
|
+
"pi-coding-agent",
|
|
17
|
+
"unipi",
|
|
18
|
+
"updater",
|
|
19
|
+
"changelog",
|
|
20
|
+
"readme"
|
|
21
|
+
],
|
|
22
|
+
"files": [
|
|
23
|
+
"index.ts",
|
|
24
|
+
"src/**/*.ts",
|
|
25
|
+
"skills/**/*",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"pi": {
|
|
29
|
+
"extensions": [
|
|
30
|
+
"src/index.ts"
|
|
31
|
+
],
|
|
32
|
+
"skills": [
|
|
33
|
+
"skills"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "node --experimental-strip-types --test tests/**/*.test.ts"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@pi-unipi/core": "*"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
47
|
+
"@mariozechner/pi-tui": "*"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^25.6.0",
|
|
51
|
+
"typescript": "^6.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: configure-updater
|
|
3
|
+
description: Guide for using and configuring the Unipi updater — auto-update, changelog browser, and readme browser.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Configure Updater
|
|
7
|
+
|
|
8
|
+
The `@pi-unipi/updater` package provides auto-update checking, a changelog browser, and a readme browser.
|
|
9
|
+
|
|
10
|
+
## Commands
|
|
11
|
+
|
|
12
|
+
### `/unipi:readme [package]`
|
|
13
|
+
|
|
14
|
+
Browse README.md files for all unipi packages.
|
|
15
|
+
|
|
16
|
+
- **No argument** — Opens a list of all packages with their versions. Select one to read its README.
|
|
17
|
+
- **With argument** (e.g., `/unipi:readme workflow`) — Opens that package's README directly.
|
|
18
|
+
|
|
19
|
+
### `/unipi:changelog`
|
|
20
|
+
|
|
21
|
+
Browse the root CHANGELOG.md in a TUI overlay.
|
|
22
|
+
|
|
23
|
+
- Shows version list with dates and status labels (✓ Current, ↑ New)
|
|
24
|
+
- Select a version to view its changelog details (Added, Fixed, Changed sections)
|
|
25
|
+
- Follows [Keep a Changelog](https://keepachangelog.com) format
|
|
26
|
+
|
|
27
|
+
### `/unipi:updater-settings`
|
|
28
|
+
|
|
29
|
+
Configure the updater module.
|
|
30
|
+
|
|
31
|
+
**Settings:**
|
|
32
|
+
- **Check Interval** — How often to check npm for updates (30min / 1h / 6h / 1d)
|
|
33
|
+
- **Auto Update** — What happens when an update is found:
|
|
34
|
+
- `disabled` — No update checks on session start
|
|
35
|
+
- `notify` — Show update overlay, user chooses to update or skip
|
|
36
|
+
- `auto` — Auto-install after 5-second countdown (press `n` to cancel)
|
|
37
|
+
|
|
38
|
+
## Config File
|
|
39
|
+
|
|
40
|
+
Config stored at `~/.unipi/config/updater/config.json`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"checkIntervalMs": 3600000,
|
|
45
|
+
"autoUpdate": "notify"
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Cache
|
|
50
|
+
|
|
51
|
+
Last-check cache at `~/.unipi/cache/updater/last-check.json`:
|
|
52
|
+
- Tracks when the last npm check was performed
|
|
53
|
+
- Stores the latest version found
|
|
54
|
+
- Remembers skipped versions (user pressed `n` to skip)
|
|
55
|
+
- Re-prompts only when a newer version than the skipped one appears
|
|
56
|
+
|
|
57
|
+
## Navigation
|
|
58
|
+
|
|
59
|
+
All TUI overlays support:
|
|
60
|
+
- `j`/`k` or ↑/↓ — Navigate
|
|
61
|
+
- `Enter` — Select/open
|
|
62
|
+
- `q`/`Esc` — Back/close
|
|
63
|
+
- `g`/`G` — Jump to top/bottom
|
|
64
|
+
- `Space` — Cycle options (settings)
|
|
65
|
+
- `h`/`l` or ←/→ — Cycle options (settings)
|
package/src/cache.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/updater — Last-check cache
|
|
3
|
+
*
|
|
4
|
+
* Reads/writes last-check.json to ~/.unipi/cache/updater/last-check.json
|
|
5
|
+
* Tracks when the last npm check was performed and what version was found.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
9
|
+
import { dirname, join } from "path";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { UPDATER_DIRS } from "@pi-unipi/core";
|
|
12
|
+
import type { LastCheckCache } from "../types.js";
|
|
13
|
+
|
|
14
|
+
/** Resolve cache path */
|
|
15
|
+
function resolveCachePath(): string {
|
|
16
|
+
const base = UPDATER_DIRS.CACHE.replace("~", homedir());
|
|
17
|
+
return join(base, "last-check.json");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Read cache from disk, returning null if missing or invalid */
|
|
21
|
+
export function readLastCheck(): LastCheckCache | null {
|
|
22
|
+
const cachePath = resolveCachePath();
|
|
23
|
+
try {
|
|
24
|
+
if (existsSync(cachePath)) {
|
|
25
|
+
const raw = readFileSync(cachePath, "utf-8");
|
|
26
|
+
const parsed = JSON.parse(raw) as LastCheckCache;
|
|
27
|
+
if (parsed.lastCheck && parsed.latestVersion) {
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch (_err) {
|
|
32
|
+
// Cache read failure — treat as missing.
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Write cache to disk, creating directory if needed */
|
|
38
|
+
export function writeLastCheck(cache: LastCheckCache): void {
|
|
39
|
+
const cachePath = resolveCachePath();
|
|
40
|
+
const dir = dirname(cachePath);
|
|
41
|
+
mkdirSync(dir, { recursive: true });
|
|
42
|
+
writeFileSync(cachePath, JSON.stringify(cache, null, 2) + "\n", "utf-8");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Check if enough time has elapsed since last check */
|
|
46
|
+
export function isCheckDue(intervalMs: number): boolean {
|
|
47
|
+
const cache = readLastCheck();
|
|
48
|
+
if (!cache) return true;
|
|
49
|
+
|
|
50
|
+
const lastCheckTime = new Date(cache.lastCheck).getTime();
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
return now - lastCheckTime >= intervalMs;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Write skipped version to cache */
|
|
56
|
+
export function writeSkippedVersion(version: string): void {
|
|
57
|
+
const cache = readLastCheck();
|
|
58
|
+
if (cache) {
|
|
59
|
+
writeLastCheck({ ...cache, skippedVersion: version });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Check if a version was skipped by the user */
|
|
64
|
+
export function isVersionSkipped(version: string): boolean {
|
|
65
|
+
const cache = readLastCheck();
|
|
66
|
+
return cache?.skippedVersion === version;
|
|
67
|
+
}
|
package/src/changelog.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/updater — Changelog parser
|
|
3
|
+
*
|
|
4
|
+
* Parses CHANGELOG.md (Keep a Changelog format) into structured ChangelogEntry[].
|
|
5
|
+
* Handles: ## [Unreleased], ## [x.y.z] — YYYY-MM-DD, ### Added/Fixed/etc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync } from "fs";
|
|
9
|
+
import type { ChangelogEntry } from "../types.js";
|
|
10
|
+
|
|
11
|
+
/** Regex for version headers: ## [x.y.z] — YYYY-MM-DD or ## [Unreleased] */
|
|
12
|
+
const VERSION_HEADER_RE = /^## \[(.+?)\](?:\s*[-—–]\s*(.+))?$/;
|
|
13
|
+
|
|
14
|
+
/** Regex for section headers: ### Added, ### Fixed, etc. */
|
|
15
|
+
const SECTION_HEADER_RE = /^### (.+)$/;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse a CHANGELOG.md file into structured entries.
|
|
19
|
+
* Returns empty array if file doesn't exist or is empty.
|
|
20
|
+
*/
|
|
21
|
+
export function parseChangelog(filePath: string): ChangelogEntry[] {
|
|
22
|
+
if (!existsSync(filePath)) return [];
|
|
23
|
+
|
|
24
|
+
const content = readFileSync(filePath, "utf-8").trim();
|
|
25
|
+
if (!content) return [];
|
|
26
|
+
|
|
27
|
+
const lines = content.split("\n");
|
|
28
|
+
const entries: ChangelogEntry[] = [];
|
|
29
|
+
|
|
30
|
+
let currentEntry: ChangelogEntry | null = null;
|
|
31
|
+
let currentSection: string | null = null;
|
|
32
|
+
let currentItems: string[] = [];
|
|
33
|
+
|
|
34
|
+
for (const line of lines) {
|
|
35
|
+
const versionMatch = line.match(VERSION_HEADER_RE);
|
|
36
|
+
if (versionMatch) {
|
|
37
|
+
// Save previous entry
|
|
38
|
+
if (currentEntry) {
|
|
39
|
+
if (currentSection && currentItems.length > 0) {
|
|
40
|
+
currentEntry.sections[currentSection] = currentItems;
|
|
41
|
+
}
|
|
42
|
+
entries.push(currentEntry);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Start new entry
|
|
46
|
+
const version = versionMatch[1].trim();
|
|
47
|
+
const date = (versionMatch[2] ?? "").trim();
|
|
48
|
+
currentEntry = {
|
|
49
|
+
version,
|
|
50
|
+
date,
|
|
51
|
+
sections: {},
|
|
52
|
+
body: "",
|
|
53
|
+
};
|
|
54
|
+
currentSection = null;
|
|
55
|
+
currentItems = [];
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!currentEntry) continue;
|
|
60
|
+
|
|
61
|
+
const sectionMatch = line.match(SECTION_HEADER_RE);
|
|
62
|
+
if (sectionMatch) {
|
|
63
|
+
// Save previous section
|
|
64
|
+
if (currentSection && currentItems.length > 0) {
|
|
65
|
+
currentEntry.sections[currentSection] = currentItems;
|
|
66
|
+
}
|
|
67
|
+
currentSection = sectionMatch[1].trim();
|
|
68
|
+
currentItems = [];
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Collect section items (lines starting with - or *)
|
|
73
|
+
const trimmed = line.trim();
|
|
74
|
+
if (currentSection && (trimmed.startsWith("- ") || trimmed.startsWith("* "))) {
|
|
75
|
+
currentItems.push(trimmed.slice(2).trim());
|
|
76
|
+
} else if (trimmed && currentSection) {
|
|
77
|
+
// Continuation of previous item
|
|
78
|
+
if (currentItems.length > 0) {
|
|
79
|
+
currentItems[currentItems.length - 1] += " " + trimmed;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Save last entry
|
|
85
|
+
if (currentEntry) {
|
|
86
|
+
if (currentSection && currentItems.length > 0) {
|
|
87
|
+
currentEntry.sections[currentSection] = currentItems;
|
|
88
|
+
}
|
|
89
|
+
entries.push(currentEntry);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Build body text for each entry (for rendering)
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
const lines: string[] = [];
|
|
95
|
+
for (const [section, items] of Object.entries(entry.sections)) {
|
|
96
|
+
lines.push(`### ${section}`);
|
|
97
|
+
for (const item of items) {
|
|
98
|
+
lines.push(`- ${item}`);
|
|
99
|
+
}
|
|
100
|
+
lines.push("");
|
|
101
|
+
}
|
|
102
|
+
entry.body = lines.join("\n").trim();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return entries;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get changelog entries for versions newer than the given version.
|
|
110
|
+
* Returns entries in newest-first order, excluding the given version.
|
|
111
|
+
*/
|
|
112
|
+
export function getNewerVersions(
|
|
113
|
+
entries: ChangelogEntry[],
|
|
114
|
+
installedVersion: string,
|
|
115
|
+
): ChangelogEntry[] {
|
|
116
|
+
const result: ChangelogEntry[] = [];
|
|
117
|
+
for (const entry of entries) {
|
|
118
|
+
if (entry.version === "Unreleased") {
|
|
119
|
+
result.push(entry);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (entry.version === installedVersion) break;
|
|
123
|
+
result.push(entry);
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Compare semver strings (simple lexicographic for x.y.z format).
|
|
130
|
+
* Returns positive if a > b, negative if a < b, 0 if equal.
|
|
131
|
+
*/
|
|
132
|
+
export function compareVersions(a: string, b: string): number {
|
|
133
|
+
const pa = a.split(".").map(Number);
|
|
134
|
+
const pb = b.split(".").map(Number);
|
|
135
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
136
|
+
const na = pa[i] ?? 0;
|
|
137
|
+
const nb = pb[i] ?? 0;
|
|
138
|
+
if (na !== nb) return na - nb;
|
|
139
|
+
}
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
package/src/checker.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/updater — NPM registry checker
|
|
3
|
+
*
|
|
4
|
+
* Fetches latest version from npm registry, compares with installed version,
|
|
5
|
+
* respects check interval from config/cache.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getPackageVersion } from "@pi-unipi/core";
|
|
9
|
+
import { loadConfig } from "./settings.js";
|
|
10
|
+
import { readLastCheck, writeLastCheck, isCheckDue } from "./cache.js";
|
|
11
|
+
import type { UpdateCheckResult } from "../types.js";
|
|
12
|
+
|
|
13
|
+
/** NPM registry URL for the unipi umbrella package */
|
|
14
|
+
const NPM_REGISTRY_URL = "https://registry.npmjs.org/@pi-unipi/unipi";
|
|
15
|
+
|
|
16
|
+
/** Resolve the installed version of @pi-unipi/unipi */
|
|
17
|
+
function getInstalledVersion(): string {
|
|
18
|
+
// Walk up from this file to find the monorepo root
|
|
19
|
+
const dir = new URL("../../..", import.meta.url).pathname;
|
|
20
|
+
return getPackageVersion(dir);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check for updates from npm registry.
|
|
25
|
+
* Respects check interval — skips if last check was recent.
|
|
26
|
+
* Returns update status and version info.
|
|
27
|
+
*/
|
|
28
|
+
export async function checkForUpdates(): Promise<UpdateCheckResult> {
|
|
29
|
+
const currentVersion = getInstalledVersion();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const config = loadConfig();
|
|
33
|
+
|
|
34
|
+
// Check if we need to fetch (interval not elapsed)
|
|
35
|
+
if (!isCheckDue(config.checkIntervalMs)) {
|
|
36
|
+
const cache = readLastCheck();
|
|
37
|
+
if (cache) {
|
|
38
|
+
return {
|
|
39
|
+
updateAvailable: cache.latestVersion !== currentVersion,
|
|
40
|
+
latestVersion: cache.latestVersion,
|
|
41
|
+
currentVersion,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fetch from npm registry
|
|
47
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
48
|
+
headers: { Accept: "application/json" },
|
|
49
|
+
signal: AbortSignal.timeout(10000), // 10s timeout
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const data = await response.json() as { "dist-tags": { latest: string } };
|
|
57
|
+
const latestVersion = data["dist-tags"]?.latest;
|
|
58
|
+
|
|
59
|
+
if (!latestVersion) {
|
|
60
|
+
throw new Error("No dist-tags.latest in npm response");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Write cache
|
|
64
|
+
writeLastCheck({
|
|
65
|
+
lastCheck: new Date().toISOString(),
|
|
66
|
+
latestVersion,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
updateAvailable: latestVersion !== currentVersion,
|
|
71
|
+
latestVersion,
|
|
72
|
+
currentVersion,
|
|
73
|
+
};
|
|
74
|
+
} catch (err: any) {
|
|
75
|
+
// Network error — return cached info if available
|
|
76
|
+
const cache = readLastCheck();
|
|
77
|
+
return {
|
|
78
|
+
updateAvailable: false,
|
|
79
|
+
latestVersion: cache?.latestVersion ?? "",
|
|
80
|
+
currentVersion,
|
|
81
|
+
error: err.message ?? "Unknown error",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/commands.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/updater — Command Registration
|
|
3
|
+
*
|
|
4
|
+
* Registers /unipi:readme [package], /unipi:changelog, /unipi:updater-settings
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
8
|
+
import { UNIPI_PREFIX, UPDATER_COMMANDS } from "@pi-unipi/core";
|
|
9
|
+
import { renderReadmeOverlay } from "./tui/readme-overlay.js";
|
|
10
|
+
import { renderChangelogOverlay } from "./tui/changelog-overlay.js";
|
|
11
|
+
import { renderSettingsOverlay } from "./tui/settings-overlay.js";
|
|
12
|
+
|
|
13
|
+
/** Common overlay options for all updater overlays */
|
|
14
|
+
const OVERLAY_OPTIONS = {
|
|
15
|
+
overlay: true,
|
|
16
|
+
overlayOptions: {
|
|
17
|
+
width: "80%",
|
|
18
|
+
minWidth: 60,
|
|
19
|
+
anchor: "center",
|
|
20
|
+
margin: 2,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Register updater commands */
|
|
25
|
+
export function registerCommands(pi: ExtensionAPI): void {
|
|
26
|
+
// /unipi:readme [package] — Open readme browser
|
|
27
|
+
pi.registerCommand(
|
|
28
|
+
`${UNIPI_PREFIX}${UPDATER_COMMANDS.README}`,
|
|
29
|
+
{
|
|
30
|
+
description: "Browse package README files",
|
|
31
|
+
handler: async (args: string, ctx: any) => {
|
|
32
|
+
const packageName = args.trim() || undefined;
|
|
33
|
+
try {
|
|
34
|
+
await ctx.ui.custom(
|
|
35
|
+
renderReadmeOverlay({ openDirect: packageName }),
|
|
36
|
+
OVERLAY_OPTIONS,
|
|
37
|
+
);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
ctx.ui.notify(`Readme overlay error: ${err}`, "error");
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// /unipi:changelog — Open changelog browser
|
|
46
|
+
pi.registerCommand(
|
|
47
|
+
`${UNIPI_PREFIX}${UPDATER_COMMANDS.CHANGELOG}`,
|
|
48
|
+
{
|
|
49
|
+
description: "Browse changelog (Keep a Changelog format)",
|
|
50
|
+
handler: async (_args: string, ctx: any) => {
|
|
51
|
+
try {
|
|
52
|
+
await ctx.ui.custom(
|
|
53
|
+
renderChangelogOverlay(),
|
|
54
|
+
OVERLAY_OPTIONS,
|
|
55
|
+
);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
ctx.ui.notify(`Changelog overlay error: ${err}`, "error");
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// /unipi:updater-settings — Open updater settings
|
|
64
|
+
pi.registerCommand(
|
|
65
|
+
`${UNIPI_PREFIX}${UPDATER_COMMANDS.UPDATER_SETTINGS}`,
|
|
66
|
+
{
|
|
67
|
+
description: "Configure updater — check interval and auto-update mode",
|
|
68
|
+
handler: async (_args: string, ctx: any) => {
|
|
69
|
+
try {
|
|
70
|
+
const result = await ctx.ui.custom(
|
|
71
|
+
renderSettingsOverlay(),
|
|
72
|
+
OVERLAY_OPTIONS,
|
|
73
|
+
);
|
|
74
|
+
if (result?.saved) {
|
|
75
|
+
ctx.ui.notify("Updater settings saved.", "info");
|
|
76
|
+
}
|
|
77
|
+
} catch (err) {
|
|
78
|
+
ctx.ui.notify(`Settings overlay error: ${err}`, "error");
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
}
|