shortcutxl 0.3.57 → 0.3.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.58]
4
+
5
+ - **Improved update reliability** - ShortcutXL now shows clearer update errors when Windows blocks a global package upgrade.
6
+
3
7
  ## [0.3.57]
4
8
 
5
9
  - **Improved Windows reliability** - Improved ShortcutXL install, upgrade, and uninstall behavior on Windows.
package/dist/cli.js CHANGED
@@ -26528,7 +26528,7 @@ var require_snapshot_recorder = __commonJS({
26528
26528
  "../../node_modules/.pnpm/undici@7.25.0/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
26529
26529
  "use strict";
26530
26530
  var { writeFile: writeFile10, readFile: readFile11, mkdir: mkdir11 } = __require("node:fs/promises");
26531
- var { dirname: dirname37, resolve: resolve22 } = __require("node:path");
26531
+ var { dirname: dirname38, resolve: resolve22 } = __require("node:path");
26532
26532
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
26533
26533
  var { InvalidArgumentError, UndiciError } = require_errors();
26534
26534
  var { hashId, isUrlExcludedFactory, normalizeHeaders: normalizeHeaders2, createHeaderFilters } = require_snapshot_utils();
@@ -26759,7 +26759,7 @@ var require_snapshot_recorder = __commonJS({
26759
26759
  throw new InvalidArgumentError("Snapshot path is required");
26760
26760
  }
26761
26761
  const resolvedPath = resolve22(path21);
26762
- await mkdir11(dirname37(resolvedPath), { recursive: true });
26762
+ await mkdir11(dirname38(resolvedPath), { recursive: true });
26763
26763
  const data = Array.from(this.#snapshots.entries()).map(([hash2, snapshot]) => ({
26764
26764
  hash: hash2,
26765
26765
  snapshot
@@ -474819,13 +474819,46 @@ var init_shell = __esm({
474819
474819
  }
474820
474820
  });
474821
474821
 
474822
+ // src/startup/python-abi.ts
474823
+ import { existsSync as existsSync42, readdirSync as readdirSync12 } from "fs";
474824
+ function listVersionedPythonRuntimeFiles(pythonDir) {
474825
+ if (!existsSync42(pythonDir)) return /* @__PURE__ */ new Set();
474826
+ try {
474827
+ return new Set(
474828
+ readdirSync12(pythonDir).filter((entry) => VERSIONED_PYTHON_RUNTIME_FILE.test(entry)).map((entry) => entry.toLowerCase())
474829
+ );
474830
+ } catch {
474831
+ return /* @__PURE__ */ new Set();
474832
+ }
474833
+ }
474834
+ function hasVersionedPythonRuntimeFileDrift(sourcePythonDir, destPythonDir) {
474835
+ const sourceFiles = listVersionedPythonRuntimeFiles(sourcePythonDir);
474836
+ if (sourceFiles.size === 0) return false;
474837
+ const destFiles = listVersionedPythonRuntimeFiles(destPythonDir);
474838
+ if (destFiles.size === 0) return false;
474839
+ for (const file2 of destFiles) {
474840
+ if (!sourceFiles.has(file2)) return true;
474841
+ }
474842
+ for (const file2 of sourceFiles) {
474843
+ if (!destFiles.has(file2)) return true;
474844
+ }
474845
+ return false;
474846
+ }
474847
+ var VERSIONED_PYTHON_RUNTIME_FILE;
474848
+ var init_python_abi = __esm({
474849
+ "src/startup/python-abi.ts"() {
474850
+ "use strict";
474851
+ VERSIONED_PYTHON_RUNTIME_FILE = /^python\d{2,}(?:\.dll|\.zip|\._pth)$/i;
474852
+ }
474853
+ });
474854
+
474822
474855
  // src/startup/sync-xll.ts
474823
474856
  import { createHash as createHash3 } from "crypto";
474824
474857
  import {
474825
474858
  copyFileSync,
474826
- existsSync as existsSync42,
474859
+ existsSync as existsSync43,
474827
474860
  mkdirSync as mkdirSync20,
474828
- readdirSync as readdirSync12,
474861
+ readdirSync as readdirSync13,
474829
474862
  readFileSync as readFileSync33,
474830
474863
  rmSync as rmSync4,
474831
474864
  statSync as statSync11,
@@ -474841,7 +474874,7 @@ function stripMotw(filePath) {
474841
474874
  }
474842
474875
  }
474843
474876
  function filesMatch(src, dest) {
474844
- if (!existsSync42(dest)) return false;
474877
+ if (!existsSync43(dest)) return false;
474845
474878
  const srcStat = statSync11(src);
474846
474879
  const destStat = statSync11(dest);
474847
474880
  return srcStat.size === destStat.size && srcStat.mtimeMs === destStat.mtimeMs;
@@ -474851,11 +474884,11 @@ function filesContentMatch(src, dest) {
474851
474884
  return hash2(src) === hash2(dest);
474852
474885
  }
474853
474886
  function syncDir(src, dest) {
474854
- if (!existsSync42(src)) return { updated: 0, staleLocked: false };
474887
+ if (!existsSync43(src)) return { updated: 0, staleLocked: false };
474855
474888
  mkdirSync20(dest, { recursive: true });
474856
474889
  let updated = 0;
474857
474890
  let staleLocked = false;
474858
- for (const entry of readdirSync12(src)) {
474891
+ for (const entry of readdirSync13(src)) {
474859
474892
  const srcPath = join58(src, entry);
474860
474893
  const destPath = join58(dest, entry);
474861
474894
  const stat8 = statSync11(srcPath);
@@ -474886,28 +474919,8 @@ function syncDir(src, dest) {
474886
474919
  }
474887
474920
  return { updated, staleLocked };
474888
474921
  }
474889
- function listVersionedPythonRuntimeFiles(pythonDir) {
474890
- if (!existsSync42(pythonDir)) return /* @__PURE__ */ new Set();
474891
- try {
474892
- return new Set(
474893
- readdirSync12(pythonDir).filter((entry) => /^python\d{2,}(?:\.dll|\.zip|\._pth)$/i.test(entry)).map((entry) => entry.toLowerCase())
474894
- );
474895
- } catch {
474896
- return /* @__PURE__ */ new Set();
474897
- }
474898
- }
474899
474922
  function hasPythonAbiDrift(shippedDir, stableDir) {
474900
- const shippedRuntimeFiles = listVersionedPythonRuntimeFiles(join58(shippedDir, "python"));
474901
- if (shippedRuntimeFiles.size === 0) return false;
474902
- const stableRuntimeFiles = listVersionedPythonRuntimeFiles(join58(stableDir, "python"));
474903
- if (stableRuntimeFiles.size === 0) return false;
474904
- for (const file2 of stableRuntimeFiles) {
474905
- if (!shippedRuntimeFiles.has(file2)) return true;
474906
- }
474907
- for (const file2 of shippedRuntimeFiles) {
474908
- if (!stableRuntimeFiles.has(file2)) return true;
474909
- }
474910
- return false;
474923
+ return hasVersionedPythonRuntimeFileDrift(join58(shippedDir, "python"), join58(stableDir, "python"));
474911
474924
  }
474912
474925
  function resetStablePythonRuntime(stableDir) {
474913
474926
  try {
@@ -474937,11 +474950,11 @@ function syncXll() {
474937
474950
  mkdirSync20(stableDir, { recursive: true });
474938
474951
  const pythonRuntimeNeedsReset = hasPythonAbiDrift(shippedDir, stableDir);
474939
474952
  try {
474940
- if (existsSync42(versionFile)) {
474953
+ if (existsSync43(versionFile)) {
474941
474954
  const stamped = readFileSync33(versionFile, "utf-8").trim();
474942
474955
  if (stamped === VERSION) {
474943
- const xllReady2 = existsSync42(stableXll);
474944
- const shippedXllReady = existsSync42(shippedXll);
474956
+ const xllReady2 = existsSync43(stableXll);
474957
+ const shippedXllReady = existsSync43(shippedXll);
474945
474958
  if (!pythonRuntimeNeedsReset && (!shippedXllReady || xllReady2 && filesContentMatch(shippedXll, stableXll))) {
474946
474959
  return { updated: 0, stableDir, xllReady: xllReady2, skipped: true, staleLocked: false };
474947
474960
  }
@@ -474956,7 +474969,7 @@ function syncXll() {
474956
474969
  hasStaleLockedFiles = true;
474957
474970
  syncFailed = true;
474958
474971
  }
474959
- if (existsSync42(shippedDir)) {
474972
+ if (existsSync43(shippedDir)) {
474960
474973
  try {
474961
474974
  const { updated, staleLocked } = syncDir(shippedDir, stableDir);
474962
474975
  totalUpdated += updated;
@@ -474977,7 +474990,7 @@ function syncXll() {
474977
474990
  } catch {
474978
474991
  }
474979
474992
  }
474980
- const xllReady = existsSync42(stableXll);
474993
+ const xllReady = existsSync43(stableXll);
474981
474994
  return {
474982
474995
  updated: totalUpdated,
474983
474996
  stableDir,
@@ -474991,13 +475004,14 @@ var init_sync_xll = __esm({
474991
475004
  "src/startup/sync-xll.ts"() {
474992
475005
  "use strict";
474993
475006
  init_config();
475007
+ init_python_abi();
474994
475008
  SYNC_VERSION_FILE = ".sync-version";
474995
475009
  PIP_VERSION_FILE = ".pip-version";
474996
475010
  }
474997
475011
  });
474998
475012
 
474999
475013
  // src/startup/startup-xll.ts
475000
- import { existsSync as existsSync43 } from "fs";
475014
+ import { existsSync as existsSync44 } from "fs";
475001
475015
  import { join as join59 } from "path";
475002
475016
  function getSourceCheckoutRequiredPaths() {
475003
475017
  return [
@@ -475011,7 +475025,7 @@ function resolveStartupXll({
475011
475025
  needsSetup
475012
475026
  }) {
475013
475027
  if (sourceCheckout) {
475014
- const missingPaths = getSourceCheckoutRequiredPaths().filter((p2) => !existsSync43(p2));
475028
+ const missingPaths = getSourceCheckoutRequiredPaths().filter((p2) => !existsSync44(p2));
475015
475029
  return missingPaths.length === 0 ? { kind: "ok", updated: 0 } : { kind: "source-missing", missingPaths };
475016
475030
  }
475017
475031
  const result = syncXll();
@@ -475713,7 +475727,7 @@ var init_mog_command = __esm({
475713
475727
  });
475714
475728
 
475715
475729
  // src/app/permissions/permissions-command.ts
475716
- import { existsSync as existsSync44, statSync as statSync12 } from "fs";
475730
+ import { existsSync as existsSync45, statSync as statSync12 } from "fs";
475717
475731
  import { join as join60 } from "path";
475718
475732
  function getGlobalSettingsPath() {
475719
475733
  return join60(getAgentDir(), "settings.json");
@@ -475724,7 +475738,7 @@ function normalizeWorkspaceRoot(input, cwd) {
475724
475738
  return "Workspace path is required";
475725
475739
  }
475726
475740
  const resolved = resolveToCwd(trimmed, cwd);
475727
- if (!existsSync44(resolved)) {
475741
+ if (!existsSync45(resolved)) {
475728
475742
  return `Path does not exist: ${resolved}`;
475729
475743
  }
475730
475744
  try {
@@ -475746,7 +475760,7 @@ function normalizeFilesystemGrantPath(input, cwd, scope) {
475746
475760
  return "Grant path is required";
475747
475761
  }
475748
475762
  const resolved = resolveToCwd(trimmed, cwd);
475749
- if (!existsSync44(resolved)) {
475763
+ if (!existsSync45(resolved)) {
475750
475764
  return `Path does not exist: ${resolved}`;
475751
475765
  }
475752
475766
  try {
@@ -476527,9 +476541,9 @@ var init_remote_skill_files = __esm({
476527
476541
  // src/app/sync/skills-download.ts
476528
476542
  import { createHash as createHash4 } from "crypto";
476529
476543
  import {
476530
- existsSync as existsSync45,
476544
+ existsSync as existsSync46,
476531
476545
  mkdirSync as mkdirSync21,
476532
- readdirSync as readdirSync13,
476546
+ readdirSync as readdirSync14,
476533
476547
  readFileSync as readFileSync34,
476534
476548
  rmSync as rmSync5,
476535
476549
  statSync as statSync13,
@@ -476543,9 +476557,9 @@ function getSkillsDir() {
476543
476557
  return join61(getAgentDir(), "skills");
476544
476558
  }
476545
476559
  function collectLocalFiles(dir, rootDir = dir) {
476546
- if (!existsSync45(dir)) return [];
476560
+ if (!existsSync46(dir)) return [];
476547
476561
  const files = [];
476548
- for (const entry of readdirSync13(dir, { withFileTypes: true })) {
476562
+ for (const entry of readdirSync14(dir, { withFileTypes: true })) {
476549
476563
  const fullPath = join61(dir, entry.name);
476550
476564
  if (entry.isDirectory()) {
476551
476565
  files.push(...collectLocalFiles(fullPath, rootDir));
@@ -476847,7 +476861,7 @@ var init_skills_download = __esm({
476847
476861
 
476848
476862
  // src/app/sync/skills-status.ts
476849
476863
  import { createHash as createHash5 } from "crypto";
476850
- import { existsSync as existsSync46, readdirSync as readdirSync14, readFileSync as readFileSync35 } from "fs";
476864
+ import { existsSync as existsSync47, readdirSync as readdirSync15, readFileSync as readFileSync35 } from "fs";
476851
476865
  import { join as join62, relative as relative10 } from "path";
476852
476866
  function sha2562(data) {
476853
476867
  return createHash5("sha256").update(data).digest("hex");
@@ -476856,13 +476870,13 @@ function getSkillsDir2() {
476856
476870
  return join62(getAgentDir(), "skills");
476857
476871
  }
476858
476872
  function listLocalSkillNames(skillsDir) {
476859
- if (!existsSync46(skillsDir)) return [];
476860
- return readdirSync14(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
476873
+ if (!existsSync47(skillsDir)) return [];
476874
+ return readdirSync15(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
476861
476875
  }
476862
476876
  function collectLocalFiles2(dir, rootDir = dir) {
476863
- if (!existsSync46(dir)) return [];
476877
+ if (!existsSync47(dir)) return [];
476864
476878
  const files = [];
476865
- for (const entry of readdirSync14(dir, { withFileTypes: true })) {
476879
+ for (const entry of readdirSync15(dir, { withFileTypes: true })) {
476866
476880
  const fullPath = join62(dir, entry.name);
476867
476881
  if (entry.isDirectory()) {
476868
476882
  files.push(...collectLocalFiles2(fullPath, rootDir));
@@ -477104,7 +477118,7 @@ var init_skills_status = __esm({
477104
477118
 
477105
477119
  // src/app/sync/skills-upload.ts
477106
477120
  import { createHash as createHash6 } from "crypto";
477107
- import { existsSync as existsSync47, readdirSync as readdirSync15, readFileSync as readFileSync36, statSync as statSync14 } from "fs";
477121
+ import { existsSync as existsSync48, readdirSync as readdirSync16, readFileSync as readFileSync36, statSync as statSync14 } from "fs";
477108
477122
  import { join as join63, relative as relative11 } from "path";
477109
477123
  function sha2563(data) {
477110
477124
  return createHash6("sha256").update(data).digest("hex");
@@ -477113,8 +477127,8 @@ function getSkillsDir3() {
477113
477127
  return join63(getAgentDir(), "skills");
477114
477128
  }
477115
477129
  function listLocalSkillNames2(skillsDir) {
477116
- if (!existsSync47(skillsDir)) return [];
477117
- return readdirSync15(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
477130
+ if (!existsSync48(skillsDir)) return [];
477131
+ return readdirSync16(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
477118
477132
  }
477119
477133
  function validateSkillName2(name, parentDirName) {
477120
477134
  const errors = [];
@@ -477137,7 +477151,7 @@ function validateSkillName2(name, parentDirName) {
477137
477151
  }
477138
477152
  function validateLocalSkill(skillName, skillDir) {
477139
477153
  const skillPath = join63(skillDir, "SKILL.md");
477140
- if (!existsSync47(skillPath)) {
477154
+ if (!existsSync48(skillPath)) {
477141
477155
  return "missing SKILL.md";
477142
477156
  }
477143
477157
  try {
@@ -477158,7 +477172,7 @@ function validateLocalSkill(skillName, skillDir) {
477158
477172
  }
477159
477173
  function collectLocalFiles3(dir, rootDir = dir) {
477160
477174
  const files = [];
477161
- for (const entry of readdirSync15(dir, { withFileTypes: true })) {
477175
+ for (const entry of readdirSync16(dir, { withFileTypes: true })) {
477162
477176
  const fullPath = join63(dir, entry.name);
477163
477177
  if (entry.isDirectory()) {
477164
477178
  files.push(...collectLocalFiles3(fullPath, rootDir));
@@ -477409,7 +477423,7 @@ var init_skills_upload = __esm({
477409
477423
  });
477410
477424
 
477411
477425
  // src/startup/interactive-commands.ts
477412
- import { existsSync as existsSync48 } from "fs";
477426
+ import { existsSync as existsSync49 } from "fs";
477413
477427
  async function fetchAuthorizedSkillTeamIds(accessToken) {
477414
477428
  const userInfo = await fetchUserInfo(accessToken);
477415
477429
  if (!userInfo) return null;
@@ -477443,7 +477457,7 @@ function createInteractiveCommandHandler(context) {
477443
477457
  }
477444
477458
  if (command === "/docs") {
477445
477459
  const docsFile = getUserDocsPath();
477446
- if (existsSync48(docsFile)) {
477460
+ if (existsSync49(docsFile)) {
477447
477461
  void openInEditor(docsFile).catch(
477448
477462
  (error2) => showStatus(`Could not open documentation: ${formatOpenError(error2)}`)
477449
477463
  );
@@ -477781,12 +477795,12 @@ var init_install_utils = __esm({
477781
477795
  });
477782
477796
 
477783
477797
  // src/startup/preflight/shared.ts
477784
- import { chmodSync as chmodSync3, existsSync as existsSync49, mkdirSync as mkdirSync22, readFileSync as readFileSync37, unlinkSync as unlinkSync8, writeFileSync as writeFileSync20 } from "fs";
477798
+ import { chmodSync as chmodSync3, existsSync as existsSync50, mkdirSync as mkdirSync22, readFileSync as readFileSync37, unlinkSync as unlinkSync8, writeFileSync as writeFileSync20 } from "fs";
477785
477799
  import { dirname as dirname34, join as join64 } from "path";
477786
477800
  async function ensureUv(platformTarget) {
477787
477801
  header("Python Toolchain");
477788
477802
  const uvPath = getUvBinPath();
477789
- if (existsSync49(uvPath)) {
477803
+ if (existsSync50(uvPath)) {
477790
477804
  const check2 = run(`"${uvPath}" --version`, { timeout: 5e3 });
477791
477805
  if (check2.ok) {
477792
477806
  ok(`uv ${check2.stdout.replace("uv ", "").trim()} installed.`);
@@ -477842,7 +477856,7 @@ async function ensureUvPython() {
477842
477856
  const uvPath = getUvBinPath();
477843
477857
  const venvDir = getUvVenvDir();
477844
477858
  const venvPython = join64(venvDir, "bin", "python3");
477845
- if (existsSync49(venvPython)) {
477859
+ if (existsSync50(venvPython)) {
477846
477860
  const check2 = run(`"${venvPython}" --version`, { timeout: 5e3 });
477847
477861
  if (check2.ok) {
477848
477862
  ok(`${check2.stdout.trim()} ready.`);
@@ -477877,7 +477891,7 @@ async function ensureUvPackages() {
477877
477891
  const uvPath = getUvBinPath();
477878
477892
  const venvDir = getUvVenvDir();
477879
477893
  const venvPython = join64(venvDir, "bin", "python3");
477880
- if (!existsSync49(venvPython)) {
477894
+ if (!existsSync50(venvPython)) {
477881
477895
  log2(
477882
477896
  "Shortcut agent will install packages after Python is ready.",
477883
477897
  "Will finish setup automatically."
@@ -477887,7 +477901,7 @@ async function ensureUvPackages() {
477887
477901
  const stampFile = join64(venvDir, ".packages-stamp");
477888
477902
  const stampValue = `${VERSION}:${UV_PIP_PACKAGES.join(",")}`;
477889
477903
  try {
477890
- if (existsSync49(stampFile) && readFileSync37(stampFile, "utf-8").trim() === stampValue) {
477904
+ if (existsSync50(stampFile) && readFileSync37(stampFile, "utf-8").trim() === stampValue) {
477891
477905
  ok("Packages already installed.", "Ready.");
477892
477906
  return;
477893
477907
  }
@@ -477971,13 +477985,13 @@ __export(mac_exports, {
477971
477985
  ensureUvPython: () => ensureUvPython,
477972
477986
  smokeTestSheetEngine: () => smokeTestSheetEngine
477973
477987
  });
477974
- import { existsSync as existsSync50 } from "fs";
477988
+ import { existsSync as existsSync51 } from "fs";
477975
477989
  async function ensureUv2() {
477976
477990
  return ensureUv("apple-darwin");
477977
477991
  }
477978
477992
  function ensureChromeMac() {
477979
477993
  header("Chrome");
477980
- if (existsSync50("/Applications/Google Chrome.app")) {
477994
+ if (existsSync51("/Applications/Google Chrome.app")) {
477981
477995
  ok("Chrome found \u2014 SEC filing support ready.");
477982
477996
  } else {
477983
477997
  log2("Chrome not installed \u2014 Shortcut agent can set this up for SEC filing support.");
@@ -478001,13 +478015,13 @@ __export(linux_exports, {
478001
478015
  ensureUvPython: () => ensureUvPython,
478002
478016
  smokeTestSheetEngine: () => smokeTestSheetEngine
478003
478017
  });
478004
- import { existsSync as existsSync51 } from "fs";
478018
+ import { existsSync as existsSync52 } from "fs";
478005
478019
  async function ensureUv3() {
478006
478020
  return ensureUv("unknown-linux-gnu");
478007
478021
  }
478008
478022
  function ensureChromeLinux() {
478009
478023
  header("Chrome");
478010
- const found = LINUX_CHROME_PATHS.find((p2) => existsSync51(p2));
478024
+ const found = LINUX_CHROME_PATHS.find((p2) => existsSync52(p2));
478011
478025
  if (found) {
478012
478026
  ok("Chrome found \u2014 SEC filing support ready.");
478013
478027
  } else {
@@ -478039,12 +478053,12 @@ __export(windows_exports, {
478039
478053
  ensureXllRegistry: () => ensureXllRegistry,
478040
478054
  smokeTestExcel: () => smokeTestExcel
478041
478055
  });
478042
- import { existsSync as existsSync52, readFileSync as readFileSync38, writeFileSync as writeFileSync21 } from "fs";
478056
+ import { existsSync as existsSync53, readFileSync as readFileSync38, writeFileSync as writeFileSync21 } from "fs";
478043
478057
  import { join as join65, resolve as resolve18 } from "path";
478044
478058
  async function ensurePipPackages() {
478045
478059
  header("Python Packages", "Calculation Engine");
478046
478060
  const pythonExe = getEmbeddedPythonExe();
478047
- if (!existsSync52(pythonExe)) {
478061
+ if (!existsSync53(pythonExe)) {
478048
478062
  log2("Shortcut agent will set up Python.", "Will finish setup automatically.");
478049
478063
  return;
478050
478064
  }
@@ -478063,7 +478077,7 @@ async function ensurePipPackages() {
478063
478077
  const pipStampFile = join65(getStableXllDir(), ".pip-version");
478064
478078
  let pipUpToDate = false;
478065
478079
  try {
478066
- pipUpToDate = existsSync52(pipStampFile) && readFileSync38(pipStampFile, "utf-8").trim() === VERSION;
478080
+ pipUpToDate = existsSync53(pipStampFile) && readFileSync38(pipStampFile, "utf-8").trim() === VERSION;
478067
478081
  } catch {
478068
478082
  }
478069
478083
  if (pipUpToDate) {
@@ -478093,7 +478107,7 @@ async function ensureChromeWindows() {
478093
478107
  return;
478094
478108
  }
478095
478109
  const pythonExe = getEmbeddedPythonExe();
478096
- if (!existsSync52(pythonExe)) {
478110
+ if (!existsSync53(pythonExe)) {
478097
478111
  log2("Shortcut agent will set up Chrome.", "Will finish setup automatically.");
478098
478112
  return;
478099
478113
  }
@@ -478177,7 +478191,7 @@ async function ensureGitBash() {
478177
478191
  function ensureXllRegistry() {
478178
478192
  header("Excel Add-in", "Excel Integration");
478179
478193
  const xllPath = resolve18(getXllPath()).replace(/\//g, "\\");
478180
- if (!existsSync52(xllPath)) {
478194
+ if (!existsSync53(xllPath)) {
478181
478195
  log2("Shortcut agent will set up the Excel add-in.", "Will finish setup automatically.");
478182
478196
  return;
478183
478197
  }
@@ -478373,7 +478387,7 @@ var diagnostics_exports = {};
478373
478387
  __export(diagnostics_exports, {
478374
478388
  collectPreflightDiagnostics: () => collectPreflightDiagnostics
478375
478389
  });
478376
- import { closeSync as closeSync2, existsSync as existsSync53, openSync as openSync2, readSync as readSync2, statSync as statSync15 } from "fs";
478390
+ import { closeSync as closeSync2, existsSync as existsSync54, openSync as openSync2, readSync as readSync2, statSync as statSync15 } from "fs";
478377
478391
  import { resolve as resolve19 } from "path";
478378
478392
  function safeRun(command, timeout = DIAGNOSTIC_TIMEOUT_MS) {
478379
478393
  try {
@@ -478498,7 +478512,7 @@ function collectXllLog() {
478498
478512
  `${process.env.TEMP ?? ""}\\shortcutxl.log`
478499
478513
  ];
478500
478514
  for (const path21 of logPaths) {
478501
- if (path21 && existsSync53(path21)) {
478515
+ if (path21 && existsSync54(path21)) {
478502
478516
  return readLogTail(path21);
478503
478517
  }
478504
478518
  }
@@ -478550,7 +478564,7 @@ function collectPreflightDiagnostics() {
478550
478564
  setCurrentStep("diagnostics");
478551
478565
  const xllPath = resolve19(getXllPath()).replace(/\//g, "\\");
478552
478566
  const diagnostics = {
478553
- xllExists: existsSync53(xllPath),
478567
+ xllExists: existsSync54(xllPath),
478554
478568
  xllPath,
478555
478569
  motw: collectMotw(xllPath),
478556
478570
  xllArch: collectXllArch(xllPath),
@@ -478634,7 +478648,7 @@ var init_preflight = __esm({
478634
478648
  });
478635
478649
 
478636
478650
  // src/startup/prune-sessions.ts
478637
- import { readdirSync as readdirSync16, rmSync as rmSync6, statSync as statSync16 } from "node:fs";
478651
+ import { readdirSync as readdirSync17, rmSync as rmSync6, statSync as statSync16 } from "node:fs";
478638
478652
  import { join as join66 } from "node:path";
478639
478653
  function pruneStaleFiles() {
478640
478654
  const cutoff = Date.now() - FOURTEEN_DAYS_MS;
@@ -478643,7 +478657,7 @@ function pruneStaleFiles() {
478643
478657
  function pruneSessionFiles(cutoff) {
478644
478658
  try {
478645
478659
  const sessionsRoot = getSessionsDir();
478646
- for (const projectDir of readdirSync16(sessionsRoot)) {
478660
+ for (const projectDir of readdirSync17(sessionsRoot)) {
478647
478661
  pruneJsonlFilesRecursively(join66(sessionsRoot, projectDir), cutoff);
478648
478662
  }
478649
478663
  } catch {
@@ -478651,7 +478665,7 @@ function pruneSessionFiles(cutoff) {
478651
478665
  }
478652
478666
  function pruneJsonlFilesRecursively(dir, cutoff) {
478653
478667
  try {
478654
- for (const entry of readdirSync16(dir, { withFileTypes: true })) {
478668
+ for (const entry of readdirSync17(dir, { withFileTypes: true })) {
478655
478669
  const entryPath = join66(dir, entry.name);
478656
478670
  if (entry.isDirectory()) {
478657
478671
  pruneJsonlFilesRecursively(entryPath, cutoff);
@@ -478751,19 +478765,38 @@ var init_session_selection = __esm({
478751
478765
 
478752
478766
  // src/startup/update-action.ts
478753
478767
  import { spawn as spawn12 } from "child_process";
478768
+ import { appendFileSync as appendFileSync6, mkdirSync as mkdirSync23 } from "fs";
478769
+ import { dirname as dirname35 } from "path";
478754
478770
  async function runForegroundUpdate(request, deps = {}) {
478755
478771
  const resolveShell = deps.resolveShell ?? getShellConfig;
478756
478772
  const resolveEnv = deps.resolveEnv ?? getShellEnv;
478757
478773
  const spawnProcess = deps.spawnProcess ?? spawn12;
478774
+ const resolveLogPath = deps.resolveLogPath ?? getDebugLogPath;
478758
478775
  const shellConfig = resolveShell();
478759
478776
  return await new Promise((resolve22, reject) => {
478777
+ const output = [];
478778
+ let outputBytes = 0;
478779
+ let outputTruncated = false;
478760
478780
  const child = spawnProcess(shellConfig.shell, [...shellConfig.args, request.command], {
478761
478781
  cwd: request.cwd ?? process.cwd(),
478762
478782
  env: resolveEnv(),
478763
- stdio: "inherit"
478783
+ stdio: ["ignore", "pipe", "pipe"]
478764
478784
  });
478785
+ const capture = (chunk) => {
478786
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
478787
+ const remaining = MAX_CAPTURED_OUTPUT_BYTES - outputBytes;
478788
+ if (remaining > 0) {
478789
+ output.push(buffer.subarray(0, remaining));
478790
+ outputBytes += Math.min(buffer.length, remaining);
478791
+ }
478792
+ if (buffer.length > remaining) {
478793
+ outputTruncated = true;
478794
+ }
478795
+ };
478796
+ child.stdout?.on("data", capture);
478797
+ child.stderr?.on("data", capture);
478765
478798
  child.once("error", reject);
478766
- child.once("exit", (exitCode, signal) => {
478799
+ child.once("close", (exitCode, signal) => {
478767
478800
  if (exitCode === 0) {
478768
478801
  resolve22({
478769
478802
  kind: "success",
@@ -478772,24 +478805,83 @@ async function runForegroundUpdate(request, deps = {}) {
478772
478805
  });
478773
478806
  return;
478774
478807
  }
478808
+ const capturedOutput = Buffer.concat(output).toString("utf-8");
478809
+ const logPath = writeUpdateFailureLog({
478810
+ command: request.command,
478811
+ exitCode,
478812
+ signal,
478813
+ output: capturedOutput,
478814
+ truncated: outputTruncated,
478815
+ resolveLogPath
478816
+ });
478775
478817
  resolve22({
478776
478818
  kind: "failed",
478777
478819
  exitCode,
478778
- signal
478820
+ signal,
478821
+ message: summarizeUpdateFailure(capturedOutput),
478822
+ logPath
478779
478823
  });
478780
478824
  });
478781
478825
  });
478782
478826
  }
478827
+ function summarizeUpdateFailure(output) {
478828
+ if (/EEXIST[\s\S]*shortcut\.cmd/i.test(output) || /File exists:[\s\S]*shortcut\.cmd/i.test(output)) {
478829
+ return [
478830
+ "ShortcutXL could not update because an existing global `shortcut` command blocked npm from replacing it.",
478831
+ "Close any running ShortcutXL terminals, then run `npm uninstall -g shortcutxl` followed by `npm install -g shortcutxl@latest`."
478832
+ ].join(" ");
478833
+ }
478834
+ if (/EPERM|ENOTEMPTY|ENOENT/i.test(output) && /AppData[\\/]+Roaming[\\/]+npm[\\/]+node_modules[\\/]+shortcutxl/i.test(output)) {
478835
+ return [
478836
+ "ShortcutXL could not update because npm could not clean up the previous global install.",
478837
+ "Close ShortcutXL terminals and retry `npm install -g shortcutxl@latest`."
478838
+ ].join(" ");
478839
+ }
478840
+ return "ShortcutXL could not update automatically. Retry with `npm install -g shortcutxl@latest`.";
478841
+ }
478842
+ function writeUpdateFailureLog({
478843
+ command,
478844
+ exitCode,
478845
+ signal,
478846
+ output,
478847
+ truncated,
478848
+ resolveLogPath
478849
+ }) {
478850
+ try {
478851
+ const logPath = resolveLogPath();
478852
+ mkdirSync23(dirname35(logPath), { recursive: true });
478853
+ appendFileSync6(
478854
+ logPath,
478855
+ [
478856
+ "",
478857
+ "--- ShortcutXL update failure ---",
478858
+ `time=${(/* @__PURE__ */ new Date()).toISOString()}`,
478859
+ `command=${command}`,
478860
+ `exitCode=${String(exitCode)}`,
478861
+ `signal=${String(signal)}`,
478862
+ truncated ? `outputTruncatedAfterBytes=${MAX_CAPTURED_OUTPUT_BYTES}` : void 0,
478863
+ output
478864
+ ].filter((line) => line !== void 0).join("\n"),
478865
+ "utf-8"
478866
+ );
478867
+ return logPath;
478868
+ } catch {
478869
+ return void 0;
478870
+ }
478871
+ }
478872
+ var MAX_CAPTURED_OUTPUT_BYTES;
478783
478873
  var init_update_action = __esm({
478784
478874
  "src/startup/update-action.ts"() {
478785
478875
  "use strict";
478786
478876
  init_bash_runtime();
478877
+ init_config();
478878
+ MAX_CAPTURED_OUTPUT_BYTES = 1024 * 1024;
478787
478879
  }
478788
478880
  });
478789
478881
 
478790
478882
  // src/startup/update-manager.ts
478791
- import { mkdirSync as mkdirSync23, readFileSync as readFileSync39, writeFileSync as writeFileSync22 } from "fs";
478792
- import { dirname as dirname35 } from "path";
478883
+ import { mkdirSync as mkdirSync24, readFileSync as readFileSync39, writeFileSync as writeFileSync22 } from "fs";
478884
+ import { dirname as dirname36 } from "path";
478793
478885
  async function resolveStartupUpdateStatus(options2 = {}, deps = {}) {
478794
478886
  const cachePath = options2.cachePath ?? getUpdateCachePath();
478795
478887
  const currentVersion = options2.currentVersion ?? VERSION;
@@ -478902,7 +478994,7 @@ function readUpdateCache(cachePath) {
478902
478994
  }
478903
478995
  }
478904
478996
  function writeUpdateCache(cachePath, cache) {
478905
- mkdirSync23(dirname35(cachePath), { recursive: true });
478997
+ mkdirSync24(dirname36(cachePath), { recursive: true });
478906
478998
  writeFileSync22(cachePath, `${JSON.stringify(cache, null, 2)}
478907
478999
  `, "utf-8");
478908
479000
  }
@@ -480350,7 +480442,7 @@ __export(uninstall_exports, {
480350
480442
  removeOwnedInstallArtifacts: () => removeOwnedInstallArtifacts,
480351
480443
  runUninstall: () => runUninstall
480352
480444
  });
480353
- import { existsSync as existsSync54, readdirSync as readdirSync17, rmSync as rmSync7, rmdirSync } from "fs";
480445
+ import { existsSync as existsSync55, readdirSync as readdirSync18, rmSync as rmSync7, rmdirSync } from "fs";
480354
480446
  import { isAbsolute as isAbsolute10, join as join67, relative as relative12, resolve as resolve20 } from "path";
480355
480447
  function isWithinDir(parentDir, targetPath) {
480356
480448
  const parent = resolve20(parentDir);
@@ -480360,7 +480452,7 @@ function isWithinDir(parentDir, targetPath) {
480360
480452
  }
480361
480453
  function removeIfInsideAgentDir(agentDir, relativePath) {
480362
480454
  const target = join67(agentDir, relativePath);
480363
- if (!isWithinDir(agentDir, target) || !existsSync54(target)) {
480455
+ if (!isWithinDir(agentDir, target) || !existsSync55(target)) {
480364
480456
  return false;
480365
480457
  }
480366
480458
  rmSync7(target, { recursive: true, force: true });
@@ -480368,10 +480460,10 @@ function removeIfInsideAgentDir(agentDir, relativePath) {
480368
480460
  }
480369
480461
  function removeEmptyDirIfInsideAgentDir(agentDir, relativePath) {
480370
480462
  const target = join67(agentDir, relativePath);
480371
- if (!isWithinDir(agentDir, target) || !existsSync54(target)) {
480463
+ if (!isWithinDir(agentDir, target) || !existsSync55(target)) {
480372
480464
  return false;
480373
480465
  }
480374
- if (readdirSync17(target).length > 0) {
480466
+ if (readdirSync18(target).length > 0) {
480375
480467
  return false;
480376
480468
  }
480377
480469
  rmdirSync(target);
@@ -480398,7 +480490,7 @@ function removeOwnedInstallArtifacts(agentDir) {
480398
480490
  }
480399
480491
  }
480400
480492
  try {
480401
- if (existsSync54(agentDir) && readdirSync17(agentDir).length === 0) {
480493
+ if (existsSync55(agentDir) && readdirSync18(agentDir).length === 0) {
480402
480494
  rmdirSync(agentDir);
480403
480495
  result.removed.push(".");
480404
480496
  }
@@ -480474,7 +480566,7 @@ function runUninstall() {
480474
480566
  }
480475
480567
  header2("Playwright Browsers");
480476
480568
  const pythonExe = getEmbeddedPythonExe();
480477
- if (existsSync54(pythonExe)) {
480569
+ if (existsSync55(pythonExe)) {
480478
480570
  const pwResult = run(`"${pythonExe}" -m playwright uninstall --all`, { timeout: 3e4 });
480479
480571
  if (pwResult.ok) {
480480
480572
  ok2("Removed Playwright browsers.");
@@ -480490,7 +480582,7 @@ function runUninstall() {
480490
480582
  }
480491
480583
  header2("Files");
480492
480584
  const agentDir = getAgentDir();
480493
- if (existsSync54(agentDir)) {
480585
+ if (existsSync55(agentDir)) {
480494
480586
  const cleanup = removeOwnedInstallArtifacts(agentDir);
480495
480587
  if (cleanup.removed.length > 0) {
480496
480588
  ok2(`Removed generated install files from ${agentDir}`);
@@ -480543,7 +480635,7 @@ __export(main_exports, {
480543
480635
  isMogRuntimeEnabledForUser: () => isMogRuntimeEnabledForUser,
480544
480636
  main: () => main
480545
480637
  });
480546
- import { existsSync as existsSync55 } from "fs";
480638
+ import { existsSync as existsSync56 } from "fs";
480547
480639
  async function resolveShortcutUserInfo(authStorage) {
480548
480640
  const accessToken = await authStorage.getApiKey(SHORTCUT_PROVIDER_ID);
480549
480641
  return accessToken ? fetchUserInfo(accessToken) : null;
@@ -480968,7 +481060,7 @@ async function main(args) {
480968
481060
  if (sandboxMode === "enabled" && sandboxAvailable) {
480969
481061
  const sandboxFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
480970
481062
  let sandboxFrame = 0;
480971
- const needsDownload = !existsSync55(getAlpineRootfsPath());
481063
+ const needsDownload = !existsSync56(getAlpineRootfsPath());
480972
481064
  const spinnerMsg = needsDownload ? "Downloading sandbox (~80 MB)..." : "Checking sandbox...";
480973
481065
  const sandboxSpinner = setInterval(() => {
480974
481066
  process.stderr.write(
@@ -481109,6 +481201,12 @@ async function main(args) {
481109
481201
  if (updateResult.kind === "failed") {
481110
481202
  const exitDetail = updateResult.exitCode !== null ? `exit code ${updateResult.exitCode}` : updateResult.signal ? `signal ${updateResult.signal}` : "unknown error";
481111
481203
  console.error(source_default.red(`Update failed (${exitDetail}). ShortcutXL will not launch.`));
481204
+ if (updateResult.message) {
481205
+ console.error(source_default.yellow(updateResult.message));
481206
+ }
481207
+ if (updateResult.logPath) {
481208
+ console.error(source_default.dim(`Details were written to ${updateResult.logPath}`));
481209
+ }
481112
481210
  process.exit(updateResult.exitCode ?? 1);
481113
481211
  }
481114
481212
  console.log(source_default.green(`Update to v${updatePromptOutcome.update.latestVersion} succeeded.`));
@@ -481571,9 +481669,9 @@ ${loop.prompt}`;
481571
481669
 
481572
481670
  // src/cli.ts
481573
481671
  var import_dotenv = __toESM(require_main(), 1);
481574
- import { dirname as dirname36, resolve as resolve21 } from "path";
481672
+ import { dirname as dirname37, resolve as resolve21 } from "path";
481575
481673
  import { fileURLToPath as fileURLToPath4 } from "url";
481576
- var __dirname3 = dirname36(fileURLToPath4(import.meta.url));
481674
+ var __dirname3 = dirname37(fileURLToPath4(import.meta.url));
481577
481675
  var agentRoot = resolve21(__dirname3, "..");
481578
481676
  import_dotenv.default.config({ path: resolve21(agentRoot, ".env.development") });
481579
481677
  process.title = "shortcut";
package/dist/main.js CHANGED
@@ -669,6 +669,12 @@ export async function main(args) {
669
669
  ? `signal ${updateResult.signal}`
670
670
  : 'unknown error';
671
671
  console.error(chalk.red(`Update failed (${exitDetail}). ShortcutXL will not launch.`));
672
+ if (updateResult.message) {
673
+ console.error(chalk.yellow(updateResult.message));
674
+ }
675
+ if (updateResult.logPath) {
676
+ console.error(chalk.dim(`Details were written to ${updateResult.logPath}`));
677
+ }
672
678
  process.exit(updateResult.exitCode ?? 1);
673
679
  }
674
680
  console.log(chalk.green(`Update to v${updatePromptOutcome.update.latestVersion} succeeded.`));
@@ -0,0 +1,3 @@
1
+ export declare function listVersionedPythonRuntimeFiles(pythonDir: string): Set<string>;
2
+ export declare function hasVersionedPythonRuntimeFileDrift(sourcePythonDir: string, destPythonDir: string): boolean;
3
+ //# sourceMappingURL=python-abi.d.ts.map
@@ -0,0 +1,32 @@
1
+ import { existsSync, readdirSync } from 'fs';
2
+ const VERSIONED_PYTHON_RUNTIME_FILE = /^python\d{2,}(?:\.dll|\.zip|\._pth)$/i;
3
+ export function listVersionedPythonRuntimeFiles(pythonDir) {
4
+ if (!existsSync(pythonDir))
5
+ return new Set();
6
+ try {
7
+ return new Set(readdirSync(pythonDir)
8
+ .filter((entry) => VERSIONED_PYTHON_RUNTIME_FILE.test(entry))
9
+ .map((entry) => entry.toLowerCase()));
10
+ }
11
+ catch {
12
+ return new Set();
13
+ }
14
+ }
15
+ export function hasVersionedPythonRuntimeFileDrift(sourcePythonDir, destPythonDir) {
16
+ const sourceFiles = listVersionedPythonRuntimeFiles(sourcePythonDir);
17
+ if (sourceFiles.size === 0)
18
+ return false;
19
+ const destFiles = listVersionedPythonRuntimeFiles(destPythonDir);
20
+ if (destFiles.size === 0)
21
+ return false;
22
+ for (const file of destFiles) {
23
+ if (!sourceFiles.has(file))
24
+ return true;
25
+ }
26
+ for (const file of sourceFiles) {
27
+ if (!destFiles.has(file))
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ //# sourceMappingURL=python-abi.js.map
@@ -22,6 +22,7 @@ import { createHash } from 'crypto';
22
22
  import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, utimesSync, writeFileSync } from 'fs';
23
23
  import { join } from 'path';
24
24
  import { getPackageDir, getStableXllDir, VERSION } from '../config.js';
25
+ import { hasVersionedPythonRuntimeFileDrift } from './python-abi.js';
25
26
  /** Strip the NTFS Zone.Identifier alternate data stream (Mark of the Web). */
26
27
  function stripMotw(filePath) {
27
28
  try {
@@ -101,34 +102,8 @@ function syncDir(src, dest) {
101
102
  }
102
103
  const SYNC_VERSION_FILE = '.sync-version';
103
104
  const PIP_VERSION_FILE = '.pip-version';
104
- function listVersionedPythonRuntimeFiles(pythonDir) {
105
- if (!existsSync(pythonDir))
106
- return new Set();
107
- try {
108
- return new Set(readdirSync(pythonDir)
109
- .filter((entry) => /^python\d{2,}(?:\.dll|\.zip|\._pth)$/i.test(entry))
110
- .map((entry) => entry.toLowerCase()));
111
- }
112
- catch {
113
- return new Set();
114
- }
115
- }
116
105
  export function hasPythonAbiDrift(shippedDir, stableDir) {
117
- const shippedRuntimeFiles = listVersionedPythonRuntimeFiles(join(shippedDir, 'python'));
118
- if (shippedRuntimeFiles.size === 0)
119
- return false;
120
- const stableRuntimeFiles = listVersionedPythonRuntimeFiles(join(stableDir, 'python'));
121
- if (stableRuntimeFiles.size === 0)
122
- return false;
123
- for (const file of stableRuntimeFiles) {
124
- if (!shippedRuntimeFiles.has(file))
125
- return true;
126
- }
127
- for (const file of shippedRuntimeFiles) {
128
- if (!stableRuntimeFiles.has(file))
129
- return true;
130
- }
131
- return false;
106
+ return hasVersionedPythonRuntimeFileDrift(join(shippedDir, 'python'), join(stableDir, 'python'));
132
107
  }
133
108
  function resetStablePythonRuntime(stableDir) {
134
109
  try {
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Foreground update execution path.
3
3
  *
4
- * Runs the updater command in the startup flow, surfaces real output to the
5
- * terminal, and returns a structured success/failure result so startup can
6
- * decide whether to continue into the shell.
4
+ * Runs the updater command in the startup flow. Updaters such as npm can emit
5
+ * huge cleanup dumps on Windows when a global install tree is locked or
6
+ * half-deleted, so stdout/stderr are captured and summarized instead of being
7
+ * streamed directly into the user's terminal.
7
8
  */
8
9
  import { spawn } from 'child_process';
9
10
  export interface UpdateLaunchRequest {
@@ -14,6 +15,8 @@ export interface ForegroundUpdateResult {
14
15
  kind: 'success' | 'failed';
15
16
  exitCode: number | null;
16
17
  signal: NodeJS.Signals | null;
18
+ message?: string;
19
+ logPath?: string;
17
20
  }
18
21
  interface UpdateActionDeps {
19
22
  spawnProcess?: typeof spawn;
@@ -22,6 +25,7 @@ interface UpdateActionDeps {
22
25
  args: string[];
23
26
  };
24
27
  resolveEnv?: () => NodeJS.ProcessEnv;
28
+ resolveLogPath?: () => string;
25
29
  }
26
30
  export declare function runForegroundUpdate(request: UpdateLaunchRequest, deps?: UpdateActionDeps): Promise<ForegroundUpdateResult>;
27
31
  export {};
@@ -1,25 +1,47 @@
1
1
  /**
2
2
  * Foreground update execution path.
3
3
  *
4
- * Runs the updater command in the startup flow, surfaces real output to the
5
- * terminal, and returns a structured success/failure result so startup can
6
- * decide whether to continue into the shell.
4
+ * Runs the updater command in the startup flow. Updaters such as npm can emit
5
+ * huge cleanup dumps on Windows when a global install tree is locked or
6
+ * half-deleted, so stdout/stderr are captured and summarized instead of being
7
+ * streamed directly into the user's terminal.
7
8
  */
8
9
  import { spawn } from 'child_process';
10
+ import { appendFileSync, mkdirSync } from 'fs';
11
+ import { dirname } from 'path';
9
12
  import { getShellConfig, getShellEnv } from '../app/tools/bash-runtime.js';
13
+ import { getDebugLogPath } from '../config.js';
14
+ const MAX_CAPTURED_OUTPUT_BYTES = 1024 * 1024;
10
15
  export async function runForegroundUpdate(request, deps = {}) {
11
16
  const resolveShell = deps.resolveShell ?? getShellConfig;
12
17
  const resolveEnv = deps.resolveEnv ?? getShellEnv;
13
18
  const spawnProcess = deps.spawnProcess ?? spawn;
19
+ const resolveLogPath = deps.resolveLogPath ?? getDebugLogPath;
14
20
  const shellConfig = resolveShell();
15
21
  return await new Promise((resolve, reject) => {
22
+ const output = [];
23
+ let outputBytes = 0;
24
+ let outputTruncated = false;
16
25
  const child = spawnProcess(shellConfig.shell, [...shellConfig.args, request.command], {
17
26
  cwd: request.cwd ?? process.cwd(),
18
27
  env: resolveEnv(),
19
- stdio: 'inherit'
28
+ stdio: ['ignore', 'pipe', 'pipe']
20
29
  });
30
+ const capture = (chunk) => {
31
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
32
+ const remaining = MAX_CAPTURED_OUTPUT_BYTES - outputBytes;
33
+ if (remaining > 0) {
34
+ output.push(buffer.subarray(0, remaining));
35
+ outputBytes += Math.min(buffer.length, remaining);
36
+ }
37
+ if (buffer.length > remaining) {
38
+ outputTruncated = true;
39
+ }
40
+ };
41
+ child.stdout?.on('data', capture);
42
+ child.stderr?.on('data', capture);
21
43
  child.once('error', reject);
22
- child.once('exit', (exitCode, signal) => {
44
+ child.once('close', (exitCode, signal) => {
23
45
  if (exitCode === 0) {
24
46
  resolve({
25
47
  kind: 'success',
@@ -28,12 +50,62 @@ export async function runForegroundUpdate(request, deps = {}) {
28
50
  });
29
51
  return;
30
52
  }
53
+ const capturedOutput = Buffer.concat(output).toString('utf-8');
54
+ const logPath = writeUpdateFailureLog({
55
+ command: request.command,
56
+ exitCode,
57
+ signal,
58
+ output: capturedOutput,
59
+ truncated: outputTruncated,
60
+ resolveLogPath
61
+ });
31
62
  resolve({
32
63
  kind: 'failed',
33
64
  exitCode,
34
- signal
65
+ signal,
66
+ message: summarizeUpdateFailure(capturedOutput),
67
+ logPath
35
68
  });
36
69
  });
37
70
  });
38
71
  }
72
+ function summarizeUpdateFailure(output) {
73
+ if (/EEXIST[\s\S]*shortcut\.cmd/i.test(output) ||
74
+ /File exists:[\s\S]*shortcut\.cmd/i.test(output)) {
75
+ return [
76
+ 'ShortcutXL could not update because an existing global `shortcut` command blocked npm from replacing it.',
77
+ 'Close any running ShortcutXL terminals, then run `npm uninstall -g shortcutxl` followed by `npm install -g shortcutxl@latest`.'
78
+ ].join(' ');
79
+ }
80
+ if (/EPERM|ENOTEMPTY|ENOENT/i.test(output) &&
81
+ /AppData[\\/]+Roaming[\\/]+npm[\\/]+node_modules[\\/]+shortcutxl/i.test(output)) {
82
+ return [
83
+ 'ShortcutXL could not update because npm could not clean up the previous global install.',
84
+ 'Close ShortcutXL terminals and retry `npm install -g shortcutxl@latest`.'
85
+ ].join(' ');
86
+ }
87
+ return 'ShortcutXL could not update automatically. Retry with `npm install -g shortcutxl@latest`.';
88
+ }
89
+ function writeUpdateFailureLog({ command, exitCode, signal, output, truncated, resolveLogPath }) {
90
+ try {
91
+ const logPath = resolveLogPath();
92
+ mkdirSync(dirname(logPath), { recursive: true });
93
+ appendFileSync(logPath, [
94
+ '',
95
+ '--- ShortcutXL update failure ---',
96
+ `time=${new Date().toISOString()}`,
97
+ `command=${command}`,
98
+ `exitCode=${String(exitCode)}`,
99
+ `signal=${String(signal)}`,
100
+ truncated ? `outputTruncatedAfterBytes=${MAX_CAPTURED_OUTPUT_BYTES}` : undefined,
101
+ output
102
+ ]
103
+ .filter((line) => line !== undefined)
104
+ .join('\n'), 'utf-8');
105
+ return logPath;
106
+ }
107
+ catch {
108
+ return undefined;
109
+ }
110
+ }
39
111
  //# sourceMappingURL=update-action.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shortcutxl",
3
- "version": "0.3.57",
3
+ "version": "0.3.58",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
Binary file