@mariozechner/pi-coding-agent 0.49.3 → 0.50.1

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.
Files changed (207) hide show
  1. package/CHANGELOG.md +110 -1
  2. package/README.md +310 -1230
  3. package/dist/cli/args.d.ts +5 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +57 -23
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/config-selector.d.ts +14 -0
  8. package/dist/cli/config-selector.d.ts.map +1 -0
  9. package/dist/cli/config-selector.js +31 -0
  10. package/dist/cli/config-selector.js.map +1 -0
  11. package/dist/cli/session-picker.d.ts.map +1 -1
  12. package/dist/cli/session-picker.js +1 -1
  13. package/dist/cli/session-picker.js.map +1 -1
  14. package/dist/core/agent-session.d.ts +60 -37
  15. package/dist/core/agent-session.d.ts.map +1 -1
  16. package/dist/core/agent-session.js +272 -69
  17. package/dist/core/agent-session.js.map +1 -1
  18. package/dist/core/auth-storage.d.ts +8 -18
  19. package/dist/core/auth-storage.d.ts.map +1 -1
  20. package/dist/core/auth-storage.js +39 -55
  21. package/dist/core/auth-storage.js.map +1 -1
  22. package/dist/core/bash-executor.d.ts.map +1 -1
  23. package/dist/core/bash-executor.js +2 -1
  24. package/dist/core/bash-executor.js.map +1 -1
  25. package/dist/core/diagnostics.d.ts +15 -0
  26. package/dist/core/diagnostics.d.ts.map +1 -0
  27. package/dist/core/diagnostics.js +2 -0
  28. package/dist/core/diagnostics.js.map +1 -0
  29. package/dist/core/export-html/template.css +9 -0
  30. package/dist/core/export-html/template.js +6 -4
  31. package/dist/core/extensions/index.d.ts +1 -1
  32. package/dist/core/extensions/index.d.ts.map +1 -1
  33. package/dist/core/extensions/index.js.map +1 -1
  34. package/dist/core/extensions/loader.d.ts +1 -1
  35. package/dist/core/extensions/loader.d.ts.map +1 -1
  36. package/dist/core/extensions/loader.js +10 -1
  37. package/dist/core/extensions/loader.js.map +1 -1
  38. package/dist/core/extensions/runner.d.ts +9 -3
  39. package/dist/core/extensions/runner.d.ts.map +1 -1
  40. package/dist/core/extensions/runner.js +39 -12
  41. package/dist/core/extensions/runner.js.map +1 -1
  42. package/dist/core/extensions/types.d.ts +112 -1
  43. package/dist/core/extensions/types.d.ts.map +1 -1
  44. package/dist/core/extensions/types.js.map +1 -1
  45. package/dist/core/footer-data-provider.d.ts +9 -2
  46. package/dist/core/footer-data-provider.d.ts.map +1 -1
  47. package/dist/core/footer-data-provider.js +13 -0
  48. package/dist/core/footer-data-provider.js.map +1 -1
  49. package/dist/core/model-registry.d.ts +42 -2
  50. package/dist/core/model-registry.d.ts.map +1 -1
  51. package/dist/core/model-registry.js +154 -44
  52. package/dist/core/model-registry.js.map +1 -1
  53. package/dist/core/model-resolver.d.ts.map +1 -1
  54. package/dist/core/model-resolver.js +3 -2
  55. package/dist/core/model-resolver.js.map +1 -1
  56. package/dist/core/package-manager.d.ts +130 -0
  57. package/dist/core/package-manager.d.ts.map +1 -0
  58. package/dist/core/package-manager.js +1177 -0
  59. package/dist/core/package-manager.js.map +1 -0
  60. package/dist/core/prompt-templates.d.ts +6 -0
  61. package/dist/core/prompt-templates.d.ts.map +1 -1
  62. package/dist/core/prompt-templates.js +114 -54
  63. package/dist/core/prompt-templates.js.map +1 -1
  64. package/dist/core/resource-loader.d.ts +160 -0
  65. package/dist/core/resource-loader.d.ts.map +1 -0
  66. package/dist/core/resource-loader.js +604 -0
  67. package/dist/core/resource-loader.js.map +1 -0
  68. package/dist/core/sdk.d.ts +14 -105
  69. package/dist/core/sdk.d.ts.map +1 -1
  70. package/dist/core/sdk.js +52 -304
  71. package/dist/core/sdk.js.map +1 -1
  72. package/dist/core/session-manager.d.ts.map +1 -1
  73. package/dist/core/session-manager.js +45 -1
  74. package/dist/core/session-manager.js.map +1 -1
  75. package/dist/core/settings-manager.d.ts +34 -16
  76. package/dist/core/settings-manager.d.ts.map +1 -1
  77. package/dist/core/settings-manager.js +104 -25
  78. package/dist/core/settings-manager.js.map +1 -1
  79. package/dist/core/skills.d.ts +18 -10
  80. package/dist/core/skills.d.ts.map +1 -1
  81. package/dist/core/skills.js +126 -93
  82. package/dist/core/skills.js.map +1 -1
  83. package/dist/core/system-prompt.d.ts +3 -27
  84. package/dist/core/system-prompt.d.ts.map +1 -1
  85. package/dist/core/system-prompt.js +16 -103
  86. package/dist/core/system-prompt.js.map +1 -1
  87. package/dist/core/tools/bash.d.ts.map +1 -1
  88. package/dist/core/tools/bash.js +2 -1
  89. package/dist/core/tools/bash.js.map +1 -1
  90. package/dist/core/tools/read.d.ts.map +1 -1
  91. package/dist/core/tools/read.js +4 -4
  92. package/dist/core/tools/read.js.map +1 -1
  93. package/dist/index.d.ts +12 -7
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +8 -6
  96. package/dist/index.js.map +1 -1
  97. package/dist/main.d.ts.map +1 -1
  98. package/dist/main.js +209 -97
  99. package/dist/main.js.map +1 -1
  100. package/dist/modes/interactive/components/bordered-loader.d.ts +5 -1
  101. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  102. package/dist/modes/interactive/components/bordered-loader.js +29 -9
  103. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  104. package/dist/modes/interactive/components/config-selector.d.ts +71 -0
  105. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
  106. package/dist/modes/interactive/components/config-selector.js +468 -0
  107. package/dist/modes/interactive/components/config-selector.js.map +1 -0
  108. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  109. package/dist/modes/interactive/components/footer.js +4 -0
  110. package/dist/modes/interactive/components/footer.js.map +1 -1
  111. package/dist/modes/interactive/components/index.d.ts +1 -0
  112. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  113. package/dist/modes/interactive/components/index.js +1 -0
  114. package/dist/modes/interactive/components/index.js.map +1 -1
  115. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  116. package/dist/modes/interactive/components/oauth-selector.js +3 -4
  117. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  118. package/dist/modes/interactive/components/session-selector.d.ts +18 -1
  119. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  120. package/dist/modes/interactive/components/session-selector.js +195 -87
  121. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  122. package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  123. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
  124. package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
  125. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
  126. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  127. package/dist/modes/interactive/components/tool-execution.js +5 -5
  128. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  129. package/dist/modes/interactive/interactive-mode.d.ts +42 -2
  130. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  131. package/dist/modes/interactive/interactive-mode.js +538 -204
  132. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  133. package/dist/modes/interactive/theme/dark.json +1 -1
  134. package/dist/modes/interactive/theme/light.json +1 -1
  135. package/dist/modes/interactive/theme/theme-schema.json +8 -1
  136. package/dist/modes/interactive/theme/theme.d.ts +8 -1
  137. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  138. package/dist/modes/interactive/theme/theme.js +72 -25
  139. package/dist/modes/interactive/theme/theme.js.map +1 -1
  140. package/dist/modes/print-mode.d.ts.map +1 -1
  141. package/dist/modes/print-mode.js +7 -74
  142. package/dist/modes/print-mode.js.map +1 -1
  143. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  144. package/dist/modes/rpc/rpc-mode.js +17 -82
  145. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  146. package/dist/utils/git.d.ts +2 -0
  147. package/dist/utils/git.d.ts.map +1 -0
  148. package/dist/utils/git.js +6 -0
  149. package/dist/utils/git.js.map +1 -0
  150. package/dist/utils/shell.d.ts +1 -0
  151. package/dist/utils/shell.d.ts.map +1 -1
  152. package/dist/utils/shell.js +14 -1
  153. package/dist/utils/shell.js.map +1 -1
  154. package/dist/utils/sleep.d.ts +5 -0
  155. package/dist/utils/sleep.d.ts.map +1 -0
  156. package/dist/utils/sleep.js +17 -0
  157. package/dist/utils/sleep.js.map +1 -0
  158. package/docs/compaction.md +23 -21
  159. package/docs/custom-provider.md +538 -0
  160. package/docs/development.md +69 -0
  161. package/docs/extensions.md +182 -118
  162. package/docs/images/doom-extension.png +0 -0
  163. package/docs/images/interactive-mode.png +0 -0
  164. package/docs/images/tree-view.png +0 -0
  165. package/docs/json.md +79 -0
  166. package/docs/keybindings.md +162 -0
  167. package/docs/models.md +193 -0
  168. package/docs/packages.md +168 -0
  169. package/docs/prompt-templates.md +67 -0
  170. package/docs/providers.md +147 -0
  171. package/docs/sdk.md +111 -178
  172. package/docs/session.md +167 -16
  173. package/docs/settings.md +216 -0
  174. package/docs/shell-aliases.md +13 -0
  175. package/docs/skills.md +111 -202
  176. package/docs/terminal-setup.md +65 -0
  177. package/docs/themes.md +295 -0
  178. package/docs/tui.md +36 -5
  179. package/docs/windows.md +17 -0
  180. package/examples/README.md +1 -0
  181. package/examples/extensions/README.md +22 -2
  182. package/examples/extensions/bookmark.ts +50 -0
  183. package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  184. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  185. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  186. package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
  187. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  188. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  189. package/examples/extensions/doom-overlay/doom/build.sh +1 -1
  190. package/examples/extensions/event-bus.ts +43 -0
  191. package/examples/extensions/message-renderer.ts +59 -0
  192. package/examples/extensions/session-name.ts +27 -0
  193. package/examples/extensions/with-deps/package-lock.json +2 -2
  194. package/examples/extensions/with-deps/package.json +1 -1
  195. package/examples/sdk/02-custom-model.ts +3 -3
  196. package/examples/sdk/03-custom-prompt.ts +20 -9
  197. package/examples/sdk/04-skills.ts +26 -27
  198. package/examples/sdk/06-extensions.ts +15 -6
  199. package/examples/sdk/07-context-files.ts +22 -18
  200. package/examples/sdk/08-prompt-templates.ts +19 -14
  201. package/examples/sdk/09-api-keys-and-oauth.ts +5 -12
  202. package/examples/sdk/10-settings.ts +3 -3
  203. package/examples/sdk/12-full-control.ts +16 -7
  204. package/examples/sdk/README.md +24 -30
  205. package/package.json +4 -4
  206. package/docs/theme.md +0 -617
  207. package/examples/extensions/chalk-logger.ts +0 -26
