pi-extmgr 0.1.26 → 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 +7 -5
- 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 +163 -0
- package/src/packages/discovery.ts +77 -262
- package/src/packages/extensions.ts +10 -5
- package/src/packages/install.ts +42 -37
- package/src/packages/management.ts +145 -99
- package/src/types/index.ts +16 -9
- package/src/ui/async-task.ts +194 -0
- package/src/ui/footer.ts +4 -8
- package/src/ui/help.ts +2 -2
- package/src/ui/package-config.ts +62 -49
- package/src/ui/remote.ts +83 -28
- package/src/ui/theme.ts +2 -2
- package/src/ui/unified.ts +104 -89
- package/src/utils/auto-update.ts +18 -64
- 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/network.ts +10 -2
- package/src/utils/notify.ts +1 -1
- package/src/utils/npm-exec.ts +3 -1
- package/src/utils/package-source.ts +84 -2
- package/src/utils/retry.ts +1 -1
- package/src/utils/settings.ts +17 -8
- package/src/utils/status.ts +16 -12
- package/src/utils/ui-helpers.ts +3 -3
|
@@ -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()}`;
|
|
@@ -100,3 +131,54 @@ export function splitGitRepoAndRef(gitSpec: string): { repo: string; ref?: strin
|
|
|
100
131
|
|
|
101
132
|
return { repo: gitSpec.slice(0, lastAt), ref: tail };
|
|
102
133
|
}
|
|
134
|
+
|
|
135
|
+
function extractGitPackageName(repoSpec: string): string {
|
|
136
|
+
if (repoSpec.startsWith("git@")) {
|
|
137
|
+
const afterColon = repoSpec.split(":").slice(1).join(":");
|
|
138
|
+
if (afterColon) {
|
|
139
|
+
const last = afterColon.split("/").pop() || afterColon;
|
|
140
|
+
return last.replace(/\.git$/i, "") || repoSpec;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const url = new URL(repoSpec);
|
|
146
|
+
const last = url.pathname.split("/").filter(Boolean).pop();
|
|
147
|
+
if (last) {
|
|
148
|
+
return last.replace(/\.git$/i, "") || repoSpec;
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// Fall back to generic path splitting below.
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const last = repoSpec.split(/[/:]/).filter(Boolean).pop();
|
|
155
|
+
return (last ? last.replace(/\.git$/i, "") : repoSpec) || repoSpec;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function parsePackageNameAndVersion(fullSource: string): {
|
|
159
|
+
name: string;
|
|
160
|
+
version?: string | undefined;
|
|
161
|
+
} {
|
|
162
|
+
const parsedNpm = parseNpmSource(fullSource);
|
|
163
|
+
if (parsedNpm) {
|
|
164
|
+
return parsedNpm;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const sourceKind = getPackageSourceKind(fullSource);
|
|
168
|
+
if (sourceKind === "git") {
|
|
169
|
+
const gitSpec = stripGitSourcePrefix(fullSource);
|
|
170
|
+
const { repo } = splitGitRepoAndRef(gitSpec);
|
|
171
|
+
return { name: extractGitPackageName(repo) };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (fullSource.includes("node_modules/")) {
|
|
175
|
+
const nmMatch = fullSource.match(/node_modules\/(.+)$/);
|
|
176
|
+
if (nmMatch?.[1]) {
|
|
177
|
+
return { name: nmMatch[1] };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const pathParts = fullSource.split(/[\\/]/);
|
|
182
|
+
const fileName = pathParts[pathParts.length - 1];
|
|
183
|
+
return { name: fileName || fullSource };
|
|
184
|
+
}
|
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,26 +1,30 @@
|
|
|
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
|
-
import {
|
|
10
|
+
import { getPackageCatalog, type PackageCatalog } from "../packages/catalog.js";
|
|
10
11
|
import { getAutoUpdateStatus } from "./auto-update.js";
|
|
11
12
|
import { normalizePackageIdentity } from "./package-source.js";
|
|
12
13
|
import { getAutoUpdateConfigAsync, saveAutoUpdateConfig } from "./settings.js";
|
|
13
14
|
|
|
15
|
+
type CatalogInstalledPackages = Awaited<ReturnType<PackageCatalog["listInstalledPackages"]>>;
|
|
16
|
+
|
|
14
17
|
function filterStaleUpdates(
|
|
15
18
|
knownUpdates: string[],
|
|
16
|
-
installedPackages:
|
|
19
|
+
installedPackages: CatalogInstalledPackages,
|
|
20
|
+
cwd: string
|
|
17
21
|
): string[] {
|
|
18
22
|
const installedIdentities = new Set(
|
|
19
23
|
installedPackages.map((pkg) =>
|
|
20
|
-
normalizePackageIdentity(
|
|
21
|
-
pkg.
|
|
22
|
-
pkg.
|
|
23
|
-
)
|
|
24
|
+
normalizePackageIdentity(pkg.source, {
|
|
25
|
+
...(pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : {}),
|
|
26
|
+
cwd: pkg.scope === "project" ? cwd : getAgentDir(),
|
|
27
|
+
})
|
|
24
28
|
)
|
|
25
29
|
);
|
|
26
30
|
return knownUpdates.filter((identity) => installedIdentities.has(identity));
|
|
@@ -34,7 +38,7 @@ export async function updateExtmgrStatus(
|
|
|
34
38
|
|
|
35
39
|
try {
|
|
36
40
|
const [packages, autoUpdateConfig] = await Promise.all([
|
|
37
|
-
|
|
41
|
+
getPackageCatalog(ctx.cwd).listInstalledPackages(),
|
|
38
42
|
getAutoUpdateConfigAsync(ctx),
|
|
39
43
|
]);
|
|
40
44
|
const statusParts: string[] = [];
|
|
@@ -50,7 +54,7 @@ export async function updateExtmgrStatus(
|
|
|
50
54
|
|
|
51
55
|
// Validate updates against actually installed packages (handles external pi update)
|
|
52
56
|
const knownUpdates = autoUpdateConfig.updatesAvailable ?? [];
|
|
53
|
-
const validUpdates = filterStaleUpdates(knownUpdates, packages);
|
|
57
|
+
const validUpdates = filterStaleUpdates(knownUpdates, packages, ctx.cwd);
|
|
54
58
|
|
|
55
59
|
// If stale updates were filtered, persist the correction
|
|
56
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
|
|
@@ -21,7 +21,7 @@ export async function confirmReload(
|
|
|
21
21
|
const confirmed = await ctx.ui.confirm("Reload Required", `${reason}\nReload pi now?`);
|
|
22
22
|
|
|
23
23
|
if (confirmed) {
|
|
24
|
-
await
|
|
24
|
+
await ctx.reload();
|
|
25
25
|
return true;
|
|
26
26
|
}
|
|
27
27
|
|