pi-extmgr 0.1.27 → 0.2.0
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 +21 -10
- package/package.json +21 -16
- package/src/commands/auto-update.ts +5 -5
- package/src/commands/cache.ts +1 -1
- package/src/commands/history.ts +5 -34
- package/src/commands/install.ts +2 -2
- package/src/commands/registry.ts +7 -7
- package/src/commands/types.ts +1 -1
- package/src/constants.ts +0 -8
- package/src/extensions/discovery.ts +125 -42
- package/src/index.ts +15 -15
- package/src/packages/catalog.ts +9 -8
- package/src/packages/discovery.ts +56 -19
- package/src/packages/extensions.ts +65 -103
- package/src/packages/install.ts +104 -74
- package/src/packages/management.ts +78 -65
- package/src/types/index.ts +20 -11
- package/src/ui/async-task.ts +101 -65
- package/src/ui/footer.ts +47 -31
- package/src/ui/help.ts +17 -13
- package/src/ui/package-config.ts +36 -48
- package/src/ui/remote.ts +714 -119
- package/src/ui/theme.ts +2 -2
- package/src/ui/unified.ts +964 -371
- package/src/utils/auto-update.ts +44 -39
- package/src/utils/cache.ts +208 -37
- package/src/utils/command.ts +1 -1
- package/src/utils/duration.ts +132 -0
- package/src/utils/format.ts +4 -33
- package/src/utils/fs.ts +8 -4
- package/src/utils/history.ts +47 -9
- package/src/utils/mode.ts +2 -2
- package/src/utils/notify.ts +1 -15
- package/src/utils/npm-exec.ts +1 -1
- package/src/utils/package-source.ts +35 -7
- package/src/utils/path-identity.ts +7 -0
- package/src/utils/relative-path-selection.ts +100 -0
- package/src/utils/settings.ts +11 -61
- package/src/utils/status.ts +12 -10
- package/src/utils/ui-helpers.ts +2 -2
- package/src/utils/retry.ts +0 -49
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
8
|
A better way to manage Pi extensions. Browse, install, enable/disable, and remove extensions from one place.
|
|
9
|
+
Built on top of Pi's native package install, update, and config flows, so extmgr stays aligned with current upstream behavior.
|
|
9
10
|
|
|
10
11
|
**🌐 [pi-extmgr landing page](https://ayagmar.github.io/pi-extmgr)**
|
|
11
12
|
|
|
@@ -15,27 +16,31 @@ A better way to manage Pi extensions. Browse, install, enable/disable, and remov
|
|
|
15
16
|
pi install npm:pi-extmgr
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
If Pi is already running, use `/reload`.
|
|
19
20
|
|
|
20
|
-
Requires Node.js `>=22.
|
|
21
|
+
Requires Node.js `>=22.20.0`.
|
|
21
22
|
|
|
22
23
|
## Features
|
|
23
24
|
|
|
24
25
|
- **Unified manager UI**
|
|
25
26
|
- Local extensions (`~/.pi/agent/extensions`, `.pi/extensions`) and installed packages in one list
|
|
26
|
-
-
|
|
27
|
+
- Grouped sections for local extensions vs installed packages
|
|
28
|
+
- Compact rows with selected-item details below the list, so large extension sets stay scannable
|
|
29
|
+
- Built-in search and filter shortcuts for large extension sets
|
|
30
|
+
- Scope indicators (global/project), status indicators, update badges, and package sizes when known
|
|
27
31
|
- **Package extension configuration panel**
|
|
28
32
|
- Configure individual extension entrypoints inside an installed package (`c` on package row)
|
|
29
33
|
- Works with manifest-declared entrypoints and conventional `extensions/` package layouts
|
|
30
34
|
- Persists to package filters in `settings.json` (no manual JSON editing)
|
|
31
35
|
- **Safe staged local extension toggles**
|
|
32
|
-
- Toggle with `Space
|
|
36
|
+
- Toggle with `Space`, apply with `S`
|
|
33
37
|
- Unsaved-change guard when leaving (save/discard/stay)
|
|
34
38
|
- **Package management**
|
|
35
39
|
- Install, update, remove from UI and command line
|
|
36
40
|
- Quick actions (`A`, `u`, `X`) and bulk update (`U`)
|
|
37
41
|
- **Remote discovery and install**
|
|
38
|
-
- npm search/browse with pagination
|
|
42
|
+
- npm search/browse with pagination, inline browse search, and keyboard page navigation
|
|
43
|
+
- Path- and git-like queries are handled explicitly instead of surfacing unrelated npm results
|
|
39
44
|
- Install by source (`npm:`, `git:`, `https://`, `ssh://`, `git@...`, local path)
|
|
40
45
|
- Supports direct GitHub `.ts` installs and standalone local install for self-contained packages
|
|
41
46
|
- Long-running discovery/detail screens now show dedicated loading UI, and cancellable reads can be aborted with `Esc`
|
|
@@ -65,20 +70,26 @@ Open the manager:
|
|
|
65
70
|
| Key | Action |
|
|
66
71
|
| ------------- | ----------------------------------------------------- |
|
|
67
72
|
| `↑↓` | Navigate |
|
|
68
|
-
| `
|
|
73
|
+
| `PageUp/Down` | Jump through longer lists |
|
|
74
|
+
| `Home/End` | Jump to top or bottom |
|
|
75
|
+
| `Space` | Toggle selected local extension on/off |
|
|
69
76
|
| `S` | Save local extension changes |
|
|
70
|
-
| `Enter` / `A` | Actions on selected
|
|
77
|
+
| `Enter` / `A` | Actions on selected item |
|
|
78
|
+
| `/` / `Ctrl+F`| Search visible items |
|
|
79
|
+
| `Tab` / `Shift+Tab` | Cycle filters |
|
|
80
|
+
| `1-5` | Filters: All / Local / Packages / Updates / Disabled |
|
|
71
81
|
| `c` | Configure selected package extensions |
|
|
72
82
|
| `u` | Update selected package directly |
|
|
83
|
+
| `V` | View full details for selected item |
|
|
73
84
|
| `X` | Remove selected item (package/local extension) |
|
|
74
85
|
| `i` | Quick install by source |
|
|
75
|
-
| `f` |
|
|
86
|
+
| `f` | Remote package search |
|
|
76
87
|
| `U` | Update all packages |
|
|
77
88
|
| `t` | Auto-update wizard |
|
|
78
89
|
| `P` / `M` | Quick actions palette |
|
|
79
90
|
| `R` | Browse remote packages |
|
|
80
91
|
| `?` / `H` | Help |
|
|
81
|
-
| `Esc` |
|
|
92
|
+
| `Esc` | Clear search or exit |
|
|
82
93
|
|
|
83
94
|
### Commands
|
|
84
95
|
|
|
@@ -155,7 +166,7 @@ Examples:
|
|
|
155
166
|
|
|
156
167
|
- **Staged local changes**: Toggle local extensions on/off, then press `S` to apply all at once.
|
|
157
168
|
- **Package extension config**: Select a package and press `c` (or Enter/A → Configure) to enable/disable individual package entrypoints.
|
|
158
|
-
- After saving package extension config,
|
|
169
|
+
- After saving package extension config, run /reload to apply changes.
|
|
159
170
|
- **Two install modes**:
|
|
160
171
|
- **Managed** (npm): Auto-updates with `pi update`, stored in pi's package cache, supports Pi package manifest/convention loading
|
|
161
172
|
- **Local** (standalone): Copies to `~/.pi/agent/extensions/{package}/`, so it only accepts runnable standalone layouts (manifest-declared/root entrypoints), requires `tar` on `PATH`, and rejects packages whose runtime `dependencies` are not already bundled with the package contents
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extmgr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Enhanced UX for managing local Pi extensions and community packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -19,14 +19,20 @@
|
|
|
19
19
|
"README.md"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
|
-
"lint": "
|
|
23
|
-
"lint:fix": "
|
|
24
|
-
"format": "
|
|
25
|
-
"format:check": "
|
|
22
|
+
"lint": "biome lint . --error-on-warnings",
|
|
23
|
+
"lint:fix": "biome check --write .",
|
|
24
|
+
"format": "biome format --write .",
|
|
25
|
+
"format:check": "biome format .",
|
|
26
26
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
27
27
|
"smoke-test": "node --import=tsx ./scripts/smoke-test.mjs",
|
|
28
28
|
"test": "node --import=tsx --test ./test/*.test.ts",
|
|
29
|
-
"check": "
|
|
29
|
+
"check": "tsc --noEmit -p tsconfig.json && node --import=tsx ./scripts/smoke-test.mjs && node --import=tsx --test ./test/*.test.ts && pnpm run lint && pnpm run format:check",
|
|
30
|
+
"release": "release-it",
|
|
31
|
+
"release:patch": "release-it patch",
|
|
32
|
+
"release:minor": "release-it minor",
|
|
33
|
+
"release:major": "release-it major",
|
|
34
|
+
"release:first": "release-it --first-release",
|
|
35
|
+
"release:ci": "node --import=tsx ./scripts/release-ci.ts",
|
|
30
36
|
"prepublishOnly": "pnpm run check",
|
|
31
37
|
"prepare": "husky"
|
|
32
38
|
},
|
|
@@ -42,22 +48,21 @@
|
|
|
42
48
|
"@mariozechner/pi-tui": "*"
|
|
43
49
|
},
|
|
44
50
|
"devDependencies": {
|
|
45
|
-
"@
|
|
46
|
-
"@mariozechner/pi-
|
|
47
|
-
"@
|
|
48
|
-
"@
|
|
49
|
-
"@
|
|
50
|
-
"eslint": "^9.38.0",
|
|
51
|
-
"eslint-config-prettier": "^10.1.8",
|
|
51
|
+
"@biomejs/biome": "^2.4.9",
|
|
52
|
+
"@mariozechner/pi-coding-agent": "^0.63.1",
|
|
53
|
+
"@mariozechner/pi-tui": "^0.63.1",
|
|
54
|
+
"@release-it/conventional-changelog": "^10.0.5",
|
|
55
|
+
"@types/node": "^22.19.10",
|
|
52
56
|
"husky": "^9.1.7",
|
|
53
|
-
"
|
|
54
|
-
"tsx": "^4.
|
|
57
|
+
"release-it": "^19.2.4",
|
|
58
|
+
"tsx": "^4.21.0",
|
|
55
59
|
"typescript": "^5.9.3"
|
|
56
60
|
},
|
|
57
61
|
"author": "ayagmar",
|
|
58
62
|
"license": "MIT",
|
|
63
|
+
"packageManager": "pnpm@10.33.0",
|
|
59
64
|
"engines": {
|
|
60
|
-
"node": ">=22.
|
|
65
|
+
"node": ">=22.20.0"
|
|
61
66
|
},
|
|
62
67
|
"repository": {
|
|
63
68
|
"type": "git",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
ExtensionAPI,
|
|
3
|
-
ExtensionCommandContext,
|
|
4
|
-
ExtensionContext,
|
|
1
|
+
import {
|
|
2
|
+
type ExtensionAPI,
|
|
3
|
+
type ExtensionCommandContext,
|
|
4
|
+
type ExtensionContext,
|
|
5
5
|
} from "@mariozechner/pi-coding-agent";
|
|
6
6
|
import {
|
|
7
7
|
disableAutoUpdate,
|
|
@@ -54,7 +54,7 @@ export async function handleAutoUpdateSubcommand(
|
|
|
54
54
|
" 3d - Check every 3 days",
|
|
55
55
|
" 1w - Check weekly",
|
|
56
56
|
" 2w - Check every 2 weeks",
|
|
57
|
-
" 1mo - Check monthly
|
|
57
|
+
" 1mo - Check monthly",
|
|
58
58
|
" daily - Check daily (alias)",
|
|
59
59
|
" weekly - Check weekly (alias)",
|
|
60
60
|
];
|
package/src/commands/cache.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { clearSearchCache } from "../packages/discovery.js";
|
|
3
3
|
import { clearRemotePackageInfoCache } from "../ui/remote.js";
|
|
4
4
|
import { clearCache } from "../utils/cache.js";
|
package/src/commands/history.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
|
+
type ChangeAction,
|
|
3
4
|
formatChangeEntry,
|
|
5
|
+
type HistoryFilters,
|
|
4
6
|
queryGlobalHistory,
|
|
5
7
|
querySessionChanges,
|
|
6
|
-
type ChangeAction,
|
|
7
|
-
type HistoryFilters,
|
|
8
8
|
} from "../utils/history.js";
|
|
9
|
+
import { parseLookbackDuration } from "../utils/duration.js";
|
|
9
10
|
import { notify } from "../utils/notify.js";
|
|
10
11
|
import { formatListOutput } from "../utils/ui-helpers.js";
|
|
11
12
|
|
|
@@ -37,36 +38,6 @@ type HistoryOptionHandler = (tokens: string[], index: number, state: HistoryPars
|
|
|
37
38
|
|
|
38
39
|
const HISTORY_ACTION_SET = new Set<ChangeAction>(HISTORY_ACTIONS);
|
|
39
40
|
|
|
40
|
-
function parseHistorySinceDuration(input: string): number | undefined {
|
|
41
|
-
const normalized = input.toLowerCase().trim();
|
|
42
|
-
const match = normalized.match(
|
|
43
|
-
/^(\d+)\s*(m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days|w|wk|wks|week|weeks|mo|mos|month|months)$/
|
|
44
|
-
);
|
|
45
|
-
if (!match) return undefined;
|
|
46
|
-
|
|
47
|
-
const value = Number.parseInt(match[1] ?? "", 10);
|
|
48
|
-
if (!Number.isFinite(value) || value <= 0) return undefined;
|
|
49
|
-
|
|
50
|
-
const unit = match[2] ?? "";
|
|
51
|
-
if (unit.startsWith("m") && !unit.startsWith("mo")) {
|
|
52
|
-
return value * 60 * 1000;
|
|
53
|
-
}
|
|
54
|
-
if (unit.startsWith("h")) {
|
|
55
|
-
return value * 60 * 60 * 1000;
|
|
56
|
-
}
|
|
57
|
-
if (unit.startsWith("d")) {
|
|
58
|
-
return value * 24 * 60 * 60 * 1000;
|
|
59
|
-
}
|
|
60
|
-
if (unit.startsWith("w")) {
|
|
61
|
-
return value * 7 * 24 * 60 * 60 * 1000;
|
|
62
|
-
}
|
|
63
|
-
if (unit.startsWith("mo")) {
|
|
64
|
-
return value * 30 * 24 * 60 * 60 * 1000;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return undefined;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
41
|
const HISTORY_OPTION_HANDLERS: Record<string, HistoryOptionHandler> = {
|
|
71
42
|
"--help": (_tokens, _index, state) => {
|
|
72
43
|
state.showHelp = true;
|
|
@@ -142,7 +113,7 @@ const HISTORY_OPTION_HANDLERS: Record<string, HistoryOptionHandler> = {
|
|
|
142
113
|
return 0;
|
|
143
114
|
}
|
|
144
115
|
|
|
145
|
-
const ms =
|
|
116
|
+
const ms = parseLookbackDuration(value);
|
|
146
117
|
if (!ms) {
|
|
147
118
|
state.errors.push(`Invalid --since duration: ${value}`);
|
|
148
119
|
} else {
|
package/src/commands/install.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {
|
|
1
|
+
import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { type InstallScope, installPackage } from "../packages/install.js";
|
|
3
3
|
import { notify } from "../utils/notify.js";
|
|
4
4
|
|
|
5
5
|
export const INSTALL_USAGE = "Usage: /extensions install <source> [--project|--global]";
|
package/src/commands/registry.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type
|
|
3
|
-
import { showInteractive, showInstalledPackagesLegacy, showListOnly } from "../ui/unified.js";
|
|
4
|
-
import { showRemote } from "../ui/remote.js";
|
|
1
|
+
import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { type AutocompleteItem } from "@mariozechner/pi-tui";
|
|
5
3
|
import {
|
|
6
4
|
promptRemove,
|
|
7
5
|
removePackage,
|
|
@@ -9,12 +7,14 @@ import {
|
|
|
9
7
|
updatePackage,
|
|
10
8
|
updatePackages,
|
|
11
9
|
} from "../packages/management.js";
|
|
10
|
+
import { showRemote } from "../ui/remote.js";
|
|
11
|
+
import { showInstalledPackagesLegacy, showInteractive, showListOnly } from "../ui/unified.js";
|
|
12
12
|
import { notify } from "../utils/notify.js";
|
|
13
|
-
import { handleInstallSubcommand, INSTALL_USAGE } from "./install.js";
|
|
14
|
-
import { handleHistorySubcommand } from "./history.js";
|
|
15
13
|
import { handleAutoUpdateSubcommand } from "./auto-update.js";
|
|
16
14
|
import { clearMetadataCacheCommand } from "./cache.js";
|
|
17
|
-
import
|
|
15
|
+
import { handleHistorySubcommand } from "./history.js";
|
|
16
|
+
import { handleInstallSubcommand, INSTALL_USAGE } from "./install.js";
|
|
17
|
+
import { type CommandDefinition, type CommandId } from "./types.js";
|
|
18
18
|
|
|
19
19
|
const REMOVE_USAGE = "Usage: /extensions remove <npm:package|git:url|path>";
|
|
20
20
|
|
package/src/commands/types.ts
CHANGED
package/src/constants.ts
CHANGED
|
@@ -26,14 +26,6 @@ export const TIMEOUTS = {
|
|
|
26
26
|
npmView: 10_000,
|
|
27
27
|
/** Full package installation timeout (3 minutes) */
|
|
28
28
|
packageInstall: 180_000,
|
|
29
|
-
/** Package update timeout (2 minutes) */
|
|
30
|
-
packageUpdate: 120_000,
|
|
31
|
-
/** Bulk package update timeout (5 minutes) */
|
|
32
|
-
packageUpdateAll: 300_000,
|
|
33
|
-
/** Package removal timeout (1 minute) */
|
|
34
|
-
packageRemove: 60_000,
|
|
35
|
-
/** Package listing timeout */
|
|
36
|
-
listPackages: 10_000,
|
|
37
29
|
/** Package metadata fetch timeout */
|
|
38
30
|
fetchPackageInfo: 30_000,
|
|
39
31
|
/** Package extraction timeout */
|
|
@@ -4,13 +4,19 @@
|
|
|
4
4
|
* This module handles discovery and management of local Pi extensions
|
|
5
5
|
* in both global (~/.pi/agent/extensions) and project (.pi/extensions) scopes.
|
|
6
6
|
*/
|
|
7
|
+
|
|
8
|
+
import { type Dirent } from "node:fs";
|
|
7
9
|
import { readdir, rename, rm } from "node:fs/promises";
|
|
8
|
-
import { basename, dirname, join, relative } from "node:path";
|
|
9
10
|
import { homedir } from "node:os";
|
|
10
|
-
import
|
|
11
|
-
import type { ExtensionEntry, Scope, State } from "../types/index.js";
|
|
11
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
12
12
|
import { DISABLED_SUFFIX } from "../constants.js";
|
|
13
|
+
import { readPackageManifest } from "../packages/extensions.js";
|
|
14
|
+
import { type ExtensionEntry, type Scope, type State } from "../types/index.js";
|
|
13
15
|
import { fileExists, readSummary } from "../utils/fs.js";
|
|
16
|
+
import {
|
|
17
|
+
normalizeRelativePath,
|
|
18
|
+
resolveRelativePathSelection,
|
|
19
|
+
} from "../utils/relative-path-selection.js";
|
|
14
20
|
|
|
15
21
|
interface RootConfig {
|
|
16
22
|
root: string;
|
|
@@ -93,8 +99,7 @@ async function discoverInRoot(
|
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
if (item.isDirectory()) {
|
|
96
|
-
|
|
97
|
-
if (entry) found.push(entry);
|
|
102
|
+
found.push(...(await parseDirectoryExtensions(root, label, scope, name)));
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
|
|
@@ -140,53 +145,131 @@ async function parseTopLevelFile(
|
|
|
140
145
|
};
|
|
141
146
|
}
|
|
142
147
|
|
|
148
|
+
function stripDisabledSuffix(path: string): string {
|
|
149
|
+
return path.replace(/\.(ts|js)\.disabled$/i, ".$1");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isExtensionEntrypointPath(path: string): boolean {
|
|
153
|
+
return /\.(ts|js)$/i.test(path);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isLocalExtensionFile(path: string): boolean {
|
|
157
|
+
return /\.(ts|js)(?:\.disabled)?$/i.test(path);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function collectLocalExtensionFiles(rootDir: string, startDir: string): Promise<string[]> {
|
|
161
|
+
const collected: string[] = [];
|
|
162
|
+
|
|
163
|
+
let entries: Dirent[];
|
|
164
|
+
try {
|
|
165
|
+
entries = await readdir(startDir, { withFileTypes: true });
|
|
166
|
+
} catch {
|
|
167
|
+
return collected;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
if (entry.name.startsWith(".")) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const absolutePath = join(startDir, entry.name);
|
|
176
|
+
if (entry.isDirectory()) {
|
|
177
|
+
collected.push(...(await collectLocalExtensionFiles(rootDir, absolutePath)));
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!entry.isFile()) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const relativePath = normalizeRelativePath(relative(rootDir, absolutePath));
|
|
186
|
+
if (isLocalExtensionFile(relativePath)) {
|
|
187
|
+
collected.push(stripDisabledSuffix(relativePath));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return collected;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function resolveManifestLocalEntrypoints(dir: string): Promise<string[] | undefined> {
|
|
195
|
+
const manifest = await readPackageManifest(dir);
|
|
196
|
+
const extensions = manifest?.pi?.extensions;
|
|
197
|
+
if (!Array.isArray(extensions)) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const entries = extensions.filter((value): value is string => typeof value === "string");
|
|
202
|
+
const allFiles = await collectLocalExtensionFiles(dir, dir);
|
|
203
|
+
return resolveRelativePathSelection(
|
|
204
|
+
allFiles,
|
|
205
|
+
entries,
|
|
206
|
+
(path, files) => isExtensionEntrypointPath(path) && files.includes(path)
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function toDirectoryExtensionEntry(
|
|
211
|
+
root: string,
|
|
212
|
+
label: string,
|
|
213
|
+
scope: Scope,
|
|
214
|
+
dir: string,
|
|
215
|
+
extensionPath: string
|
|
216
|
+
): Promise<ExtensionEntry | undefined> {
|
|
217
|
+
const normalizedPath = normalizeRelativePath(extensionPath);
|
|
218
|
+
const activePath = join(dir, normalizedPath);
|
|
219
|
+
const disabledPath = `${activePath}${DISABLED_SUFFIX}`;
|
|
220
|
+
|
|
221
|
+
let state: State;
|
|
222
|
+
let summaryPath: string;
|
|
223
|
+
if (await fileExists(activePath)) {
|
|
224
|
+
state = "enabled";
|
|
225
|
+
summaryPath = activePath;
|
|
226
|
+
} else if (await fileExists(disabledPath)) {
|
|
227
|
+
state = "disabled";
|
|
228
|
+
summaryPath = disabledPath;
|
|
229
|
+
} else {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
id: `${scope}:${activePath}`,
|
|
235
|
+
scope,
|
|
236
|
+
state,
|
|
237
|
+
activePath,
|
|
238
|
+
disabledPath,
|
|
239
|
+
displayName: `${label}/${normalizeRelativePath(relative(root, activePath))}`,
|
|
240
|
+
summary: await readSummary(summaryPath),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
143
244
|
/**
|
|
144
|
-
* Parse a directory containing
|
|
145
|
-
*
|
|
146
|
-
* @param root - Root directory path
|
|
147
|
-
* @param label - Display label for the root
|
|
148
|
-
* @param scope - "global" or "project"
|
|
149
|
-
* @param dirName - Name of the directory to parse
|
|
150
|
-
* @returns ExtensionEntry if index file found, undefined otherwise
|
|
245
|
+
* Parse a directory containing a manifest-declared entrypoint or index.ts/js file as one or more
|
|
246
|
+
* extension entries.
|
|
151
247
|
*/
|
|
152
|
-
async function
|
|
248
|
+
async function parseDirectoryExtensions(
|
|
153
249
|
root: string,
|
|
154
250
|
label: string,
|
|
155
251
|
scope: Scope,
|
|
156
252
|
dirName: string
|
|
157
|
-
): Promise<ExtensionEntry
|
|
253
|
+
): Promise<ExtensionEntry[]> {
|
|
158
254
|
const dir = join(root, dirName);
|
|
255
|
+
const manifestEntrypoints = await resolveManifestLocalEntrypoints(dir);
|
|
159
256
|
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
scope,
|
|
168
|
-
state: "enabled",
|
|
169
|
-
activePath,
|
|
170
|
-
disabledPath,
|
|
171
|
-
displayName: `${label}/${dirName}/index${ext}`,
|
|
172
|
-
summary: await readSummary(activePath),
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (await fileExists(disabledPath)) {
|
|
177
|
-
return {
|
|
178
|
-
id: `${scope}:${activePath}`,
|
|
179
|
-
scope,
|
|
180
|
-
state: "disabled",
|
|
181
|
-
activePath,
|
|
182
|
-
disabledPath,
|
|
183
|
-
displayName: `${label}/${dirName}/index${ext}`,
|
|
184
|
-
summary: await readSummary(disabledPath),
|
|
185
|
-
};
|
|
186
|
-
}
|
|
257
|
+
if (manifestEntrypoints !== undefined) {
|
|
258
|
+
const entries = await Promise.all(
|
|
259
|
+
manifestEntrypoints.map((extensionPath) =>
|
|
260
|
+
toDirectoryExtensionEntry(root, label, scope, dir, extensionPath)
|
|
261
|
+
)
|
|
262
|
+
);
|
|
263
|
+
return entries.filter((entry): entry is ExtensionEntry => Boolean(entry));
|
|
187
264
|
}
|
|
188
265
|
|
|
189
|
-
|
|
266
|
+
const fallbackEntries = await Promise.all(
|
|
267
|
+
["index.ts", "index.js"].map((extensionPath) =>
|
|
268
|
+
toDirectoryExtensionEntry(root, label, scope, dir, extensionPath)
|
|
269
|
+
)
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
return fallbackEntries.filter((entry): entry is ExtensionEntry => Boolean(entry)).slice(0, 1);
|
|
190
273
|
}
|
|
191
274
|
|
|
192
275
|
/**
|
package/src/index.ts
CHANGED
|
@@ -3,21 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Entry point - exports the main extension function
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
7
|
-
ExtensionAPI,
|
|
8
|
-
ExtensionCommandContext,
|
|
9
|
-
ExtensionContext,
|
|
10
|
-
} from "@mariozechner/pi-coding-agent";
|
|
11
|
-
import { isPackageSource } from "./utils/format.js";
|
|
12
|
-
import { installPackage } from "./packages/install.js";
|
|
13
|
-
import { tokenizeArgs } from "./utils/command.js";
|
|
14
|
-
import { updateExtmgrStatus } from "./utils/status.js";
|
|
15
6
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type
|
|
19
|
-
} from "
|
|
20
|
-
import {
|
|
7
|
+
type ExtensionAPI,
|
|
8
|
+
type ExtensionCommandContext,
|
|
9
|
+
type ExtensionContext,
|
|
10
|
+
} from "@mariozechner/pi-coding-agent";
|
|
11
|
+
import { createAutoUpdateNotificationHandler } from "./commands/auto-update.js";
|
|
21
12
|
import {
|
|
22
13
|
getExtensionsAutocompleteItems,
|
|
23
14
|
resolveCommand,
|
|
@@ -25,7 +16,16 @@ import {
|
|
|
25
16
|
showNonInteractiveHelp,
|
|
26
17
|
showUnknownCommandMessage,
|
|
27
18
|
} from "./commands/registry.js";
|
|
28
|
-
import {
|
|
19
|
+
import { installPackage } from "./packages/install.js";
|
|
20
|
+
import {
|
|
21
|
+
type ContextProvider,
|
|
22
|
+
startAutoUpdateTimer,
|
|
23
|
+
stopAutoUpdateTimer,
|
|
24
|
+
} from "./utils/auto-update.js";
|
|
25
|
+
import { tokenizeArgs } from "./utils/command.js";
|
|
26
|
+
import { isPackageSource } from "./utils/format.js";
|
|
27
|
+
import { getAutoUpdateConfig, hydrateAutoUpdateConfig } from "./utils/settings.js";
|
|
28
|
+
import { updateExtmgrStatus } from "./utils/status.js";
|
|
29
29
|
|
|
30
30
|
async function executeExtensionsCommand(
|
|
31
31
|
args: string,
|
package/src/packages/catalog.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DefaultPackageManager,
|
|
3
3
|
getAgentDir,
|
|
4
|
-
SettingsManager,
|
|
5
4
|
type PackageSource,
|
|
6
5
|
type ProgressEvent,
|
|
6
|
+
SettingsManager,
|
|
7
7
|
} from "@mariozechner/pi-coding-agent";
|
|
8
|
-
import type
|
|
8
|
+
import { type InstalledPackage, type Scope } from "../types/index.js";
|
|
9
9
|
import { normalizePackageIdentity, parsePackageNameAndVersion } from "../utils/package-source.js";
|
|
10
10
|
|
|
11
11
|
type PiScope = "user" | "project";
|
|
@@ -57,14 +57,15 @@ function createPackageRecord(
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function dedupeInstalledPackages(packages: InstalledPackage[]): InstalledPackage[] {
|
|
60
|
+
function dedupeInstalledPackages(packages: InstalledPackage[], cwd: string): InstalledPackage[] {
|
|
61
61
|
const byIdentity = new Map<string, InstalledPackage>();
|
|
62
62
|
|
|
63
63
|
for (const pkg of packages) {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } :
|
|
67
|
-
|
|
64
|
+
const baseCwd = pkg.scope === "project" ? cwd : getAgentDir();
|
|
65
|
+
const identity = normalizePackageIdentity(pkg.source, {
|
|
66
|
+
...(pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : {}),
|
|
67
|
+
cwd: baseCwd,
|
|
68
|
+
});
|
|
68
69
|
|
|
69
70
|
if (!byIdentity.has(identity)) {
|
|
70
71
|
byIdentity.set(identity, pkg);
|
|
@@ -97,7 +98,7 @@ function createDefaultPackageCatalog(cwd: string): PackageCatalog {
|
|
|
97
98
|
|
|
98
99
|
const installed = [...projectPackages, ...globalPackages];
|
|
99
100
|
return Promise.resolve(
|
|
100
|
-
options?.dedupe === false ? installed : dedupeInstalledPackages(installed)
|
|
101
|
+
options?.dedupe === false ? installed : dedupeInstalledPackages(installed, cwd)
|
|
101
102
|
);
|
|
102
103
|
},
|
|
103
104
|
|