ledung 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,7 @@ that changes `src/`, `web/` (not `web/dist/`) or `scripts/` without one (a commi
10
10
  is nothing user-facing in it).
11
11
 
12
12
  ## 2026-11-02
13
+ - Started at sign-in on macOS or Linux, or from Ledung.app, Ledung now gets the same PATH as in your terminal, so Update Ledung, Ledung's GitHub features and the tools your sessions use work after a login too; a shell profile that hangs delays the start by about ten seconds instead of stopping it. Switch Start Ledung when I sign in off and on in Settings to get it (#464)
13
14
  - Settings → About has an update channel, Public or Dev: on Dev an npm-installed Ledung gets dev builds before they go to everyone, and Update & restart now checks npm and updates an npm install with one click (#461)
14
15
 
15
16
  ## 2026-11-01
package/dist/main.js CHANGED
@@ -4015,15 +4015,15 @@ var Shortcuts = class {
4015
4015
  await this.writeLinks([file], ["--no-open"], null, "Start Ledung in the background");
4016
4016
  } else if (kind === "mac") {
4017
4017
  const run8 = [...this.nodeOptions(), script, ...startsInBackground(this.opts.launch) ? ["--foreground"] : [], "--no-open"].map(shQuote).join(" ");
4018
- const missing = 'echo "Ledung cannot find Node.js. Install Node.js 24, or switch Start at sign-in off and on in Settings in Ledung." >&2; exit 1';
4019
- fs13.writeFileSync(file, AGENT_PLIST(["/bin/sh", "-c", [...findNode(node), `[ -x "$NODE" ] || { ${missing}; }`, `exec "$NODE" ${run8}`].join("\n")], this.logFile()));
4018
+ fs13.writeFileSync(file, AGENT_PLIST(["/bin/sh", "-c", signInScript(node, run8, "/bin/zsh").join("\n")], this.logFile()));
4020
4019
  } else {
4020
+ const run8 = [...this.nodeOptions(), script, "--no-open"].map(shQuote).join(" ");
4021
4021
  fs13.writeFileSync(file, [
4022
4022
  "[Desktop Entry]",
4023
4023
  "Type=Application",
4024
4024
  "Name=Ledung",
4025
4025
  "Comment=Start Ledung in the background. Made by Ledung's Settings (issue #456).",
4026
- `Exec=${[node, ...this.nodeOptions(), script, "--no-open"].map(desktopArg).join(" ")}`,
4026
+ `Exec=${["/bin/sh", "-c", signInScript(node, run8, "/bin/sh").join("; ")].map(desktopArg).join(" ")}`,
4027
4027
  "Terminal=false",
4028
4028
  "X-GNOME-Autostart-enabled=true",
4029
4029
  ""
@@ -4054,6 +4054,9 @@ var Shortcuts = class {
4054
4054
  ...findNode(node),
4055
4055
  `[ -x "$NODE" ] || { osascript -e 'display alert "Ledung cannot find Node.js" message "Install Node.js 24, or add the shortcut again in Settings in Ledung."'; exit 1; }`,
4056
4056
  `"$NODE" ${run8} --open-only && exit 0`,
4057
+ // Only for a start: opening a running Ledung's page doesn't wait for the login shell.
4058
+ ...loginPath("/bin/zsh"),
4059
+ ...nodeOnPath(),
4057
4060
  startsInBackground(this.opts.launch) ? `exec "$NODE" ${run8}` : `${LOG_FILE_SETTING}=${shQuote(this.logFile())} nohup "$NODE" ${run8} >> ${shQuote(this.logFile())} 2>&1 &`,
4058
4061
  ""
4059
4062
  ].join("\n"), { mode: 493 });
@@ -4071,6 +4074,23 @@ var Shortcuts = class {
4071
4074
  function findNode(node) {
4072
4075
  return [`NODE=${shQuote(node)}`, `[ -x "$NODE" ] || NODE=$("\${SHELL:-/bin/zsh}" -lic 'command -v node' 2>/dev/null | tail -n 1)`];
4073
4076
  }
4077
+ var LOGIN_PATH_TICKS = 40;
4078
+ var PATH_MARKER = "LEDUNG_PATH=";
4079
+ function loginPath(fallbackShell) {
4080
+ const ask = `"\${SHELL:-${fallbackShell}}" -lic 'printf "\\n${PATH_MARKER}%s\\n" "$PATH"' </dev/null >"$T" 2>/dev/null & P=$!`;
4081
+ return [
4082
+ 'L=; T=$(mktemp "${TMPDIR:-/tmp}/ledung-path.XXXXXX" 2>/dev/null) || T=',
4083
+ `[ -z "$T" ] || { ${ask}; I=0; while kill -0 "$P" 2>/dev/null && [ "$I" -lt ${LOGIN_PATH_TICKS} ]; do sleep 0.2; I=$((I + 1)); done; kill -KILL "$P" 2>/dev/null; L=$(sed -n 's/^${PATH_MARKER}//p' "$T" | tail -n 1); rm -f "$T"; } 2>/dev/null`,
4084
+ 'if [ -n "$L" ]; then PATH=$L; LEDUNG_START_PATH=login; else LEDUNG_START_PATH=fallback; fi'
4085
+ ];
4086
+ }
4087
+ function nodeOnPath() {
4088
+ return ['case ":$PATH:" in *":${NODE%/*}:"*) ;; *) PATH="$PATH:${NODE%/*}" ;; esac', "export PATH LEDUNG_START_PATH"];
4089
+ }
4090
+ function signInScript(node, run8, fallbackShell) {
4091
+ const missing = 'echo "Ledung cannot find Node.js. Install Node.js 24, or switch Start at sign-in off and on in Settings in Ledung." >&2; exit 1';
4092
+ return [...loginPath(fallbackShell), ...findNode(node), `[ -x "$NODE" ] || { ${missing}; }`, ...nodeOnPath(), `exec "$NODE" ${run8}`];
4093
+ }
4074
4094
  function isOurApp(app) {
4075
4095
  try {
4076
4096
  return fs13.readFileSync(path16.join(app, "Contents", "Info.plist"), "utf8").includes(`<string>${BUNDLE_ID}</string>`);
@@ -15298,6 +15318,7 @@ var LINK_PAIRINGS_KEPT = 20;
15298
15318
  var LINK_PAIR_TIMES_KEY = "link-pair-times";
15299
15319
  var WAITING_POLL_MS = 3e4;
15300
15320
  var WAITING_PUSH_MS = 1e3;
15321
+ var exercised463 = false;
15301
15322
  var LINKED_HERE = "Computers are linked and unlinked";
15302
15323
  var MAX_BODY2 = 1024 * 1024;
15303
15324
  var NOT_PAIRED = "This device is not paired with Ledung. Pair it from Settings \u2192 Phones in Ledung on the computer.";
@@ -15677,6 +15698,7 @@ var LedungServer = class {
15677
15698
  if (this.remote) this.setRemote(this.remote);
15678
15699
  }
15679
15700
  close() {
15701
+ this.closed = true;
15680
15702
  if (this.waitingPoll) clearInterval(this.waitingPoll);
15681
15703
  if (this.waitingPush) clearTimeout(this.waitingPush);
15682
15704
  for (const ws of this.clients) ws.close(1001, "Ledung is shutting down.");
@@ -16031,6 +16053,11 @@ var LedungServer = class {
16031
16053
  waitingThere = /* @__PURE__ */ new Map();
16032
16054
  waitingPoll = null;
16033
16055
  waitingPush = null;
16056
+ /**
16057
+ * Set by close: an exchange still on its way then changes nothing and starts no new one, as the database may be
16058
+ * closed by the time its answer comes (#463: it threw "database is not open" after a test's end).
16059
+ */
16060
+ closed = false;
16034
16061
  /** The count last sent to the linked computers, so an unchanged one isn't sent again. */
16035
16062
  waitingSent = null;
16036
16063
  /** What waits for the user here, as this computer's page counts it (App: questions, prompts, cards). */
@@ -16039,7 +16066,7 @@ var LedungServer = class {
16039
16066
  }
16040
16067
  /** Something that may change the count happened: sent to the linked computers shortly, if it did change. */
16041
16068
  waitingChanged() {
16042
- if (this.waitingPush || !this.linked || !this.servers.length) return;
16069
+ if (this.closed || this.waitingPush || !this.linked || !this.servers.length) return;
16043
16070
  this.waitingPush = setTimeout(() => {
16044
16071
  this.waitingPush = null;
16045
16072
  if (this.waitingNow() !== this.waitingSent) this.exchangeWaiting();
@@ -16049,7 +16076,7 @@ var LedungServer = class {
16049
16076
  /** Sends this computer's count to every linked one and keeps theirs; one that doesn't answer loses its badge. */
16050
16077
  exchangeWaiting() {
16051
16078
  const linked = this.linked;
16052
- if (!linked) return;
16079
+ if (!linked || this.closed) return;
16053
16080
  const count = this.waitingNow();
16054
16081
  this.waitingSent = count;
16055
16082
  for (const link of linked.list()) {
@@ -16058,6 +16085,13 @@ var LedungServer = class {
16058
16085
  }
16059
16086
  /** A linked computer's count, or undefined when it is offline: the open pages get its tab's badge at once. */
16060
16087
  noteWaiting(id, raw) {
16088
+ if (this.closed) {
16089
+ if (!exercised463) {
16090
+ exercised463 = true;
16091
+ exercisedLine("EXERCISED #463 a linked computer's answer after close was let go");
16092
+ }
16093
+ return;
16094
+ }
16061
16095
  const count = typeof raw === "number" && Number.isInteger(raw) && raw >= 0 && raw < 1e6 ? raw : void 0;
16062
16096
  if (this.waitingThere.get(id) === count) return;
16063
16097
  if (count === void 0) this.waitingThere.delete(id);
@@ -21142,6 +21176,8 @@ async function main() {
21142
21176
  }
21143
21177
  if (PACKAGED && createDefaultConfig()) console.log(`Wrote a default config to ${configPath()}.`);
21144
21178
  const config = loadConfig();
21179
+ const startPath = envSetting("START_PATH");
21180
+ if (startPath) exercisedLine(`EXERCISED #464 started with the ${startPath === "login" ? "login shell's" : "fallback"} PATH`);
21145
21181
  const remote = config.remote ?? savedRemote(remoteDir());
21146
21182
  if (!fs38.existsSync(databaseFile())) console.log(ANTHROPIC_NOTICE);
21147
21183
  const db = openDatabase();
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "ledung",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "ledung",
9
- "version": "1.1.0",
9
+ "version": "1.3.0",
10
10
  "license": "AGPL-3.0-only",
11
11
  "dependencies": {
12
12
  "@anthropic-ai/claude-agent-sdk": "0.3.295",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ledung",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "license": "AGPL-3.0-only",
5
5
  "author": "Project Ledung <martin@projectledung.com>",
6
6
  "description": "Runs several Claude Code sessions locally and shows them in one place.",