@codex-infinity/pi-infinity 0.52.4 → 0.60.2

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 (269) hide show
  1. package/CHANGELOG.md +387 -0
  2. package/README.md +97 -66
  3. package/dist/bun/cli.d.ts +3 -0
  4. package/dist/bun/cli.d.ts.map +1 -0
  5. package/dist/bun/cli.js +6 -0
  6. package/dist/bun/cli.js.map +1 -0
  7. package/dist/bun/register-bedrock.d.ts +2 -0
  8. package/dist/bun/register-bedrock.d.ts.map +1 -0
  9. package/dist/bun/register-bedrock.js +4 -0
  10. package/dist/bun/register-bedrock.js.map +1 -0
  11. package/dist/cli/args.d.ts +2 -0
  12. package/dist/cli/args.d.ts.map +1 -1
  13. package/dist/cli/args.js +17 -6
  14. package/dist/cli/args.js.map +1 -1
  15. package/dist/cli/initial-message.d.ts +18 -0
  16. package/dist/cli/initial-message.d.ts.map +1 -0
  17. package/dist/cli/initial-message.js +22 -0
  18. package/dist/cli/initial-message.js.map +1 -0
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +2 -0
  21. package/dist/cli.js.map +1 -1
  22. package/dist/core/agent-session.d.ts +42 -6
  23. package/dist/core/agent-session.d.ts.map +1 -1
  24. package/dist/core/agent-session.js +346 -72
  25. package/dist/core/agent-session.js.map +1 -1
  26. package/dist/core/auth-storage.d.ts +1 -0
  27. package/dist/core/auth-storage.d.ts.map +1 -1
  28. package/dist/core/auth-storage.js +27 -2
  29. package/dist/core/auth-storage.js.map +1 -1
  30. package/dist/core/bash-executor.d.ts +6 -7
  31. package/dist/core/bash-executor.d.ts.map +1 -1
  32. package/dist/core/bash-executor.js +8 -107
  33. package/dist/core/bash-executor.js.map +1 -1
  34. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  35. package/dist/core/compaction/branch-summarization.js +1 -0
  36. package/dist/core/compaction/branch-summarization.js.map +1 -1
  37. package/dist/core/compaction/compaction.d.ts.map +1 -1
  38. package/dist/core/compaction/compaction.js +6 -1
  39. package/dist/core/compaction/compaction.js.map +1 -1
  40. package/dist/core/compaction/utils.d.ts +3 -0
  41. package/dist/core/compaction/utils.d.ts.map +1 -1
  42. package/dist/core/compaction/utils.js +16 -1
  43. package/dist/core/compaction/utils.js.map +1 -1
  44. package/dist/core/exec.d.ts.map +1 -1
  45. package/dist/core/exec.js +7 -3
  46. package/dist/core/exec.js.map +1 -1
  47. package/dist/core/export-html/index.d.ts +5 -2
  48. package/dist/core/export-html/index.d.ts.map +1 -1
  49. package/dist/core/export-html/index.js +4 -3
  50. package/dist/core/export-html/index.js.map +1 -1
  51. package/dist/core/export-html/template.js +11 -14
  52. package/dist/core/export-html/tool-renderer.d.ts +5 -2
  53. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  54. package/dist/core/export-html/tool-renderer.js +17 -4
  55. package/dist/core/export-html/tool-renderer.js.map +1 -1
  56. package/dist/core/extensions/index.d.ts +2 -2
  57. package/dist/core/extensions/index.d.ts.map +1 -1
  58. package/dist/core/extensions/index.js +1 -1
  59. package/dist/core/extensions/index.js.map +1 -1
  60. package/dist/core/extensions/loader.d.ts.map +1 -1
  61. package/dist/core/extensions/loader.js +37 -11
  62. package/dist/core/extensions/loader.js.map +1 -1
  63. package/dist/core/extensions/runner.d.ts +8 -4
  64. package/dist/core/extensions/runner.d.ts.map +1 -1
  65. package/dist/core/extensions/runner.js +77 -8
  66. package/dist/core/extensions/runner.js.map +1 -1
  67. package/dist/core/extensions/types.d.ts +56 -4
  68. package/dist/core/extensions/types.d.ts.map +1 -1
  69. package/dist/core/extensions/types.js.map +1 -1
  70. package/dist/core/extensions/wrapper.d.ts +4 -11
  71. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  72. package/dist/core/extensions/wrapper.js +4 -78
  73. package/dist/core/extensions/wrapper.js.map +1 -1
  74. package/dist/core/footer-data-provider.d.ts +6 -1
  75. package/dist/core/footer-data-provider.d.ts.map +1 -1
  76. package/dist/core/footer-data-provider.js +83 -37
  77. package/dist/core/footer-data-provider.js.map +1 -1
  78. package/dist/core/index.d.ts +1 -1
  79. package/dist/core/index.d.ts.map +1 -1
  80. package/dist/core/index.js +1 -1
  81. package/dist/core/index.js.map +1 -1
  82. package/dist/core/keybindings.d.ts +3 -0
  83. package/dist/core/keybindings.d.ts.map +1 -1
  84. package/dist/core/keybindings.js +22 -12
  85. package/dist/core/keybindings.js.map +1 -1
  86. package/dist/core/model-registry.d.ts +11 -0
  87. package/dist/core/model-registry.d.ts.map +1 -1
  88. package/dist/core/model-registry.js +56 -16
  89. package/dist/core/model-registry.js.map +1 -1
  90. package/dist/core/model-resolver.d.ts +6 -0
  91. package/dist/core/model-resolver.d.ts.map +1 -1
  92. package/dist/core/model-resolver.js +122 -39
  93. package/dist/core/model-resolver.js.map +1 -1
  94. package/dist/core/package-manager.d.ts +19 -1
  95. package/dist/core/package-manager.d.ts.map +1 -1
  96. package/dist/core/package-manager.js +290 -57
  97. package/dist/core/package-manager.js.map +1 -1
  98. package/dist/core/resolve-config-value.d.ts.map +1 -1
  99. package/dist/core/resolve-config-value.js +43 -8
  100. package/dist/core/resolve-config-value.js.map +1 -1
  101. package/dist/core/resource-loader.d.ts.map +1 -1
  102. package/dist/core/resource-loader.js +4 -7
  103. package/dist/core/resource-loader.js.map +1 -1
  104. package/dist/core/sdk.d.ts +1 -1
  105. package/dist/core/sdk.d.ts.map +1 -1
  106. package/dist/core/sdk.js +7 -0
  107. package/dist/core/sdk.js.map +1 -1
  108. package/dist/core/session-manager.d.ts +1 -0
  109. package/dist/core/session-manager.d.ts.map +1 -1
  110. package/dist/core/session-manager.js +21 -15
  111. package/dist/core/session-manager.js.map +1 -1
  112. package/dist/core/settings-manager.d.ts +10 -0
  113. package/dist/core/settings-manager.d.ts.map +1 -1
  114. package/dist/core/settings-manager.js +59 -5
  115. package/dist/core/settings-manager.js.map +1 -1
  116. package/dist/core/skills.d.ts +3 -2
  117. package/dist/core/skills.d.ts.map +1 -1
  118. package/dist/core/skills.js +29 -8
  119. package/dist/core/skills.js.map +1 -1
  120. package/dist/core/slash-commands.d.ts.map +1 -1
  121. package/dist/core/slash-commands.js +3 -2
  122. package/dist/core/slash-commands.js.map +1 -1
  123. package/dist/core/system-prompt.d.ts +4 -0
  124. package/dist/core/system-prompt.d.ts.map +1 -1
  125. package/dist/core/system-prompt.js +43 -29
  126. package/dist/core/system-prompt.js.map +1 -1
  127. package/dist/core/tools/bash.d.ts +8 -0
  128. package/dist/core/tools/bash.d.ts.map +1 -1
  129. package/dist/core/tools/bash.js +77 -69
  130. package/dist/core/tools/bash.js.map +1 -1
  131. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  132. package/dist/core/tools/edit-diff.js +1 -0
  133. package/dist/core/tools/edit-diff.js.map +1 -1
  134. package/dist/core/tools/find.d.ts.map +1 -1
  135. package/dist/core/tools/find.js +6 -3
  136. package/dist/core/tools/find.js.map +1 -1
  137. package/dist/core/tools/index.d.ts +1 -1
  138. package/dist/core/tools/index.d.ts.map +1 -1
  139. package/dist/core/tools/index.js +1 -1
  140. package/dist/core/tools/index.js.map +1 -1
  141. package/dist/index.d.ts +3 -3
  142. package/dist/index.d.ts.map +1 -1
  143. package/dist/index.js +2 -2
  144. package/dist/index.js.map +1 -1
  145. package/dist/main.d.ts.map +1 -1
  146. package/dist/main.js +116 -36
  147. package/dist/main.js.map +1 -1
  148. package/dist/modes/interactive/components/extension-editor.d.ts +5 -2
  149. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  150. package/dist/modes/interactive/components/extension-editor.js +9 -0
  151. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  152. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  153. package/dist/modes/interactive/components/footer.js +8 -23
  154. package/dist/modes/interactive/components/footer.js.map +1 -1
  155. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  156. package/dist/modes/interactive/components/login-dialog.js +1 -1
  157. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  158. package/dist/modes/interactive/components/model-selector.d.ts +1 -1
  159. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  160. package/dist/modes/interactive/components/model-selector.js +1 -1
  161. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  162. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  163. package/dist/modes/interactive/components/oauth-selector.js +1 -1
  164. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  165. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  166. package/dist/modes/interactive/components/session-selector.js +1 -1
  167. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  168. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  169. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  170. package/dist/modes/interactive/components/settings-selector.js +15 -1
  171. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  172. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
  173. package/dist/modes/interactive/components/show-images-selector.js +5 -1
  174. package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
  175. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
  176. package/dist/modes/interactive/components/theme-selector.js +5 -1
  177. package/dist/modes/interactive/components/theme-selector.js.map +1 -1
  178. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  179. package/dist/modes/interactive/components/thinking-selector.js +5 -1
  180. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  181. package/dist/modes/interactive/components/tool-execution.d.ts +7 -0
  182. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  183. package/dist/modes/interactive/components/tool-execution.js +158 -7
  184. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  185. package/dist/modes/interactive/components/tree-selector.d.ts +21 -2
  186. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  187. package/dist/modes/interactive/components/tree-selector.js +127 -10
  188. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  189. package/dist/modes/interactive/components/user-message.d.ts +1 -0
  190. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  191. package/dist/modes/interactive/components/user-message.js +12 -0
  192. package/dist/modes/interactive/components/user-message.js.map +1 -1
  193. package/dist/modes/interactive/interactive-mode.d.ts +5 -1
  194. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  195. package/dist/modes/interactive/interactive-mode.js +215 -71
  196. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  197. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  198. package/dist/modes/interactive/theme/theme.js +5 -0
  199. package/dist/modes/interactive/theme/theme.js.map +1 -1
  200. package/dist/modes/rpc/jsonl.d.ts +17 -0
  201. package/dist/modes/rpc/jsonl.d.ts.map +1 -0
  202. package/dist/modes/rpc/jsonl.js +49 -0
  203. package/dist/modes/rpc/jsonl.js.map +1 -0
  204. package/dist/modes/rpc/rpc-client.d.ts +1 -1
  205. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  206. package/dist/modes/rpc/rpc-client.js +7 -11
  207. package/dist/modes/rpc/rpc-client.js.map +1 -1
  208. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  209. package/dist/modes/rpc/rpc-mode.js +9 -11
  210. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  211. package/dist/utils/child-process.d.ts +11 -0
  212. package/dist/utils/child-process.d.ts.map +1 -0
  213. package/dist/utils/child-process.js +78 -0
  214. package/dist/utils/child-process.js.map +1 -0
  215. package/dist/utils/clipboard-image.d.ts.map +1 -1
  216. package/dist/utils/clipboard-image.js +94 -11
  217. package/dist/utils/clipboard-image.js.map +1 -1
  218. package/dist/utils/clipboard-native.d.ts +1 -0
  219. package/dist/utils/clipboard-native.d.ts.map +1 -1
  220. package/dist/utils/clipboard-native.js.map +1 -1
  221. package/dist/utils/clipboard.d.ts +1 -1
  222. package/dist/utils/clipboard.d.ts.map +1 -1
  223. package/dist/utils/clipboard.js +27 -16
  224. package/dist/utils/clipboard.js.map +1 -1
  225. package/dist/utils/exif-orientation.d.ts +5 -0
  226. package/dist/utils/exif-orientation.d.ts.map +1 -0
  227. package/dist/utils/exif-orientation.js +158 -0
  228. package/dist/utils/exif-orientation.js.map +1 -0
  229. package/dist/utils/image-convert.d.ts.map +1 -1
  230. package/dist/utils/image-convert.js +5 -1
  231. package/dist/utils/image-convert.js.map +1 -1
  232. package/dist/utils/image-resize.d.ts.map +1 -1
  233. package/dist/utils/image-resize.js +6 -1
  234. package/dist/utils/image-resize.js.map +1 -1
  235. package/dist/utils/tools-manager.d.ts.map +1 -1
  236. package/dist/utils/tools-manager.js +66 -21
  237. package/dist/utils/tools-manager.js.map +1 -1
  238. package/docs/compaction.md +2 -0
  239. package/docs/custom-provider.md +57 -9
  240. package/docs/extensions.md +125 -12
  241. package/docs/keybindings.md +11 -1
  242. package/docs/models.md +44 -2
  243. package/docs/packages.md +9 -0
  244. package/docs/providers.md +10 -1
  245. package/docs/rpc.md +44 -7
  246. package/docs/sdk.md +2 -2
  247. package/docs/settings.md +11 -0
  248. package/docs/terminal-setup.md +39 -3
  249. package/docs/tmux.md +61 -0
  250. package/docs/tree.md +9 -0
  251. package/examples/extensions/README.md +2 -0
  252. package/examples/extensions/antigravity-image-gen.ts +8 -5
  253. package/examples/extensions/built-in-tool-renderer.ts +246 -0
  254. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  255. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  256. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  257. package/examples/extensions/custom-provider-gitlab-duo/test.ts +2 -2
  258. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  259. package/examples/extensions/dynamic-tools.ts +74 -0
  260. package/examples/extensions/overlay-qa-tests.ts +468 -1
  261. package/examples/extensions/preset.ts +2 -3
  262. package/examples/extensions/provider-payload.ts +14 -0
  263. package/examples/extensions/sandbox/index.ts +2 -3
  264. package/examples/extensions/subagent/agents.ts +2 -3
  265. package/examples/extensions/tool-override.ts +2 -3
  266. package/examples/extensions/with-deps/index.ts +1 -5
  267. package/examples/extensions/with-deps/package-lock.json +2 -2
  268. package/examples/extensions/with-deps/package.json +1 -1
  269. package/package.json +10 -7
