xtrm-tools 0.7.4 → 0.7.9

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 (85) hide show
  1. package/.xtrm/config/pi/extensions/custom-footer/.pi/structured-returns/83051fe4-97da-4e2c-bdaa-343b32f4e714.combined.log +7 -0
  2. package/.xtrm/config/pi/extensions/custom-footer/.pi/structured-returns/83051fe4-97da-4e2c-bdaa-343b32f4e714.stderr.log +0 -0
  3. package/.xtrm/config/pi/extensions/custom-footer/.pi/structured-returns/83051fe4-97da-4e2c-bdaa-343b32f4e714.stdout.log +7 -0
  4. package/.xtrm/registry.json +537 -565
  5. package/.xtrm/skills/default/gitnexus-cli/SKILL.md +82 -0
  6. package/.xtrm/skills/default/gitnexus-exploring/SKILL.md +78 -0
  7. package/.xtrm/skills/default/gitnexus-guide/SKILL.md +64 -0
  8. package/.xtrm/skills/default/init-session/SKILL.md +3 -0
  9. package/.xtrm/skills/default/last30days/SKILL.md +1 -1
  10. package/.xtrm/skills/default/last30days/scripts/lib/youtube_yt.py +59 -40
  11. package/.xtrm/skills/default/sync-docs/references/doc-structure.md +1 -1
  12. package/.xtrm/skills/default/sync-docs/references/schema.md +1 -1
  13. package/.xtrm/skills/default/sync-docs/scripts/doc_structure_analyzer.py +2 -2
  14. package/.xtrm/skills/default/using-specialists/SKILL.md +346 -138
  15. package/.xtrm/skills/default/using-specialists/SKILL.safe.md +1082 -0
  16. package/.xtrm/skills/default/using-specialists/SKILL.ultra.md +1082 -0
  17. package/.xtrm/skills/default/vaultctl/SKILL.md +150 -0
  18. package/CHANGELOG.md +4 -4
  19. package/cli/dist/index.cjs +392 -194
  20. package/cli/dist/index.cjs.map +1 -1
  21. package/cli/package.json +1 -1
  22. package/package.json +8 -7
  23. package/packages/pi-extensions/MIGRATION_NOTES.md +39 -0
  24. package/packages/pi-extensions/README.md +43 -0
  25. package/packages/pi-extensions/extensions/README.md +5 -0
  26. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/beads/index.ts +1 -1
  27. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/beads/package.json +1 -4
  28. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/custom-footer/index.ts +1 -1
  29. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/custom-footer/package.json +1 -4
  30. package/packages/pi-extensions/extensions/custom-provider-qwen-cli/package.json +16 -0
  31. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/pi-serena-compact/package.json +9 -2
  32. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/quality-gates/index.ts +1 -1
  33. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/quality-gates/package.json +1 -4
  34. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/service-skills/index.ts +1 -1
  35. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/service-skills/package.json +1 -4
  36. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/session-flow/index.ts +1 -1
  37. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/session-flow/package.json +1 -4
  38. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/xtrm-loader/index.ts +1 -1
  39. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/xtrm-loader/package.json +1 -4
  40. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/xtrm-ui/index.ts +1 -1
  41. package/packages/pi-extensions/package.json +46 -0
  42. package/packages/pi-extensions/src/core/README.md +9 -0
  43. package/packages/pi-extensions/src/core/index.ts +5 -0
  44. package/packages/pi-extensions/src/extensions/auto-session-name.ts +3 -0
  45. package/packages/pi-extensions/src/extensions/auto-update.ts +3 -0
  46. package/packages/pi-extensions/src/extensions/beads.ts +3 -0
  47. package/packages/pi-extensions/src/extensions/compact-header.ts +3 -0
  48. package/packages/pi-extensions/src/extensions/custom-footer.ts +3 -0
  49. package/packages/pi-extensions/src/extensions/custom-provider-qwen-cli.ts +3 -0
  50. package/packages/pi-extensions/src/extensions/git-checkpoint.ts +3 -0
  51. package/packages/pi-extensions/src/extensions/lsp-bootstrap.ts +3 -0
  52. package/packages/pi-extensions/src/extensions/pi-serena-compact.ts +3 -0
  53. package/packages/pi-extensions/src/extensions/quality-gates.ts +3 -0
  54. package/packages/pi-extensions/src/extensions/service-skills.ts +3 -0
  55. package/packages/pi-extensions/src/extensions/session-flow.ts +3 -0
  56. package/packages/pi-extensions/src/extensions/xtrm-loader.ts +3 -0
  57. package/packages/pi-extensions/src/extensions/xtrm-ui.ts +3 -0
  58. package/packages/pi-extensions/src/index.ts +9 -0
  59. package/packages/pi-extensions/src/registry.ts +52 -0
  60. package/packages/pi-extensions/src/shared/index.ts +1 -0
  61. package/packages/pi-extensions/src/shared/legacy-path-map.ts +23 -0
  62. package/{.xtrm/config/pi/extensions/xtrm-ui/themes → packages/pi-extensions/themes/xtrm-ui}/pidex-dark.json +3 -7
  63. package/{.xtrm/config/pi/extensions/xtrm-ui/themes → packages/pi-extensions/themes/xtrm-ui}/pidex-light.json +0 -4
  64. package/.xtrm/config/pi/extensions/core/package.json +0 -18
  65. package/.xtrm/config/pi/extensions/custom-provider-qwen-cli/package.json +0 -1
  66. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/auto-session-name/index.ts +0 -0
  67. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/auto-session-name/package.json +0 -0
  68. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/auto-update/index.ts +0 -0
  69. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/auto-update/package.json +0 -0
  70. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/compact-header/index.ts +0 -0
  71. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/compact-header/package.json +0 -0
  72. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/custom-provider-qwen-cli/index.ts +0 -0
  73. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/git-checkpoint/index.ts +0 -0
  74. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/git-checkpoint/package.json +0 -0
  75. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/lsp-bootstrap/index.ts +0 -0
  76. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/lsp-bootstrap/package.json +0 -0
  77. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/pi-serena-compact/index.ts +0 -0
  78. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/xtrm-ui/format.ts +0 -0
  79. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/xtrm-ui/package.json +0 -0
  80. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/adapter.ts +0 -0
  81. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/guard-rules.ts +0 -0
  82. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/lib.ts +0 -0
  83. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/logger.ts +0 -0
  84. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/runner.ts +0 -0
  85. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/session-state.ts +0 -0