@@ -0,0 +1,1177 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
4
+ import { homedir, tmpdir } from "node:os";
5
+ import { basename, dirname, join, relative, resolve } from "node:path";
6
+ import { minimatch } from "minimatch";
7
+ import { CONFIG_DIR_NAME } from "../config.js";
8
+ import { looksLikeGitUrl } from "../utils/git.js";
9
+ const RESOURCE_TYPES = ["extensions", "skills", "prompts", "themes"];
10
+ const FILE_PATTERNS = {
11
+ extensions: /\.(ts|js)$/,
12
+ skills: /\.md$/,
13
+ prompts: /\.md$/,
14
+ themes: /\.json$/,
15
+ };
16
+ function isPattern(s) {
17
+ return s.startsWith("!") || s.startsWith("+") || s.startsWith("-") || s.includes("*") || s.includes("?");
18
+ }
19
+ function splitPatterns(entries) {
20
+ const plain = [];
21
+ const patterns = [];
22
+ for (const entry of entries) {
23
+ if (isPattern(entry)) {
24
+ patterns.push(entry);
25
+ }
26
+ else {
27
+ plain.push(entry);
28
+ }
29
+ }
30
+ return { plain, patterns };
31
+ }
32
+ function collectFiles(dir, filePattern, skipNodeModules = true) {
33
+ const files = [];
34
+ if (!existsSync(dir))
35
+ return files;
36
+ try {
37
+ const entries = readdirSync(dir, { withFileTypes: true });
38
+ for (const entry of entries) {
39
+ if (entry.name.startsWith("."))
40
+ continue;
41
+ if (skipNodeModules && entry.name === "node_modules")
42
+ continue;
43
+ const fullPath = join(dir, entry.name);
44
+ let isDir = entry.isDirectory();
45
+ let isFile = entry.isFile();
46
+ if (entry.isSymbolicLink()) {
47
+ try {
48
+ const stats = statSync(fullPath);
49
+ isDir = stats.isDirectory();
50
+ isFile = stats.isFile();
51
+ }
52
+ catch {
53
+ continue;
54
+ }
55
+ }
56
+ if (isDir) {
57
+ files.push(...collectFiles(fullPath, filePattern, skipNodeModules));
58
+ }
59
+ else if (isFile && filePattern.test(entry.name)) {
60
+ files.push(fullPath);
61
+ }
62
+ }
63
+ }
64
+ catch {
65
+ // Ignore errors
66
+ }
67
+ return files;
68
+ }
69
+ function collectSkillEntries(dir, includeRootFiles = true) {
70
+ const entries = [];
71
+ if (!existsSync(dir))
72
+ return entries;
73
+ try {
74
+ const dirEntries = readdirSync(dir, { withFileTypes: true });
75
+ for (const entry of dirEntries) {
76
+ if (entry.name.startsWith("."))
77
+ continue;
78
+ if (entry.name === "node_modules")
79
+ continue;
80
+ const fullPath = join(dir, entry.name);
81
+ let isDir = entry.isDirectory();
82
+ let isFile = entry.isFile();
83
+ if (entry.isSymbolicLink()) {
84
+ try {
85
+ const stats = statSync(fullPath);
86
+ isDir = stats.isDirectory();
87
+ isFile = stats.isFile();
88
+ }
89
+ catch {
90
+ continue;
91
+ }
92
+ }
93
+ if (isDir) {
94
+ entries.push(...collectSkillEntries(fullPath, false));
95
+ }
96
+ else if (isFile) {
97
+ const isRootMd = includeRootFiles && entry.name.endsWith(".md");
98
+ const isSkillMd = !includeRootFiles && entry.name === "SKILL.md";
99
+ if (isRootMd || isSkillMd) {
100
+ entries.push(fullPath);
101
+ }
102
+ }
103
+ }
104
+ }
105
+ catch {
106
+ // Ignore errors
107
+ }
108
+ return entries;
109
+ }
110
+ function collectAutoSkillEntries(dir, includeRootFiles = true) {
111
+ return collectSkillEntries(dir, includeRootFiles);
112
+ }
113
+ function collectAutoPromptEntries(dir) {
114
+ const entries = [];
115
+ if (!existsSync(dir))
116
+ return entries;
117
+ try {
118
+ const dirEntries = readdirSync(dir, { withFileTypes: true });
119
+ for (const entry of dirEntries) {
120
+ if (entry.name.startsWith("."))
121
+ continue;
122
+ if (entry.name === "node_modules")
123
+ continue;
124
+ const fullPath = join(dir, entry.name);
125
+ let isFile = entry.isFile();
126
+ if (entry.isSymbolicLink()) {
127
+ try {
128
+ isFile = statSync(fullPath).isFile();
129
+ }
130
+ catch {
131
+ continue;
132
+ }
133
+ }
134
+ if (isFile && entry.name.endsWith(".md")) {
135
+ entries.push(fullPath);
136
+ }
137
+ }
138
+ }
139
+ catch {
140
+ // Ignore errors
141
+ }
142
+ return entries;
143
+ }
144
+ function collectAutoThemeEntries(dir) {
145
+ const entries = [];
146
+ if (!existsSync(dir))
147
+ return entries;
148
+ try {
149
+ const dirEntries = readdirSync(dir, { withFileTypes: true });
150
+ for (const entry of dirEntries) {
151
+ if (entry.name.startsWith("."))
152
+ continue;
153
+ if (entry.name === "node_modules")
154
+ continue;
155
+ const fullPath = join(dir, entry.name);
156
+ let isFile = entry.isFile();
157
+ if (entry.isSymbolicLink()) {
158
+ try {
159
+ isFile = statSync(fullPath).isFile();
160
+ }
161
+ catch {
162
+ continue;
163
+ }
164
+ }
165
+ if (isFile && entry.name.endsWith(".json")) {
166
+ entries.push(fullPath);
167
+ }
168
+ }
169
+ }
170
+ catch {
171
+ // Ignore errors
172
+ }
173
+ return entries;
174
+ }
175
+ function readPiManifestFile(packageJsonPath) {
176
+ try {
177
+ const content = readFileSync(packageJsonPath, "utf-8");
178
+ const pkg = JSON.parse(content);
179
+ return pkg.pi ?? null;
180
+ }
181
+ catch {
182
+ return null;
183
+ }
184
+ }
185
+ function resolveExtensionEntries(dir) {
186
+ const packageJsonPath = join(dir, "package.json");
187
+ if (existsSync(packageJsonPath)) {
188
+ const manifest = readPiManifestFile(packageJsonPath);
189
+ if (manifest?.extensions?.length) {
190
+ const entries = [];
191
+ for (const extPath of manifest.extensions) {
192
+ const resolvedExtPath = resolve(dir, extPath);
193
+ if (existsSync(resolvedExtPath)) {
194
+ entries.push(resolvedExtPath);
195
+ }
196
+ }
197
+ if (entries.length > 0) {
198
+ return entries;
199
+ }
200
+ }
201
+ }
202
+ const indexTs = join(dir, "index.ts");
203
+ const indexJs = join(dir, "index.js");
204
+ if (existsSync(indexTs)) {
205
+ return [indexTs];
206
+ }
207
+ if (existsSync(indexJs)) {
208
+ return [indexJs];
209
+ }
210
+ return null;
211
+ }
212
+ function collectAutoExtensionEntries(dir) {
213
+ const entries = [];
214
+ if (!existsSync(dir))
215
+ return entries;
216
+ try {
217
+ const dirEntries = readdirSync(dir, { withFileTypes: true });
218
+ for (const entry of dirEntries) {
219
+ if (entry.name.startsWith("."))
220
+ continue;
221
+ if (entry.name === "node_modules")
222
+ continue;
223
+ const fullPath = join(dir, entry.name);
224
+ let isDir = entry.isDirectory();
225
+ let isFile = entry.isFile();
226
+ if (entry.isSymbolicLink()) {
227
+ try {
228
+ const stats = statSync(fullPath);
229
+ isDir = stats.isDirectory();
230
+ isFile = stats.isFile();
231
+ }
232
+ catch {
233
+ continue;
234
+ }
235
+ }
236
+ if (isFile && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))) {
237
+ entries.push(fullPath);
238
+ }
239
+ else if (isDir) {
240
+ const resolvedEntries = resolveExtensionEntries(fullPath);
241
+ if (resolvedEntries) {
242
+ entries.push(...resolvedEntries);
243
+ }
244
+ }
245
+ }
246
+ }
247
+ catch {
248
+ // Ignore errors
249
+ }
250
+ return entries;
251
+ }
252
+ function matchesAnyPattern(filePath, patterns, baseDir) {
253
+ const rel = relative(baseDir, filePath);
254
+ const name = basename(filePath);
255
+ const isSkillFile = name === "SKILL.md";
256
+ const parentDir = isSkillFile ? dirname(filePath) : undefined;
257
+ const parentRel = isSkillFile ? relative(baseDir, parentDir) : undefined;
258
+ const parentName = isSkillFile ? basename(parentDir) : undefined;
259
+ return patterns.some((pattern) => {
260
+ if (minimatch(rel, pattern) || minimatch(name, pattern) || minimatch(filePath, pattern)) {
261
+ return true;
262
+ }
263
+ if (!isSkillFile)
264
+ return false;
265
+ return minimatch(parentRel, pattern) || minimatch(parentName, pattern) || minimatch(parentDir, pattern);
266
+ });
267
+ }
268
+ function normalizeExactPattern(pattern) {
269
+ if (pattern.startsWith("./") || pattern.startsWith(".\\")) {
270
+ return pattern.slice(2);
271
+ }
272
+ return pattern;
273
+ }
274
+ function matchesAnyExactPattern(filePath, patterns, baseDir) {
275
+ if (patterns.length === 0)
276
+ return false;
277
+ const rel = relative(baseDir, filePath);
278
+ const name = basename(filePath);
279
+ const isSkillFile = name === "SKILL.md";
280
+ const parentDir = isSkillFile ? dirname(filePath) : undefined;
281
+ const parentRel = isSkillFile ? relative(baseDir, parentDir) : undefined;
282
+ return patterns.some((pattern) => {
283
+ const normalized = normalizeExactPattern(pattern);
284
+ if (normalized === rel || normalized === filePath) {
285
+ return true;
286
+ }
287
+ if (!isSkillFile)
288
+ return false;
289
+ return normalized === parentRel || normalized === parentDir;
290
+ });
291
+ }
292
+ function getOverridePatterns(entries) {
293
+ return entries.filter((pattern) => pattern.startsWith("!") || pattern.startsWith("+") || pattern.startsWith("-"));
294
+ }
295
+ function isEnabledByOverrides(filePath, patterns, baseDir) {
296
+ const overrides = getOverridePatterns(patterns);
297
+ const excludes = overrides.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
298
+ const forceIncludes = overrides.filter((pattern) => pattern.startsWith("+")).map((pattern) => pattern.slice(1));
299
+ const forceExcludes = overrides.filter((pattern) => pattern.startsWith("-")).map((pattern) => pattern.slice(1));
300
+ let enabled = true;
301
+ if (excludes.length > 0 && matchesAnyPattern(filePath, excludes, baseDir)) {
302
+ enabled = false;
303
+ }
304
+ if (forceIncludes.length > 0 && matchesAnyExactPattern(filePath, forceIncludes, baseDir)) {
305
+ enabled = true;
306
+ }
307
+ if (forceExcludes.length > 0 && matchesAnyExactPattern(filePath, forceExcludes, baseDir)) {
308
+ enabled = false;
309
+ }
310
+ return enabled;
311
+ }
312
+ /**
313
+ * Apply patterns to paths and return a Set of enabled paths.
314
+ * Pattern types:
315
+ * - Plain patterns: include matching paths
316
+ * - `!pattern`: exclude matching paths
317
+ * - `+path`: force-include exact path (overrides exclusions)
318
+ * - `-path`: force-exclude exact path (overrides force-includes)
319
+ */
320
+ function applyPatterns(allPaths, patterns, baseDir) {
321
+ const includes = [];
322
+ const excludes = [];
323
+ const forceIncludes = [];
324
+ const forceExcludes = [];
325
+ for (const p of patterns) {
326
+ if (p.startsWith("+")) {
327
+ forceIncludes.push(p.slice(1));
328
+ }
329
+ else if (p.startsWith("-")) {
330
+ forceExcludes.push(p.slice(1));
331
+ }
332
+ else if (p.startsWith("!")) {
333
+ excludes.push(p.slice(1));
334
+ }
335
+ else {
336
+ includes.push(p);
337
+ }
338
+ }
339
+ // Step 1: Apply includes (or all if no includes)
340
+ let result;
341
+ if (includes.length === 0) {
342
+ result = [...allPaths];
343
+ }
344
+ else {
345
+ result = allPaths.filter((filePath) => matchesAnyPattern(filePath, includes, baseDir));
346
+ }
347
+ // Step 2: Apply excludes
348
+ if (excludes.length > 0) {
349
+ result = result.filter((filePath) => !matchesAnyPattern(filePath, excludes, baseDir));
350
+ }
351
+ // Step 3: Force-include (add back from allPaths, overriding exclusions)
352
+ if (forceIncludes.length > 0) {
353
+ for (const filePath of allPaths) {
354
+ if (!result.includes(filePath) && matchesAnyExactPattern(filePath, forceIncludes, baseDir)) {
355
+ result.push(filePath);
356
+ }
357
+ }
358
+ }
359
+ // Step 4: Force-exclude (remove even if included or force-included)
360
+ if (forceExcludes.length > 0) {
361
+ result = result.filter((filePath) => !matchesAnyExactPattern(filePath, forceExcludes, baseDir));
362
+ }
363
+ return new Set(result);
364
+ }
365
+ export class DefaultPackageManager {
366
+ cwd;
367
+ agentDir;
368
+ settingsManager;
369
+ globalNpmRoot;
370
+ progressCallback;
371
+ constructor(options) {
372
+ this.cwd = options.cwd;
373
+ this.agentDir = options.agentDir;
374
+ this.settingsManager = options.settingsManager;
375
+ }
376
+ setProgressCallback(callback) {
377
+ this.progressCallback = callback;
378
+ }
379
+ getInstalledPath(source, scope) {
380
+ const parsed = this.parseSource(source);
381
+ if (parsed.type === "npm") {
382
+ const path = this.getNpmInstallPath(parsed, scope);
383
+ return existsSync(path) ? path : undefined;
384
+ }
385
+ if (parsed.type === "git") {
386
+ const path = this.getGitInstallPath(parsed, scope);
387
+ return existsSync(path) ? path : undefined;
388
+ }
389
+ if (parsed.type === "local") {
390
+ const path = this.resolvePath(parsed.path);
391
+ return existsSync(path) ? path : undefined;
392
+ }
393
+ return undefined;
394
+ }
395
+ emitProgress(event) {
396
+ this.progressCallback?.(event);
397
+ }
398
+ async withProgress(action, source, message, operation) {
399
+ this.emitProgress({ type: "start", action, source, message });
400
+ try {
401
+ await operation();
402
+ this.emitProgress({ type: "complete", action, source });
403
+ }
404
+ catch (error) {
405
+ const errorMessage = error instanceof Error ? error.message : String(error);
406
+ this.emitProgress({ type: "error", action, source, message: errorMessage });
407
+ throw error;
408
+ }
409
+ }
410
+ async resolve(onMissing) {
411
+ const accumulator = this.createAccumulator();
412
+ const globalSettings = this.settingsManager.getGlobalSettings();
413
+ const projectSettings = this.settingsManager.getProjectSettings();
414
+ // Collect all packages with scope
415
+ const allPackages = [];
416
+ for (const pkg of globalSettings.packages ?? []) {
417
+ allPackages.push({ pkg, scope: "user" });
418
+ }
419
+ for (const pkg of projectSettings.packages ?? []) {
420
+ allPackages.push({ pkg, scope: "project" });
421
+ }
422
+ // Dedupe: project scope wins over global for same package identity
423
+ const packageSources = this.dedupePackages(allPackages);
424
+ await this.resolvePackageSources(packageSources, accumulator, onMissing);
425
+ const globalBaseDir = this.agentDir;
426
+ const projectBaseDir = join(this.cwd, CONFIG_DIR_NAME);
427
+ for (const resourceType of RESOURCE_TYPES) {
428
+ const target = this.getTargetMap(accumulator, resourceType);
429
+ const globalEntries = (globalSettings[resourceType] ?? []);
430
+ const projectEntries = (projectSettings[resourceType] ?? []);
431
+ this.resolveLocalEntries(globalEntries, resourceType, target, {
432
+ source: "local",
433
+ scope: "user",
434
+ origin: "top-level",
435
+ }, globalBaseDir);
436
+ this.resolveLocalEntries(projectEntries, resourceType, target, {
437
+ source: "local",
438
+ scope: "project",
439
+ origin: "top-level",
440
+ }, projectBaseDir);
441
+ }
442
+ this.addAutoDiscoveredResources(accumulator, globalSettings, projectSettings, globalBaseDir, projectBaseDir);
443
+ return this.toResolvedPaths(accumulator);
444
+ }
445
+ async resolveExtensionSources(sources, options) {
446
+ const accumulator = this.createAccumulator();
447
+ const scope = options?.temporary ? "temporary" : options?.local ? "project" : "user";
448
+ const packageSources = sources.map((source) => ({ pkg: source, scope }));
449
+ await this.resolvePackageSources(packageSources, accumulator);
450
+ return this.toResolvedPaths(accumulator);
451
+ }
452
+ async install(source, options) {
453
+ const parsed = this.parseSource(source);
454
+ const scope = options?.local ? "project" : "user";
455
+ await this.withProgress("install", source, `Installing ${source}...`, async () => {
456
+ if (parsed.type === "npm") {
457
+ await this.installNpm(parsed, scope, false);
458
+ return;
459
+ }
460
+ if (parsed.type === "git") {
461
+ await this.installGit(parsed, scope);
462
+ return;
463
+ }
464
+ throw new Error(`Unsupported install source: ${source}`);
465
+ });
466
+ }
467
+ async remove(source, options) {
468
+ const parsed = this.parseSource(source);
469
+ const scope = options?.local ? "project" : "user";
470
+ await this.withProgress("remove", source, `Removing ${source}...`, async () => {
471
+ if (parsed.type === "npm") {
472
+ await this.uninstallNpm(parsed, scope);
473
+ return;
474
+ }
475
+ if (parsed.type === "git") {
476
+ await this.removeGit(parsed, scope);
477
+ return;
478
+ }
479
+ throw new Error(`Unsupported remove source: ${source}`);
480
+ });
481
+ }
482
+ async update(source) {
483
+ if (source) {
484
+ await this.updateSourceForScope(source, "user");
485
+ await this.updateSourceForScope(source, "project");
486
+ return;
487
+ }
488
+ const globalSettings = this.settingsManager.getGlobalSettings();
489
+ const projectSettings = this.settingsManager.getProjectSettings();
490
+ for (const extension of globalSettings.extensions ?? []) {
491
+ await this.updateSourceForScope(extension, "user");
492
+ }
493
+ for (const extension of projectSettings.extensions ?? []) {
494
+ await this.updateSourceForScope(extension, "project");
495
+ }
496
+ }
497
+ async updateSourceForScope(source, scope) {
498
+ const parsed = this.parseSource(source);
499
+ if (parsed.type === "npm") {
500
+ if (parsed.pinned)
501
+ return;
502
+ await this.withProgress("update", source, `Updating ${source}...`, async () => {
503
+ await this.installNpm(parsed, scope, false);
504
+ });
505
+ return;
506
+ }
507
+ if (parsed.type === "git") {
508
+ if (parsed.pinned)
509
+ return;
510
+ await this.withProgress("update", source, `Updating ${source}...`, async () => {
511
+ await this.updateGit(parsed, scope);
512
+ });
513
+ return;
514
+ }
515
+ }
516
+ async resolvePackageSources(sources, accumulator, onMissing) {
517
+ for (const { pkg, scope } of sources) {
518
+ const sourceStr = typeof pkg === "string" ? pkg : pkg.source;
519
+ const filter = typeof pkg === "object" ? pkg : undefined;
520
+ const parsed = this.parseSource(sourceStr);
521
+ const metadata = { source: sourceStr, scope, origin: "package" };
522
+ if (parsed.type === "local") {
523
+ this.resolveLocalExtensionSource(parsed, accumulator, filter, metadata);
524
+ continue;
525
+ }
526
+ const installMissing = async () => {
527
+ if (!onMissing) {
528
+ await this.installParsedSource(parsed, scope);
529
+ return true;
530
+ }
531
+ const action = await onMissing(sourceStr);
532
+ if (action === "skip")
533
+ return false;
534
+ if (action === "error")
535
+ throw new Error(`Missing source: ${sourceStr}`);
536
+ await this.installParsedSource(parsed, scope);
537
+ return true;
538
+ };
539
+ if (parsed.type === "npm") {
540
+ const installedPath = this.getNpmInstallPath(parsed, scope);
541
+ const needsInstall = !existsSync(installedPath) || (await this.npmNeedsUpdate(parsed, installedPath));
542
+ if (needsInstall) {
543
+ const installed = await installMissing();
544
+ if (!installed)
545
+ continue;
546
+ }
547
+ metadata.baseDir = installedPath;
548
+ this.collectPackageResources(installedPath, accumulator, filter, metadata);
549
+ continue;
550
+ }
551
+ if (parsed.type === "git") {
552
+ const installedPath = this.getGitInstallPath(parsed, scope);
553
+ if (!existsSync(installedPath)) {
554
+ const installed = await installMissing();
555
+ if (!installed)
556
+ continue;
557
+ }
558
+ metadata.baseDir = installedPath;
559
+ this.collectPackageResources(installedPath, accumulator, filter, metadata);
560
+ }
561
+ }
562
+ }
563
+ resolveLocalExtensionSource(source, accumulator, filter, metadata) {
564
+ const resolved = this.resolvePath(source.path);
565
+ if (!existsSync(resolved)) {
566
+ return;
567
+ }
568
+ try {
569
+ const stats = statSync(resolved);
570
+ if (stats.isFile()) {
571
+ metadata.baseDir = dirname(resolved);
572
+ this.addResource(accumulator.extensions, resolved, metadata, true);
573
+ return;
574
+ }
575
+ if (stats.isDirectory()) {
576
+ metadata.baseDir = resolved;
577
+ const resources = this.collectPackageResources(resolved, accumulator, filter, metadata);
578
+ if (!resources) {
579
+ this.addResource(accumulator.extensions, resolved, metadata, true);
580
+ }
581
+ }
582
+ }
583
+ catch {
584
+ return;
585
+ }
586
+ }
587
+ async installParsedSource(parsed, scope) {
588
+ if (parsed.type === "npm") {
589
+ await this.installNpm(parsed, scope, scope === "temporary");
590
+ return;
591
+ }
592
+ if (parsed.type === "git") {
593
+ await this.installGit(parsed, scope);
594
+ return;
595
+ }
596
+ }
597
+ parseSource(source) {
598
+ if (source.startsWith("npm:")) {
599
+ const spec = source.slice("npm:".length).trim();
600
+ const { name, version } = this.parseNpmSpec(spec);
601
+ return {
602
+ type: "npm",
603
+ spec,
604
+ name,
605
+ pinned: Boolean(version),
606
+ };
607
+ }
608
+ if (source.startsWith("git:") || looksLikeGitUrl(source)) {
609
+ const repoSpec = source.startsWith("git:") ? source.slice("git:".length).trim() : source;
610
+ const [repo, ref] = repoSpec.split("@");
611
+ const normalized = repo.replace(/^https?:\/\//, "").replace(/\.git$/, "");
612
+ const parts = normalized.split("/");
613
+ const host = parts.shift() ?? "";
614
+ const repoPath = parts.join("/");
615
+ return {
616
+ type: "git",
617
+ repo: normalized,
618
+ host,
619
+ path: repoPath,
620
+ ref,
621
+ pinned: Boolean(ref),
622
+ };
623
+ }
624
+ return { type: "local", path: source };
625
+ }
626
+ /**
627
+ * Check if an npm package needs to be updated.
628
+ * - For unpinned packages: check if registry has a newer version
629
+ * - For pinned packages: check if installed version matches the pinned version
630
+ */
631
+ async npmNeedsUpdate(source, installedPath) {
632
+ const installedVersion = this.getInstalledNpmVersion(installedPath);
633
+ if (!installedVersion)
634
+ return true;
635
+ const { version: pinnedVersion } = this.parseNpmSpec(source.spec);
636
+ if (pinnedVersion) {
637
+ // Pinned: check if installed matches pinned (exact match for now)
638
+ return installedVersion !== pinnedVersion;
639
+ }
640
+ // Unpinned: check registry for latest version
641
+ try {
642
+ const latestVersion = await this.getLatestNpmVersion(source.name);
643
+ return latestVersion !== installedVersion;
644
+ }
645
+ catch {
646
+ // If we can't check registry, assume it's fine
647
+ return false;
648
+ }
649
+ }
650
+ getInstalledNpmVersion(installedPath) {
651
+ const packageJsonPath = join(installedPath, "package.json");
652
+ if (!existsSync(packageJsonPath))
653
+ return undefined;
654
+ try {
655
+ const content = readFileSync(packageJsonPath, "utf-8");
656
+ const pkg = JSON.parse(content);
657
+ return pkg.version;
658
+ }
659
+ catch {
660
+ return undefined;
661
+ }
662
+ }
663
+ async getLatestNpmVersion(packageName) {
664
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
665
+ if (!response.ok)
666
+ throw new Error(`Failed to fetch npm registry: ${response.status}`);
667
+ const data = (await response.json());
668
+ return data.version;
669
+ }
670
+ /**
671
+ * Get a unique identity for a package, ignoring version/ref.
672
+ * Used to detect when the same package is in both global and project settings.
673
+ */
674
+ getPackageIdentity(source) {
675
+ const parsed = this.parseSource(source);
676
+ if (parsed.type === "npm") {
677
+ return `npm:${parsed.name}`;
678
+ }
679
+ if (parsed.type === "git") {
680
+ return `git:${parsed.repo}`;
681
+ }
682
+ // For local paths, use the absolute resolved path
683
+ return `local:${this.resolvePath(parsed.path)}`;
684
+ }
685
+ /**
686
+ * Dedupe packages: if same package identity appears in both global and project,
687
+ * keep only the project one (project wins).
688
+ */
689
+ dedupePackages(packages) {
690
+ const seen = new Map();
691
+ for (const entry of packages) {
692
+ const sourceStr = typeof entry.pkg === "string" ? entry.pkg : entry.pkg.source;
693
+ const identity = this.getPackageIdentity(sourceStr);
694
+ const existing = seen.get(identity);
695
+ if (!existing) {
696
+ seen.set(identity, entry);
697
+ }
698
+ else if (entry.scope === "project" && existing.scope === "user") {
699
+ // Project wins over user
700
+ seen.set(identity, entry);
701
+ }
702
+ // If existing is project and new is global, keep existing (project)
703
+ // If both are same scope, keep first one
704
+ }
705
+ return Array.from(seen.values());
706
+ }
707
+ parseNpmSpec(spec) {
708
+ const match = spec.match(/^(@?[^@]+(?:\/[^@]+)?)(?:@(.+))?$/);
709
+ if (!match) {
710
+ return { name: spec };
711
+ }
712
+ const name = match[1] ?? spec;
713
+ const version = match[2];
714
+ return { name, version };
715
+ }
716
+ async installNpm(source, scope, temporary) {
717
+ if (scope === "user" && !temporary) {
718
+ await this.runCommand("npm", ["install", "-g", source.spec]);
719
+ return;
720
+ }
721
+ const installRoot = this.getNpmInstallRoot(scope, temporary);
722
+ this.ensureNpmProject(installRoot);
723
+ await this.runCommand("npm", ["install", source.spec, "--prefix", installRoot]);
724
+ }
725
+ async uninstallNpm(source, scope) {
726
+ if (scope === "user") {
727
+ await this.runCommand("npm", ["uninstall", "-g", source.name]);
728
+ return;
729
+ }
730
+ const installRoot = this.getNpmInstallRoot(scope, false);
731
+ if (!existsSync(installRoot)) {
732
+ return;
733
+ }
734
+ await this.runCommand("npm", ["uninstall", source.name, "--prefix", installRoot]);
735
+ }
736
+ async installGit(source, scope) {
737
+ const targetDir = this.getGitInstallPath(source, scope);
738
+ if (existsSync(targetDir)) {
739
+ return;
740
+ }
741
+ const gitRoot = this.getGitInstallRoot(scope);
742
+ if (gitRoot) {
743
+ this.ensureGitIgnore(gitRoot);
744
+ }
745
+ mkdirSync(dirname(targetDir), { recursive: true });
746
+ const cloneUrl = source.repo.startsWith("http") ? source.repo : `https://${source.repo}`;
747
+ await this.runCommand("git", ["clone", cloneUrl, targetDir]);
748
+ if (source.ref) {
749
+ await this.runCommand("git", ["checkout", source.ref], { cwd: targetDir });
750
+ }
751
+ const packageJsonPath = join(targetDir, "package.json");
752
+ if (existsSync(packageJsonPath)) {
753
+ await this.runCommand("npm", ["install"], { cwd: targetDir });
754
+ }
755
+ }
756
+ async updateGit(source, scope) {
757
+ const targetDir = this.getGitInstallPath(source, scope);
758
+ if (!existsSync(targetDir)) {
759
+ await this.installGit(source, scope);
760
+ return;
761
+ }
762
+ // Fetch latest from remote (handles force-push by getting new history)
763
+ await this.runCommand("git", ["fetch", "--prune", "origin"], { cwd: targetDir });
764
+ // Reset to upstream tracking branch (handles force-push gracefully)
765
+ await this.runCommand("git", ["reset", "--hard", "@{upstream}"], { cwd: targetDir });
766
+ // Clean untracked files (extensions should be pristine)
767
+ await this.runCommand("git", ["clean", "-fdx"], { cwd: targetDir });
768
+ const packageJsonPath = join(targetDir, "package.json");
769
+ if (existsSync(packageJsonPath)) {
770
+ await this.runCommand("npm", ["install"], { cwd: targetDir });
771
+ }
772
+ }
773
+ async removeGit(source, scope) {
774
+ const targetDir = this.getGitInstallPath(source, scope);
775
+ if (!existsSync(targetDir))
776
+ return;
777
+ rmSync(targetDir, { recursive: true, force: true });
778
+ this.pruneEmptyGitParents(targetDir, this.getGitInstallRoot(scope));
779
+ }
780
+ pruneEmptyGitParents(targetDir, installRoot) {
781
+ if (!installRoot)
782
+ return;
783
+ const resolvedRoot = resolve(installRoot);
784
+ let current = dirname(targetDir);
785
+ while (current.startsWith(resolvedRoot) && current !== resolvedRoot) {
786
+ if (!existsSync(current)) {
787
+ current = dirname(current);
788
+ continue;
789
+ }
790
+ const entries = readdirSync(current);
791
+ if (entries.length > 0) {
792
+ break;
793
+ }
794
+ try {
795
+ rmSync(current, { recursive: true, force: true });
796
+ }
797
+ catch {
798
+ break;
799
+ }
800
+ current = dirname(current);
801
+ }
802
+ }
803
+ ensureNpmProject(installRoot) {
804
+ if (!existsSync(installRoot)) {
805
+ mkdirSync(installRoot, { recursive: true });
806
+ }
807
+ this.ensureGitIgnore(installRoot);
808
+ const packageJsonPath = join(installRoot, "package.json");
809
+ if (!existsSync(packageJsonPath)) {
810
+ const pkgJson = { name: "pi-extensions", private: true };
811
+ writeFileSync(packageJsonPath, JSON.stringify(pkgJson, null, 2), "utf-8");
812
+ }
813
+ }
814
+ ensureGitIgnore(dir) {
815
+ if (!existsSync(dir)) {
816
+ mkdirSync(dir, { recursive: true });
817
+ }
818
+ const ignorePath = join(dir, ".gitignore");
819
+ if (!existsSync(ignorePath)) {
820
+ writeFileSync(ignorePath, "*\n!.gitignore\n", "utf-8");
821
+ }
822
+ }
823
+ getNpmInstallRoot(scope, temporary) {
824
+ if (temporary) {
825
+ return this.getTemporaryDir("npm");
826
+ }
827
+ if (scope === "project") {
828
+ return join(this.cwd, CONFIG_DIR_NAME, "npm");
829
+ }
830
+ return join(this.getGlobalNpmRoot(), "..");
831
+ }
832
+ getGlobalNpmRoot() {
833
+ if (this.globalNpmRoot) {
834
+ return this.globalNpmRoot;
835
+ }
836
+ const result = this.runCommandSync("npm", ["root", "-g"]);
837
+ this.globalNpmRoot = result.trim();
838
+ return this.globalNpmRoot;
839
+ }
840
+ getNpmInstallPath(source, scope) {
841
+ if (scope === "temporary") {
842
+ return join(this.getTemporaryDir("npm"), "node_modules", source.name);
843
+ }
844
+ if (scope === "project") {
845
+ return join(this.cwd, CONFIG_DIR_NAME, "npm", "node_modules", source.name);
846
+ }
847
+ return join(this.getGlobalNpmRoot(), source.name);
848
+ }
849
+ getGitInstallPath(source, scope) {
850
+ if (scope === "temporary") {
851
+ return this.getTemporaryDir(`git-${source.host}`, source.path);
852
+ }
853
+ if (scope === "project") {
854
+ return join(this.cwd, CONFIG_DIR_NAME, "git", source.host, source.path);
855
+ }
856
+ return join(this.agentDir, "git", source.host, source.path);
857
+ }
858
+ getGitInstallRoot(scope) {
859
+ if (scope === "temporary") {
860
+ return undefined;
861
+ }
862
+ if (scope === "project") {
863
+ return join(this.cwd, CONFIG_DIR_NAME, "git");
864
+ }
865
+ return join(this.agentDir, "git");
866
+ }
867
+ getTemporaryDir(prefix, suffix) {
868
+ const hash = createHash("sha256")
869
+ .update(`${prefix}-${suffix ?? ""}`)
870
+ .digest("hex")
871
+ .slice(0, 8);
872
+ return join(tmpdir(), "pi-extensions", prefix, hash, suffix ?? "");
873
+ }
874
+ resolvePath(input) {
875
+ const trimmed = input.trim();
876
+ if (trimmed === "~")
877
+ return homedir();
878
+ if (trimmed.startsWith("~/"))
879
+ return join(homedir(), trimmed.slice(2));
880
+ if (trimmed.startsWith("~"))
881
+ return join(homedir(), trimmed.slice(1));
882
+ return resolve(this.cwd, trimmed);
883
+ }
884
+ resolvePathFromBase(input, baseDir) {
885
+ const trimmed = input.trim();
886
+ if (trimmed === "~")
887
+ return homedir();
888
+ if (trimmed.startsWith("~/"))
889
+ return join(homedir(), trimmed.slice(2));
890
+ if (trimmed.startsWith("~"))
891
+ return join(homedir(), trimmed.slice(1));
892
+ return resolve(baseDir, trimmed);
893
+ }
894
+ collectPackageResources(packageRoot, accumulator, filter, metadata) {
895
+ if (filter) {
896
+ for (const resourceType of RESOURCE_TYPES) {
897
+ const patterns = filter[resourceType];
898
+ const target = this.getTargetMap(accumulator, resourceType);
899
+ if (patterns !== undefined) {
900
+ this.applyPackageFilter(packageRoot, patterns, resourceType, target, metadata);
901
+ }
902
+ else {
903
+ this.collectDefaultResources(packageRoot, resourceType, target, metadata);
904
+ }
905
+ }
906
+ return true;
907
+ }
908
+ const manifest = this.readPiManifest(packageRoot);
909
+ if (manifest) {
910
+ for (const resourceType of RESOURCE_TYPES) {
911
+ const entries = manifest[resourceType];
912
+ this.addManifestEntries(entries, packageRoot, resourceType, this.getTargetMap(accumulator, resourceType), metadata);
913
+ }
914
+ return true;
915
+ }
916
+ let hasAnyDir = false;
917
+ for (const resourceType of RESOURCE_TYPES) {
918
+ const dir = join(packageRoot, resourceType);
919
+ if (existsSync(dir)) {
920
+ // Collect all files from the directory (all enabled by default)
921
+ const files = resourceType === "skills" ? collectSkillEntries(dir) : collectFiles(dir, FILE_PATTERNS[resourceType]);
922
+ for (const f of files) {
923
+ this.addResource(this.getTargetMap(accumulator, resourceType), f, metadata, true);
924
+ }
925
+ hasAnyDir = true;
926
+ }
927
+ }
928
+ return hasAnyDir;
929
+ }
930
+ collectDefaultResources(packageRoot, resourceType, target, metadata) {
931
+ const manifest = this.readPiManifest(packageRoot);
932
+ const entries = manifest?.[resourceType];
933
+ if (entries) {
934
+ this.addManifestEntries(entries, packageRoot, resourceType, target, metadata);
935
+ return;
936
+ }
937
+ const dir = join(packageRoot, resourceType);
938
+ if (existsSync(dir)) {
939
+ // Collect all files from the directory (all enabled by default)
940
+ const files = resourceType === "skills" ? collectSkillEntries(dir) : collectFiles(dir, FILE_PATTERNS[resourceType]);
941
+ for (const f of files) {
942
+ this.addResource(target, f, metadata, true);
943
+ }
944
+ }
945
+ }
946
+ applyPackageFilter(packageRoot, userPatterns, resourceType, target, metadata) {
947
+ const { allFiles, enabledByManifest } = this.collectManifestFiles(packageRoot, resourceType);
948
+ if (userPatterns.length === 0) {
949
+ // No user patterns, just use manifest filtering
950
+ for (const f of allFiles) {
951
+ this.addResource(target, f, metadata, enabledByManifest.has(f));
952
+ }
953
+ return;
954
+ }
955
+ // Apply user patterns on top of manifest-enabled files
956
+ const enabledByUser = applyPatterns(allFiles, userPatterns, packageRoot);
957
+ for (const f of allFiles) {
958
+ const enabled = enabledByUser.has(f);
959
+ this.addResource(target, f, metadata, enabled);
960
+ }
961
+ }
962
+ /**
963
+ * Collect all files from a package for a resource type, applying manifest patterns.
964
+ * Returns { allFiles, enabledByManifest } where enabledByManifest is the set of files
965
+ * that pass the manifest's own patterns.
966
+ */
967
+ collectManifestFiles(packageRoot, resourceType) {
968
+ const manifest = this.readPiManifest(packageRoot);
969
+ const entries = manifest?.[resourceType];
970
+ if (entries && entries.length > 0) {
971
+ const allFiles = this.collectFilesFromManifestEntries(entries, packageRoot, resourceType);
972
+ const manifestPatterns = entries.filter(isPattern);
973
+ const enabledByManifest = manifestPatterns.length > 0 ? applyPatterns(allFiles, manifestPatterns, packageRoot) : new Set(allFiles);
974
+ return { allFiles: Array.from(enabledByManifest), enabledByManifest };
975
+ }
976
+ const conventionDir = join(packageRoot, resourceType);
977
+ if (!existsSync(conventionDir)) {
978
+ return { allFiles: [], enabledByManifest: new Set() };
979
+ }
980
+ const allFiles = resourceType === "skills"
981
+ ? collectSkillEntries(conventionDir)
982
+ : collectFiles(conventionDir, FILE_PATTERNS[resourceType]);
983
+ return { allFiles, enabledByManifest: new Set(allFiles) };
984
+ }
985
+ readPiManifest(packageRoot) {
986
+ const packageJsonPath = join(packageRoot, "package.json");
987
+ if (!existsSync(packageJsonPath)) {
988
+ return null;
989
+ }
990
+ try {
991
+ const content = readFileSync(packageJsonPath, "utf-8");
992
+ const pkg = JSON.parse(content);
993
+ return pkg.pi ?? null;
994
+ }
995
+ catch {
996
+ return null;
997
+ }
998
+ }
999
+ addManifestEntries(entries, root, resourceType, target, metadata) {
1000
+ if (!entries)
1001
+ return;
1002
+ const allFiles = this.collectFilesFromManifestEntries(entries, root, resourceType);
1003
+ const patterns = entries.filter(isPattern);
1004
+ const enabledPaths = applyPatterns(allFiles, patterns, root);
1005
+ for (const f of allFiles) {
1006
+ if (enabledPaths.has(f)) {
1007
+ this.addResource(target, f, metadata, true);
1008
+ }
1009
+ }
1010
+ }
1011
+ collectFilesFromManifestEntries(entries, root, resourceType) {
1012
+ const plain = entries.filter((entry) => !isPattern(entry));
1013
+ const resolved = plain.map((entry) => resolve(root, entry));
1014
+ return this.collectFilesFromPaths(resolved, resourceType);
1015
+ }
1016
+ resolveLocalEntries(entries, resourceType, target, metadata, baseDir) {
1017
+ if (entries.length === 0)
1018
+ return;
1019
+ // Collect all files from plain entries (non-pattern entries)
1020
+ const { plain, patterns } = splitPatterns(entries);
1021
+ const resolvedPlain = plain.map((p) => this.resolvePathFromBase(p, baseDir));
1022
+ const allFiles = this.collectFilesFromPaths(resolvedPlain, resourceType);
1023
+ // Determine which files are enabled based on patterns
1024
+ const enabledPaths = applyPatterns(allFiles, patterns, baseDir);
1025
+ // Add all files with their enabled state
1026
+ for (const f of allFiles) {
1027
+ this.addResource(target, f, metadata, enabledPaths.has(f));
1028
+ }
1029
+ }
1030
+ addAutoDiscoveredResources(accumulator, globalSettings, projectSettings, globalBaseDir, projectBaseDir) {
1031
+ const userMetadata = {
1032
+ source: "auto",
1033
+ scope: "user",
1034
+ origin: "top-level",
1035
+ baseDir: globalBaseDir,
1036
+ };
1037
+ const projectMetadata = {
1038
+ source: "auto",
1039
+ scope: "project",
1040
+ origin: "top-level",
1041
+ baseDir: projectBaseDir,
1042
+ };
1043
+ const userOverrides = {
1044
+ extensions: (globalSettings.extensions ?? []),
1045
+ skills: (globalSettings.skills ?? []),
1046
+ prompts: (globalSettings.prompts ?? []),
1047
+ themes: (globalSettings.themes ?? []),
1048
+ };
1049
+ const projectOverrides = {
1050
+ extensions: (projectSettings.extensions ?? []),
1051
+ skills: (projectSettings.skills ?? []),
1052
+ prompts: (projectSettings.prompts ?? []),
1053
+ themes: (projectSettings.themes ?? []),
1054
+ };
1055
+ const userDirs = {
1056
+ extensions: join(globalBaseDir, "extensions"),
1057
+ skills: join(globalBaseDir, "skills"),
1058
+ prompts: join(globalBaseDir, "prompts"),
1059
+ themes: join(globalBaseDir, "themes"),
1060
+ };
1061
+ const projectDirs = {
1062
+ extensions: join(projectBaseDir, "extensions"),
1063
+ skills: join(projectBaseDir, "skills"),
1064
+ prompts: join(projectBaseDir, "prompts"),
1065
+ themes: join(projectBaseDir, "themes"),
1066
+ };
1067
+ const addResources = (resourceType, paths, metadata, overrides, baseDir) => {
1068
+ const target = this.getTargetMap(accumulator, resourceType);
1069
+ for (const path of paths) {
1070
+ const enabled = isEnabledByOverrides(path, overrides, baseDir);
1071
+ this.addResource(target, path, metadata, enabled);
1072
+ }
1073
+ };
1074
+ addResources("extensions", collectAutoExtensionEntries(userDirs.extensions), userMetadata, userOverrides.extensions, globalBaseDir);
1075
+ addResources("skills", collectAutoSkillEntries(userDirs.skills), userMetadata, userOverrides.skills, globalBaseDir);
1076
+ addResources("prompts", collectAutoPromptEntries(userDirs.prompts), userMetadata, userOverrides.prompts, globalBaseDir);
1077
+ addResources("themes", collectAutoThemeEntries(userDirs.themes), userMetadata, userOverrides.themes, globalBaseDir);
1078
+ addResources("extensions", collectAutoExtensionEntries(projectDirs.extensions), projectMetadata, projectOverrides.extensions, projectBaseDir);
1079
+ addResources("skills", collectAutoSkillEntries(projectDirs.skills), projectMetadata, projectOverrides.skills, projectBaseDir);
1080
+ addResources("prompts", collectAutoPromptEntries(projectDirs.prompts), projectMetadata, projectOverrides.prompts, projectBaseDir);
1081
+ addResources("themes", collectAutoThemeEntries(projectDirs.themes), projectMetadata, projectOverrides.themes, projectBaseDir);
1082
+ }
1083
+ collectFilesFromPaths(paths, resourceType) {
1084
+ const files = [];
1085
+ for (const p of paths) {
1086
+ if (!existsSync(p))
1087
+ continue;
1088
+ try {
1089
+ const stats = statSync(p);
1090
+ if (stats.isFile()) {
1091
+ files.push(p);
1092
+ }
1093
+ else if (stats.isDirectory()) {
1094
+ if (resourceType === "skills") {
1095
+ files.push(...collectSkillEntries(p));
1096
+ }
1097
+ else {
1098
+ files.push(...collectFiles(p, FILE_PATTERNS[resourceType]));
1099
+ }
1100
+ }
1101
+ }
1102
+ catch {
1103
+ // Ignore errors
1104
+ }
1105
+ }
1106
+ return files;
1107
+ }
1108
+ getTargetMap(accumulator, resourceType) {
1109
+ switch (resourceType) {
1110
+ case "extensions":
1111
+ return accumulator.extensions;
1112
+ case "skills":
1113
+ return accumulator.skills;
1114
+ case "prompts":
1115
+ return accumulator.prompts;
1116
+ case "themes":
1117
+ return accumulator.themes;
1118
+ default:
1119
+ throw new Error(`Unknown resource type: ${resourceType}`);
1120
+ }
1121
+ }
1122
+ addResource(map, path, metadata, enabled) {
1123
+ if (!path)
1124
+ return;
1125
+ if (!map.has(path)) {
1126
+ map.set(path, { metadata, enabled });
1127
+ }
1128
+ }
1129
+ createAccumulator() {
1130
+ return {
1131
+ extensions: new Map(),
1132
+ skills: new Map(),
1133
+ prompts: new Map(),
1134
+ themes: new Map(),
1135
+ };
1136
+ }
1137
+ toResolvedPaths(accumulator) {
1138
+ const toResolved = (entries) => {
1139
+ return Array.from(entries.entries()).map(([path, { metadata, enabled }]) => ({
1140
+ path,
1141
+ enabled,
1142
+ metadata,
1143
+ }));
1144
+ };
1145
+ return {
1146
+ extensions: toResolved(accumulator.extensions),
1147
+ skills: toResolved(accumulator.skills),
1148
+ prompts: toResolved(accumulator.prompts),
1149
+ themes: toResolved(accumulator.themes),
1150
+ };
1151
+ }
1152
+ runCommand(command, args, options) {
1153
+ return new Promise((resolvePromise, reject) => {
1154
+ const child = spawn(command, args, {
1155
+ cwd: options?.cwd,
1156
+ stdio: "inherit",
1157
+ });
1158
+ child.on("error", reject);
1159
+ child.on("exit", (code) => {
1160
+ if (code === 0) {
1161
+ resolvePromise();
1162
+ }
1163
+ else {
1164
+ reject(new Error(`${command} ${args.join(" ")} failed with code ${code}`));
1165
+ }
1166
+ });
1167
+ });
1168
+ }
1169
+ runCommandSync(command, args) {
1170
+ const result = spawnSync(command, args, { stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" });
1171
+ if (result.status !== 0) {
1172
+ throw new Error(`Failed to run ${command} ${args.join(" ")}: ${result.stderr || result.stdout}`);
1173
+ }
1174
+ return (result.stdout || result.stderr || "").trim();
1175
+ }
1176
+ }
1177
+ //# sourceMappingURL=package-manager.js.map