pi-extmgr 0.1.27 → 0.1.28
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 +4 -3
- package/package.json +13 -16
- package/src/commands/auto-update.ts +4 -4
- package/src/commands/cache.ts +1 -1
- package/src/commands/history.ts +3 -3
- package/src/commands/install.ts +2 -2
- package/src/commands/registry.ts +7 -7
- package/src/commands/types.ts +1 -1
- package/src/extensions/discovery.ts +4 -3
- package/src/index.ts +15 -15
- package/src/packages/catalog.ts +9 -8
- package/src/packages/discovery.ts +22 -19
- package/src/packages/extensions.ts +10 -5
- package/src/packages/install.ts +19 -18
- package/src/packages/management.ts +53 -27
- package/src/types/index.ts +16 -9
- package/src/ui/async-task.ts +101 -65
- package/src/ui/footer.ts +4 -8
- package/src/ui/help.ts +2 -2
- package/src/ui/package-config.ts +36 -48
- package/src/ui/remote.ts +14 -11
- package/src/ui/theme.ts +2 -2
- package/src/ui/unified.ts +64 -82
- package/src/utils/auto-update.ts +10 -10
- package/src/utils/cache.ts +3 -3
- package/src/utils/command.ts +1 -1
- package/src/utils/format.ts +4 -3
- package/src/utils/history.ts +4 -2
- package/src/utils/mode.ts +1 -1
- package/src/utils/notify.ts +1 -1
- package/src/utils/npm-exec.ts +1 -1
- package/src/utils/package-source.ts +33 -2
- package/src/utils/retry.ts +1 -1
- package/src/utils/settings.ts +17 -8
- package/src/utils/status.ts +12 -10
- package/src/utils/ui-helpers.ts +2 -2
package/src/ui/unified.ts
CHANGED
|
@@ -2,19 +2,23 @@
|
|
|
2
2
|
* Unified extension manager UI
|
|
3
3
|
* Displays local extensions and installed packages in one view
|
|
4
4
|
*/
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import {
|
|
6
|
+
DynamicBorder,
|
|
7
|
+
type ExtensionAPI,
|
|
8
|
+
type ExtensionCommandContext,
|
|
9
|
+
getSettingsListTheme,
|
|
10
|
+
type Theme,
|
|
11
|
+
} from "@mariozechner/pi-coding-agent";
|
|
8
12
|
import {
|
|
9
13
|
Container,
|
|
14
|
+
Key,
|
|
15
|
+
matchesKey,
|
|
16
|
+
type SettingItem,
|
|
10
17
|
SettingsList,
|
|
11
|
-
Text,
|
|
12
18
|
Spacer,
|
|
13
|
-
|
|
14
|
-
matchesKey,
|
|
15
|
-
Key,
|
|
19
|
+
Text,
|
|
16
20
|
} from "@mariozechner/pi-tui";
|
|
17
|
-
import
|
|
21
|
+
import { UI } from "../constants.js";
|
|
18
22
|
import {
|
|
19
23
|
discoverExtensions,
|
|
20
24
|
removeLocalExtension,
|
|
@@ -22,34 +26,40 @@ import {
|
|
|
22
26
|
} from "../extensions/discovery.js";
|
|
23
27
|
import { getInstalledPackages } from "../packages/discovery.js";
|
|
24
28
|
import {
|
|
25
|
-
updatePackageWithOutcome,
|
|
26
29
|
removePackageWithOutcome,
|
|
27
|
-
updatePackagesWithOutcome,
|
|
28
30
|
showInstalledPackagesList,
|
|
31
|
+
updatePackagesWithOutcome,
|
|
32
|
+
updatePackageWithOutcome,
|
|
29
33
|
} from "../packages/management.js";
|
|
30
|
-
import { showRemote } from "./remote.js";
|
|
31
|
-
import { showHelp } from "./help.js";
|
|
32
|
-
import { runTaskWithLoader } from "./async-task.js";
|
|
33
|
-
import { formatEntry as formatExtEntry, dynamicTruncate, formatBytes } from "../utils/format.js";
|
|
34
34
|
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} from "
|
|
41
|
-
import { buildFooterState, buildFooterShortcuts, getPendingToggleChangeCount } from "./footer.js";
|
|
42
|
-
import { logExtensionDelete, logExtensionToggle } from "../utils/history.js";
|
|
35
|
+
type InstalledPackage,
|
|
36
|
+
type LocalUnifiedItem,
|
|
37
|
+
type State,
|
|
38
|
+
type UnifiedAction,
|
|
39
|
+
type UnifiedItem,
|
|
40
|
+
} from "../types/index.js";
|
|
43
41
|
import { getKnownUpdates, promptAutoUpdateWizard } from "../utils/auto-update.js";
|
|
44
|
-
import { updateExtmgrStatus } from "../utils/status.js";
|
|
45
42
|
import { parseChoiceByLabel } from "../utils/command.js";
|
|
43
|
+
import { dynamicTruncate, formatBytes, formatEntry as formatExtEntry } from "../utils/format.js";
|
|
44
|
+
import { logExtensionDelete, logExtensionToggle } from "../utils/history.js";
|
|
45
|
+
import { hasCustomUI, runCustomUI } from "../utils/mode.js";
|
|
46
46
|
import { notify } from "../utils/notify.js";
|
|
47
|
-
import { confirmReload } from "../utils/ui-helpers.js";
|
|
48
47
|
import { getPackageSourceKind, normalizePackageIdentity } from "../utils/package-source.js";
|
|
49
|
-
import { hasCustomUI, runCustomUI } from "../utils/mode.js";
|
|
50
48
|
import { getSettingsListSelectedIndex } from "../utils/settings-list.js";
|
|
51
|
-
import {
|
|
49
|
+
import { updateExtmgrStatus } from "../utils/status.js";
|
|
50
|
+
import { confirmReload } from "../utils/ui-helpers.js";
|
|
51
|
+
import { runTaskWithLoader } from "./async-task.js";
|
|
52
|
+
import { buildFooterShortcuts, buildFooterState, getPendingToggleChangeCount } from "./footer.js";
|
|
53
|
+
import { showHelp } from "./help.js";
|
|
52
54
|
import { configurePackageExtensions } from "./package-config.js";
|
|
55
|
+
import { showRemote } from "./remote.js";
|
|
56
|
+
import {
|
|
57
|
+
formatSize,
|
|
58
|
+
getChangeMarker,
|
|
59
|
+
getPackageIcon,
|
|
60
|
+
getScopeIcon,
|
|
61
|
+
getStatusIcon,
|
|
62
|
+
} from "./theme.js";
|
|
53
63
|
|
|
54
64
|
async function showInteractiveFallback(
|
|
55
65
|
ctx: ExtensionCommandContext,
|
|
@@ -190,8 +200,8 @@ async function showInteractiveOnce(
|
|
|
190
200
|
if (!item) continue;
|
|
191
201
|
|
|
192
202
|
if (item.type === "local") {
|
|
193
|
-
const currentState = staged.get(item.id) ?? item.state
|
|
194
|
-
const changed =
|
|
203
|
+
const currentState = staged.get(item.id) ?? item.state;
|
|
204
|
+
const changed = currentState !== item.originalState;
|
|
195
205
|
settingsItem.label = formatUnifiedItemLabel(item, currentState, theme, changed);
|
|
196
206
|
} else {
|
|
197
207
|
settingsItem.label = formatUnifiedItemLabel(item, "enabled", theme, false);
|
|
@@ -209,7 +219,11 @@ async function showInteractiveOnce(
|
|
|
209
219
|
if (!item || item.type !== "local") return;
|
|
210
220
|
|
|
211
221
|
const state = newValue as State;
|
|
212
|
-
|
|
222
|
+
if (state === item.originalState) {
|
|
223
|
+
staged.delete(id);
|
|
224
|
+
} else {
|
|
225
|
+
staged.set(id, state);
|
|
226
|
+
}
|
|
213
227
|
|
|
214
228
|
const settingsItem = settingsItems.find((x) => x.id === id);
|
|
215
229
|
if (settingsItem) {
|
|
@@ -380,16 +394,7 @@ export function buildUnifiedItems(
|
|
|
380
394
|
isDuplicate = true;
|
|
381
395
|
break;
|
|
382
396
|
}
|
|
383
|
-
if (
|
|
384
|
-
pkgResolvedNormalized &&
|
|
385
|
-
(localPath.startsWith(`${pkgResolvedNormalized}/`) ||
|
|
386
|
-
pkgResolvedNormalized.startsWith(localPath))
|
|
387
|
-
) {
|
|
388
|
-
isDuplicate = true;
|
|
389
|
-
break;
|
|
390
|
-
}
|
|
391
|
-
const localDir = localPath.split("/").slice(0, -1).join("/");
|
|
392
|
-
if (pkgResolvedNormalized && pkgResolvedNormalized === localDir) {
|
|
397
|
+
if (pkgResolvedNormalized && localPath.startsWith(`${pkgResolvedNormalized}/`)) {
|
|
393
398
|
isDuplicate = true;
|
|
394
399
|
break;
|
|
395
400
|
}
|
|
@@ -400,7 +405,6 @@ export function buildUnifiedItems(
|
|
|
400
405
|
type: "package",
|
|
401
406
|
id: `pkg:${pkg.source}`,
|
|
402
407
|
displayName: pkg.name,
|
|
403
|
-
summary: pkg.description || `${pkg.source} (${pkg.scope})`,
|
|
404
408
|
scope: pkg.scope,
|
|
405
409
|
source: pkg.source,
|
|
406
410
|
version: pkg.version,
|
|
@@ -432,8 +436,8 @@ function buildSettingsItems(
|
|
|
432
436
|
): SettingItem[] {
|
|
433
437
|
return items.map((item) => {
|
|
434
438
|
if (item.type === "local") {
|
|
435
|
-
const currentState = staged.get(item.id) ?? item.state
|
|
436
|
-
const changed =
|
|
439
|
+
const currentState = staged.get(item.id) ?? item.state;
|
|
440
|
+
const changed = currentState !== item.originalState;
|
|
437
441
|
return {
|
|
438
442
|
id: item.id,
|
|
439
443
|
label: formatUnifiedItemLabel(item, currentState, theme, changed),
|
|
@@ -458,7 +462,7 @@ function formatUnifiedItemLabel(
|
|
|
458
462
|
changed = false
|
|
459
463
|
): string {
|
|
460
464
|
if (item.type === "local") {
|
|
461
|
-
const statusIcon = getStatusIcon(theme, state
|
|
465
|
+
const statusIcon = getStatusIcon(theme, state);
|
|
462
466
|
const scopeIcon = getScopeIcon(theme, item.scope);
|
|
463
467
|
const changeMarker = getChangeMarker(theme, changed);
|
|
464
468
|
const name = theme.bold(item.displayName);
|
|
@@ -466,7 +470,7 @@ function formatUnifiedItemLabel(
|
|
|
466
470
|
return `${statusIcon} [${scopeIcon}] ${name} - ${summary}${changeMarker}`;
|
|
467
471
|
}
|
|
468
472
|
|
|
469
|
-
const sourceKind = getPackageSourceKind(item.source
|
|
473
|
+
const sourceKind = getPackageSourceKind(item.source);
|
|
470
474
|
const pkgIcon = getPackageIcon(
|
|
471
475
|
theme,
|
|
472
476
|
sourceKind === "npm" || sourceKind === "git" || sourceKind === "local" ? sourceKind : "local"
|
|
@@ -500,8 +504,8 @@ function formatUnifiedItemLabel(
|
|
|
500
504
|
return `${pkgIcon} [${scopeIcon}] ${name}${version}${updateBadge} - ${summary}`;
|
|
501
505
|
}
|
|
502
506
|
|
|
503
|
-
function getToggleItemsForApply(items: UnifiedItem[]):
|
|
504
|
-
return items.filter((item) => item.type === "local");
|
|
507
|
+
function getToggleItemsForApply(items: UnifiedItem[]): LocalUnifiedItem[] {
|
|
508
|
+
return items.filter((item): item is LocalUnifiedItem => item.type === "local");
|
|
505
509
|
}
|
|
506
510
|
|
|
507
511
|
async function applyToggleChangesFromManager(
|
|
@@ -510,7 +514,7 @@ async function applyToggleChangesFromManager(
|
|
|
510
514
|
ctx: ExtensionCommandContext,
|
|
511
515
|
pi: ExtensionAPI,
|
|
512
516
|
options?: { promptReload?: boolean }
|
|
513
|
-
): Promise<{ changed: number; reloaded: boolean }> {
|
|
517
|
+
): Promise<{ changed: number; reloaded: boolean; hasErrors: boolean }> {
|
|
514
518
|
const toggleItems = getToggleItemsForApply(items);
|
|
515
519
|
const apply = await applyStagedChanges(toggleItems, staged, pi);
|
|
516
520
|
|
|
@@ -529,24 +533,14 @@ async function applyToggleChangesFromManager(
|
|
|
529
533
|
const shouldPromptReload = options?.promptReload ?? true;
|
|
530
534
|
|
|
531
535
|
if (shouldPromptReload) {
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
"Local extensions changed. Reload pi now?"
|
|
535
|
-
);
|
|
536
|
-
|
|
537
|
-
if (shouldReload) {
|
|
538
|
-
await ctx.reload();
|
|
539
|
-
return { changed: apply.changed, reloaded: true };
|
|
540
|
-
}
|
|
541
|
-
} else {
|
|
542
|
-
ctx.ui.notify(
|
|
543
|
-
"Changes saved. Reload pi later to fully apply extension state updates.",
|
|
544
|
-
"info"
|
|
545
|
-
);
|
|
536
|
+
const reloaded = await confirmReload(ctx, "Local extensions changed.");
|
|
537
|
+
return { changed: apply.changed, reloaded, hasErrors: apply.errors.length > 0 };
|
|
546
538
|
}
|
|
539
|
+
|
|
540
|
+
ctx.ui.notify("Changes saved. Reload pi later to fully apply extension state updates.", "info");
|
|
547
541
|
}
|
|
548
542
|
|
|
549
|
-
return { changed: apply.changed, reloaded: false };
|
|
543
|
+
return { changed: apply.changed, reloaded: false, hasErrors: apply.errors.length > 0 };
|
|
550
544
|
}
|
|
551
545
|
|
|
552
546
|
async function resolvePendingChangesBeforeLeave(
|
|
@@ -556,7 +550,7 @@ async function resolvePendingChangesBeforeLeave(
|
|
|
556
550
|
ctx: ExtensionCommandContext,
|
|
557
551
|
pi: ExtensionAPI,
|
|
558
552
|
destinationLabel: string
|
|
559
|
-
): Promise<"continue" | "stay"
|
|
553
|
+
): Promise<"continue" | "stay"> {
|
|
560
554
|
const pendingCount = getPendingToggleChangeCount(staged, byId);
|
|
561
555
|
if (pendingCount === 0) return "continue";
|
|
562
556
|
|
|
@@ -574,10 +568,10 @@ async function resolvePendingChangesBeforeLeave(
|
|
|
574
568
|
return "continue";
|
|
575
569
|
}
|
|
576
570
|
|
|
577
|
-
const
|
|
571
|
+
const apply = await applyToggleChangesFromManager(items, staged, ctx, pi, {
|
|
578
572
|
promptReload: false,
|
|
579
573
|
});
|
|
580
|
-
return
|
|
574
|
+
return apply.changed === 0 && apply.hasErrors ? "stay" : "continue";
|
|
581
575
|
}
|
|
582
576
|
|
|
583
577
|
const PALETTE_OPTIONS = {
|
|
@@ -648,7 +642,6 @@ async function navigateWithPendingGuard(
|
|
|
648
642
|
QUICK_DESTINATION_LABELS[destination]
|
|
649
643
|
);
|
|
650
644
|
if (pending === "stay") return "stay";
|
|
651
|
-
if (pending === "exit") return "exit";
|
|
652
645
|
|
|
653
646
|
switch (destination) {
|
|
654
647
|
case "install":
|
|
@@ -703,6 +696,7 @@ async function handleUnifiedAction(
|
|
|
703
696
|
if (choice === "Save and exit") {
|
|
704
697
|
const apply = await applyToggleChangesFromManager(items, staged, ctx, pi);
|
|
705
698
|
if (apply.reloaded) return true;
|
|
699
|
+
if (apply.changed === 0 && apply.hasErrors) return false;
|
|
706
700
|
}
|
|
707
701
|
}
|
|
708
702
|
|
|
@@ -712,7 +706,6 @@ async function handleUnifiedAction(
|
|
|
712
706
|
if (result.type === "remote") {
|
|
713
707
|
const pending = await resolvePendingChangesBeforeLeave(items, staged, byId, ctx, pi, "Remote");
|
|
714
708
|
if (pending === "stay") return false;
|
|
715
|
-
if (pending === "exit") return true;
|
|
716
709
|
|
|
717
710
|
await showRemote("", ctx, pi);
|
|
718
711
|
return false;
|
|
@@ -721,7 +714,6 @@ async function handleUnifiedAction(
|
|
|
721
714
|
if (result.type === "help") {
|
|
722
715
|
const pending = await resolvePendingChangesBeforeLeave(items, staged, byId, ctx, pi, "Help");
|
|
723
716
|
if (pending === "stay") return false;
|
|
724
|
-
if (pending === "exit") return true;
|
|
725
717
|
|
|
726
718
|
showHelp(ctx);
|
|
727
719
|
return false;
|
|
@@ -778,7 +770,6 @@ async function handleUnifiedAction(
|
|
|
778
770
|
pendingDestination
|
|
779
771
|
);
|
|
780
772
|
if (pending === "stay") return false;
|
|
781
|
-
if (pending === "exit") return true;
|
|
782
773
|
|
|
783
774
|
if (item.type === "local") {
|
|
784
775
|
if (result.action !== "remove") return false;
|
|
@@ -790,7 +781,7 @@ async function handleUnifiedAction(
|
|
|
790
781
|
if (!confirmed) return false;
|
|
791
782
|
|
|
792
783
|
const removal = await removeLocalExtension(
|
|
793
|
-
{ activePath: item.activePath
|
|
784
|
+
{ activePath: item.activePath, disabledPath: item.disabledPath },
|
|
794
785
|
ctx.cwd
|
|
795
786
|
);
|
|
796
787
|
if (!removal.ok) {
|
|
@@ -805,16 +796,11 @@ async function handleUnifiedAction(
|
|
|
805
796
|
"info"
|
|
806
797
|
);
|
|
807
798
|
|
|
808
|
-
|
|
809
|
-
if (reloaded) {
|
|
810
|
-
return true;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
return false;
|
|
799
|
+
return await confirmReload(ctx, "Extension removed.");
|
|
814
800
|
}
|
|
815
801
|
|
|
816
802
|
const pkg: InstalledPackage = {
|
|
817
|
-
source: item.source
|
|
803
|
+
source: item.source,
|
|
818
804
|
name: item.displayName,
|
|
819
805
|
...(item.version ? { version: item.version } : {}),
|
|
820
806
|
scope: item.scope,
|
|
@@ -860,7 +846,7 @@ async function handleUnifiedAction(
|
|
|
860
846
|
}
|
|
861
847
|
|
|
862
848
|
async function applyStagedChanges(
|
|
863
|
-
items:
|
|
849
|
+
items: LocalUnifiedItem[],
|
|
864
850
|
staged: Map<string, State>,
|
|
865
851
|
pi: ExtensionAPI
|
|
866
852
|
) {
|
|
@@ -868,10 +854,6 @@ async function applyStagedChanges(
|
|
|
868
854
|
const errors: string[] = [];
|
|
869
855
|
|
|
870
856
|
for (const item of items) {
|
|
871
|
-
if (item.type !== "local" || !item.originalState || !item.activePath || !item.disabledPath) {
|
|
872
|
-
continue;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
857
|
const target = staged.get(item.id) ?? item.originalState;
|
|
876
858
|
if (target === item.originalState) continue;
|
|
877
859
|
|
package/src/utils/auto-update.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-update logic and background checker
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
5
|
-
ExtensionAPI,
|
|
6
|
-
ExtensionCommandContext,
|
|
7
|
-
ExtensionContext,
|
|
4
|
+
import {
|
|
5
|
+
type ExtensionAPI,
|
|
6
|
+
type ExtensionCommandContext,
|
|
7
|
+
type ExtensionContext,
|
|
8
8
|
} from "@mariozechner/pi-coding-agent";
|
|
9
9
|
import { getPackageCatalog } from "../packages/catalog.js";
|
|
10
|
+
import { logAutoUpdateConfig } from "./history.js";
|
|
10
11
|
import { notify } from "./notify.js";
|
|
12
|
+
import { normalizePackageIdentity } from "./package-source.js";
|
|
11
13
|
import {
|
|
14
|
+
type AutoUpdateConfig,
|
|
15
|
+
calculateNextCheck,
|
|
12
16
|
getAutoUpdateConfig,
|
|
13
|
-
saveAutoUpdateConfig,
|
|
14
17
|
getScheduleInterval,
|
|
15
|
-
calculateNextCheck,
|
|
16
18
|
parseDuration,
|
|
17
|
-
|
|
19
|
+
saveAutoUpdateConfig,
|
|
18
20
|
} from "./settings.js";
|
|
19
|
-
import { normalizePackageIdentity } from "./package-source.js";
|
|
20
|
-
import { logAutoUpdateConfig } from "./history.js";
|
|
21
21
|
|
|
22
|
-
import { startTimer, stopTimer
|
|
22
|
+
import { isTimerRunning, startTimer, stopTimer } from "./timer.js";
|
|
23
23
|
|
|
24
24
|
// Context provider for safe session handling
|
|
25
25
|
export type ContextProvider = () => (ExtensionCommandContext | ExtensionContext) | undefined;
|
package/src/utils/cache.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Persistent cache for package metadata to reduce npm API calls
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
import { join } from "node:path";
|
|
4
|
+
import { access, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
6
5
|
import { homedir } from "node:os";
|
|
7
|
-
import
|
|
6
|
+
import { join } from "node:path";
|
|
8
7
|
import { CACHE_LIMITS } from "../constants.js";
|
|
8
|
+
import { type InstalledPackage, type NpmPackage } from "../types/index.js";
|
|
9
9
|
import { parseNpmSource } from "./format.js";
|
|
10
10
|
|
|
11
11
|
const CACHE_DIR = process.env.PI_EXTMGR_CACHE_DIR
|
package/src/utils/command.ts
CHANGED
package/src/utils/format.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Formatting utilities
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
4
|
+
import { type ExtensionEntry, type InstalledPackage } from "../types/index.js";
|
|
5
5
|
|
|
6
6
|
export function truncate(text: string, maxLength: number): string {
|
|
7
7
|
if (text.length <= maxLength) return text;
|
|
8
|
-
return text.slice(0, maxLength
|
|
8
|
+
if (maxLength <= 3) return text.slice(0, maxLength);
|
|
9
|
+
return `${text.slice(0, maxLength - 3)}...`;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -54,7 +55,7 @@ export function formatBytes(bytes: number): string {
|
|
|
54
55
|
const k = 1024;
|
|
55
56
|
const sizes = ["B", "KB", "MB", "GB"];
|
|
56
57
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
57
|
-
return `${parseFloat((bytes /
|
|
58
|
+
return `${parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
const GIT_PATTERNS = {
|
package/src/utils/history.ts
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Extension change history tracking using pi.appendEntry()
|
|
3
3
|
* This persists extension management actions to the session
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
import { type Dirent } from "node:fs";
|
|
6
7
|
import { readdir, readFile } from "node:fs/promises";
|
|
7
8
|
import { homedir } from "node:os";
|
|
8
9
|
import { join } from "node:path";
|
|
10
|
+
import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
9
11
|
|
|
10
12
|
export type ChangeAction =
|
|
11
13
|
| "extension_toggle"
|
|
@@ -278,7 +280,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
278
280
|
async function walkSessionFiles(dir: string): Promise<string[]> {
|
|
279
281
|
const result: string[] = [];
|
|
280
282
|
|
|
281
|
-
let entries;
|
|
283
|
+
let entries: Dirent<string>[];
|
|
282
284
|
try {
|
|
283
285
|
entries = await readdir(dir, { withFileTypes: true, encoding: "utf8" });
|
|
284
286
|
} catch {
|
package/src/utils/mode.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* UI capability helpers
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
4
|
+
import { type ExtensionCommandContext, type ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
5
5
|
import { notify } from "./notify.js";
|
|
6
6
|
|
|
7
7
|
type AnyContext = ExtensionCommandContext | ExtensionContext;
|
package/src/utils/notify.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized notification handling for UI and non-UI modes
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
4
|
+
import { type ExtensionCommandContext, type ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
5
5
|
|
|
6
6
|
export type NotifyLevel = "info" | "warning" | "error";
|
|
7
7
|
|
package/src/utils/npm-exec.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { execPath, platform } from "node:process";
|
|
3
|
-
import type
|
|
3
|
+
import { type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
4
4
|
|
|
5
5
|
interface NpmCommandResolutionOptions {
|
|
6
6
|
platform?: NodeJS.Platform;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Package source parsing helpers shared across discovery/management flows.
|
|
3
3
|
*/
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join, resolve as resolvePath } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
4
7
|
import { parseNpmSource } from "./format.js";
|
|
5
8
|
|
|
6
9
|
export type PackageSourceKind = "npm" | "git" | "local" | "unknown";
|
|
@@ -61,9 +64,35 @@ export function stripGitSourcePrefix(source: string): string {
|
|
|
61
64
|
return withoutGitPlus.startsWith("git:") ? withoutGitPlus.slice(4) : withoutGitPlus;
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
function resolveLocalSourceForIdentity(source: string, cwd?: string): string {
|
|
68
|
+
if (source.startsWith("file://")) {
|
|
69
|
+
try {
|
|
70
|
+
return fileURLToPath(source);
|
|
71
|
+
} catch {
|
|
72
|
+
return source;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (source.startsWith("~/")) {
|
|
77
|
+
return join(homedir(), source.slice(2));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
cwd &&
|
|
82
|
+
(source.startsWith("./") ||
|
|
83
|
+
source.startsWith("../") ||
|
|
84
|
+
source.startsWith(".\\") ||
|
|
85
|
+
source.startsWith("..\\"))
|
|
86
|
+
) {
|
|
87
|
+
return resolvePath(cwd, source);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return source;
|
|
91
|
+
}
|
|
92
|
+
|
|
64
93
|
export function normalizePackageIdentity(
|
|
65
94
|
source: string,
|
|
66
|
-
options?: { resolvedPath?: string }
|
|
95
|
+
options?: { resolvedPath?: string; cwd?: string }
|
|
67
96
|
): string {
|
|
68
97
|
const normalized = sanitizeSource(source);
|
|
69
98
|
const kind = getPackageSourceKind(normalized);
|
|
@@ -80,7 +109,9 @@ export function normalizePackageIdentity(
|
|
|
80
109
|
}
|
|
81
110
|
|
|
82
111
|
if (kind === "local") {
|
|
83
|
-
|
|
112
|
+
const localSource =
|
|
113
|
+
options?.resolvedPath ?? resolveLocalSourceForIdentity(normalized, options?.cwd);
|
|
114
|
+
return `local:${normalizeLocalSourceIdentity(localSource)}`;
|
|
84
115
|
}
|
|
85
116
|
|
|
86
117
|
return `raw:${normalized.replace(/\\/g, "/").toLowerCase()}`;
|
package/src/utils/retry.ts
CHANGED
package/src/utils/settings.ts
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
* Auto-update settings storage
|
|
3
3
|
* Persists to disk so config survives across pi sessions.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
ExtensionCommandContext,
|
|
8
|
-
ExtensionContext,
|
|
9
|
-
} from "@mariozechner/pi-coding-agent";
|
|
10
|
-
import { readFile, writeFile, mkdir, rename, rm } from "node:fs/promises";
|
|
5
|
+
|
|
6
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
11
7
|
import { homedir } from "node:os";
|
|
12
8
|
import { join } from "node:path";
|
|
9
|
+
import {
|
|
10
|
+
type ExtensionAPI,
|
|
11
|
+
type ExtensionCommandContext,
|
|
12
|
+
type ExtensionContext,
|
|
13
|
+
} from "@mariozechner/pi-coding-agent";
|
|
13
14
|
import { fileExists } from "./fs.js";
|
|
14
15
|
import { normalizePackageIdentity } from "./package-source.js";
|
|
15
16
|
|
|
@@ -328,8 +329,16 @@ export function parseDuration(input: string): { ms: number; display: string } |
|
|
|
328
329
|
/^(\d+)\s*(h|hr|hrs|hour|hours|d|day|days|w|wk|wks|week|weeks|m|mo|mos|month|months)$/
|
|
329
330
|
);
|
|
330
331
|
if (durationMatch) {
|
|
331
|
-
const
|
|
332
|
-
|
|
332
|
+
const [, rawValue, rawUnit] = durationMatch;
|
|
333
|
+
if (!rawValue || !rawUnit) {
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const value = Number.parseInt(rawValue, 10);
|
|
338
|
+
const unit = rawUnit[0];
|
|
339
|
+
if (!unit) {
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
333
342
|
|
|
334
343
|
let ms: number;
|
|
335
344
|
let display: string;
|
package/src/utils/status.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Status bar helpers for extmgr
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
5
|
-
ExtensionAPI,
|
|
6
|
-
ExtensionCommandContext,
|
|
7
|
-
ExtensionContext,
|
|
4
|
+
import {
|
|
5
|
+
type ExtensionAPI,
|
|
6
|
+
type ExtensionCommandContext,
|
|
7
|
+
type ExtensionContext,
|
|
8
|
+
getAgentDir,
|
|
8
9
|
} from "@mariozechner/pi-coding-agent";
|
|
9
10
|
import { getPackageCatalog, type PackageCatalog } from "../packages/catalog.js";
|
|
10
11
|
import { getAutoUpdateStatus } from "./auto-update.js";
|
|
@@ -15,14 +16,15 @@ type CatalogInstalledPackages = Awaited<ReturnType<PackageCatalog["listInstalled
|
|
|
15
16
|
|
|
16
17
|
function filterStaleUpdates(
|
|
17
18
|
knownUpdates: string[],
|
|
18
|
-
installedPackages: CatalogInstalledPackages
|
|
19
|
+
installedPackages: CatalogInstalledPackages,
|
|
20
|
+
cwd: string
|
|
19
21
|
): string[] {
|
|
20
22
|
const installedIdentities = new Set(
|
|
21
23
|
installedPackages.map((pkg) =>
|
|
22
|
-
normalizePackageIdentity(
|
|
23
|
-
pkg.
|
|
24
|
-
pkg.
|
|
25
|
-
)
|
|
24
|
+
normalizePackageIdentity(pkg.source, {
|
|
25
|
+
...(pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : {}),
|
|
26
|
+
cwd: pkg.scope === "project" ? cwd : getAgentDir(),
|
|
27
|
+
})
|
|
26
28
|
)
|
|
27
29
|
);
|
|
28
30
|
return knownUpdates.filter((identity) => installedIdentities.has(identity));
|
|
@@ -52,7 +54,7 @@ export async function updateExtmgrStatus(
|
|
|
52
54
|
|
|
53
55
|
// Validate updates against actually installed packages (handles external pi update)
|
|
54
56
|
const knownUpdates = autoUpdateConfig.updatesAvailable ?? [];
|
|
55
|
-
const validUpdates = filterStaleUpdates(knownUpdates, packages);
|
|
57
|
+
const validUpdates = filterStaleUpdates(knownUpdates, packages, ctx.cwd);
|
|
56
58
|
|
|
57
59
|
// If stale updates were filtered, persist the correction
|
|
58
60
|
if (validUpdates.length !== knownUpdates.length) {
|
package/src/utils/ui-helpers.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Common UI helper patterns
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
5
|
-
import { notify } from "./notify.js";
|
|
4
|
+
import { type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
6
5
|
import { UI } from "../constants.js";
|
|
6
|
+
import { notify } from "./notify.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Confirm and trigger reload
|