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.
@@ -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
- return `local:${normalizeLocalSourceIdentity(options?.resolvedPath ?? normalized)}`;
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
+ }
@@ -23,7 +23,7 @@ export async function retryWithBackoff<T>(
23
23
  if (attempt < maxAttempts) {
24
24
  const delay =
25
25
  backoff === "exponential"
26
- ? delayMs * Math.pow(2, attempt - 1)
26
+ ? delayMs * 2 ** (attempt - 1)
27
27
  : backoff === "linear"
28
28
  ? delayMs * attempt
29
29
  : delayMs;
@@ -2,14 +2,15 @@
2
2
  * Auto-update settings storage
3
3
  * Persists to disk so config survives across pi sessions.
4
4
  */
5
- import type {
6
- ExtensionAPI,
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 value = parseInt(durationMatch[1]!, 10);
332
- const unit = durationMatch[2]![0]; // First character of unit
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;
@@ -1,26 +1,30 @@
1
1
  /**
2
2
  * Status bar helpers for extmgr
3
3
  */
4
- import type {
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 { getInstalledPackages } from "../packages/discovery.js";
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: Awaited<ReturnType<typeof getInstalledPackages>>
19
+ installedPackages: CatalogInstalledPackages,
20
+ cwd: string
17
21
  ): string[] {
18
22
  const installedIdentities = new Set(
19
23
  installedPackages.map((pkg) =>
20
- normalizePackageIdentity(
21
- pkg.source,
22
- pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : undefined
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
- getInstalledPackages(ctx, pi),
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) {
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Common UI helper patterns
3
3
  */
4
- import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
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 (ctx as ExtensionCommandContext & { reload: () => Promise<void> }).reload();
24
+ await ctx.reload();
25
25
  return true;
26
26
  }
27
27