@pi-unipi/unipi 2.17.1 → 2.17.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.
- package/CHANGELOG.md +7 -0
- package/package.json +3 -3
- package/packages/footer/package.json +1 -1
- package/packages/footer/src/index.ts +14 -7
- package/packages/unipi/bundled.js +127 -74
- package/packages/updater/package.json +1 -1
- package/packages/updater/src/changelog.ts +7 -4
- package/packages/updater/src/index.ts +3 -1
- package/packages/updater/src/remote-changelog.ts +61 -0
- package/packages/updater/src/tui/update-overlay.ts +12 -10
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.17.2] — 2026-09-15
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `footer`: **the startup update prompt no longer becomes unclosable after ~3.5s.** The deferred Glance editor install (`installGlanceEditor` in `src/index.ts`) calls `setEditorComponent()`, which internally re-focuses the editor and silently stole keyboard focus from whatever overlay was open — the updater prompt kept rendering while `q`/`n`/Esc typed into the input box. The install now snapshots the focused overlay (`tui.isOverlayFocused()` / `getFocusedComponent()`) and restores it after the swap, protecting the updater prompt, the boot dashboard, and any other overlay. New `tests/glance-focus.test.ts`.
|
|
14
|
+
- `updater`: **the update prompt now shows the release notes for the version being offered.** It previously parsed the `CHANGELOG.md` shipped with the *installed* package, which by definition never contains the newer version's entry, so it only ever showed an empty `Unreleased` header. New `src/remote-changelog.ts` fetches `raw.githubusercontent.com/Neuron-Mr-White/unipi/v<latest>/CHANGELOG.md` (5s timeout, falls back to `main` when the tag is not pushed yet, then to the local file when offline) and caches tag-served content under `~/.unipi/cache/updater/changelog-<version>.md`. `parseChangelog()` is split into `parseChangelogContent()` + a file wrapper; `getNewerVersions()` drops an empty `Unreleased` entry; the empty state reads `No changelog available for <version> (offline?)`.
|
|
15
|
+
|
|
9
16
|
## [2.17.1] — 2026-09-15
|
|
10
17
|
|
|
11
18
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/unipi",
|
|
3
|
-
"version": "2.17.
|
|
3
|
+
"version": "2.17.2",
|
|
4
4
|
"description": "All-in-one extension suite for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"@pi-unipi/command-enchantment": "2.17.0",
|
|
91
91
|
"@pi-unipi/compactor": "2.17.0",
|
|
92
92
|
"@pi-unipi/core": "2.17.0",
|
|
93
|
-
"@pi-unipi/footer": "2.17.
|
|
93
|
+
"@pi-unipi/footer": "2.17.2",
|
|
94
94
|
"@pi-unipi/image": "2.17.0",
|
|
95
95
|
"@pi-unipi/fusion": "2.17.1",
|
|
96
96
|
"@pi-unipi/info-screen": "2.17.0",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"@pi-unipi/ralph": "2.17.0",
|
|
104
104
|
"@pi-unipi/subagents": "2.17.0",
|
|
105
105
|
"@pi-unipi/trajectory": "2.17.0",
|
|
106
|
-
"@pi-unipi/updater": "2.17.
|
|
106
|
+
"@pi-unipi/updater": "2.17.2",
|
|
107
107
|
"@pi-unipi/utility": "2.17.0",
|
|
108
108
|
"@pi-unipi/web-api": "2.17.0",
|
|
109
109
|
"@pi-unipi/workflow": "2.17.0"
|
|
@@ -153,13 +153,10 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
153
153
|
// Glance-style input surface (pi-glance-inspired). Preserves all default
|
|
154
154
|
// editor behavior via CustomEditor subclassing; only paint differs.
|
|
155
155
|
//
|
|
156
|
-
// FOCUS-SAFETY DEFERRAL:
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
// and strand the dashboard unclosable (q/Esc would type into the editor).
|
|
161
|
-
// The boot overlay auto-closes after ~2s; we install after a grace period
|
|
162
|
-
// longer than any sane bootTimeoutMs.
|
|
156
|
+
// FOCUS-SAFETY DEFERRAL: the timer remains a grace period for the boot
|
|
157
|
+
// dashboard, but installGlanceEditor also restores any overlay focus after
|
|
158
|
+
// setEditorComponent() calls ui.setFocus(newEditor). This covers the
|
|
159
|
+
// updater prompt too, so q/Esc cannot be stranded in the editor.
|
|
163
160
|
state.glanceInstallTimer = setTimeout(() => installGlanceEditor(state, ctx), 3500);
|
|
164
161
|
|
|
165
162
|
// Sync TPS cursor with persisted assistant messages so streaming-hook
|
|
@@ -420,6 +417,15 @@ function installGlanceEditor(
|
|
|
420
417
|
): void {
|
|
421
418
|
if (st.glanceInstalled || !st.piContext || !st.glanceMode) return;
|
|
422
419
|
try {
|
|
420
|
+
const tui = st.tuiRef as (import("@earendil-works/pi-tui").TUI & {
|
|
421
|
+
isOverlayFocused?: () => boolean;
|
|
422
|
+
getFocusedComponent?: () => import("@earendil-works/pi-tui").Component | null;
|
|
423
|
+
}) | null | undefined;
|
|
424
|
+
const overlayFocused = tui !== undefined && tui !== null
|
|
425
|
+
&& (typeof tui.isOverlayFocused === "function" ? tui.isOverlayFocused() : tui.hasOverlay());
|
|
426
|
+
const overlayOwner = overlayFocused && typeof tui?.getFocusedComponent === "function"
|
|
427
|
+
? tui.getFocusedComponent() ?? null
|
|
428
|
+
: null;
|
|
423
429
|
const piCtx = st.piContext as Record<string, unknown> | undefined;
|
|
424
430
|
const cwd = (piCtx?.sessionManager as any)?.getCwd?.() ?? (piCtx as any)?.cwd ?? process.cwd();
|
|
425
431
|
const workspace = String(cwd).split("/").filter(Boolean).pop() ?? "~";
|
|
@@ -445,6 +451,7 @@ function installGlanceEditor(
|
|
|
445
451
|
};
|
|
446
452
|
}),
|
|
447
453
|
);
|
|
454
|
+
if (overlayOwner && tui) tui.setFocus(overlayOwner);
|
|
448
455
|
st.glanceInstalled = true;
|
|
449
456
|
} catch {
|
|
450
457
|
st.glanceInstalled = false;
|
|
@@ -6978,7 +6978,7 @@ var init_hooks = __esm({
|
|
|
6978
6978
|
if (!debug) return;
|
|
6979
6979
|
try {
|
|
6980
6980
|
import("node:fs").then(
|
|
6981
|
-
({ writeFileSync:
|
|
6981
|
+
({ writeFileSync: writeFileSync49 }) => writeFileSync49("/tmp/compactor-debug.json", JSON.stringify(data, null, 2))
|
|
6982
6982
|
).catch(() => {
|
|
6983
6983
|
});
|
|
6984
6984
|
} catch {
|
|
@@ -15246,8 +15246,8 @@ function resolvePiPackageRoot() {
|
|
|
15246
15246
|
return void 0;
|
|
15247
15247
|
}
|
|
15248
15248
|
}
|
|
15249
|
-
function isRunnableNodeScript(filePath,
|
|
15250
|
-
if (!
|
|
15249
|
+
function isRunnableNodeScript(filePath, existsSync65) {
|
|
15250
|
+
if (!existsSync65(filePath)) return false;
|
|
15251
15251
|
return /\.(?:mjs|cjs|js)$/i.test(filePath);
|
|
15252
15252
|
}
|
|
15253
15253
|
function normalizePath(filePath) {
|
|
@@ -15258,16 +15258,16 @@ function isStandalonePiExecutable(execPath) {
|
|
|
15258
15258
|
return /^pi(?:\.exe)?$/i.test(executableName ?? "");
|
|
15259
15259
|
}
|
|
15260
15260
|
function resolvePiCliScript(deps = {}) {
|
|
15261
|
-
const
|
|
15261
|
+
const existsSync65 = deps.existsSync ?? fs9.existsSync;
|
|
15262
15262
|
const realpathSync9 = deps.realpathSync ?? fs9.realpathSync;
|
|
15263
|
-
const
|
|
15263
|
+
const readFileSync56 = deps.readFileSync ?? ((filePath, encoding) => fs9.readFileSync(filePath, encoding));
|
|
15264
15264
|
const argv1 = deps.argv1 ?? process.argv[1];
|
|
15265
15265
|
if (argv1) {
|
|
15266
15266
|
const argvPath = normalizePath(argv1);
|
|
15267
|
-
if (isRunnableNodeScript(argvPath,
|
|
15267
|
+
if (isRunnableNodeScript(argvPath, existsSync65)) {
|
|
15268
15268
|
try {
|
|
15269
15269
|
const canonicalArgvPath = realpathSync9(argvPath);
|
|
15270
|
-
if (isRunnableNodeScript(canonicalArgvPath,
|
|
15270
|
+
if (isRunnableNodeScript(canonicalArgvPath, existsSync65) && findPiPackageRootFromEntry(canonicalArgvPath)) {
|
|
15271
15271
|
return canonicalArgvPath;
|
|
15272
15272
|
}
|
|
15273
15273
|
} catch {
|
|
@@ -15285,12 +15285,12 @@ function resolvePiCliScript(deps = {}) {
|
|
|
15285
15285
|
return path9.join(packageRoot, "package.json");
|
|
15286
15286
|
});
|
|
15287
15287
|
const packageJsonPath = resolvePackageJson();
|
|
15288
|
-
const packageJson = JSON.parse(
|
|
15288
|
+
const packageJson = JSON.parse(readFileSync56(packageJsonPath, "utf8"));
|
|
15289
15289
|
const binField = packageJson.bin;
|
|
15290
15290
|
const binPath = typeof binField === "string" ? binField : binField?.pi ?? Object.values(binField ?? {})[0];
|
|
15291
15291
|
if (!binPath) return void 0;
|
|
15292
15292
|
const candidate = path9.resolve(path9.dirname(packageJsonPath), binPath);
|
|
15293
|
-
if (isRunnableNodeScript(candidate,
|
|
15293
|
+
if (isRunnableNodeScript(candidate, existsSync65)) {
|
|
15294
15294
|
return candidate;
|
|
15295
15295
|
}
|
|
15296
15296
|
} catch {
|
|
@@ -42422,15 +42422,15 @@ function compactorExtension(pi) {
|
|
|
42422
42422
|
config = loadConfig4(cwd);
|
|
42423
42423
|
currentSessionId = `${ctx.sessionId ?? "default"}${getWorktreeSuffix()}`;
|
|
42424
42424
|
try {
|
|
42425
|
-
const { existsSync:
|
|
42426
|
-
const { join:
|
|
42425
|
+
const { existsSync: existsSync65 } = await import("node:fs");
|
|
42426
|
+
const { join: join78 } = await import("node:path");
|
|
42427
42427
|
const strategies = [
|
|
42428
42428
|
{ key: "commits", config: config.commits }
|
|
42429
42429
|
];
|
|
42430
42430
|
for (const { key, config: strat } of strategies) {
|
|
42431
42431
|
if (strat.autoDetect === "git") {
|
|
42432
|
-
const gitDir =
|
|
42433
|
-
if (!
|
|
42432
|
+
const gitDir = join78(cwd, ".git");
|
|
42433
|
+
if (!existsSync65(gitDir)) {
|
|
42434
42434
|
strat.enabled = false;
|
|
42435
42435
|
}
|
|
42436
42436
|
}
|
|
@@ -46449,11 +46449,14 @@ function applyGlanceMode(st, cmdCtx) {
|
|
|
46449
46449
|
function installGlanceEditor(st, uiHost) {
|
|
46450
46450
|
if (st.glanceInstalled || !st.piContext || !st.glanceMode) return;
|
|
46451
46451
|
try {
|
|
46452
|
+
const tui = st.tuiRef;
|
|
46453
|
+
const overlayFocused = tui !== void 0 && tui !== null && (typeof tui.isOverlayFocused === "function" ? tui.isOverlayFocused() : tui.hasOverlay());
|
|
46454
|
+
const overlayOwner = overlayFocused && typeof tui?.getFocusedComponent === "function" ? tui.getFocusedComponent() ?? null : null;
|
|
46452
46455
|
const piCtx = st.piContext;
|
|
46453
46456
|
const cwd = piCtx?.sessionManager?.getCwd?.() ?? piCtx?.cwd ?? process.cwd();
|
|
46454
46457
|
const workspace = String(cwd).split("/").filter(Boolean).pop() ?? "~";
|
|
46455
46458
|
uiHost.ui.setEditorComponent(
|
|
46456
|
-
(
|
|
46459
|
+
(tui2, theme, keybindings) => new GlanceEditor(tui2, theme, keybindings, () => {
|
|
46457
46460
|
const p = st.piContext;
|
|
46458
46461
|
const usage2 = typeof p?.getContextUsage === "function" ? p.getContextUsage() : void 0;
|
|
46459
46462
|
const model = p?.model;
|
|
@@ -46472,6 +46475,7 @@ function installGlanceEditor(st, uiHost) {
|
|
|
46472
46475
|
};
|
|
46473
46476
|
})
|
|
46474
46477
|
);
|
|
46478
|
+
if (overlayOwner && tui) tui.setFocus(overlayOwner);
|
|
46475
46479
|
st.glanceInstalled = true;
|
|
46476
46480
|
} catch {
|
|
46477
46481
|
st.glanceInstalled = false;
|
|
@@ -46968,9 +46972,12 @@ var VERSION_HEADER_RE = /^## \[(.+?)\](?:\s*[-—–]\s*(.+))?$/;
|
|
|
46968
46972
|
var SECTION_HEADER_RE = /^### (.+)$/;
|
|
46969
46973
|
function parseChangelog(filePath) {
|
|
46970
46974
|
if (!existsSync56(filePath)) return [];
|
|
46971
|
-
|
|
46972
|
-
|
|
46973
|
-
|
|
46975
|
+
return parseChangelogContent(readFileSync47(filePath, "utf-8"));
|
|
46976
|
+
}
|
|
46977
|
+
function parseChangelogContent(content) {
|
|
46978
|
+
const trimmed = content.trim();
|
|
46979
|
+
if (!trimmed) return [];
|
|
46980
|
+
const lines = trimmed.split("\n");
|
|
46974
46981
|
const entries = [];
|
|
46975
46982
|
let currentEntry = null;
|
|
46976
46983
|
let currentSection = null;
|
|
@@ -47006,12 +47013,12 @@ function parseChangelog(filePath) {
|
|
|
47006
47013
|
currentItems = [];
|
|
47007
47014
|
continue;
|
|
47008
47015
|
}
|
|
47009
|
-
const
|
|
47010
|
-
if (currentSection && (
|
|
47011
|
-
currentItems.push(
|
|
47012
|
-
} else if (
|
|
47016
|
+
const trimmed2 = line.trim();
|
|
47017
|
+
if (currentSection && (trimmed2.startsWith("- ") || trimmed2.startsWith("* "))) {
|
|
47018
|
+
currentItems.push(trimmed2.slice(2).trim());
|
|
47019
|
+
} else if (trimmed2 && currentSection) {
|
|
47013
47020
|
if (currentItems.length > 0) {
|
|
47014
|
-
currentItems[currentItems.length - 1] += " " +
|
|
47021
|
+
currentItems[currentItems.length - 1] += " " + trimmed2;
|
|
47015
47022
|
}
|
|
47016
47023
|
}
|
|
47017
47024
|
}
|
|
@@ -47038,7 +47045,7 @@ function getNewerVersions(entries, installedVersion) {
|
|
|
47038
47045
|
const result2 = [];
|
|
47039
47046
|
for (const entry of entries) {
|
|
47040
47047
|
if (entry.version === "Unreleased") {
|
|
47041
|
-
result2.push(entry);
|
|
47048
|
+
if (Object.keys(entry.sections).length > 0) result2.push(entry);
|
|
47042
47049
|
continue;
|
|
47043
47050
|
}
|
|
47044
47051
|
if (!isNewerVersion(entry.version, installedVersion)) break;
|
|
@@ -47475,15 +47482,17 @@ function padVisible3(content, targetWidth) {
|
|
|
47475
47482
|
const pad2 = Math.max(0, targetWidth - vw);
|
|
47476
47483
|
return content + " ".repeat(pad2);
|
|
47477
47484
|
}
|
|
47478
|
-
function renderUpdateOverlay(checkResult) {
|
|
47485
|
+
function renderUpdateOverlay(checkResult, providedNewerVersions) {
|
|
47479
47486
|
return (tui, theme, _kb, done) => {
|
|
47480
47487
|
const config = loadConfig5();
|
|
47481
|
-
let newerVersions = [];
|
|
47482
|
-
|
|
47483
|
-
|
|
47484
|
-
|
|
47485
|
-
|
|
47486
|
-
|
|
47488
|
+
let newerVersions = providedNewerVersions ?? [];
|
|
47489
|
+
if (providedNewerVersions === void 0) {
|
|
47490
|
+
const changelogPath = resolveChangelogPath();
|
|
47491
|
+
try {
|
|
47492
|
+
const entries = parseChangelog(changelogPath);
|
|
47493
|
+
newerVersions = getNewerVersions(entries, checkResult.currentVersion);
|
|
47494
|
+
} catch (_err) {
|
|
47495
|
+
}
|
|
47487
47496
|
}
|
|
47488
47497
|
const contentLines = [];
|
|
47489
47498
|
for (const entry of newerVersions) {
|
|
@@ -47496,7 +47505,7 @@ function renderUpdateOverlay(checkResult) {
|
|
|
47496
47505
|
contentLines.push("");
|
|
47497
47506
|
}
|
|
47498
47507
|
if (contentLines.length === 0) {
|
|
47499
|
-
contentLines.push(` ${theme.fg("muted",
|
|
47508
|
+
contentLines.push(` ${theme.fg("muted", `No changelog available for ${checkResult.latestVersion} (offline?).`)}`);
|
|
47500
47509
|
}
|
|
47501
47510
|
const state2 = {
|
|
47502
47511
|
result: checkResult,
|
|
@@ -47633,6 +47642,49 @@ function renderUpdateOverlay(checkResult) {
|
|
|
47633
47642
|
};
|
|
47634
47643
|
}
|
|
47635
47644
|
|
|
47645
|
+
// packages/updater/src/remote-changelog.ts
|
|
47646
|
+
init_core();
|
|
47647
|
+
import { existsSync as existsSync60, mkdirSync as mkdirSync38, readFileSync as readFileSync50, writeFileSync as writeFileSync42 } from "node:fs";
|
|
47648
|
+
import { homedir as homedir34 } from "node:os";
|
|
47649
|
+
import { join as join69 } from "node:path";
|
|
47650
|
+
var CHANGELOG_RAW_BASE = "https://raw.githubusercontent.com/Neuron-Mr-White/unipi";
|
|
47651
|
+
function cacheDirectory(opts) {
|
|
47652
|
+
return (opts?.cacheDir ?? UPDATER_DIRS.CACHE).replace("~", homedir34());
|
|
47653
|
+
}
|
|
47654
|
+
async function fetchRemoteChangelog(version, opts = {}) {
|
|
47655
|
+
const cacheDir2 = cacheDirectory(opts);
|
|
47656
|
+
const cachePath = join69(cacheDir2, `changelog-${version}.md`);
|
|
47657
|
+
try {
|
|
47658
|
+
if (existsSync60(cachePath)) return readFileSync50(cachePath, "utf8");
|
|
47659
|
+
} catch {
|
|
47660
|
+
}
|
|
47661
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
47662
|
+
const signal = AbortSignal.timeout(opts.timeoutMs ?? 5e3);
|
|
47663
|
+
try {
|
|
47664
|
+
let response = await fetchImpl(`${CHANGELOG_RAW_BASE}/v${version}/CHANGELOG.md`, { signal });
|
|
47665
|
+
const cacheable = response.ok;
|
|
47666
|
+
if (response.status === 404) {
|
|
47667
|
+
response = await fetchImpl(`${CHANGELOG_RAW_BASE}/main/CHANGELOG.md`, { signal });
|
|
47668
|
+
}
|
|
47669
|
+
if (!response.ok) return null;
|
|
47670
|
+
const content = await response.text();
|
|
47671
|
+
if (cacheable) {
|
|
47672
|
+
mkdirSync38(cacheDir2, { recursive: true });
|
|
47673
|
+
writeFileSync42(cachePath, content, "utf8");
|
|
47674
|
+
}
|
|
47675
|
+
return content;
|
|
47676
|
+
} catch {
|
|
47677
|
+
return null;
|
|
47678
|
+
}
|
|
47679
|
+
}
|
|
47680
|
+
async function loadUpdateChangelog(currentVersion, latestVersion, opts) {
|
|
47681
|
+
const remote = await fetchRemoteChangelog(latestVersion, opts);
|
|
47682
|
+
const entries = remote === null ? [] : parseChangelogContent(remote);
|
|
47683
|
+
const newer = remote === null ? [] : getNewerVersions(entries, currentVersion);
|
|
47684
|
+
if (newer.length > 0) return newer;
|
|
47685
|
+
return getNewerVersions(parseChangelog(resolveChangelogPath()), currentVersion);
|
|
47686
|
+
}
|
|
47687
|
+
|
|
47636
47688
|
// packages/updater/src/index.ts
|
|
47637
47689
|
var VERSION10 = getPackageVersion(new URL("..", import.meta.url).pathname);
|
|
47638
47690
|
function updaterExtension(pi) {
|
|
@@ -47726,8 +47778,9 @@ function updaterExtension(pi) {
|
|
|
47726
47778
|
latestVersion: result2.latestVersion
|
|
47727
47779
|
});
|
|
47728
47780
|
if (ctx.hasUI) {
|
|
47781
|
+
const entries = await loadUpdateChangelog(result2.currentVersion, result2.latestVersion);
|
|
47729
47782
|
const updateResult = await ctx.ui.custom(
|
|
47730
|
-
renderUpdateOverlay(result2),
|
|
47783
|
+
renderUpdateOverlay(result2, entries),
|
|
47731
47784
|
{
|
|
47732
47785
|
overlay: true,
|
|
47733
47786
|
overlayOptions: {
|
|
@@ -47755,8 +47808,8 @@ init_core();
|
|
|
47755
47808
|
import { Key as Key19, matchesKey as matchesKey25 } from "@earendil-works/pi-tui";
|
|
47756
47809
|
|
|
47757
47810
|
// packages/input-shortcuts/src/registers.ts
|
|
47758
|
-
import { existsSync as
|
|
47759
|
-
import { dirname as dirname45, join as
|
|
47811
|
+
import { existsSync as existsSync61, mkdirSync as mkdirSync39, readFileSync as readFileSync51, renameSync as renameSync13, writeFileSync as writeFileSync43 } from "node:fs";
|
|
47812
|
+
import { dirname as dirname45, join as join70 } from "node:path";
|
|
47760
47813
|
|
|
47761
47814
|
// packages/input-shortcuts/src/types.ts
|
|
47762
47815
|
var DEFAULT_CONFIG8 = {
|
|
@@ -47779,7 +47832,7 @@ var RegisterStore = class {
|
|
|
47779
47832
|
filePath;
|
|
47780
47833
|
loaded = false;
|
|
47781
47834
|
constructor(baseDir) {
|
|
47782
|
-
this.filePath = baseDir ?
|
|
47835
|
+
this.filePath = baseDir ? join70(baseDir, REGISTERS_FILE) : REGISTERS_FILE;
|
|
47783
47836
|
}
|
|
47784
47837
|
/** Get the stash register contents. */
|
|
47785
47838
|
getStash() {
|
|
@@ -47803,8 +47856,8 @@ var RegisterStore = class {
|
|
|
47803
47856
|
if (this.loaded) return;
|
|
47804
47857
|
this.loaded = true;
|
|
47805
47858
|
try {
|
|
47806
|
-
if (
|
|
47807
|
-
const raw =
|
|
47859
|
+
if (existsSync61(this.filePath)) {
|
|
47860
|
+
const raw = readFileSync51(this.filePath, "utf-8");
|
|
47808
47861
|
const parsed = JSON.parse(raw);
|
|
47809
47862
|
this.data = {
|
|
47810
47863
|
stash: typeof parsed.stash === "string" ? parsed.stash : "",
|
|
@@ -47821,11 +47874,11 @@ var RegisterStore = class {
|
|
|
47821
47874
|
save() {
|
|
47822
47875
|
try {
|
|
47823
47876
|
const dir = dirname45(this.filePath);
|
|
47824
|
-
if (!
|
|
47825
|
-
|
|
47877
|
+
if (!existsSync61(dir)) {
|
|
47878
|
+
mkdirSync39(dir, { recursive: true });
|
|
47826
47879
|
}
|
|
47827
47880
|
const tmpPath = this.filePath + ".tmp";
|
|
47828
|
-
|
|
47881
|
+
writeFileSync43(tmpPath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
47829
47882
|
renameSync13(tmpPath, this.filePath);
|
|
47830
47883
|
} catch {
|
|
47831
47884
|
}
|
|
@@ -48037,8 +48090,8 @@ var ChordOverlay = class extends Container2 {
|
|
|
48037
48090
|
|
|
48038
48091
|
// packages/input-shortcuts/src/settings-overlay.ts
|
|
48039
48092
|
import { SettingsList as SettingsList3 } from "@earendil-works/pi-tui";
|
|
48040
|
-
import { existsSync as
|
|
48041
|
-
import { dirname as dirname46, join as
|
|
48093
|
+
import { existsSync as existsSync62, mkdirSync as mkdirSync40, readFileSync as readFileSync52, renameSync as renameSync14, writeFileSync as writeFileSync44 } from "node:fs";
|
|
48094
|
+
import { dirname as dirname46, join as join71 } from "node:path";
|
|
48042
48095
|
var ALT_KEY_OPTIONS = [
|
|
48043
48096
|
"alt+a",
|
|
48044
48097
|
"alt+b",
|
|
@@ -48070,10 +48123,10 @@ var ALT_KEY_OPTIONS = [
|
|
|
48070
48123
|
var CONFLICTS = /* @__PURE__ */ new Set(["alt+e"]);
|
|
48071
48124
|
var FREE_ALT_KEYS = ALT_KEY_OPTIONS.filter((k) => !CONFLICTS.has(k));
|
|
48072
48125
|
function loadConfig6(baseDir) {
|
|
48073
|
-
const filePath = baseDir ?
|
|
48126
|
+
const filePath = baseDir ? join71(baseDir, CONFIG_FILE) : CONFIG_FILE;
|
|
48074
48127
|
try {
|
|
48075
|
-
if (
|
|
48076
|
-
const raw =
|
|
48128
|
+
if (existsSync62(filePath)) {
|
|
48129
|
+
const raw = readFileSync52(filePath, "utf-8");
|
|
48077
48130
|
const parsed = JSON.parse(raw);
|
|
48078
48131
|
return {
|
|
48079
48132
|
chordKey: typeof parsed.chordKey === "string" ? parsed.chordKey : DEFAULT_CONFIG8.chordKey,
|
|
@@ -48085,14 +48138,14 @@ function loadConfig6(baseDir) {
|
|
|
48085
48138
|
return { ...DEFAULT_CONFIG8 };
|
|
48086
48139
|
}
|
|
48087
48140
|
function saveConfig5(config, baseDir) {
|
|
48088
|
-
const filePath = baseDir ?
|
|
48141
|
+
const filePath = baseDir ? join71(baseDir, CONFIG_FILE) : CONFIG_FILE;
|
|
48089
48142
|
try {
|
|
48090
48143
|
const dir = dirname46(filePath);
|
|
48091
|
-
if (!
|
|
48092
|
-
|
|
48144
|
+
if (!existsSync62(dir)) {
|
|
48145
|
+
mkdirSync40(dir, { recursive: true });
|
|
48093
48146
|
}
|
|
48094
48147
|
const tmpPath = filePath + ".tmp";
|
|
48095
|
-
|
|
48148
|
+
writeFileSync44(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
48096
48149
|
renameSync14(tmpPath, filePath);
|
|
48097
48150
|
} catch {
|
|
48098
48151
|
}
|
|
@@ -50304,13 +50357,13 @@ function src_default5(pi) {
|
|
|
50304
50357
|
|
|
50305
50358
|
// packages/fusion/src/index.ts
|
|
50306
50359
|
init_core();
|
|
50307
|
-
import { homedir as
|
|
50308
|
-
import { join as
|
|
50360
|
+
import { homedir as homedir37 } from "node:os";
|
|
50361
|
+
import { join as join76 } from "node:path";
|
|
50309
50362
|
|
|
50310
50363
|
// packages/fusion/src/preset.ts
|
|
50311
|
-
import { existsSync as
|
|
50312
|
-
import { homedir as
|
|
50313
|
-
import { dirname as dirname48, join as
|
|
50364
|
+
import { existsSync as existsSync64, mkdirSync as mkdirSync43, readFileSync as readFileSync55, writeFileSync as writeFileSync47, renameSync as renameSync15 } from "node:fs";
|
|
50365
|
+
import { homedir as homedir36 } from "node:os";
|
|
50366
|
+
import { dirname as dirname48, join as join74 } from "node:path";
|
|
50314
50367
|
var PRESET_SCHEMA_VERSION = 1;
|
|
50315
50368
|
var EFFORT_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
50316
50369
|
var RECENT_LIMIT = 5;
|
|
@@ -50326,11 +50379,11 @@ function emptyPreset() {
|
|
|
50326
50379
|
prices: {}
|
|
50327
50380
|
};
|
|
50328
50381
|
}
|
|
50329
|
-
function globalPresetPath(home =
|
|
50330
|
-
return
|
|
50382
|
+
function globalPresetPath(home = homedir36()) {
|
|
50383
|
+
return join74(home, ".unipi", "config", "fusion", "preset.json");
|
|
50331
50384
|
}
|
|
50332
50385
|
function projectPresetPath(cwd) {
|
|
50333
|
-
return
|
|
50386
|
+
return join74(cwd, ".unipi", "fusion-preset.json");
|
|
50334
50387
|
}
|
|
50335
50388
|
function modelKey(model) {
|
|
50336
50389
|
return `${model.provider}/${model.id}`;
|
|
@@ -50424,13 +50477,13 @@ function mergePresets(base, over) {
|
|
|
50424
50477
|
}
|
|
50425
50478
|
function readJson4(path41) {
|
|
50426
50479
|
try {
|
|
50427
|
-
if (!
|
|
50428
|
-
return JSON.parse(
|
|
50480
|
+
if (!existsSync64(path41)) return void 0;
|
|
50481
|
+
return JSON.parse(readFileSync55(path41, "utf8"));
|
|
50429
50482
|
} catch {
|
|
50430
50483
|
return void 0;
|
|
50431
50484
|
}
|
|
50432
50485
|
}
|
|
50433
|
-
function loadPreset(cwd, home =
|
|
50486
|
+
function loadPreset(cwd, home = homedir36()) {
|
|
50434
50487
|
const globalPath = globalPresetPath(home);
|
|
50435
50488
|
const projectPath = projectPresetPath(cwd);
|
|
50436
50489
|
const globalRaw = readJson4(globalPath);
|
|
@@ -50441,9 +50494,9 @@ function loadPreset(cwd, home = homedir35()) {
|
|
|
50441
50494
|
return { preset: preset2, globalPath, projectPath, hasProjectLayer };
|
|
50442
50495
|
}
|
|
50443
50496
|
function writeJsonAtomic2(path41, value) {
|
|
50444
|
-
|
|
50497
|
+
mkdirSync43(dirname48(path41), { recursive: true });
|
|
50445
50498
|
const tmp = `${path41}.${String(process.pid)}.tmp`;
|
|
50446
|
-
|
|
50499
|
+
writeFileSync47(tmp, `${JSON.stringify(value, null, 2)}
|
|
50447
50500
|
`, "utf8");
|
|
50448
50501
|
renameSync15(tmp, path41);
|
|
50449
50502
|
}
|
|
@@ -51038,9 +51091,9 @@ var PresetEditor = class {
|
|
|
51038
51091
|
|
|
51039
51092
|
// packages/fusion/src/sidekick-runtime.ts
|
|
51040
51093
|
import { spawn as defaultSpawn2 } from "node:child_process";
|
|
51041
|
-
import { mkdirSync as
|
|
51094
|
+
import { mkdirSync as mkdirSync44, unlinkSync as unlinkSync12, writeFileSync as writeFileSync48 } from "node:fs";
|
|
51042
51095
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
51043
|
-
import { dirname as dirname49, join as
|
|
51096
|
+
import { dirname as dirname49, join as join75 } from "node:path";
|
|
51044
51097
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
51045
51098
|
var emptyUsage = () => ({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 });
|
|
51046
51099
|
var SidekickRuntime = class {
|
|
@@ -51080,9 +51133,9 @@ var SidekickRuntime = class {
|
|
|
51080
51133
|
this.promptPath = void 0;
|
|
51081
51134
|
}
|
|
51082
51135
|
spawn() {
|
|
51083
|
-
|
|
51084
|
-
this.promptPath =
|
|
51085
|
-
|
|
51136
|
+
mkdirSync44(dirname49(this.cfg.sessionFile), { recursive: true });
|
|
51137
|
+
this.promptPath = join75(tmpdir7(), `unipi-fusion-${randomUUID11()}.txt`);
|
|
51138
|
+
writeFileSync48(this.promptPath, this.cfg.systemPrompt, "utf8");
|
|
51086
51139
|
const command = this.cfg.command ?? getPiSpawnCommand([
|
|
51087
51140
|
"--mode",
|
|
51088
51141
|
"rpc",
|
|
@@ -51501,7 +51554,7 @@ ${progressText(runtime, id)}`);
|
|
|
51501
51554
|
var MODEL_COMMAND = `${UNIPI_PREFIX}model`;
|
|
51502
51555
|
var PRESET_COMMAND = `${UNIPI_PREFIX}fusion-preset`;
|
|
51503
51556
|
function sidekickSessionPath(leadSessionId) {
|
|
51504
|
-
return
|
|
51557
|
+
return join76(homedir37(), ".unipi", "state", "fusion", "sidekick", `${leadSessionId ?? "default"}.jsonl`);
|
|
51505
51558
|
}
|
|
51506
51559
|
function registryOf(ctx) {
|
|
51507
51560
|
const r = ctx.modelRegistry;
|
|
@@ -51964,9 +52017,9 @@ var PrefixIntegrityTracker = class {
|
|
|
51964
52017
|
};
|
|
51965
52018
|
|
|
51966
52019
|
// packages/trajectory/src/telemetry.ts
|
|
51967
|
-
import { appendFileSync, closeSync as closeSync4, mkdirSync as
|
|
51968
|
-
import { homedir as
|
|
51969
|
-
import { join as
|
|
52020
|
+
import { appendFileSync, closeSync as closeSync4, mkdirSync as mkdirSync45, openSync as openSync4, readSync as readSync3, statSync as statSync18 } from "node:fs";
|
|
52021
|
+
import { homedir as homedir38 } from "node:os";
|
|
52022
|
+
import { join as join77 } from "node:path";
|
|
51970
52023
|
var SECRET_KEY = /authorization|api[-_]?key|token|cookie|secret|password|credential/i;
|
|
51971
52024
|
var SECRET_VALUE = /^(?:bearer\s+|sk-[a-z0-9_-]{12,}|gh[pousr]_[a-z0-9]{12,}|AIza[a-z0-9_-]{20,})/i;
|
|
51972
52025
|
var MAX_STRING = 2e5;
|
|
@@ -52030,9 +52083,9 @@ function redactTelemetry(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
52030
52083
|
var TelemetrySidecar = class {
|
|
52031
52084
|
file;
|
|
52032
52085
|
state;
|
|
52033
|
-
constructor(sessionId, root =
|
|
52034
|
-
|
|
52035
|
-
this.file =
|
|
52086
|
+
constructor(sessionId, root = join77(homedir38(), ".unipi", "trajectory")) {
|
|
52087
|
+
mkdirSync45(root, { recursive: true, mode: 448 });
|
|
52088
|
+
this.file = join77(root, `${sessionId.replace(/[^a-zA-Z0-9._-]/g, "_")}.jsonl`);
|
|
52036
52089
|
this.state = stateFor(this.file);
|
|
52037
52090
|
}
|
|
52038
52091
|
append(event) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/updater",
|
|
3
|
-
"version": "2.17.
|
|
3
|
+
"version": "2.17.2",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -47,11 +47,14 @@ const SECTION_HEADER_RE = /^### (.+)$/;
|
|
|
47
47
|
*/
|
|
48
48
|
export function parseChangelog(filePath: string): ChangelogEntry[] {
|
|
49
49
|
if (!existsSync(filePath)) return [];
|
|
50
|
+
return parseChangelogContent(readFileSync(filePath, "utf-8"));
|
|
51
|
+
}
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
export function parseChangelogContent(content: string): ChangelogEntry[] {
|
|
54
|
+
const trimmed = content.trim();
|
|
55
|
+
if (!trimmed) return [];
|
|
53
56
|
|
|
54
|
-
const lines =
|
|
57
|
+
const lines = trimmed.split("\n");
|
|
55
58
|
const entries: ChangelogEntry[] = [];
|
|
56
59
|
|
|
57
60
|
let currentEntry: ChangelogEntry | null = null;
|
|
@@ -143,7 +146,7 @@ export function getNewerVersions(
|
|
|
143
146
|
const result: ChangelogEntry[] = [];
|
|
144
147
|
for (const entry of entries) {
|
|
145
148
|
if (entry.version === "Unreleased") {
|
|
146
|
-
result.push(entry);
|
|
149
|
+
if (Object.keys(entry.sections).length > 0) result.push(entry);
|
|
147
150
|
continue;
|
|
148
151
|
}
|
|
149
152
|
// Compare rather than test for equality. Stopping only on an exact match
|
|
@@ -23,6 +23,7 @@ import { loadConfig } from "./settings.js";
|
|
|
23
23
|
import { checkForUpdates } from "./checker.js";
|
|
24
24
|
import { isVersionSkipped } from "./cache.js";
|
|
25
25
|
import { renderUpdateOverlay } from "./tui/update-overlay.js";
|
|
26
|
+
import { loadUpdateChangelog } from "./remote-changelog.js";
|
|
26
27
|
|
|
27
28
|
/** Package version */
|
|
28
29
|
const VERSION = getPackageVersion(new URL("..", import.meta.url).pathname);
|
|
@@ -142,8 +143,9 @@ export default function updaterExtension(pi: ExtensionAPI): void {
|
|
|
142
143
|
|
|
143
144
|
// Show update overlay if UI is available
|
|
144
145
|
if (ctx.hasUI) {
|
|
146
|
+
const entries = await loadUpdateChangelog(result.currentVersion, result.latestVersion);
|
|
145
147
|
const updateResult = await ctx.ui.custom(
|
|
146
|
-
renderUpdateOverlay(result),
|
|
148
|
+
renderUpdateOverlay(result, entries),
|
|
147
149
|
{
|
|
148
150
|
overlay: true,
|
|
149
151
|
overlayOptions: {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { UPDATER_DIRS } from "@pi-unipi/core";
|
|
5
|
+
import type { ChangelogEntry } from "../types.js";
|
|
6
|
+
import { getNewerVersions, parseChangelogContent, parseChangelog, resolveChangelogPath } from "./changelog.js";
|
|
7
|
+
|
|
8
|
+
export const CHANGELOG_RAW_BASE = "https://raw.githubusercontent.com/Neuron-Mr-White/unipi";
|
|
9
|
+
|
|
10
|
+
export interface RemoteChangelogOptions {
|
|
11
|
+
fetchImpl?: typeof fetch;
|
|
12
|
+
cacheDir?: string;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function cacheDirectory(opts?: RemoteChangelogOptions): string {
|
|
17
|
+
return (opts?.cacheDir ?? UPDATER_DIRS.CACHE).replace("~", homedir());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function fetchRemoteChangelog(version: string, opts: RemoteChangelogOptions = {}): Promise<string | null> {
|
|
21
|
+
const cacheDir = cacheDirectory(opts);
|
|
22
|
+
const cachePath = join(cacheDir, `changelog-${version}.md`);
|
|
23
|
+
try {
|
|
24
|
+
if (existsSync(cachePath)) return readFileSync(cachePath, "utf8");
|
|
25
|
+
} catch {
|
|
26
|
+
// Continue with the network request.
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
30
|
+
const signal = AbortSignal.timeout(opts.timeoutMs ?? 5000);
|
|
31
|
+
try {
|
|
32
|
+
let response = await fetchImpl(`${CHANGELOG_RAW_BASE}/v${version}/CHANGELOG.md`, { signal });
|
|
33
|
+
// The release tag is immutable, so its changelog is safe to cache forever.
|
|
34
|
+
// The `main` fallback (tag not pushed yet) is not: never cache it.
|
|
35
|
+
const cacheable = response.ok;
|
|
36
|
+
if (response.status === 404) {
|
|
37
|
+
response = await fetchImpl(`${CHANGELOG_RAW_BASE}/main/CHANGELOG.md`, { signal });
|
|
38
|
+
}
|
|
39
|
+
if (!response.ok) return null;
|
|
40
|
+
const content = await response.text();
|
|
41
|
+
if (cacheable) {
|
|
42
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
43
|
+
writeFileSync(cachePath, content, "utf8");
|
|
44
|
+
}
|
|
45
|
+
return content;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function loadUpdateChangelog(
|
|
52
|
+
currentVersion: string,
|
|
53
|
+
latestVersion: string,
|
|
54
|
+
opts?: RemoteChangelogOptions,
|
|
55
|
+
): Promise<ChangelogEntry[]> {
|
|
56
|
+
const remote = await fetchRemoteChangelog(latestVersion, opts);
|
|
57
|
+
const entries = remote === null ? [] : parseChangelogContent(remote);
|
|
58
|
+
const newer = remote === null ? [] : getNewerVersions(entries, currentVersion);
|
|
59
|
+
if (newer.length > 0) return newer;
|
|
60
|
+
return getNewerVersions(parseChangelog(resolveChangelogPath()), currentVersion);
|
|
61
|
+
}
|
|
@@ -40,7 +40,7 @@ interface UpdateState {
|
|
|
40
40
|
/**
|
|
41
41
|
* Render the update available overlay.
|
|
42
42
|
*/
|
|
43
|
-
export function renderUpdateOverlay(checkResult: UpdateCheckResult) {
|
|
43
|
+
export function renderUpdateOverlay(checkResult: UpdateCheckResult, providedNewerVersions?: ChangelogEntry[]) {
|
|
44
44
|
return (
|
|
45
45
|
tui: import("@earendil-works/pi-tui").TUI,
|
|
46
46
|
theme: Theme,
|
|
@@ -49,14 +49,16 @@ export function renderUpdateOverlay(checkResult: UpdateCheckResult) {
|
|
|
49
49
|
) => {
|
|
50
50
|
const config = loadConfig();
|
|
51
51
|
|
|
52
|
-
// Load changelog for newer versions
|
|
53
|
-
let newerVersions: ChangelogEntry[] = [];
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
// Load changelog for newer versions unless the caller already fetched it.
|
|
53
|
+
let newerVersions: ChangelogEntry[] = providedNewerVersions ?? [];
|
|
54
|
+
if (providedNewerVersions === undefined) {
|
|
55
|
+
const changelogPath = resolveChangelogPath();
|
|
56
|
+
try {
|
|
57
|
+
const entries = parseChangelog(changelogPath);
|
|
58
|
+
newerVersions = getNewerVersions(entries, checkResult.currentVersion);
|
|
59
|
+
} catch (_err) {
|
|
60
|
+
// No changelog
|
|
61
|
+
}
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
// Build content lines from changelog using markdown renderer
|
|
@@ -74,7 +76,7 @@ export function renderUpdateOverlay(checkResult: UpdateCheckResult) {
|
|
|
74
76
|
contentLines.push("");
|
|
75
77
|
}
|
|
76
78
|
if (contentLines.length === 0) {
|
|
77
|
-
contentLines.push(` ${theme.fg("muted",
|
|
79
|
+
contentLines.push(` ${theme.fg("muted", `No changelog available for ${checkResult.latestVersion} (offline?).`)}`);
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
const state: UpdateState = {
|