@@ -7,6 +7,14 @@ import ignore from "ignore";
7
7
  import { minimatch } from "minimatch";
8
8
  import { CONFIG_DIR_NAME } from "../config.js";
9
9
  import { parseGitUrl } from "../utils/git.js";
10
+ const NETWORK_TIMEOUT_MS = 10000;
11
+ const UPDATE_CHECK_CONCURRENCY = 4;
12
+ function isOfflineModeEnabled() {
13
+ const value = process.env.PI_OFFLINE;
14
+ if (!value)
15
+ return false;
16
+ return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
17
+ }
10
18
  const RESOURCE_TYPES = ["extensions", "skills", "prompts", "themes"];
11
19
  const FILE_PATTERNS = {
12
20
  extensions: /\.(ts|js)$/,
@@ -18,6 +26,9 @@ const IGNORE_FILE_NAMES = [".gitignore", ".ignore", ".fdignore"];
18
26
  function toPosixPath(p) {
19
27
  return p.split(sep).join("/");
20
28
  }
29
+ function getHomeDir() {
30
+ return process.env.HOME || homedir();
31
+ }
21
32
  function prefixIgnorePattern(line, prefix) {
22
33
  const trimmed = line.trim();
23
34
  if (!trimmed)
@@ -376,43 +387,50 @@ function collectResourceFiles(dir, resourceType) {
376
387
  return collectFiles(dir, FILE_PATTERNS[resourceType]);
377
388
  }
378
389
  function matchesAnyPattern(filePath, patterns, baseDir) {
379
- const rel = relative(baseDir, filePath);
390
+ const rel = toPosixPath(relative(baseDir, filePath));
380
391
  const name = basename(filePath);
392
+ const filePathPosix = toPosixPath(filePath);
381
393
  const isSkillFile = name === "SKILL.md";
382
394
  const parentDir = isSkillFile ? dirname(filePath) : undefined;
383
- const parentRel = isSkillFile ? relative(baseDir, parentDir) : undefined;
395
+ const parentRel = isSkillFile ? toPosixPath(relative(baseDir, parentDir)) : undefined;
384
396
  const parentName = isSkillFile ? basename(parentDir) : undefined;
397
+ const parentDirPosix = isSkillFile ? toPosixPath(parentDir) : undefined;
385
398
  return patterns.some((pattern) => {
386
- if (minimatch(rel, pattern) || minimatch(name, pattern) || minimatch(filePath, pattern)) {
399
+ const normalizedPattern = toPosixPath(pattern);
400
+ if (minimatch(rel, normalizedPattern) ||
401
+ minimatch(name, normalizedPattern) ||
402
+ minimatch(filePathPosix, normalizedPattern)) {
387
403
  return true;
388
404
  }
389
405
  if (!isSkillFile)
390
406
  return false;
391
- return minimatch(parentRel, pattern) || minimatch(parentName, pattern) || minimatch(parentDir, pattern);
407
+ return (minimatch(parentRel, normalizedPattern) ||
408
+ minimatch(parentName, normalizedPattern) ||
409
+ minimatch(parentDirPosix, normalizedPattern));
392
410
  });
393
411
  }
394
412
  function normalizeExactPattern(pattern) {
395
- if (pattern.startsWith("./") || pattern.startsWith(".\\")) {
396
- return pattern.slice(2);
397
- }
398
- return pattern;
413
+ const normalized = pattern.startsWith("./") || pattern.startsWith(".\\") ? pattern.slice(2) : pattern;
414
+ return toPosixPath(normalized);
399
415
  }
400
416
  function matchesAnyExactPattern(filePath, patterns, baseDir) {
401
417
  if (patterns.length === 0)
402
418
  return false;
403
- const rel = relative(baseDir, filePath);
419
+ const rel = toPosixPath(relative(baseDir, filePath));
404
420
  const name = basename(filePath);
421
+ const filePathPosix = toPosixPath(filePath);
405
422
  const isSkillFile = name === "SKILL.md";
406
423
  const parentDir = isSkillFile ? dirname(filePath) : undefined;
407
- const parentRel = isSkillFile ? relative(baseDir, parentDir) : undefined;
424
+ const parentRel = isSkillFile ? toPosixPath(relative(baseDir, parentDir)) : undefined;
425
+ const parentDirPosix = isSkillFile ? toPosixPath(parentDir) : undefined;
408
426
  return patterns.some((pattern) => {
409
427
  const normalized = normalizeExactPattern(pattern);
410
- if (normalized === rel || normalized === filePath) {
428
+ if (normalized === rel || normalized === filePathPosix) {
411
429
  return true;
412
430
  }
413
431
  if (!isSkillFile)
414
432
  return false;
415
- return normalized === parentRel || normalized === parentDir;
433
+ return normalized === parentRel || normalized === parentDirPosix;
416
434
  });
417
435
  }
418
436
  function getOverridePatterns(entries) {
@@ -493,6 +511,7 @@ export class DefaultPackageManager {
493
511
  agentDir;
494
512
  settingsManager;
495
513
  globalNpmRoot;
514
+ globalNpmRootCommandKey;
496
515
  progressCallback;
497
516
  constructor(options) {
498
517
  this.cwd = options.cwd;
@@ -573,14 +592,14 @@ export class DefaultPackageManager {
573
592
  const accumulator = this.createAccumulator();
574
593
  const globalSettings = this.settingsManager.getGlobalSettings();
575
594
  const projectSettings = this.settingsManager.getProjectSettings();
576
- // Collect all packages with scope
595
+ // Collect all packages with scope (project first so cwd resources win collisions)
577
596
  const allPackages = [];
578
- for (const pkg of globalSettings.packages ?? []) {
579
- allPackages.push({ pkg, scope: "user" });
580
- }
581
597
  for (const pkg of projectSettings.packages ?? []) {
582
598
  allPackages.push({ pkg, scope: "project" });
583
599
  }
600
+ for (const pkg of globalSettings.packages ?? []) {
601
+ allPackages.push({ pkg, scope: "user" });
602
+ }
584
603
  // Dedupe: project scope wins over global for same package identity
585
604
  const packageSources = this.dedupePackages(allPackages);
586
605
  await this.resolvePackageSources(packageSources, accumulator, onMissing);
@@ -590,16 +609,16 @@ export class DefaultPackageManager {
590
609
  const target = this.getTargetMap(accumulator, resourceType);
591
610
  const globalEntries = (globalSettings[resourceType] ?? []);
592
611
  const projectEntries = (projectSettings[resourceType] ?? []);
593
- this.resolveLocalEntries(globalEntries, resourceType, target, {
594
- source: "local",
595
- scope: "user",
596
- origin: "top-level",
597
- }, globalBaseDir);
598
612
  this.resolveLocalEntries(projectEntries, resourceType, target, {
599
613
  source: "local",
600
614
  scope: "project",
601
615
  origin: "top-level",
602
616
  }, projectBaseDir);
617
+ this.resolveLocalEntries(globalEntries, resourceType, target, {
618
+ source: "local",
619
+ scope: "user",
620
+ origin: "top-level",
621
+ }, globalBaseDir);
603
622
  }
604
623
  this.addAutoDiscoveredResources(accumulator, globalSettings, projectSettings, globalBaseDir, projectBaseDir);
605
624
  return this.toResolvedPaths(accumulator);
@@ -669,6 +688,9 @@ export class DefaultPackageManager {
669
688
  }
670
689
  }
671
690
  async updateSourceForScope(source, scope) {
691
+ if (isOfflineModeEnabled()) {
692
+ return;
693
+ }
672
694
  const parsed = this.parseSource(source);
673
695
  if (parsed.type === "npm") {
674
696
  if (parsed.pinned)
@@ -687,6 +709,62 @@ export class DefaultPackageManager {
687
709
  return;
688
710
  }
689
711
  }
712
+ async checkForAvailableUpdates() {
713
+ if (isOfflineModeEnabled()) {
714
+ return [];
715
+ }
716
+ const globalSettings = this.settingsManager.getGlobalSettings();
717
+ const projectSettings = this.settingsManager.getProjectSettings();
718
+ const allPackages = [];
719
+ for (const pkg of projectSettings.packages ?? []) {
720
+ allPackages.push({ pkg, scope: "project" });
721
+ }
722
+ for (const pkg of globalSettings.packages ?? []) {
723
+ allPackages.push({ pkg, scope: "user" });
724
+ }
725
+ const packageSources = this.dedupePackages(allPackages);
726
+ const checks = packageSources
727
+ .filter((entry) => entry.scope !== "temporary")
728
+ .map((entry) => async () => {
729
+ const source = typeof entry.pkg === "string" ? entry.pkg : entry.pkg.source;
730
+ const parsed = this.parseSource(source);
731
+ if (parsed.type === "local" || parsed.pinned) {
732
+ return undefined;
733
+ }
734
+ if (parsed.type === "npm") {
735
+ const installedPath = this.getNpmInstallPath(parsed, entry.scope);
736
+ if (!existsSync(installedPath)) {
737
+ return undefined;
738
+ }
739
+ const hasUpdate = await this.npmHasAvailableUpdate(parsed, installedPath);
740
+ if (!hasUpdate) {
741
+ return undefined;
742
+ }
743
+ return {
744
+ source,
745
+ displayName: parsed.name,
746
+ type: "npm",
747
+ scope: entry.scope,
748
+ };
749
+ }
750
+ const installedPath = this.getGitInstallPath(parsed, entry.scope);
751
+ if (!existsSync(installedPath)) {
752
+ return undefined;
753
+ }
754
+ const hasUpdate = await this.gitHasAvailableUpdate(installedPath);
755
+ if (!hasUpdate) {
756
+ return undefined;
757
+ }
758
+ return {
759
+ source,
760
+ displayName: `${parsed.host}/${parsed.path}`,
761
+ type: "git",
762
+ scope: entry.scope,
763
+ };
764
+ });
765
+ const results = await this.runWithConcurrency(checks, UPDATE_CHECK_CONCURRENCY);
766
+ return results.filter((result) => result !== undefined);
767
+ }
690
768
  async resolvePackageSources(sources, accumulator, onMissing) {
691
769
  for (const { pkg, scope } of sources) {
692
770
  const sourceStr = typeof pkg === "string" ? pkg : pkg.source;
@@ -699,6 +777,9 @@ export class DefaultPackageManager {
699
777
  continue;
700
778
  }
701
779
  const installMissing = async () => {
780
+ if (isOfflineModeEnabled()) {
781
+ return false;
782
+ }
702
783
  if (!onMissing) {
703
784
  await this.installParsedSource(parsed, scope);
704
785
  return true;
@@ -713,7 +794,8 @@ export class DefaultPackageManager {
713
794
  };
714
795
  if (parsed.type === "npm") {
715
796
  const installedPath = this.getNpmInstallPath(parsed, scope);
716
- const needsInstall = !existsSync(installedPath) || (await this.npmNeedsUpdate(parsed, installedPath));
797
+ const needsInstall = !existsSync(installedPath) ||
798
+ (parsed.pinned && !(await this.installedNpmMatchesPinnedVersion(parsed, installedPath)));
717
799
  if (needsInstall) {
718
800
  const installed = await installMissing();
719
801
  if (!installed)
@@ -730,7 +812,7 @@ export class DefaultPackageManager {
730
812
  if (!installed)
731
813
  continue;
732
814
  }
733
- else if (scope === "temporary" && !parsed.pinned) {
815
+ else if (scope === "temporary" && !parsed.pinned && !isOfflineModeEnabled()) {
734
816
  await this.refreshTemporaryGitSource(parsed, sourceStr);
735
817
  }
736
818
  metadata.baseDir = installedPath;
@@ -839,27 +921,30 @@ export class DefaultPackageManager {
839
921
  }
840
922
  return { type: "local", path: source };
841
923
  }
842
- /**
843
- * Check if an npm package needs to be updated.
844
- * - For unpinned packages: check if registry has a newer version
845
- * - For pinned packages: check if installed version matches the pinned version
846
- */
847
- async npmNeedsUpdate(source, installedPath) {
924
+ async installedNpmMatchesPinnedVersion(source, installedPath) {
848
925
  const installedVersion = this.getInstalledNpmVersion(installedPath);
849
- if (!installedVersion)
850
- return true;
926
+ if (!installedVersion) {
927
+ return false;
928
+ }
851
929
  const { version: pinnedVersion } = this.parseNpmSpec(source.spec);
852
- if (pinnedVersion) {
853
- // Pinned: check if installed matches pinned (exact match for now)
854
- return installedVersion !== pinnedVersion;
930
+ if (!pinnedVersion) {
931
+ return true;
932
+ }
933
+ return installedVersion === pinnedVersion;
934
+ }
935
+ async npmHasAvailableUpdate(source, installedPath) {
936
+ if (isOfflineModeEnabled()) {
937
+ return false;
938
+ }
939
+ const installedVersion = this.getInstalledNpmVersion(installedPath);
940
+ if (!installedVersion) {
941
+ return false;
855
942
  }
856
- // Unpinned: check registry for latest version
857
943
  try {
858
944
  const latestVersion = await this.getLatestNpmVersion(source.name);
859
945
  return latestVersion !== installedVersion;
860
946
  }
861
947
  catch {
862
- // If we can't check registry, assume it's fine
863
948
  return false;
864
949
  }
865
950
  }
@@ -877,12 +962,92 @@ export class DefaultPackageManager {
877
962
  }
878
963
  }
879
964
  async getLatestNpmVersion(packageName) {
880
- const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
965
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
966
+ signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
967
+ });
881
968
  if (!response.ok)
882
969
  throw new Error(`Failed to fetch npm registry: ${response.status}`);
883
970
  const data = (await response.json());
884
971
  return data.version;
885
972
  }
973
+ async gitHasAvailableUpdate(installedPath) {
974
+ if (isOfflineModeEnabled()) {
975
+ return false;
976
+ }
977
+ try {
978
+ const localHead = await this.runCommandCapture("git", ["rev-parse", "HEAD"], {
979
+ cwd: installedPath,
980
+ timeoutMs: NETWORK_TIMEOUT_MS,
981
+ });
982
+ const remoteHead = await this.getRemoteGitHead(installedPath);
983
+ return localHead.trim() !== remoteHead.trim();
984
+ }
985
+ catch {
986
+ return false;
987
+ }
988
+ }
989
+ async getRemoteGitHead(installedPath) {
990
+ const upstreamRef = await this.getGitUpstreamRef(installedPath);
991
+ if (upstreamRef) {
992
+ const remoteHead = await this.runGitRemoteCommand(installedPath, ["ls-remote", "origin", upstreamRef]);
993
+ const match = remoteHead.match(/^([0-9a-f]{40})\s+/m);
994
+ if (match?.[1]) {
995
+ return match[1];
996
+ }
997
+ }
998
+ const remoteHead = await this.runGitRemoteCommand(installedPath, ["ls-remote", "origin", "HEAD"]);
999
+ const match = remoteHead.match(/^([0-9a-f]{40})\s+HEAD$/m);
1000
+ if (!match?.[1]) {
1001
+ throw new Error("Failed to determine remote HEAD");
1002
+ }
1003
+ return match[1];
1004
+ }
1005
+ async getGitUpstreamRef(installedPath) {
1006
+ try {
1007
+ const upstream = await this.runCommandCapture("git", ["rev-parse", "--abbrev-ref", "@{upstream}"], {
1008
+ cwd: installedPath,
1009
+ timeoutMs: NETWORK_TIMEOUT_MS,
1010
+ });
1011
+ const trimmed = upstream.trim();
1012
+ if (!trimmed.startsWith("origin/")) {
1013
+ return undefined;
1014
+ }
1015
+ const branch = trimmed.slice("origin/".length);
1016
+ return branch ? `refs/heads/${branch}` : undefined;
1017
+ }
1018
+ catch {
1019
+ return undefined;
1020
+ }
1021
+ }
1022
+ runGitRemoteCommand(installedPath, args) {
1023
+ return this.runCommandCapture("git", args, {
1024
+ cwd: installedPath,
1025
+ timeoutMs: NETWORK_TIMEOUT_MS,
1026
+ env: {
1027
+ GIT_TERMINAL_PROMPT: "0",
1028
+ },
1029
+ });
1030
+ }
1031
+ async runWithConcurrency(tasks, limit) {
1032
+ if (tasks.length === 0) {
1033
+ return [];
1034
+ }
1035
+ const results = new Array(tasks.length);
1036
+ let nextIndex = 0;
1037
+ const workerCount = Math.max(1, Math.min(limit, tasks.length));
1038
+ const worker = async () => {
1039
+ while (true) {
1040
+ const index = nextIndex;
1041
+ nextIndex += 1;
1042
+ if (index >= tasks.length) {
1043
+ return;
1044
+ }
1045
+ results[index] = await tasks[index]();
1046
+ }
1047
+ };
1048
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
1049
+ return results;
1050
+ }
886
1051
  /**
887
1052
  * Get a unique identity for a package, ignoring version/ref.
888
1053
  * Used to detect when the same package is in both global and project settings.
@@ -935,25 +1100,44 @@ export class DefaultPackageManager {
935
1100
  const version = match[2];
936
1101
  return { name, version };
937
1102
  }
1103
+ getNpmCommand() {
1104
+ const configuredCommand = this.settingsManager.getNpmCommand();
1105
+ if (!configuredCommand || configuredCommand.length === 0) {
1106
+ return { command: "npm", args: [] };
1107
+ }
1108
+ const [command, ...args] = configuredCommand;
1109
+ if (!command) {
1110
+ throw new Error("Invalid npmCommand: first array entry must be a non-empty command");
1111
+ }
1112
+ return { command, args };
1113
+ }
1114
+ async runNpmCommand(args, options) {
1115
+ const npmCommand = this.getNpmCommand();
1116
+ await this.runCommand(npmCommand.command, [...npmCommand.args, ...args], options);
1117
+ }
1118
+ runNpmCommandSync(args) {
1119
+ const npmCommand = this.getNpmCommand();
1120
+ return this.runCommandSync(npmCommand.command, [...npmCommand.args, ...args]);
1121
+ }
938
1122
  async installNpm(source, scope, temporary) {
939
1123
  if (scope === "user" && !temporary) {
940
- await this.runCommand("npm", ["install", "-g", source.spec]);
1124
+ await this.runNpmCommand(["install", "-g", source.spec]);
941
1125
  return;
942
1126
  }
943
1127
  const installRoot = this.getNpmInstallRoot(scope, temporary);
944
1128
  this.ensureNpmProject(installRoot);
945
- await this.runCommand("npm", ["install", source.spec, "--prefix", installRoot]);
1129
+ await this.runNpmCommand(["install", source.spec, "--prefix", installRoot]);
946
1130
  }
947
1131
  async uninstallNpm(source, scope) {
948
1132
  if (scope === "user") {
949
- await this.runCommand("npm", ["uninstall", "-g", source.name]);
1133
+ await this.runNpmCommand(["uninstall", "-g", source.name]);
950
1134
  return;
951
1135
  }
952
1136
  const installRoot = this.getNpmInstallRoot(scope, false);
953
1137
  if (!existsSync(installRoot)) {
954
1138
  return;
955
1139
  }
956
- await this.runCommand("npm", ["uninstall", source.name, "--prefix", installRoot]);
1140
+ await this.runNpmCommand(["uninstall", source.name, "--prefix", installRoot]);
957
1141
  }
958
1142
  async installGit(source, scope) {
959
1143
  const targetDir = this.getGitInstallPath(source, scope);
@@ -971,7 +1155,7 @@ export class DefaultPackageManager {
971
1155
  }
972
1156
  const packageJsonPath = join(targetDir, "package.json");
973
1157
  if (existsSync(packageJsonPath)) {
974
- await this.runCommand("npm", ["install"], { cwd: targetDir });
1158
+ await this.runNpmCommand(["install"], { cwd: targetDir });
975
1159
  }
976
1160
  }
977
1161
  async updateGit(source, scope) {
@@ -994,10 +1178,13 @@ export class DefaultPackageManager {
994
1178
  await this.runCommand("git", ["clean", "-fdx"], { cwd: targetDir });
995
1179
  const packageJsonPath = join(targetDir, "package.json");
996
1180
  if (existsSync(packageJsonPath)) {
997
- await this.runCommand("npm", ["install"], { cwd: targetDir });
1181
+ await this.runNpmCommand(["install"], { cwd: targetDir });
998
1182
  }
999
1183
  }
1000
1184
  async refreshTemporaryGitSource(source, sourceStr) {
1185
+ if (isOfflineModeEnabled()) {
1186
+ return;
1187
+ }
1001
1188
  try {
1002
1189
  await this.withProgress("pull", sourceStr, `Refreshing ${sourceStr}...`, async () => {
1003
1190
  await this.updateGit(source, "temporary");
@@ -1067,11 +1254,14 @@ export class DefaultPackageManager {
1067
1254
  return join(this.getGlobalNpmRoot(), "..");
1068
1255
  }
1069
1256
  getGlobalNpmRoot() {
1070
- if (this.globalNpmRoot) {
1257
+ const npmCommand = this.getNpmCommand();
1258
+ const commandKey = [npmCommand.command, ...npmCommand.args].join("\0");
1259
+ if (this.globalNpmRoot && this.globalNpmRootCommandKey === commandKey) {
1071
1260
  return this.globalNpmRoot;
1072
1261
  }
1073
- const result = this.runCommandSync("npm", ["root", "-g"]);
1262
+ const result = this.runNpmCommandSync(["root", "-g"]);
1074
1263
  this.globalNpmRoot = result.trim();
1264
+ this.globalNpmRootCommandKey = commandKey;
1075
1265
  return this.globalNpmRoot;
1076
1266
  }
1077
1267
  getNpmInstallPath(source, scope) {
@@ -1120,21 +1310,21 @@ export class DefaultPackageManager {
1120
1310
  resolvePath(input) {
1121
1311
  const trimmed = input.trim();
1122
1312
  if (trimmed === "~")
1123
- return homedir();
1313
+ return getHomeDir();
1124
1314
  if (trimmed.startsWith("~/"))
1125
- return join(homedir(), trimmed.slice(2));
1315
+ return join(getHomeDir(), trimmed.slice(2));
1126
1316
  if (trimmed.startsWith("~"))
1127
- return join(homedir(), trimmed.slice(1));
1317
+ return join(getHomeDir(), trimmed.slice(1));
1128
1318
  return resolve(this.cwd, trimmed);
1129
1319
  }
1130
1320
  resolvePathFromBase(input, baseDir) {
1131
1321
  const trimmed = input.trim();
1132
1322
  if (trimmed === "~")
1133
- return homedir();
1323
+ return getHomeDir();
1134
1324
  if (trimmed.startsWith("~/"))
1135
- return join(homedir(), trimmed.slice(2));
1325
+ return join(getHomeDir(), trimmed.slice(2));
1136
1326
  if (trimmed.startsWith("~"))
1137
- return join(homedir(), trimmed.slice(1));
1327
+ return join(getHomeDir(), trimmed.slice(1));
1138
1328
  return resolve(baseDir, trimmed);
1139
1329
  }
1140
1330
  collectPackageResources(packageRoot, accumulator, filter, metadata) {
@@ -1308,8 +1498,8 @@ export class DefaultPackageManager {
1308
1498
  prompts: join(projectBaseDir, "prompts"),
1309
1499
  themes: join(projectBaseDir, "themes"),
1310
1500
  };
1311
- const userAgentsSkillsDir = join(homedir(), ".agents", "skills");
1312
- const projectAgentsSkillDirs = collectAncestorAgentsSkillDirs(this.cwd);
1501
+ const userAgentsSkillsDir = join(getHomeDir(), ".agents", "skills");
1502
+ const projectAgentsSkillDirs = collectAncestorAgentsSkillDirs(this.cwd).filter((dir) => resolve(dir) !== resolve(userAgentsSkillsDir));
1313
1503
  const addResources = (resourceType, paths, metadata, overrides, baseDir) => {
1314
1504
  const target = this.getTargetMap(accumulator, resourceType);
1315
1505
  for (const path of paths) {
@@ -1317,10 +1507,6 @@ export class DefaultPackageManager {
1317
1507
  this.addResource(target, path, metadata, enabled);
1318
1508
  }
1319
1509
  };
1320
- addResources("extensions", collectAutoExtensionEntries(userDirs.extensions), userMetadata, userOverrides.extensions, globalBaseDir);
1321
- addResources("skills", [...collectAutoSkillEntries(userDirs.skills), ...collectAutoSkillEntries(userAgentsSkillsDir)], userMetadata, userOverrides.skills, globalBaseDir);
1322
- addResources("prompts", collectAutoPromptEntries(userDirs.prompts), userMetadata, userOverrides.prompts, globalBaseDir);
1323
- addResources("themes", collectAutoThemeEntries(userDirs.themes), userMetadata, userOverrides.themes, globalBaseDir);
1324
1510
  addResources("extensions", collectAutoExtensionEntries(projectDirs.extensions), projectMetadata, projectOverrides.extensions, projectBaseDir);
1325
1511
  addResources("skills", [
1326
1512
  ...collectAutoSkillEntries(projectDirs.skills),
@@ -1328,6 +1514,10 @@ export class DefaultPackageManager {
1328
1514
  ], projectMetadata, projectOverrides.skills, projectBaseDir);
1329
1515
  addResources("prompts", collectAutoPromptEntries(projectDirs.prompts), projectMetadata, projectOverrides.prompts, projectBaseDir);
1330
1516
  addResources("themes", collectAutoThemeEntries(projectDirs.themes), projectMetadata, projectOverrides.themes, projectBaseDir);
1517
+ addResources("extensions", collectAutoExtensionEntries(userDirs.extensions), userMetadata, userOverrides.extensions, globalBaseDir);
1518
+ addResources("skills", [...collectAutoSkillEntries(userDirs.skills), ...collectAutoSkillEntries(userAgentsSkillsDir)], userMetadata, userOverrides.skills, globalBaseDir);
1519
+ addResources("prompts", collectAutoPromptEntries(userDirs.prompts), userMetadata, userOverrides.prompts, globalBaseDir);
1520
+ addResources("themes", collectAutoThemeEntries(userDirs.themes), userMetadata, userOverrides.themes, globalBaseDir);
1331
1521
  }
1332
1522
  collectFilesFromPaths(paths, resourceType) {
1333
1523
  const files = [];
@@ -1393,6 +1583,49 @@ export class DefaultPackageManager {
1393
1583
  themes: toResolved(accumulator.themes),
1394
1584
  };
1395
1585
  }
1586
+ runCommandCapture(command, args, options) {
1587
+ return new Promise((resolvePromise, reject) => {
1588
+ const child = spawn(command, args, {
1589
+ cwd: options?.cwd,
1590
+ stdio: ["ignore", "pipe", "pipe"],
1591
+ shell: process.platform === "win32",
1592
+ env: options?.env ? { ...process.env, ...options.env } : process.env,
1593
+ });
1594
+ let stdout = "";
1595
+ let stderr = "";
1596
+ let timedOut = false;
1597
+ const timeout = typeof options?.timeoutMs === "number"
1598
+ ? setTimeout(() => {
1599
+ timedOut = true;
1600
+ child.kill();
1601
+ }, options.timeoutMs)
1602
+ : undefined;
1603
+ child.stdout?.on("data", (data) => {
1604
+ stdout += data.toString();
1605
+ });
1606
+ child.stderr?.on("data", (data) => {
1607
+ stderr += data.toString();
1608
+ });
1609
+ child.on("error", (error) => {
1610
+ if (timeout)
1611
+ clearTimeout(timeout);
1612
+ reject(error);
1613
+ });
1614
+ child.on("exit", (code) => {
1615
+ if (timeout)
1616
+ clearTimeout(timeout);
1617
+ if (timedOut) {
1618
+ reject(new Error(`${command} ${args.join(" ")} timed out after ${options?.timeoutMs}ms`));
1619
+ return;
1620
+ }
1621
+ if (code === 0) {
1622
+ resolvePromise(stdout.trim());
1623
+ return;
1624
+ }
1625
+ reject(new Error(`${command} ${args.join(" ")} failed with code ${code}: ${stderr || stdout}`));
1626
+ });
1627
+ });
1628
+ }
1396
1629
  runCommand(command, args, options) {
1397
1630
  return new Promise((resolvePromise, reject) => {
1398
1631
  const child = spawn(command, args, {