@@ -29623,11 +29623,15 @@ async function runClaudeRuntimeSyncPhase(opts) {
29623
29623
  const hooksConfig = await import_fs_extra2.default.readJson(hooksConfigPath);
29624
29624
  const projectHooksDir = import_path2.default.join(repoRoot, ".xtrm", "hooks");
29625
29625
  const generatedHooks = resolveHooksForProjectRuntime(hooksConfig.hooks ?? {}, projectHooksDir);
29626
+ const generatedStatusLine = resolveStatusLineForProjectRuntime(hooksConfig.statusLine, projectHooksDir);
29626
29627
  const settingsPath = isGlobal ? import_path2.default.join(import_os.default.homedir(), ".claude", "settings.json") : import_path2.default.join(repoRoot, ".claude", "settings.json");
29627
29628
  const hasExistingSettings = await import_fs_extra2.default.pathExists(settingsPath);
29628
29629
  const baseSettings = await readBaseSettings(settingsTemplatePath);
29629
29630
  const existingSettings = hasExistingSettings ? await readSettings(settingsPath) : {};
29630
29631
  const mergedSettings = hasExistingSettings ? { ...existingSettings, hooks: generatedHooks } : { ...baseSettings, hooks: generatedHooks };
29632
+ if (generatedStatusLine) {
29633
+ mergedSettings.statusLine = generatedStatusLine;
29634
+ }
29631
29635
  if (prune) {
29632
29636
  delete mergedSettings.enabledPlugins;
29633
29637
  delete mergedSettings.extraKnownMarketplaces;
@@ -29671,6 +29675,7 @@ async function runClaudeRuntimeSyncPhase(opts) {
29671
29675
  console.log(t.success(" \u2713 Removed plugin-era settings keys (enabledPlugins, extraKnownMarketplaces)"));
29672
29676
  }
29673
29677
  console.log(t.success(" \u2713 Claude settings hooks synced\n"));
29678
+ await ensureGlobalStatusLine();
29674
29679
  return {
29675
29680
  settingsPath,
29676
29681
  hooksEventsWritten,
@@ -29678,6 +29683,24 @@ async function runClaudeRuntimeSyncPhase(opts) {
29678
29683
  wroteSettings: true
29679
29684
  };
29680
29685
  }
29686
+ async function ensureGlobalStatusLine() {
29687
+ const homeDir = import_os.default.homedir();
29688
+ const statuslineHookPath = import_path2.default.join(homeDir, ".xtrm", "hooks", "statusline.mjs");
29689
+ const globalSettingsPath = import_path2.default.join(homeDir, ".claude", "settings.json");
29690
+ if (!await import_fs_extra2.default.pathExists(statuslineHookPath)) {
29691
+ return;
29692
+ }
29693
+ const expectedCommand = `node "${statuslineHookPath}"`;
29694
+ const settings = await readSettings(globalSettingsPath);
29695
+ const currentCommand = settings.statusLine?.command;
29696
+ if (currentCommand === expectedCommand) {
29697
+ return;
29698
+ }
29699
+ settings.statusLine = { type: "command", command: expectedCommand };
29700
+ await import_fs_extra2.default.ensureDir(import_path2.default.dirname(globalSettingsPath));
29701
+ await import_fs_extra2.default.writeJson(globalSettingsPath, settings, { spaces: 2 });
29702
+ console.log(t.success(` \u2713 Wired statusline \u2192 ~/.xtrm/hooks/statusline.mjs`));
29703
+ }
29681
29704
  function resolveHooksForProjectRuntime(hooks, projectHooksDir) {
29682
29705
  const normalizedHooksDir = normalizeHookCommandPath(projectHooksDir);
29683
29706
  const rewrittenHooks = {};
@@ -29697,6 +29720,36 @@ function resolveHooksForProjectRuntime(hooks, projectHooksDir) {
29697
29720
  }
29698
29721
  return rewrittenHooks;
29699
29722
  }
29723
+ function resolveStatusLineForProjectRuntime(statusLineConfig, projectHooksDir) {
29724
+ if (!statusLineConfig?.script) {
29725
+ return void 0;
29726
+ }
29727
+ const normalizedHooksDir = normalizeHookCommandPath(projectHooksDir);
29728
+ const resolvedScriptPath = resolveStatusLineScriptPath(statusLineConfig.script, normalizedHooksDir);
29729
+ return {
29730
+ type: "command",
29731
+ command: buildScriptCommand(statusLineConfig.script, resolvedScriptPath)
29732
+ };
29733
+ }
29734
+ function resolveStatusLineScriptPath(script, normalizedHooksDir) {
29735
+ const pluginRootPattern = /^(?:\$\{CLAUDE_PLUGIN_ROOT\}|\$CLAUDE_PLUGIN_ROOT)\/hooks\/(.+)$/;
29736
+ const pluginRootMatch = script.match(pluginRootPattern);
29737
+ if (pluginRootMatch?.[1]) {
29738
+ return normalizeHookCommandPath(import_path2.default.join(normalizedHooksDir, pluginRootMatch[1]));
29739
+ }
29740
+ return normalizeHookCommandPath(import_path2.default.join(normalizedHooksDir, script));
29741
+ }
29742
+ function buildScriptCommand(scriptName, resolvedPath) {
29743
+ const ext = import_path2.default.extname(scriptName).toLowerCase();
29744
+ if (ext === ".js" || ext === ".cjs" || ext === ".mjs") {
29745
+ return `node "${resolvedPath}"`;
29746
+ }
29747
+ if (ext === ".sh") {
29748
+ return `bash "${resolvedPath}"`;
29749
+ }
29750
+ const pythonBin = process.platform === "win32" ? "python" : "python3";
29751
+ return `${pythonBin} "${resolvedPath}"`;
29752
+ }
29700
29753
  function rewritePluginRootCommandToProjectHookPath(command, normalizedHooksDir) {
29701
29754
  const pluginRootPatterns = [
29702
29755
  /\$\{CLAUDE_PLUGIN_ROOT\}\/hooks\/([^\s"']+)/g,
@@ -43838,6 +43891,13 @@ async function setRuntimeEnabledPacks(skillsRoot, runtime, packNames) {
43838
43891
  }
43839
43892
 
43840
43893
  // src/core/skills-materializer.ts
43894
+ var PI_NPM_PROVIDED_SKILLS = /* @__PURE__ */ new Set([
43895
+ "gitnexus-debugging",
43896
+ "gitnexus-exploring",
43897
+ "gitnexus-impact-analysis",
43898
+ "gitnexus-pr-review",
43899
+ "gitnexus-refactoring"
43900
+ ]);
43841
43901
  function sortByName(entries) {
43842
43902
  return [...entries].sort((a, b) => a.name.localeCompare(b.name));
43843
43903
  }
@@ -43876,11 +43936,12 @@ async function selectRuntimeSkills(runtime, skillsRoot) {
43876
43936
  const defaultSkills = await discoverDefaultSkills(skillsRoot);
43877
43937
  const enabledPackSkills = await collectEnabledPackSkills(skillsRoot, enabledPacks);
43878
43938
  const allSkills = sortByName([...defaultSkills, ...enabledPackSkills]);
43879
- assertNoRuntimeCollisions(runtime, allSkills);
43939
+ const filteredSkills = runtime === "pi" ? allSkills.filter((s) => !PI_NPM_PROVIDED_SKILLS.has(s.name)) : allSkills;
43940
+ assertNoRuntimeCollisions(runtime, filteredSkills);
43880
43941
  return {
43881
43942
  runtime,
43882
43943
  enabledPacks: [...enabledPacks],
43883
- skills: allSkills
43944
+ skills: filteredSkills
43884
43945
  };
43885
43946
  }
43886
43947
  async function buildRuntimeTempView(runtime, skillsRoot, selectedSkills) {
@@ -44049,6 +44110,26 @@ function resolveStatuslineScript(worktreePath) {
44049
44110
  if ((0, import_node_fs.existsSync)(repoStatusline)) return repoStatusline;
44050
44111
  return null;
44051
44112
  }
44113
+ function ensureWorktreeSpecialists(worktreePath, mainRepoPath) {
44114
+ const worktreeSpecialistsRoot = import_node_path5.default.join(worktreePath, ".specialists");
44115
+ (0, import_node_fs.mkdirSync)(worktreeSpecialistsRoot, { recursive: true });
44116
+ const specialistDirs = ["default", "user"];
44117
+ for (const dirName of specialistDirs) {
44118
+ const sourceDir = import_node_path5.default.join(mainRepoPath, ".specialists", dirName);
44119
+ if (!(0, import_node_fs.existsSync)(sourceDir)) continue;
44120
+ const targetDir = import_node_path5.default.join(worktreeSpecialistsRoot, dirName);
44121
+ const symlinkTarget = import_node_path5.default.relative(import_node_path5.default.dirname(targetDir), sourceDir);
44122
+ try {
44123
+ const existing = (0, import_node_fs.lstatSync)(targetDir);
44124
+ if (existing.isSymbolicLink() && (0, import_node_fs.readlinkSync)(targetDir) === symlinkTarget) {
44125
+ continue;
44126
+ }
44127
+ (0, import_node_fs.rmSync)(targetDir, { recursive: true, force: true });
44128
+ } catch {
44129
+ }
44130
+ (0, import_node_fs.symlinkSync)(symlinkTarget, targetDir, "dir");
44131
+ }
44132
+ }
44052
44133
  function sessionMetaPath(worktreePath) {
44053
44134
  return import_node_path5.default.join(worktreePath, ".xtrm", "session-meta.json");
44054
44135
  }
@@ -44156,6 +44237,12 @@ async function launchWorktreeSession(opts) {
44156
44237
  }
44157
44238
  }
44158
44239
  }
44240
+ try {
44241
+ ensureWorktreeSpecialists(worktreePath, mainRepoRoot);
44242
+ } catch (error48) {
44243
+ const message = error48 instanceof Error ? error48.message : String(error48);
44244
+ console.log(kleur_default.dim(` warning: could not provision specialist definitions (${message})`));
44245
+ }
44159
44246
  const localSettings = {};
44160
44247
  const statuslinePath = resolveStatuslineScript(worktreePath);
44161
44248
  if (statuslinePath) {
@@ -44682,21 +44769,51 @@ var import_child_process3 = require("child_process");
44682
44769
  var import_fs_extra9 = __toESM(require_lib(), 1);
44683
44770
  var import_path4 = __toESM(require("path"), 1);
44684
44771
  var import_node_os = require("os");
44772
+ var MANAGED_PI_EXTENSION_SOURCE_CANDIDATES = [
44773
+ ["packages", "pi-extensions", "extensions"],
44774
+ [".xtrm", "extensions"]
44775
+ ];
44776
+ function resolveFirstExistingPath(rootDir, candidates) {
44777
+ for (const candidate of candidates) {
44778
+ const candidatePath = import_path4.default.join(rootDir, ...candidate);
44779
+ if (import_fs_extra9.default.existsSync(candidatePath)) {
44780
+ return candidatePath;
44781
+ }
44782
+ }
44783
+ return null;
44784
+ }
44685
44785
  function resolvePkgRoot() {
44686
44786
  const candidates = [
44687
44787
  import_path4.default.resolve(__dirname, "../.."),
44688
44788
  import_path4.default.resolve(__dirname, "../../..")
44689
44789
  ];
44690
- for (const c of candidates) {
44691
- if (import_fs_extra9.default.existsSync(import_path4.default.join(c, ".xtrm", "extensions"))) return c;
44790
+ for (const candidateRoot of candidates) {
44791
+ if (resolveFirstExistingPath(candidateRoot, MANAGED_PI_EXTENSION_SOURCE_CANDIDATES)) {
44792
+ return candidateRoot;
44793
+ }
44692
44794
  }
44693
44795
  return candidates[0];
44694
44796
  }
44797
+ function resolveManagedPiExtensionsSourceDir(pkgRoot = resolvePkgRoot()) {
44798
+ return resolveFirstExistingPath(pkgRoot, MANAGED_PI_EXTENSION_SOURCE_CANDIDATES);
44799
+ }
44800
+ function resolveManagedPiCoreSourceDir(pkgRoot = resolvePkgRoot()) {
44801
+ return resolveFirstExistingPath(pkgRoot, [
44802
+ ["packages", "pi-extensions", "src", "core"],
44803
+ [".xtrm", "extensions", "core"]
44804
+ ]);
44805
+ }
44695
44806
  var PI_AGENT_DIR = process.env.PI_AGENT_DIR || import_path4.default.join((0, import_node_os.homedir)(), ".pi", "agent");
44696
44807
  var PI_MCP_ADAPTER_OVERRIDE_DIR = import_path4.default.join(PI_AGENT_DIR, "extensions", "pi-mcp-adapter");
44697
44808
  var PI_MCP_ADAPTER_REQUIRED_ENTRY = "commands.js";
44698
44809
  var PROJECT_EXTENSIONS_ENTRY = "../.xtrm/extensions";
44699
44810
  var PROJECT_SKILLS_ENTRY = "../.xtrm/skills/active/pi";
44811
+ var PROJECT_EXTENSION_PACKAGE_ID = "npm:@jaggerxtrm/pi-extensions";
44812
+ var CONFLICTING_PI_PACKAGE_IDS = /* @__PURE__ */ new Set(["npm:pi-dex"]);
44813
+ var LEGACY_PROJECT_EXTENSION_ENTRIES = /* @__PURE__ */ new Set([
44814
+ PROJECT_EXTENSIONS_ENTRY,
44815
+ ".xtrm/extensions"
44816
+ ]);
44700
44817
  var MANAGED_EXTENSIONS = [
44701
44818
  { id: "core", displayName: "@xtrm/pi-core", isLibrary: true, required: true },
44702
44819
  { id: "auto-session-name", displayName: "auto-session-name", required: false },
@@ -44726,6 +44843,10 @@ var ALWAYS_GLOBAL_INSTALL_PACKAGE_IDS = /* @__PURE__ */ new Set([
44726
44843
  "npm:pi-gitnexus",
44727
44844
  "npm:pi-serena-tools"
44728
44845
  ]);
44846
+ var PROJECT_REQUIRED_PACKAGE_IDS = [
44847
+ PROJECT_EXTENSION_PACKAGE_ID,
44848
+ ...MANAGED_PACKAGES.map((pkg) => pkg.id)
44849
+ ];
44729
44850
  function getInstalledPiPackages() {
44730
44851
  const result = (0, import_child_process3.spawnSync)("pi", ["list"], { encoding: "utf8", stdio: "pipe" });
44731
44852
  if (result.status !== 0) return [];
@@ -44860,6 +44981,20 @@ function renderPiRuntimePlan(plan) {
44860
44981
  console.log(kleur_default.yellow(" \u26A0 Missing required items.\n"));
44861
44982
  }
44862
44983
  }
44984
+ function getProjectRequiredPackageStatuses(installedPkgIds) {
44985
+ return PROJECT_REQUIRED_PACKAGE_IDS.map((packageId) => {
44986
+ const managed = MANAGED_PACKAGES.find((pkg2) => pkg2.id === packageId);
44987
+ const pkg = managed ?? {
44988
+ id: PROJECT_EXTENSION_PACKAGE_ID,
44989
+ displayName: "@jaggerxtrm/pi-extensions",
44990
+ required: true
44991
+ };
44992
+ return {
44993
+ pkg,
44994
+ installed: installedPkgIds.includes(packageId)
44995
+ };
44996
+ });
44997
+ }
44863
44998
  function mergePiSyncResults(base, incoming) {
44864
44999
  return {
44865
45000
  extensionsAdded: [...base.extensionsAdded, ...incoming.extensionsAdded],
@@ -44880,10 +45015,63 @@ async function isPackagePresentInPiAgent(agentDir, piPackageId) {
44880
45015
  const packageDir = import_path4.default.join(agentDir, "npm", "node_modules", npmPackageName);
44881
45016
  return import_fs_extra9.default.pathExists(packageDir);
44882
45017
  }
44883
- async function ensureAlwaysGlobalPiPackages(dryRun, log, agentDir = PI_AGENT_DIR, installRunner = (piPackageId) => {
44884
- const installResult = (0, import_child_process3.spawnSync)("pi", ["install", piPackageId], { stdio: "pipe", encoding: "utf8" });
44885
- return installResult.status;
44886
- }) {
45018
+ var NPMJS_REGISTRY_URL = "https://registry.npmjs.org";
45019
+ function runPiPackageInstall(piPackageId, env2) {
45020
+ const installResult = (0, import_child_process3.spawnSync)("pi", ["install", piPackageId], {
45021
+ stdio: "pipe",
45022
+ encoding: "utf8",
45023
+ env: env2
45024
+ });
45025
+ return {
45026
+ status: installResult.status,
45027
+ stdout: installResult.stdout ?? "",
45028
+ stderr: installResult.stderr ?? ""
45029
+ };
45030
+ }
45031
+ function getPiPackageInstallOutput(result) {
45032
+ return `${result.stdout}
45033
+ ${result.stderr}`.trim();
45034
+ }
45035
+ function buildNpmjsRegistryEnv(baseEnv = process.env) {
45036
+ return {
45037
+ ...baseEnv,
45038
+ NPM_CONFIG_REGISTRY: NPMJS_REGISTRY_URL,
45039
+ npm_config_registry: NPMJS_REGISTRY_URL
45040
+ };
45041
+ }
45042
+ function shouldRetryPiInstallViaNpmjs(piPackageId, output) {
45043
+ if (piPackageId !== PROJECT_EXTENSION_PACKAGE_ID) return false;
45044
+ const normalizedOutput = output.toLowerCase();
45045
+ return normalizedOutput.includes("npmmirror") && normalizedOutput.includes("404");
45046
+ }
45047
+ function getPiPackageInstallFailureHint(piPackageId, output) {
45048
+ if (!shouldRetryPiInstallViaNpmjs(piPackageId, output)) {
45049
+ return [];
45050
+ }
45051
+ return [
45052
+ `detected registry mirror 404 for ${piPackageId}`,
45053
+ `best fix: npm config set @jaggerxtrm:registry ${NPMJS_REGISTRY_URL}`
45054
+ ];
45055
+ }
45056
+ function installPiPackageWithFallback(piPackageId, log, installRunner = runPiPackageInstall) {
45057
+ const initialResult = installRunner(piPackageId);
45058
+ const initialOutput = getPiPackageInstallOutput(initialResult);
45059
+ if ((initialResult.status ?? 1) === 0) {
45060
+ return { status: initialResult.status, output: initialOutput, retriedWithNpmjs: false };
45061
+ }
45062
+ if (!shouldRetryPiInstallViaNpmjs(piPackageId, initialOutput)) {
45063
+ return { status: initialResult.status, output: initialOutput, retriedWithNpmjs: false };
45064
+ }
45065
+ log?.(kleur_default.dim(`Detected npmmirror 404 for ${piPackageId}; retrying via ${NPMJS_REGISTRY_URL}`));
45066
+ const retriedResult = installRunner(piPackageId, buildNpmjsRegistryEnv());
45067
+ const retriedOutput = getPiPackageInstallOutput(retriedResult);
45068
+ return {
45069
+ status: retriedResult.status,
45070
+ output: [initialOutput, retriedOutput].filter(Boolean).join("\n"),
45071
+ retriedWithNpmjs: true
45072
+ };
45073
+ }
45074
+ async function ensureAlwaysGlobalPiPackages(dryRun, log, agentDir = PI_AGENT_DIR, installRunner = runPiPackageInstall) {
44887
45075
  const installed = [];
44888
45076
  const failed = [];
44889
45077
  const packagesToEnsure = MANAGED_PACKAGES.filter((pkg) => ALWAYS_GLOBAL_INSTALL_PACKAGE_IDS.has(pkg.id));
@@ -44895,14 +45083,17 @@ async function ensureAlwaysGlobalPiPackages(dryRun, log, agentDir = PI_AGENT_DIR
44895
45083
  log?.(`[DRY RUN] pi install ${pkg.id}`);
44896
45084
  continue;
44897
45085
  }
44898
- const installStatus = installRunner(pkg.id);
44899
- if (installStatus === 0) {
45086
+ const installAttempt = installPiPackageWithFallback(pkg.id, log, installRunner);
45087
+ if (installAttempt.status === 0) {
44900
45088
  installed.push(pkg.id);
44901
- log?.(`${sym.ok} ${pkg.displayName} (global)`);
45089
+ log?.(`${sym.ok} ${pkg.displayName} (global${installAttempt.retriedWithNpmjs ? ", npmjs fallback" : ""})`);
44902
45090
  continue;
44903
45091
  }
44904
45092
  failed.push(pkg.id);
44905
45093
  log?.(kleur_default.yellow(`\u26A0 ${pkg.displayName} \u2014 global install failed`));
45094
+ for (const hint of getPiPackageInstallFailureHint(pkg.id, installAttempt.output)) {
45095
+ log?.(kleur_default.yellow(` \u2192 ${hint}`));
45096
+ }
44906
45097
  }
44907
45098
  return { installed, failed };
44908
45099
  }
@@ -45042,30 +45233,90 @@ async function cleanupLegacyProjectExtensionCopies(projectRoot, dryRun, log) {
45042
45233
  }
45043
45234
  return { removed, failed };
45044
45235
  }
45236
+ function normalizeStringArray(value) {
45237
+ if (!Array.isArray(value)) return [];
45238
+ return value.filter((entry) => typeof entry === "string");
45239
+ }
45240
+ function pruneConflictingPiPackageEntries(entries) {
45241
+ const kept = [];
45242
+ const removed = [];
45243
+ for (const entry of entries) {
45244
+ if (CONFLICTING_PI_PACKAGE_IDS.has(entry)) {
45245
+ removed.push(entry);
45246
+ continue;
45247
+ }
45248
+ kept.push(entry);
45249
+ }
45250
+ return { kept, removed };
45251
+ }
45252
+ async function pruneConflictingPiPackagesFromSettings(settingsPath, scopeLabel, dryRun, log) {
45253
+ if (!await import_fs_extra9.default.pathExists(settingsPath)) {
45254
+ return [];
45255
+ }
45256
+ let existingSettings = {};
45257
+ try {
45258
+ existingSettings = await import_fs_extra9.default.readJson(settingsPath);
45259
+ } catch {
45260
+ return [];
45261
+ }
45262
+ const existingPackages = normalizeStringArray(existingSettings.packages);
45263
+ const { kept, removed } = pruneConflictingPiPackageEntries(existingPackages);
45264
+ if (removed.length === 0) {
45265
+ return [];
45266
+ }
45267
+ if (dryRun) {
45268
+ log?.(kleur_default.dim(`[DRY RUN] would remove conflicting Pi package(s) from ${scopeLabel}: ${removed.join(", ")}`));
45269
+ return removed;
45270
+ }
45271
+ await import_fs_extra9.default.writeJson(settingsPath, { ...existingSettings, packages: kept }, { spaces: 2 });
45272
+ log?.(kleur_default.dim(`Removed conflicting Pi package(s) from ${scopeLabel}: ${removed.join(", ")}`));
45273
+ return removed;
45274
+ }
45275
+ async function cleanupConflictingPiPackageSettings(projectRoot, dryRun, log) {
45276
+ await pruneConflictingPiPackagesFromSettings(
45277
+ import_path4.default.join(PI_AGENT_DIR, "settings.json"),
45278
+ "~/.pi/agent/settings.json",
45279
+ dryRun,
45280
+ log
45281
+ );
45282
+ await pruneConflictingPiPackagesFromSettings(
45283
+ import_path4.default.join(projectRoot, ".pi", "settings.json"),
45284
+ `${projectRoot}/.pi/settings.json`,
45285
+ dryRun,
45286
+ log
45287
+ );
45288
+ }
45045
45289
  async function updatePiSettings(projectRoot, dryRun, log) {
45046
- const piSettingsPath = import_path4.default.join(projectRoot, ".pi", "settings.json");
45290
+ const piDirPath = import_path4.default.join(projectRoot, ".pi");
45291
+ const piSettingsPath = import_path4.default.join(piDirPath, "settings.json");
45047
45292
  if (dryRun) {
45048
- log?.(kleur_default.dim(`[DRY RUN] would update .pi/settings.json`));
45293
+ log?.(kleur_default.dim(`[DRY RUN] would ensure .pi/settings.json with ${PROJECT_EXTENSION_PACKAGE_ID}`));
45049
45294
  return;
45050
45295
  }
45296
+ await import_fs_extra9.default.ensureDir(piDirPath);
45051
45297
  let existingSettings = {};
45052
45298
  try {
45053
45299
  existingSettings = await import_fs_extra9.default.readJson(piSettingsPath);
45054
45300
  } catch {
45055
- }
45056
- const existingPackages = (existingSettings.packages ?? []).filter(
45057
- (p) => !p.startsWith("./extensions/")
45058
- );
45059
- await import_fs_extra9.default.ensureDir(import_path4.default.join(projectRoot, ".pi"));
45060
- await import_fs_extra9.default.writeJson(piSettingsPath, {
45301
+ existingSettings = {};
45302
+ }
45303
+ const LEGACY_PACKAGE_IDS = /* @__PURE__ */ new Set(["npm:@xtrm/pi-extensions", "./extensions/"]);
45304
+ const existingProjectPackages = normalizeStringArray(existingSettings.packages).filter((entry) => !LEGACY_PACKAGE_IDS.has(entry) && !entry.startsWith("./extensions/"));
45305
+ const { kept: existingPackages } = pruneConflictingPiPackageEntries(existingProjectPackages);
45306
+ if (!existingPackages.includes(PROJECT_EXTENSION_PACKAGE_ID)) {
45307
+ existingPackages.push(PROJECT_EXTENSION_PACKAGE_ID);
45308
+ }
45309
+ const existingSkills = normalizeStringArray(existingSettings.skills).filter((entry) => entry !== PROJECT_SKILLS_ENTRY);
45310
+ existingSkills.unshift(PROJECT_SKILLS_ENTRY);
45311
+ const existingExtensions = normalizeStringArray(existingSettings.extensions).filter((entry) => !LEGACY_PROJECT_EXTENSION_ENTRIES.has(entry));
45312
+ const nextSettings = {
45061
45313
  ...existingSettings,
45062
- extensions: [],
45063
- // Empty = Pi uses global ~/.pi/agent/extensions/
45064
- skills: [PROJECT_SKILLS_ENTRY],
45065
- packages: []
45066
- // Empty = packages installed globally at ~/.pi/agent/settings.json
45067
- }, { spaces: 2 });
45068
- log?.(kleur_default.dim(`Updated .pi/settings.json \u2192 global extensions + packages + .xtrm/skills/active/pi`));
45314
+ extensions: existingExtensions,
45315
+ skills: existingSkills,
45316
+ packages: existingPackages
45317
+ };
45318
+ await import_fs_extra9.default.writeJson(piSettingsPath, nextSettings, { spaces: 2 });
45319
+ log?.(kleur_default.dim(`Updated .pi/settings.json \u2192 ${PROJECT_EXTENSION_PACKAGE_ID} + ${PROJECT_SKILLS_ENTRY}`));
45069
45320
  }
45070
45321
  async function executePiSync(plan, sourceDir, targetDir, opts = {}) {
45071
45322
  const {
@@ -45135,13 +45386,16 @@ async function executePiSync(plan, sourceDir, targetDir, opts = {}) {
45135
45386
  continue;
45136
45387
  }
45137
45388
  try {
45138
- const r = (0, import_child_process3.spawnSync)("pi", installArgs, { stdio: "pipe", encoding: "utf8" });
45139
- if (r.status === 0) {
45389
+ const installAttempt = installPiPackageWithFallback(pkg.id, log);
45390
+ if (installAttempt.status === 0) {
45140
45391
  result.packagesInstalled.push(pkg.id);
45141
- log(`${sym.ok} ${pkg.displayName}`);
45392
+ log(`${sym.ok} ${pkg.displayName}${installAttempt.retriedWithNpmjs ? " (npmjs fallback)" : ""}`);
45142
45393
  } else {
45143
45394
  result.failed.push(pkg.id);
45144
45395
  log(kleur_default.yellow(`\u26A0 ${pkg.displayName} \u2014 install failed`));
45396
+ for (const hint of getPiPackageInstallFailureHint(pkg.id, installAttempt.output)) {
45397
+ log(kleur_default.yellow(` \u2192 ${hint}`));
45398
+ }
45145
45399
  }
45146
45400
  } catch (err) {
45147
45401
  result.failed.push(pkg.id);
@@ -45150,114 +45404,20 @@ async function executePiSync(plan, sourceDir, targetDir, opts = {}) {
45150
45404
  }
45151
45405
  return result;
45152
45406
  }
45153
- var EXTENSION_SOURCE_DIR = "ext-src";
45154
- async function linkExtensionsToGlobal(repoRoot, dryRun = false, log = (msg) => console.log(kleur_default.dim(` ${msg}`))) {
45155
- const globalExtDir = import_path4.default.join(PI_AGENT_DIR, "extensions");
45156
- const repoExtDir = import_path4.default.join(repoRoot, ".xtrm", EXTENSION_SOURCE_DIR);
45157
- const linked = [];
45158
- const failed = [];
45159
- if (!await import_fs_extra9.default.pathExists(repoExtDir)) {
45160
- log("No .xtrm/ext-src/ found \u2014 skipping global link");
45161
- return { linked, failed };
45162
- }
45163
- if (dryRun) {
45164
- log("[DRY RUN] would create extension symlinks in ~/.pi/agent/extensions/");
45165
- return { linked, failed };
45166
- }
45167
- await import_fs_extra9.default.ensureDir(globalExtDir);
45168
- const coreNodeModulesDir = import_path4.default.join(globalExtDir, "node_modules", "@xtrm");
45169
- const coreSymlinkPath = import_path4.default.join(coreNodeModulesDir, "pi-core");
45170
- const coreRelativeTarget = import_path4.default.join("..", "..", "core");
45171
- try {
45172
- await import_fs_extra9.default.ensureDir(coreNodeModulesDir);
45173
- const existing = await import_fs_extra9.default.lstat(coreSymlinkPath).catch(() => null);
45174
- if (existing) await import_fs_extra9.default.remove(coreSymlinkPath);
45175
- await import_fs_extra9.default.symlink(coreRelativeTarget, coreSymlinkPath);
45176
- log("\u2713 @xtrm/pi-core \u2192 global node_modules");
45177
- } catch (err) {
45178
- log(kleur_default.yellow(`\u26A0 @xtrm/pi-core symlink: ${err}`));
45179
- }
45180
- const entries = await import_fs_extra9.default.readdir(repoExtDir, { withFileTypes: true });
45181
- for (const entry of entries) {
45182
- if (!entry.isDirectory()) continue;
45183
- if (entry.name === "node_modules") continue;
45184
- const extPath = import_path4.default.join(repoExtDir, entry.name);
45185
- const globalLink = import_path4.default.join(globalExtDir, entry.name);
45186
- try {
45187
- const existing = await import_fs_extra9.default.lstat(globalLink).catch(() => null);
45188
- if (existing) {
45189
- if (existing.isSymbolicLink()) {
45190
- const currentTarget = await import_fs_extra9.default.readlink(globalLink);
45191
- const resolvedTarget = import_path4.default.resolve(import_path4.default.dirname(globalLink), currentTarget);
45192
- if (resolvedTarget === import_path4.default.resolve(extPath)) {
45193
- continue;
45194
- }
45195
- }
45196
- await import_fs_extra9.default.remove(globalLink);
45197
- }
45198
- await import_fs_extra9.default.symlink(extPath, globalLink);
45199
- linked.push(entry.name);
45200
- log(`\u2713 ${entry.name} \u2192 .xtrm/${EXTENSION_SOURCE_DIR}/${entry.name}`);
45201
- } catch (err) {
45202
- failed.push(entry.name);
45203
- log(kleur_default.red(`\u2717 ${entry.name}: ${err}`));
45204
- }
45205
- }
45206
- return { linked, failed };
45207
- }
45208
- async function ensureNpmPackageExtensionSymlinks(log) {
45209
- const os9 = require("os");
45210
- const homeDir = os9.homedir();
45211
- const extensionsDir = import_path4.default.join(homeDir, ".pi", "agent", "extensions");
45212
- await import_fs_extra9.default.ensureDir(extensionsDir);
45213
- const npmPrefix = (0, import_child_process3.spawnSync)("npm", ["prefix", "-g"], { encoding: "utf8" }).stdout.trim();
45214
- const globalNodeModules = import_path4.default.join(npmPrefix, "lib", "node_modules");
45215
- const npmPackages = [
45216
- { packageName: "pi-gitnexus", symlinkName: "gitnexus" },
45217
- { packageName: "pi-serena-tools", symlinkName: "serena" }
45218
- ];
45219
- for (const { packageName, symlinkName } of npmPackages) {
45220
- const packagePath = import_path4.default.join(globalNodeModules, packageName);
45221
- const symlinkPath = import_path4.default.join(extensionsDir, symlinkName);
45222
- const packageExists = await import_fs_extra9.default.pathExists(packagePath);
45223
- if (!packageExists) {
45224
- log?.(kleur_default.yellow(` \u26A0 ${packageName} not found in ${globalNodeModules}, skipping symlink`));
45225
- continue;
45226
- }
45227
- const symlinkExists = await import_fs_extra9.default.lstat(symlinkPath).catch(() => null);
45228
- if (symlinkExists?.isSymbolicLink()) {
45229
- const currentTarget = await import_fs_extra9.default.readlink(symlinkPath);
45230
- const resolvedTarget = import_path4.default.resolve(extensionsDir, currentTarget);
45231
- if (resolvedTarget === packagePath) {
45232
- log?.(kleur_default.dim(` \u2713 ${symlinkName} symlink already correct`));
45233
- continue;
45234
- }
45235
- log?.(kleur_default.dim(` Removing stale ${symlinkName} symlink`));
45236
- await import_fs_extra9.default.remove(symlinkPath);
45237
- } else if (symlinkExists) {
45238
- log?.(kleur_default.dim(` Removing stale ${symlinkName} (not a symlink)`));
45239
- await import_fs_extra9.default.remove(symlinkPath);
45240
- }
45241
- const relativeTarget = import_path4.default.relative(extensionsDir, packagePath);
45242
- await import_fs_extra9.default.symlink(relativeTarget, symlinkPath);
45243
- log?.(kleur_default.dim(` Created ${symlinkName} symlink \u2192 ${relativeTarget}`));
45244
- }
45245
- }
45246
45407
  async function runPiRuntimeSync(opts = {}) {
45247
45408
  const { dryRun = false, isGlobal = false, projectRoot } = opts;
45248
45409
  const pkgRoot = resolvePkgRoot();
45249
- const sourceDir = import_path4.default.join(pkgRoot, ".xtrm", "extensions");
45410
+ const sourceDir = resolveManagedPiExtensionsSourceDir(pkgRoot);
45250
45411
  const resolvedProjectRoot = projectRoot || process.cwd();
45251
45412
  const log = (msg) => console.log(kleur_default.dim(` ${msg}`));
45252
- const emptyResult = {
45413
+ const result = {
45253
45414
  extensionsAdded: [],
45254
45415
  extensionsUpdated: [],
45255
45416
  extensionsRemoved: [],
45256
45417
  packagesInstalled: [],
45257
45418
  failed: []
45258
45419
  };
45259
- const result = { ...emptyResult };
45260
- if (!await import_fs_extra9.default.pathExists(sourceDir)) {
45420
+ if (!sourceDir || !await import_fs_extra9.default.pathExists(sourceDir)) {
45261
45421
  console.log(kleur_default.dim("\n Managed extensions: skipped (not bundled in npm package)\n"));
45262
45422
  return result;
45263
45423
  }
@@ -45265,6 +45425,7 @@ async function runPiRuntimeSync(opts = {}) {
45265
45425
  if (preflight.staleOverride.remediated) {
45266
45426
  result.extensionsRemoved.push("pi-mcp-adapter");
45267
45427
  }
45428
+ await cleanupConflictingPiPackageSettings(resolvedProjectRoot, dryRun, log);
45268
45429
  if (isGlobal) {
45269
45430
  const targetDir = import_path4.default.join(PI_AGENT_DIR, "extensions");
45270
45431
  const plan = await inventoryPiRuntime(sourceDir, targetDir);
@@ -45277,32 +45438,47 @@ async function runPiRuntimeSync(opts = {}) {
45277
45438
  });
45278
45439
  return mergePiSyncResults(result, synced);
45279
45440
  }
45280
- const linkResult = await linkExtensionsToGlobal(resolvedProjectRoot, dryRun, log);
45281
- result.extensionsAdded.push(...linkResult.linked);
45282
- result.failed.push(...linkResult.failed);
45283
- await ensureNpmPackageExtensionSymlinks(log);
45284
45441
  const installedPkgIds = getInstalledPiPackages();
45285
- const packageStatuses = [];
45286
- const missingPackages = [];
45287
- for (const pkg of MANAGED_PACKAGES) {
45288
- const isInstalled = installedPkgIds.includes(pkg.id);
45289
- const status = { pkg, installed: isInstalled };
45290
- packageStatuses.push(status);
45291
- if (!isInstalled) missingPackages.push(status);
45292
- }
45442
+ const packageStatuses = getProjectRequiredPackageStatuses(installedPkgIds);
45443
+ const missingPackages = packageStatuses.filter((status) => !status.installed);
45293
45444
  console.log(kleur_default.bold("\n Pi Runtime"));
45294
45445
  console.log(kleur_default.dim(" " + "-".repeat(50)));
45295
- console.log(kleur_default.dim(` Extensions: ~/.pi/agent/extensions/ \u2192 .xtrm/${EXTENSION_SOURCE_DIR}/`));
45296
- const pkgOk = packageStatuses.filter((s) => s.installed).length;
45446
+ const extensionPackageInstalled = packageStatuses.some(
45447
+ (status) => status.pkg.id === PROJECT_EXTENSION_PACKAGE_ID && status.installed
45448
+ );
45449
+ console.log(kleur_default.dim(` Extensions: ${extensionPackageInstalled ? "package installed" : "package missing"} (${PROJECT_EXTENSION_PACKAGE_ID})`));
45450
+ const pkgOk = packageStatuses.filter((status) => status.installed).length;
45297
45451
  console.log(kleur_default.dim(` Packages: ${pkgOk}/${packageStatuses.length} installed`));
45298
45452
  if (missingPackages.length > 0) {
45299
- const names = missingPackages.map((s) => s.pkg.displayName).join(", ");
45453
+ const names = missingPackages.map((status) => status.pkg.displayName).join(", ");
45300
45454
  console.log(kleur_default.yellow(` Missing: ${names}`));
45301
45455
  }
45302
45456
  console.log(kleur_default.dim(" " + "-".repeat(50)));
45303
45457
  const legacyCleanup = await cleanupLegacyProjectExtensionCopies(resolvedProjectRoot, dryRun, log);
45304
45458
  result.extensionsRemoved.push(...legacyCleanup.removed);
45305
45459
  result.failed.push(...legacyCleanup.failed);
45460
+ const globalExtDir = import_path4.default.join(PI_AGENT_DIR, "extensions");
45461
+ if (await import_fs_extra9.default.pathExists(globalExtDir)) {
45462
+ const MANAGED_EXT_IDS = new Set(MANAGED_EXTENSIONS.map((e) => e.id));
45463
+ const STALE_SYMLINKS = /* @__PURE__ */ new Set([...MANAGED_EXT_IDS, "core", "gitnexus", "serena"]);
45464
+ const globalEntries = await import_fs_extra9.default.readdir(globalExtDir, { withFileTypes: true });
45465
+ for (const entry of globalEntries) {
45466
+ if (entry.isSymbolicLink() && STALE_SYMLINKS.has(entry.name)) {
45467
+ if (!dryRun) {
45468
+ await import_fs_extra9.default.remove(import_path4.default.join(globalExtDir, entry.name));
45469
+ }
45470
+ result.extensionsRemoved.push(entry.name);
45471
+ log(`Removed stale global symlink: ${entry.name}`);
45472
+ }
45473
+ }
45474
+ const staleNodeModules = import_path4.default.join(globalExtDir, "node_modules");
45475
+ if (await import_fs_extra9.default.pathExists(staleNodeModules)) {
45476
+ if (!dryRun) {
45477
+ await import_fs_extra9.default.remove(staleNodeModules);
45478
+ }
45479
+ log("Removed stale global extensions/node_modules");
45480
+ }
45481
+ }
45306
45482
  for (const status of missingPackages) {
45307
45483
  const { pkg } = status;
45308
45484
  if (dryRun) {
@@ -45310,13 +45486,16 @@ async function runPiRuntimeSync(opts = {}) {
45310
45486
  continue;
45311
45487
  }
45312
45488
  try {
45313
- const r = (0, import_child_process3.spawnSync)("pi", ["install", pkg.id], { stdio: "pipe", encoding: "utf8" });
45314
- if (r.status === 0) {
45489
+ const installAttempt = installPiPackageWithFallback(pkg.id, log);
45490
+ if (installAttempt.status === 0) {
45315
45491
  result.packagesInstalled.push(pkg.id);
45316
- log(`${sym.ok} ${pkg.displayName}`);
45317
- } else {
45318
- result.failed.push(pkg.id);
45319
- log(kleur_default.yellow(`\u26A0 ${pkg.displayName} \u2014 install failed`));
45492
+ log(`${sym.ok} ${pkg.displayName}${installAttempt.retriedWithNpmjs ? " (npmjs fallback)" : ""}`);
45493
+ continue;
45494
+ }
45495
+ result.failed.push(pkg.id);
45496
+ log(kleur_default.yellow(`\u26A0 ${pkg.displayName} \u2014 install failed`));
45497
+ for (const hint of getPiPackageInstallFailureHint(pkg.id, installAttempt.output)) {
45498
+ log(kleur_default.yellow(` \u2192 ${hint}`));
45320
45499
  }
45321
45500
  } catch (err) {
45322
45501
  result.failed.push(pkg.id);
@@ -45338,12 +45517,8 @@ async function runPiRuntimeSync(opts = {}) {
45338
45517
  }
45339
45518
  }
45340
45519
  await updatePiSettings(resolvedProjectRoot, dryRun, log);
45341
- const requiredFailed = missingPackages.filter(
45342
- (s) => s.pkg.required && result.failed.includes(s.pkg.id)
45343
- );
45344
- if (missingPackages.length === 0 || result.failed.length === 0) {
45345
- console.log(t.success(" \u2713 All required items present.\n"));
45346
- } else if (requiredFailed.length === 0) {
45520
+ const requiredFailed = missingPackages.filter((status) => status.pkg.required && result.failed.includes(status.pkg.id));
45521
+ if (requiredFailed.length === 0) {
45347
45522
  console.log(t.success(" \u2713 All required items present.\n"));
45348
45523
  } else {
45349
45524
  console.log(kleur_default.yellow(" \u26A0 Missing required items.\n"));
@@ -45580,21 +45755,21 @@ function hasSettingsEntry(entries, expectedEntry) {
45580
45755
  });
45581
45756
  }
45582
45757
  async function getPiProjectPointer(projectRoot) {
45583
- const projectExtensionsDir = import_path7.default.join(projectRoot, ".xtrm", "extensions");
45584
45758
  const settingsPath = import_path7.default.join(projectRoot, ".pi", "settings.json");
45585
- const hasProjectExtensions = await import_fs_extra11.default.pathExists(projectExtensionsDir);
45586
45759
  const hasSettingsFile = await import_fs_extra11.default.pathExists(settingsPath);
45587
45760
  if (!hasSettingsFile) {
45588
- return { hasProjectExtensions, pointsToXtrmExtensions: false };
45761
+ return { hasProjectSettings: false, hasProjectExtensionPackage: false, pointsToXtrmExtensions: false };
45589
45762
  }
45590
45763
  try {
45591
45764
  const settings = await import_fs_extra11.default.readJson(settingsPath);
45765
+ const packageEntries = Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
45592
45766
  return {
45593
- hasProjectExtensions,
45767
+ hasProjectSettings: true,
45768
+ hasProjectExtensionPackage: packageEntries.includes("npm:@jaggerxtrm/pi-extensions"),
45594
45769
  pointsToXtrmExtensions: hasSettingsEntry(settings.extensions, "../.xtrm/extensions")
45595
45770
  };
45596
45771
  } catch {
45597
- return { hasProjectExtensions, pointsToXtrmExtensions: false };
45772
+ return { hasProjectSettings: true, hasProjectExtensionPackage: false, pointsToXtrmExtensions: false };
45598
45773
  }
45599
45774
  }
45600
45775
  function createPiCommand() {
@@ -45618,31 +45793,29 @@ function createPiCommand() {
45618
45793
  const projectRoot = resolveProjectRoot();
45619
45794
  const pointer = await getPiProjectPointer(projectRoot);
45620
45795
  const bundleRoot = await findRepoRoot();
45621
- const sourceDir = import_path7.default.join(bundleRoot, ".xtrm", "extensions");
45796
+ const sourceDir = resolveManagedPiExtensionsSourceDir(bundleRoot);
45622
45797
  const globalTargetDir = import_path7.default.join(PI_AGENT_DIR4, "extensions");
45623
- if (!await import_fs_extra11.default.pathExists(sourceDir)) {
45798
+ if (!sourceDir || !await import_fs_extra11.default.pathExists(sourceDir)) {
45624
45799
  console.log(kleur_default.dim(" \u25CB managed extensions not bundled in this install\n"));
45625
45800
  return;
45626
45801
  }
45627
45802
  const plan = await inventoryPiRuntime(sourceDir, globalTargetDir);
45628
45803
  const pkgOk = plan.packages.filter((s) => s.installed).length;
45629
- if (pointer.hasProjectExtensions && pointer.pointsToXtrmExtensions) {
45804
+ const projectScoped = pointer.hasProjectExtensionPackage || pointer.pointsToXtrmExtensions;
45805
+ if (projectScoped) {
45630
45806
  console.log(kleur_default.dim(" Scope: project"));
45631
- console.log(kleur_default.dim(" Extensions: via .xtrm/extensions (.pi/settings.json)"));
45807
+ console.log(kleur_default.dim(` Extensions: package mode (npm:@jaggerxtrm/pi-extensions${pointer.hasProjectExtensionPackage ? "" : " missing"})`));
45632
45808
  } else {
45633
45809
  console.log(kleur_default.dim(" Scope: global"));
45634
45810
  const extOk = plan.extensions.filter((s) => s.installed && !s.stale).length;
45635
45811
  console.log(kleur_default.dim(` Extensions: ${extOk}/${plan.extensions.length} up-to-date`));
45636
45812
  }
45637
45813
  console.log(kleur_default.dim(` Packages: ${pkgOk}/${plan.packages.length} installed`));
45638
- if (pointer.hasProjectExtensions && !pointer.pointsToXtrmExtensions) {
45639
- console.log(kleur_default.yellow(" Settings: .pi/settings.json is not pointing to ../.xtrm/extensions"));
45640
- }
45641
45814
  if (plan.missingPackages.length > 0) {
45642
45815
  const names = plan.missingPackages.map((s) => s.pkg.displayName).join(", ");
45643
45816
  console.log(kleur_default.yellow(` Packages: ${names}`));
45644
45817
  }
45645
- if (!pointer.pointsToXtrmExtensions) {
45818
+ if (!projectScoped) {
45646
45819
  if (plan.missingExtensions.length > 0) {
45647
45820
  const names = plan.missingExtensions.map((s) => s.ext.displayName).join(", ");
45648
45821
  console.log(kleur_default.yellow(` Missing: ${names}`));
@@ -45655,13 +45828,16 @@ function createPiCommand() {
45655
45828
  console.log(kleur_default.red(` Orphaned: ${plan.orphanedExtensions.join(", ")}`));
45656
45829
  }
45657
45830
  }
45658
- const projectModeOk = pointer.hasProjectExtensions ? pointer.pointsToXtrmExtensions : true;
45659
- const hasGlobalDrift = !pointer.pointsToXtrmExtensions && !plan.allPresent;
45831
+ const hasProjectSettingsDrift = !pointer.hasProjectSettings || !pointer.hasProjectExtensionPackage;
45832
+ const hasGlobalDrift = !projectScoped && !plan.allPresent;
45660
45833
  const hasPackageDrift = plan.missingPackages.length > 0;
45661
- if (projectModeOk && !hasGlobalDrift && !hasPackageDrift) {
45834
+ if (!hasProjectSettingsDrift && !hasGlobalDrift && !hasPackageDrift) {
45662
45835
  console.log(t.success("\n \u2713 Pi runtime configuration looks healthy\n"));
45663
45836
  return;
45664
45837
  }
45838
+ if (hasProjectSettingsDrift) {
45839
+ console.log(kleur_default.yellow(" Settings: .pi/settings.json missing managed npm:@jaggerxtrm/pi-extensions entry"));
45840
+ }
45665
45841
  console.log(kleur_default.dim("\n \u2192 run: xt pi reload\n"));
45666
45842
  });
45667
45843
  cmd.command("doctor").description("Diagnostic checks: pi installed, extensions deployed, packages present, orphaned extensions").action(async () => {
@@ -45692,7 +45868,8 @@ function createPiCommand() {
45692
45868
  const projectRoot = resolveProjectRoot();
45693
45869
  const pointer = await getPiProjectPointer(projectRoot);
45694
45870
  const bundleRoot = await findRepoRoot();
45695
- const sourceDir = import_path7.default.join(bundleRoot, ".xtrm", "extensions");
45871
+ const sourceDir = resolveManagedPiExtensionsSourceDir(bundleRoot);
45872
+ const coreSourceDir = resolveManagedPiCoreSourceDir(bundleRoot);
45696
45873
  const globalTargetDir = import_path7.default.join(PI_AGENT_DIR4, "extensions");
45697
45874
  try {
45698
45875
  const staleOverride = await remediateStalePiMcpAdapterOverride(false);
@@ -45709,7 +45886,7 @@ function createPiCommand() {
45709
45886
  allOk = false;
45710
45887
  }
45711
45888
  try {
45712
- const coreStatus = await ensureCorePackageSymlink(import_path7.default.join(sourceDir, "core"), projectRoot, false);
45889
+ const coreStatus = coreSourceDir ? await ensureCorePackageSymlink(coreSourceDir, projectRoot, false) : "missing-source";
45713
45890
  if (coreStatus === "repaired" || coreStatus === "created") {
45714
45891
  console.log(t.success(" \u2713 repaired .xtrm/extensions/node_modules/@xtrm/pi-core symlink"));
45715
45892
  } else if (coreStatus === "ok") {
@@ -45721,15 +45898,21 @@ function createPiCommand() {
45721
45898
  console.log(kleur_default.yellow(` \u26A0 failed to ensure @xtrm/pi-core symlink: ${error48}`));
45722
45899
  allOk = false;
45723
45900
  }
45724
- if (!await import_fs_extra11.default.pathExists(sourceDir)) {
45901
+ if (!sourceDir || !await import_fs_extra11.default.pathExists(sourceDir)) {
45725
45902
  console.log(kleur_default.dim(" \u25CB managed extensions not bundled in this install"));
45726
45903
  } else {
45727
45904
  const plan = await inventoryPiRuntime(sourceDir, globalTargetDir);
45728
- if (pointer.hasProjectExtensions && pointer.pointsToXtrmExtensions) {
45729
- console.log(t.success(" \u2713 project runtime points to .xtrm/extensions"));
45730
- } else if (pointer.hasProjectExtensions) {
45731
- console.log(kleur_default.yellow(" \u26A0 .pi/settings.json does not include ../.xtrm/extensions"));
45905
+ const projectScoped = pointer.hasProjectExtensionPackage || pointer.pointsToXtrmExtensions;
45906
+ if (!pointer.hasProjectSettings) {
45907
+ console.log(kleur_default.yellow(" \u26A0 missing .pi/settings.json; run xt pi reload to bootstrap project Pi settings"));
45732
45908
  allOk = false;
45909
+ } else if (projectScoped) {
45910
+ if (pointer.hasProjectExtensionPackage) {
45911
+ console.log(t.success(" \u2713 project runtime uses npm:@jaggerxtrm/pi-extensions"));
45912
+ } else {
45913
+ console.log(kleur_default.yellow(" \u26A0 legacy project extension pointer detected; run xt pi reload to migrate"));
45914
+ allOk = false;
45915
+ }
45733
45916
  } else if (plan.missingExtensions.length === 0 && plan.staleExtensions.length === 0 && plan.orphanedExtensions.length === 0) {
45734
45917
  console.log(t.success(` \u2713 global extensions deployed (${plan.extensions.length})`));
45735
45918
  } else {
@@ -45867,8 +46050,17 @@ async function scaffoldSkillsDefaultFromPackage(params) {
45867
46050
  const { packageRoot, userXtrmDir, dryRun } = params;
45868
46051
  const sourceDir = import_path8.default.join(packageRoot, ".xtrm", "skills", "default");
45869
46052
  const targetDir = import_path8.default.join(userXtrmDir, "skills", "default");
45870
- if (await import_fs_extra13.default.pathExists(targetDir)) {
45871
- return "noop";
46053
+ const stat = await import_fs_extra13.default.lstat(targetDir).catch(() => null);
46054
+ if (stat) {
46055
+ if (stat.isSymbolicLink()) {
46056
+ const isValid = await import_fs_extra13.default.pathExists(targetDir);
46057
+ if (isValid) {
46058
+ return "noop";
46059
+ }
46060
+ await import_fs_extra13.default.remove(targetDir);
46061
+ } else {
46062
+ return "noop";
46063
+ }
45872
46064
  }
45873
46065
  if (dryRun) {
45874
46066
  return "noop";
@@ -45935,12 +46127,16 @@ async function installFromRegistry(params) {
45935
46127
  installed: 0,
45936
46128
  upToDate: drift.upToDate.length,
45937
46129
  driftedSkipped: drift.drifted.length,
45938
- forced: 0
46130
+ forced: 0,
46131
+ expectedInstalls: 0,
46132
+ missingSourceSkipped: 0
45939
46133
  };
45940
46134
  }
45941
46135
  }
45942
46136
  let installed = 0;
45943
46137
  let forced = 0;
46138
+ let expectedInstalls = 0;
46139
+ let missingSourceSkipped = 0;
45944
46140
  for (const asset of Object.values(registry2.assets)) {
45945
46141
  for (const [filePath] of Object.entries(asset.files)) {
45946
46142
  const relativePath = toUserRelativePath(asset.source_dir, filePath);
@@ -45963,6 +46159,13 @@ async function installFromRegistry(params) {
45963
46159
  if (isDrifted && force) {
45964
46160
  forced += 1;
45965
46161
  }
46162
+ expectedInstalls += 1;
46163
+ const sourceExists = await import_fs_extra13.default.pathExists(sourcePath);
46164
+ if (!sourceExists) {
46165
+ missingSourceSkipped += 1;
46166
+ console.log(kleur_default.yellow(` \u26A0 Skipping missing source file: ${toPosix2(import_path8.default.relative(packageRoot, sourcePath))}`));
46167
+ continue;
46168
+ }
45966
46169
  if (dryRun) {
45967
46170
  const action = isDrifted ? "overwrite" : "install";
45968
46171
  console.log(kleur_default.dim(` [DRY RUN] would ${action} ${relativePath}`));
@@ -45978,7 +46181,9 @@ async function installFromRegistry(params) {
45978
46181
  installed,
45979
46182
  upToDate: upToDateSet.size,
45980
46183
  driftedSkipped: force ? 0 : driftedSet.size,
45981
- forced
46184
+ forced,
46185
+ expectedInstalls,
46186
+ missingSourceSkipped
45982
46187
  };
45983
46188
  }
45984
46189
 
@@ -46496,9 +46701,8 @@ function verifyClaudeRuntime(projectRoot) {
46496
46701
  return fallbackResult;
46497
46702
  }
46498
46703
  async function verifyPiRuntime(projectRoot) {
46499
- const pkgRoot = resolvePkgRoot2();
46500
- const sourceDir = import_path10.default.join(pkgRoot, ".xtrm", "extensions");
46501
- if (!await import_fs_extra16.default.pathExists(sourceDir)) {
46704
+ const sourceDir = resolveManagedPiExtensionsSourceDir();
46705
+ if (!sourceDir || !await import_fs_extra16.default.pathExists(sourceDir)) {
46502
46706
  return {
46503
46707
  extensions: [],
46504
46708
  packages: [],
@@ -46524,16 +46728,6 @@ ${gnStatus.stderr ?? ""}`.toLowerCase();
46524
46728
  const instructionHeaders = agentsMd || claudeMd;
46525
46729
  return { beadsInitialized, gitnexusIndexed, instructionHeaders };
46526
46730
  }
46527
- function resolvePkgRoot2() {
46528
- const candidates = [
46529
- import_path10.default.resolve(__dirname, "../.."),
46530
- import_path10.default.resolve(__dirname, "../../..")
46531
- ];
46532
- for (const c of candidates) {
46533
- if (import_fs_extra16.default.existsSync(import_path10.default.join(c, ".xtrm", "extensions"))) return c;
46534
- }
46535
- return candidates[0];
46536
- }
46537
46731
  async function runInitVerification(projectRoot) {
46538
46732
  const machinePlan = verifyMachineBootstrap();
46539
46733
  const claudeResult = verifyClaudeRuntime(projectRoot);
@@ -48555,7 +48749,7 @@ async function runProjectInit(opts = {}) {
48555
48749
  const userXtrmDir = ctx.targets[0];
48556
48750
  const registryPath = import_path15.default.join(packageRoot, ".xtrm", "registry.json");
48557
48751
  const registry2 = await import_fs_extra21.default.readJson(registryPath);
48558
- await installFromRegistry({
48752
+ const registryInstallStats = await installFromRegistry({
48559
48753
  packageRoot,
48560
48754
  registry: registry2,
48561
48755
  userXtrmDir,
@@ -48563,6 +48757,10 @@ async function runProjectInit(opts = {}) {
48563
48757
  force: false,
48564
48758
  yes: true
48565
48759
  });
48760
+ if (registryInstallStats.missingSourceSkipped > 0) {
48761
+ console.log(kleur_default.yellow(` \u26A0 Registry/source mismatch: skipped ${registryInstallStats.missingSourceSkipped} missing source file${registryInstallStats.missingSourceSkipped === 1 ? "" : "s"}.`));
48762
+ console.log(kleur_default.yellow(" Init continued, but some skills/files may be absent until registry payload is corrected."));
48763
+ }
48566
48764
  await scaffoldSkillsDefaultFromPackage({ packageRoot, userXtrmDir, dryRun: false });
48567
48765
  const mcpSync = await syncProjectMcpConfig(projectRoot, { preserveExistingFile: true });
48568
48766
  if (mcpSync.wroteFile) {