@particle-academy/fancy-term-host 0.2.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -746,9 +746,10 @@ interface CwdHook {
746
746
  * Coverage (all overlay, never clobber the user's own prompt/rc):
747
747
  * - **bash** — prepend an OSC-7 `printf` to `PROMPT_COMMAND` (env only). The
748
748
  * portable, reliable case (Git Bash on Windows, bash on POSIX).
749
- * - **zsh** — point `ZDOTDIR` at a generated dir whose `.zshrc` sources the
750
- * user's real `.zshrc`, restores `ZDOTDIR`, then registers a `precmd`
751
- * emitter; `.zshenv` sources the user's real `.zshenv` first.
749
+ * - **zsh** — point `ZDOTDIR` at a generated dir that overlays the FULL login
750
+ * startup chain (`.zshenv` `.zprofile` `.zshrc`), each sourcing the
751
+ * user's real counterpart, restores `ZDOTDIR` in `.zshrc` (so `~/.zlogin`
752
+ * loads from the real dir), then registers a `precmd` emitter.
752
753
  * - **fish** — prepend a generated dir to `XDG_DATA_DIRS` carrying a
753
754
  * `fish/vendor_conf.d/osc7.fish` that hooks `--on-event fish_prompt`
754
755
  * (vendor conf overlays the user's config rather than replacing it).
package/dist/index.d.ts CHANGED
@@ -746,9 +746,10 @@ interface CwdHook {
746
746
  * Coverage (all overlay, never clobber the user's own prompt/rc):
747
747
  * - **bash** — prepend an OSC-7 `printf` to `PROMPT_COMMAND` (env only). The
748
748
  * portable, reliable case (Git Bash on Windows, bash on POSIX).
749
- * - **zsh** — point `ZDOTDIR` at a generated dir whose `.zshrc` sources the
750
- * user's real `.zshrc`, restores `ZDOTDIR`, then registers a `precmd`
751
- * emitter; `.zshenv` sources the user's real `.zshenv` first.
749
+ * - **zsh** — point `ZDOTDIR` at a generated dir that overlays the FULL login
750
+ * startup chain (`.zshenv` `.zprofile` `.zshrc`), each sourcing the
751
+ * user's real counterpart, restores `ZDOTDIR` in `.zshrc` (so `~/.zlogin`
752
+ * loads from the real dir), then registers a `precmd` emitter.
752
753
  * - **fish** — prepend a generated dir to `XDG_DATA_DIRS` carrying a
753
754
  * `fish/vendor_conf.d/osc7.fish` that hooks `--on-event fish_prompt`
754
755
  * (vendor conf overlays the user's config rather than replacing it).
package/dist/index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  import { resolveSpawnCwd, toNativeCwd } from './chunk-V3MW5UTX.js';
2
2
  export { resolveSpawnCwd, toNativeCwd } from './chunk-V3MW5UTX.js';
3
- export { ptyHostScriptPath } from './chunk-DB3SIE53.js';
4
- import { FrameDecoder, PROTOCOL_VERSION, encodeFrame, readPidfile, pidfileUsable, deletePidfile } from './chunk-7NXYRMNA.js';
5
- export { FrameDecoder, PROTOCOL_VERSION, deletePidfile, encodeFrame, isPidAlive, pidfilePath, pidfileUsable, readPidfile, resolveHostScript, socketPathFor, userHash, writePidfile } from './chunk-7NXYRMNA.js';
3
+ export { ptyHostScriptPath } from './chunk-Q4WC2TIE.js';
4
+ import { FrameDecoder, PROTOCOL_VERSION, encodeFrame, readPidfile, pidfileUsable, deletePidfile, isPidAlive, terminateHost, awaitPidGone } from './chunk-2A4TFTNQ.js';
5
+ export { FrameDecoder, PROTOCOL_VERSION, deletePidfile, encodeFrame, isPidAlive, pidfilePath, pidfileUsable, readPidfile, resolveHostScript, socketPathFor, userHash, writePidfile } from './chunk-2A4TFTNQ.js';
6
6
  import { spawn } from 'node-pty';
7
7
  import { EventEmitter } from 'events';
8
8
  import fs2 from 'fs';
9
9
  import path from 'path';
10
10
  import os from 'os';
11
11
  import crypto from 'crypto';
12
+ import { execFileSync } from 'child_process';
12
13
  import zlib from 'zlib';
13
14
  import net from 'net';
14
15
 
@@ -127,6 +128,27 @@ function unixCandidates() {
127
128
  }
128
129
  ];
129
130
  }
131
+ function parseDsclUserShell(output) {
132
+ const m = /UserShell:\s*(\S+)/.exec(output);
133
+ return m ? m[1] : null;
134
+ }
135
+ function macUserShell() {
136
+ try {
137
+ const user = os.userInfo().username;
138
+ const out = execFileSync("dscl", [".", "-read", `/Users/${user}`, "UserShell"], {
139
+ encoding: "utf8",
140
+ timeout: 2e3
141
+ });
142
+ return parseDsclUserShell(out);
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+ function resolveLoginShell(env = process.env, platform = process.platform, macLookup = macUserShell) {
148
+ if (env.SHELL) return env.SHELL;
149
+ if (platform === "darwin") return macLookup();
150
+ return null;
151
+ }
130
152
  function detectShells() {
131
153
  const candidates = process.platform === "win32" ? windowsCandidates() : unixCandidates();
132
154
  const found = [];
@@ -135,14 +157,18 @@ function detectShells() {
135
157
  if (command) found.push({ id: c.id, label: c.label, command, args: c.args });
136
158
  }
137
159
  if (process.platform !== "win32") {
138
- const login = process.env.SHELL;
139
- if (login && !found.some((s) => s.command === login) && fs2.existsSync(login)) {
140
- found.unshift({
160
+ const login = resolveLoginShell();
161
+ if (login && fs2.existsSync(login)) {
162
+ const existing = found.find((s) => s.command === login);
163
+ const rest = found.filter((s) => s.command !== login);
164
+ const head = existing ?? {
141
165
  id: path.basename(login),
142
166
  label: path.basename(login),
143
167
  command: login,
144
168
  args: ["-l"]
145
- });
169
+ };
170
+ found.length = 0;
171
+ found.push(head, ...rest);
146
172
  }
147
173
  }
148
174
  return found;
@@ -204,6 +230,12 @@ function cwdHookSpawn(command, settings) {
204
230
  path.join(dir, ".zshenv"),
205
231
  `# fancy-term-host (generated)
206
232
  [ -f "${orig}/.zshenv" ] && source "${orig}/.zshenv"
233
+ `
234
+ );
235
+ const okProfile = writeShim(
236
+ path.join(dir, ".zprofile"),
237
+ `# fancy-term-host (generated)
238
+ [ -f "${orig}/.zprofile" ] && source "${orig}/.zprofile"
207
239
  `
208
240
  );
209
241
  const okRc = writeShim(
@@ -216,7 +248,7 @@ typeset -ga precmd_functions
216
248
  precmd_functions+=(__fth_osc7)
217
249
  `
218
250
  );
219
- return okEnv && okRc ? { env: { ZDOTDIR: dir }, args: [] } : empty;
251
+ return okEnv && okProfile && okRc ? { env: { ZDOTDIR: dir }, args: [] } : empty;
220
252
  }
221
253
  if (kind === "fish") {
222
254
  const dir = hookDir();
@@ -992,6 +1024,35 @@ async function awaitUsableHost(userData, timeoutMs = 4e3) {
992
1024
  }
993
1025
  return false;
994
1026
  }
1027
+ async function reapHost(userData, pf) {
1028
+ if (pf && isPidAlive(pf.pid) && terminateHost(pf.pid)) {
1029
+ await awaitPidGone(pf.pid, 2e3);
1030
+ }
1031
+ deletePidfile(userData);
1032
+ }
1033
+ async function connectOrSpawnHost(userData, spawner, snapshots) {
1034
+ let pf = readPidfile(userData);
1035
+ if (pidfileUsable(pf)) {
1036
+ try {
1037
+ return await HostClient.connect(pf.socketPath, snapshots);
1038
+ } catch {
1039
+ await reapHost(userData, pf);
1040
+ pf = null;
1041
+ }
1042
+ } else if (pf) {
1043
+ await reapHost(userData, pf);
1044
+ pf = null;
1045
+ }
1046
+ deletePidfile(userData);
1047
+ const hostScript = spawner.resolveHostScript();
1048
+ if (!hostScript) return null;
1049
+ spawner.spawnDetached(hostScript, { GENIE_USERDATA: userData });
1050
+ const up = await awaitUsableHost(userData);
1051
+ if (!up) return null;
1052
+ pf = readPidfile(userData);
1053
+ if (!pf) return null;
1054
+ return await HostClient.connect(pf.socketPath, snapshots);
1055
+ }
995
1056
  async function initTerminalBackend() {
996
1057
  setActiveBackend(inProcessBackend());
997
1058
  if (!deps || !detachedEnabled()) {
@@ -1000,33 +1061,17 @@ async function initTerminalBackend() {
1000
1061
  const { spawner, snapshots } = deps;
1001
1062
  const userData = spawner.userDataDir();
1002
1063
  try {
1003
- const hostScript = spawner.resolveHostScript();
1004
- let pf = readPidfile(userData);
1005
- if (!pidfileUsable(pf)) {
1006
- deletePidfile(userData);
1007
- if (!hostScript) {
1008
- status(
1009
- "Detached terminals unavailable (host not found) \u2014 using in-process. Sessions won't survive a full quit."
1010
- );
1011
- return { host: false, reattachIds: [] };
1012
- }
1013
- spawner.spawnDetached(hostScript, { GENIE_USERDATA: userData });
1014
- const up = await awaitUsableHost(userData);
1015
- if (!up) {
1016
- status(
1017
- "Detached terminals unavailable (host didn't start) \u2014 using in-process. Sessions won't survive a full quit."
1018
- );
1019
- return { host: false, reattachIds: [] };
1020
- }
1021
- pf = readPidfile(userData);
1022
- }
1023
- if (!pf) {
1064
+ const c = await connectOrSpawnHost(userData, spawner, snapshots);
1065
+ if (!c) {
1066
+ client = null;
1067
+ usingHost = false;
1068
+ setActiveBackend(inProcessBackend());
1024
1069
  status(
1025
1070
  "Detached terminals unavailable \u2014 using in-process. Sessions won't survive a full quit."
1026
1071
  );
1027
1072
  return { host: false, reattachIds: [] };
1028
1073
  }
1029
- client = await HostClient.connect(pf.socketPath, snapshots);
1074
+ client = c;
1030
1075
  client.on("error", onHostError);
1031
1076
  setActiveBackend(client);
1032
1077
  usingHost = true;