claude-use 11.53.0 → 11.54.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.cjs CHANGED
@@ -140,7 +140,7 @@ var init_proxyBypass = __esm({
140
140
 
141
141
  // src/config/statusline.ts
142
142
  function distinctList(member) {
143
- return import_zod.z.array(member).refine((items) => new Set(items).size === items.length, "must not repeat a segment");
143
+ return import_zod.z.array(member).refine((items) => new Set(items).size === items.length, "must not repeat an entry");
144
144
  }
145
145
  function isSingleVisibleGrapheme(text) {
146
146
  return [...GRAPHEMES.segment(text)].length === 1 && !/[\p{C}\p{Z}]/u.test(text);
@@ -165,12 +165,12 @@ function resolveStatusline(settings) {
165
165
  }
166
166
  return resolved;
167
167
  }
168
- var import_zod, STATUSLINE_HEADER_SEGMENTS, STATUSLINE_ROWS, STATUSLINE_COLOUR_NAMES, DEFAULT_HEADER_SEGMENTS, DEFAULT_ROWS, STATUSLINE_PATH_LEVELS, MAX_PERCENT, MAX_STATUSLINE_PALETTE_INDEX, MAX_STATUSLINE_LABEL_CHARS, MAX_BAR_CELLS, LABEL_RE2, colourValue, GRAPHEMES, barCharacter, labelText, wholePercent, SHAPES, StatuslineSchema, ResolvedStatuslineSchema, STATUSLINE_DEFAULTS, STATUSLINE_ENV;
168
+ var import_zod, STATUSLINE_HEADER_SEGMENTS, STATUSLINE_ROWS, STATUSLINE_COLOUR_NAMES, DEFAULT_HEADER_SEGMENTS, DEFAULT_ROWS, STATUSLINE_PATH_LEVELS, MAX_PERCENT, MAX_STATUSLINE_PALETTE_INDEX, MAX_STATUSLINE_LABEL_CHARS, MAX_BAR_CELLS, MAX_ACCOUNT_USER_CHARS, MAX_DISPLAY_TEXT_CHARS, LABEL_RE2, OPTIONAL_LABEL_RE, colourValue, GRAPHEMES, barCharacter, labelText, overrideText, wholePercent, SHAPES, StatuslineSchema, ResolvedStatuslineSchema, STATUSLINE_DEFAULTS, STATUSLINE_ENV;
169
169
  var init_statusline = __esm({
170
170
  "src/config/statusline.ts"() {
171
171
  "use strict";
172
172
  import_zod = require("zod");
173
- STATUSLINE_HEADER_SEGMENTS = ["model", "provider", "route", "account", "identity", "headroom", "project", "vcs"];
173
+ STATUSLINE_HEADER_SEGMENTS = ["model", "provider", "route", "account", "identity", "headroom", "project", "vcs", "effort", "advisor", "version", "outputStyle"];
174
174
  STATUSLINE_ROWS = ["context", "quota", "addedDirs", "gitFiles"];
175
175
  STATUSLINE_COLOUR_NAMES = [
176
176
  "default",
@@ -199,17 +199,39 @@ var init_statusline = __esm({
199
199
  MAX_STATUSLINE_PALETTE_INDEX = 255;
200
200
  MAX_STATUSLINE_LABEL_CHARS = 12;
201
201
  MAX_BAR_CELLS = 40;
202
+ MAX_ACCOUNT_USER_CHARS = 254;
203
+ MAX_DISPLAY_TEXT_CHARS = 80;
202
204
  LABEL_RE2 = /^[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*$/u;
205
+ OPTIONAL_LABEL_RE = /^(?:[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*)?$/u;
203
206
  colourValue = import_zod.z.union([import_zod.z.enum(STATUSLINE_COLOUR_NAMES), import_zod.z.number().int().min(0).max(MAX_STATUSLINE_PALETTE_INDEX), import_zod.z.string().regex(/^#[0-9a-fA-F]{6}$/, "must be a colour name, a palette index from 0 to 255 or #rrggbb")]);
204
207
  GRAPHEMES = new Intl.Segmenter(void 0, { granularity: "grapheme" });
205
208
  barCharacter = import_zod.z.string().refine(isSingleVisibleGrapheme, "must be a single visible character");
206
209
  labelText = import_zod.z.string().max(MAX_STATUSLINE_LABEL_CHARS).regex(LABEL_RE2, "must be visible text without control characters");
210
+ overrideText = import_zod.z.string().max(MAX_DISPLAY_TEXT_CHARS).regex(OPTIONAL_LABEL_RE, "must be visible text without control characters");
207
211
  wholePercent = import_zod.z.number().int().min(1).max(MAX_PERCENT);
208
212
  SHAPES = {
209
213
  layout: {
210
214
  header: distinctList(import_zod.z.enum(STATUSLINE_HEADER_SEGMENTS)).describe("The header segments to draw, in drawing order; a segment left out is hidden, and an empty list hides the header."),
211
215
  rows: distinctList(import_zod.z.enum(STATUSLINE_ROWS)).describe("The rows to draw below the header, in drawing order; a row left out is hidden, and an empty list hides every row.")
212
216
  },
217
+ model: {
218
+ format: import_zod.z.enum(["full", "compact", "short"]).describe("How the model's name is written: full is the name as the client gives it, compact drops a context-window suffix such as (1M context), and short also drops a leading Claude."),
219
+ override: overrideText.describe("A name to show in place of the client's own model name, whatever model is running; empty shows the model's own name. A dynamic mode's served model still follows it."),
220
+ source: import_zod.z.enum(["client", "transcript", "auto"]).describe("Where the model's name comes from: client is the name the client reports, transcript is the model the session's last reply came from (what a proxy actually served), and auto uses the transcript's model only when it is not a Claude model, which is what a proxy redirect looks like.")
221
+ },
222
+ provider: {
223
+ name: overrideText.describe("A label to show in place of the launch's provider name, or on its own when the launch has no provider; empty shows the provider's own name.")
224
+ },
225
+ account: {
226
+ parts: distinctList(import_zod.z.enum(["user", "organisation", "plan"])).describe("What the account segment says about the login account, in drawing order: the account's email address, its organisation (shown for team and enterprise accounts) and its plan. A part left out is hidden, and an empty list hides the segment."),
227
+ userLength: import_zod.z.number().int().min(0).max(MAX_ACCOUNT_USER_CHARS).describe("The most characters of the account's email address to show, with an ellipsis after a cut one; 0 shows all of it.")
228
+ },
229
+ effort: {
230
+ format: import_zod.z.enum(["full", "symbol", "text"]).describe("How the reasoning effort is written: full is a symbol and the level (a filled circle that grows with the level, then the name), symbol is the symbol alone, and text is the level's name alone. A level with no symbol is always written as its name.")
231
+ },
232
+ advisor: {
233
+ override: overrideText.describe("A name to show for the advisor model in place of the one the session's transcript records; empty shows the transcript's, prettified.")
234
+ },
213
235
  bands: {
214
236
  warnAt: wholePercent.describe("The whole percentage of use at which a bar turns yellow; it must be below criticalAt."),
215
237
  criticalAt: wholePercent.describe("The whole percentage of use at which a bar turns red.")
@@ -257,6 +279,11 @@ var init_statusline = __esm({
257
279
  };
258
280
  StatuslineSchema = import_zod.z.strictObject({
259
281
  layout: import_zod.z.strictObject(SHAPES.layout).partial().optional(),
282
+ model: import_zod.z.strictObject(SHAPES.model).partial().optional(),
283
+ provider: import_zod.z.strictObject(SHAPES.provider).partial().optional(),
284
+ account: import_zod.z.strictObject(SHAPES.account).partial().optional(),
285
+ effort: import_zod.z.strictObject(SHAPES.effort).partial().optional(),
286
+ advisor: import_zod.z.strictObject(SHAPES.advisor).partial().optional(),
260
287
  bands: import_zod.z.strictObject(SHAPES.bands).partial().optional(),
261
288
  labels: import_zod.z.strictObject(SHAPES.labels).partial().optional(),
262
289
  bar: import_zod.z.strictObject(SHAPES.bar).partial().optional(),
@@ -267,6 +294,11 @@ var init_statusline = __esm({
267
294
  });
268
295
  ResolvedStatuslineSchema = import_zod.z.strictObject({
269
296
  layout: import_zod.z.strictObject(SHAPES.layout),
297
+ model: import_zod.z.strictObject(SHAPES.model),
298
+ provider: import_zod.z.strictObject(SHAPES.provider),
299
+ account: import_zod.z.strictObject(SHAPES.account),
300
+ effort: import_zod.z.strictObject(SHAPES.effort),
301
+ advisor: import_zod.z.strictObject(SHAPES.advisor),
270
302
  bands: import_zod.z.strictObject(SHAPES.bands),
271
303
  labels: import_zod.z.strictObject(SHAPES.labels),
272
304
  bar: import_zod.z.strictObject(SHAPES.bar),
@@ -277,6 +309,11 @@ var init_statusline = __esm({
277
309
  });
278
310
  STATUSLINE_DEFAULTS = {
279
311
  layout: { header: [...DEFAULT_HEADER_SEGMENTS], rows: [...DEFAULT_ROWS] },
312
+ model: { format: "full", override: "", source: "client" },
313
+ provider: { name: "" },
314
+ account: { parts: ["user", "organisation", "plan"], userLength: 0 },
315
+ effort: { format: "full" },
316
+ advisor: { override: "" },
280
317
  bands: { warnAt: 70, criticalAt: 90 },
281
318
  labels: { fiveHour: "5h", sevenDay: "Wk" },
282
319
  bar: { width: 10, filled: "\u2588", empty: "\u2591" },
@@ -3791,9 +3828,9 @@ async function serveFrontDoor(idleShutdownMinutes, ports, options) {
3791
3828
  const watchdog = ports.startWatchdog?.();
3792
3829
  let idleSince;
3793
3830
  let ticks = 0;
3794
- const supersedingOwner = () => {
3831
+ const supersedingOwner = async () => {
3795
3832
  const owner = readFrontDoorState(fs14, paths.frontdoorStateFile)?.supervisorPid;
3796
- return owner !== void 0 && owner !== ports.ownPid && ports.isRunning(owner) ? owner : void 0;
3833
+ return owner !== void 0 && owner !== ports.ownPid && (await ports.runningAmong([owner])).has(owner) ? owner : void 0;
3797
3834
  };
3798
3835
  let supersededBy;
3799
3836
  let served;
@@ -3818,7 +3855,7 @@ async function serveFrontDoor(idleShutdownMinutes, ports, options) {
3818
3855
  watchdog?.stop();
3819
3856
  ports.stopBackgroundWork?.();
3820
3857
  const drained = [direct.drain(), connect3.drain(), http3.drain()];
3821
- if (supersedingOwner() === void 0) {
3858
+ if (await supersedingOwner() === void 0) {
3822
3859
  writeFrontDoorState(fs14, paths.frontdoorStateFile, sticky());
3823
3860
  }
3824
3861
  markFrontDoorDraining(fs14, paths.frontdoorDrainingDir, ports.ownPid);
@@ -3839,7 +3876,7 @@ async function serveFrontDoor(idleShutdownMinutes, ports, options) {
3839
3876
  };
3840
3877
  const shutdown = async () => {
3841
3878
  await closeServing();
3842
- if (supersedingOwner() === void 0) {
3879
+ if (await supersedingOwner() === void 0) {
3843
3880
  writeFrontDoorState(fs14, paths.frontdoorStateFile, sticky());
3844
3881
  }
3845
3882
  return 0;
@@ -3855,13 +3892,14 @@ async function serveFrontDoor(idleShutdownMinutes, ports, options) {
3855
3892
  }
3856
3893
  ticks += 1;
3857
3894
  const listed = listFrontDoorSessions(fs14, paths.frontdoorSessionsDir);
3858
- const pruned = pruneDeadFrontDoorSessions(fs14, paths.frontdoorSessionsDir, ports.isRunning);
3895
+ const running = await ports.runningAmong(listed.map((session) => session.pid));
3896
+ const pruned = pruneDeadFrontDoorSessions(fs14, paths.frontdoorSessionsDir, (pid) => running.has(pid));
3859
3897
  ports.observeLaunchRegistry(listed, pruned);
3860
3898
  ports.checkQuotaExpiry();
3861
3899
  const prunedPids = new Set(pruned);
3862
3900
  const live = listed.filter((session) => !prunedPids.has(session.pid));
3863
3901
  if (supersededBy === void 0) {
3864
- supersededBy = supersedingOwner();
3902
+ supersededBy = await supersedingOwner();
3865
3903
  if (supersededBy !== void 0) {
3866
3904
  ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: supervisor ${String(supersededBy)} now owns the front door; taking no new sessions and exiting once the sessions this door serves have ended`);
3867
3905
  ports.doorHealth.superseded(supersededBy);
@@ -4361,7 +4399,7 @@ var init_package = __esm({
4361
4399
  "package.json"() {
4362
4400
  package_default = {
4363
4401
  name: "claude-use",
4364
- version: "11.53.0",
4402
+ version: "11.54.1",
4365
4403
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
4366
4404
  license: "MIT",
4367
4405
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -5615,6 +5653,7 @@ function realIsProcessAlive(pid) {
5615
5653
  }
5616
5654
  }
5617
5655
  function realIsProcessZombie(pid) {
5656
+ refuseToBlockInRole("a synchronous process-table read");
5618
5657
  const result = (0, import_node_child_process2.spawnSync)("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
5619
5658
  return result.status === 0 && result.stdout.trim().startsWith("Z");
5620
5659
  }
@@ -5622,14 +5661,17 @@ function realIsProcessRunning(pid) {
5622
5661
  return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
5623
5662
  }
5624
5663
  function realIsFrontDoorSupervisor(pid) {
5664
+ refuseToBlockInRole("a synchronous process-table read");
5625
5665
  const result = (0, import_node_child_process2.spawnSync)("ps", ["-o", "pid=,command=", "-p", String(pid)], { encoding: "utf8" });
5626
5666
  return result.status === 0 && parseFrontDoorSupervisorPids(result.stdout).includes(pid);
5627
5667
  }
5628
5668
  function realProcessCommandLine(pid) {
5669
+ refuseToBlockInRole("a synchronous process-table read");
5629
5670
  const result = (0, import_node_child_process2.spawnSync)("ps", ["-o", "command=", "-p", String(pid)], { encoding: "utf8", timeout: PS_TIMEOUT_MS });
5630
5671
  return result.status === 0 ? result.stdout.trim() : void 0;
5631
5672
  }
5632
5673
  function realFrontDoorLiveness(pids) {
5674
+ refuseToBlockInRole("a synchronous process-table read");
5633
5675
  return frontDoorLivenessOf(pids, (asked) => {
5634
5676
  const result = (0, import_node_child_process2.spawnSync)("ps", ["-o", "pid=,stat=,command=", "-p", asked.map(String).join(",")], { encoding: "utf8", timeout: PS_TIMEOUT_MS });
5635
5677
  return { status: result.status, stdout: result.stdout, stderr: result.stderr, ...result.error === void 0 ? {} : { error: result.error } };
package/dist/index.mjs CHANGED
@@ -119,7 +119,7 @@ var init_proxyBypass = __esm({
119
119
  // src/config/statusline.ts
120
120
  import { z } from "zod";
121
121
  function distinctList(member) {
122
- return z.array(member).refine((items) => new Set(items).size === items.length, "must not repeat a segment");
122
+ return z.array(member).refine((items) => new Set(items).size === items.length, "must not repeat an entry");
123
123
  }
124
124
  function isSingleVisibleGrapheme(text) {
125
125
  return [...GRAPHEMES.segment(text)].length === 1 && !/[\p{C}\p{Z}]/u.test(text);
@@ -144,11 +144,11 @@ function resolveStatusline(settings) {
144
144
  }
145
145
  return resolved;
146
146
  }
147
- var STATUSLINE_HEADER_SEGMENTS, STATUSLINE_ROWS, STATUSLINE_COLOUR_NAMES, DEFAULT_HEADER_SEGMENTS, DEFAULT_ROWS, STATUSLINE_PATH_LEVELS, MAX_PERCENT, MAX_STATUSLINE_PALETTE_INDEX, MAX_STATUSLINE_LABEL_CHARS, MAX_BAR_CELLS, LABEL_RE2, colourValue, GRAPHEMES, barCharacter, labelText, wholePercent, SHAPES, StatuslineSchema, ResolvedStatuslineSchema, STATUSLINE_DEFAULTS, STATUSLINE_ENV;
147
+ var STATUSLINE_HEADER_SEGMENTS, STATUSLINE_ROWS, STATUSLINE_COLOUR_NAMES, DEFAULT_HEADER_SEGMENTS, DEFAULT_ROWS, STATUSLINE_PATH_LEVELS, MAX_PERCENT, MAX_STATUSLINE_PALETTE_INDEX, MAX_STATUSLINE_LABEL_CHARS, MAX_BAR_CELLS, MAX_ACCOUNT_USER_CHARS, MAX_DISPLAY_TEXT_CHARS, LABEL_RE2, OPTIONAL_LABEL_RE, colourValue, GRAPHEMES, barCharacter, labelText, overrideText, wholePercent, SHAPES, StatuslineSchema, ResolvedStatuslineSchema, STATUSLINE_DEFAULTS, STATUSLINE_ENV;
148
148
  var init_statusline = __esm({
149
149
  "src/config/statusline.ts"() {
150
150
  "use strict";
151
- STATUSLINE_HEADER_SEGMENTS = ["model", "provider", "route", "account", "identity", "headroom", "project", "vcs"];
151
+ STATUSLINE_HEADER_SEGMENTS = ["model", "provider", "route", "account", "identity", "headroom", "project", "vcs", "effort", "advisor", "version", "outputStyle"];
152
152
  STATUSLINE_ROWS = ["context", "quota", "addedDirs", "gitFiles"];
153
153
  STATUSLINE_COLOUR_NAMES = [
154
154
  "default",
@@ -177,17 +177,39 @@ var init_statusline = __esm({
177
177
  MAX_STATUSLINE_PALETTE_INDEX = 255;
178
178
  MAX_STATUSLINE_LABEL_CHARS = 12;
179
179
  MAX_BAR_CELLS = 40;
180
+ MAX_ACCOUNT_USER_CHARS = 254;
181
+ MAX_DISPLAY_TEXT_CHARS = 80;
180
182
  LABEL_RE2 = /^[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*$/u;
183
+ OPTIONAL_LABEL_RE = /^(?:[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*)?$/u;
181
184
  colourValue = z.union([z.enum(STATUSLINE_COLOUR_NAMES), z.number().int().min(0).max(MAX_STATUSLINE_PALETTE_INDEX), z.string().regex(/^#[0-9a-fA-F]{6}$/, "must be a colour name, a palette index from 0 to 255 or #rrggbb")]);
182
185
  GRAPHEMES = new Intl.Segmenter(void 0, { granularity: "grapheme" });
183
186
  barCharacter = z.string().refine(isSingleVisibleGrapheme, "must be a single visible character");
184
187
  labelText = z.string().max(MAX_STATUSLINE_LABEL_CHARS).regex(LABEL_RE2, "must be visible text without control characters");
188
+ overrideText = z.string().max(MAX_DISPLAY_TEXT_CHARS).regex(OPTIONAL_LABEL_RE, "must be visible text without control characters");
185
189
  wholePercent = z.number().int().min(1).max(MAX_PERCENT);
186
190
  SHAPES = {
187
191
  layout: {
188
192
  header: distinctList(z.enum(STATUSLINE_HEADER_SEGMENTS)).describe("The header segments to draw, in drawing order; a segment left out is hidden, and an empty list hides the header."),
189
193
  rows: distinctList(z.enum(STATUSLINE_ROWS)).describe("The rows to draw below the header, in drawing order; a row left out is hidden, and an empty list hides every row.")
190
194
  },
195
+ model: {
196
+ format: z.enum(["full", "compact", "short"]).describe("How the model's name is written: full is the name as the client gives it, compact drops a context-window suffix such as (1M context), and short also drops a leading Claude."),
197
+ override: overrideText.describe("A name to show in place of the client's own model name, whatever model is running; empty shows the model's own name. A dynamic mode's served model still follows it."),
198
+ source: z.enum(["client", "transcript", "auto"]).describe("Where the model's name comes from: client is the name the client reports, transcript is the model the session's last reply came from (what a proxy actually served), and auto uses the transcript's model only when it is not a Claude model, which is what a proxy redirect looks like.")
199
+ },
200
+ provider: {
201
+ name: overrideText.describe("A label to show in place of the launch's provider name, or on its own when the launch has no provider; empty shows the provider's own name.")
202
+ },
203
+ account: {
204
+ parts: distinctList(z.enum(["user", "organisation", "plan"])).describe("What the account segment says about the login account, in drawing order: the account's email address, its organisation (shown for team and enterprise accounts) and its plan. A part left out is hidden, and an empty list hides the segment."),
205
+ userLength: z.number().int().min(0).max(MAX_ACCOUNT_USER_CHARS).describe("The most characters of the account's email address to show, with an ellipsis after a cut one; 0 shows all of it.")
206
+ },
207
+ effort: {
208
+ format: z.enum(["full", "symbol", "text"]).describe("How the reasoning effort is written: full is a symbol and the level (a filled circle that grows with the level, then the name), symbol is the symbol alone, and text is the level's name alone. A level with no symbol is always written as its name.")
209
+ },
210
+ advisor: {
211
+ override: overrideText.describe("A name to show for the advisor model in place of the one the session's transcript records; empty shows the transcript's, prettified.")
212
+ },
191
213
  bands: {
192
214
  warnAt: wholePercent.describe("The whole percentage of use at which a bar turns yellow; it must be below criticalAt."),
193
215
  criticalAt: wholePercent.describe("The whole percentage of use at which a bar turns red.")
@@ -235,6 +257,11 @@ var init_statusline = __esm({
235
257
  };
236
258
  StatuslineSchema = z.strictObject({
237
259
  layout: z.strictObject(SHAPES.layout).partial().optional(),
260
+ model: z.strictObject(SHAPES.model).partial().optional(),
261
+ provider: z.strictObject(SHAPES.provider).partial().optional(),
262
+ account: z.strictObject(SHAPES.account).partial().optional(),
263
+ effort: z.strictObject(SHAPES.effort).partial().optional(),
264
+ advisor: z.strictObject(SHAPES.advisor).partial().optional(),
238
265
  bands: z.strictObject(SHAPES.bands).partial().optional(),
239
266
  labels: z.strictObject(SHAPES.labels).partial().optional(),
240
267
  bar: z.strictObject(SHAPES.bar).partial().optional(),
@@ -245,6 +272,11 @@ var init_statusline = __esm({
245
272
  });
246
273
  ResolvedStatuslineSchema = z.strictObject({
247
274
  layout: z.strictObject(SHAPES.layout),
275
+ model: z.strictObject(SHAPES.model),
276
+ provider: z.strictObject(SHAPES.provider),
277
+ account: z.strictObject(SHAPES.account),
278
+ effort: z.strictObject(SHAPES.effort),
279
+ advisor: z.strictObject(SHAPES.advisor),
248
280
  bands: z.strictObject(SHAPES.bands),
249
281
  labels: z.strictObject(SHAPES.labels),
250
282
  bar: z.strictObject(SHAPES.bar),
@@ -255,6 +287,11 @@ var init_statusline = __esm({
255
287
  });
256
288
  STATUSLINE_DEFAULTS = {
257
289
  layout: { header: [...DEFAULT_HEADER_SEGMENTS], rows: [...DEFAULT_ROWS] },
290
+ model: { format: "full", override: "", source: "client" },
291
+ provider: { name: "" },
292
+ account: { parts: ["user", "organisation", "plan"], userLength: 0 },
293
+ effort: { format: "full" },
294
+ advisor: { override: "" },
258
295
  bands: { warnAt: 70, criticalAt: 90 },
259
296
  labels: { fiveHour: "5h", sevenDay: "Wk" },
260
297
  bar: { width: 10, filled: "\u2588", empty: "\u2591" },
@@ -3768,9 +3805,9 @@ async function serveFrontDoor(idleShutdownMinutes, ports, options) {
3768
3805
  const watchdog = ports.startWatchdog?.();
3769
3806
  let idleSince;
3770
3807
  let ticks = 0;
3771
- const supersedingOwner = () => {
3808
+ const supersedingOwner = async () => {
3772
3809
  const owner = readFrontDoorState(fs14, paths.frontdoorStateFile)?.supervisorPid;
3773
- return owner !== void 0 && owner !== ports.ownPid && ports.isRunning(owner) ? owner : void 0;
3810
+ return owner !== void 0 && owner !== ports.ownPid && (await ports.runningAmong([owner])).has(owner) ? owner : void 0;
3774
3811
  };
3775
3812
  let supersededBy;
3776
3813
  let served;
@@ -3795,7 +3832,7 @@ async function serveFrontDoor(idleShutdownMinutes, ports, options) {
3795
3832
  watchdog?.stop();
3796
3833
  ports.stopBackgroundWork?.();
3797
3834
  const drained = [direct.drain(), connect3.drain(), http3.drain()];
3798
- if (supersedingOwner() === void 0) {
3835
+ if (await supersedingOwner() === void 0) {
3799
3836
  writeFrontDoorState(fs14, paths.frontdoorStateFile, sticky());
3800
3837
  }
3801
3838
  markFrontDoorDraining(fs14, paths.frontdoorDrainingDir, ports.ownPid);
@@ -3816,7 +3853,7 @@ async function serveFrontDoor(idleShutdownMinutes, ports, options) {
3816
3853
  };
3817
3854
  const shutdown = async () => {
3818
3855
  await closeServing();
3819
- if (supersedingOwner() === void 0) {
3856
+ if (await supersedingOwner() === void 0) {
3820
3857
  writeFrontDoorState(fs14, paths.frontdoorStateFile, sticky());
3821
3858
  }
3822
3859
  return 0;
@@ -3832,13 +3869,14 @@ async function serveFrontDoor(idleShutdownMinutes, ports, options) {
3832
3869
  }
3833
3870
  ticks += 1;
3834
3871
  const listed = listFrontDoorSessions(fs14, paths.frontdoorSessionsDir);
3835
- const pruned = pruneDeadFrontDoorSessions(fs14, paths.frontdoorSessionsDir, ports.isRunning);
3872
+ const running = await ports.runningAmong(listed.map((session) => session.pid));
3873
+ const pruned = pruneDeadFrontDoorSessions(fs14, paths.frontdoorSessionsDir, (pid) => running.has(pid));
3836
3874
  ports.observeLaunchRegistry(listed, pruned);
3837
3875
  ports.checkQuotaExpiry();
3838
3876
  const prunedPids = new Set(pruned);
3839
3877
  const live = listed.filter((session) => !prunedPids.has(session.pid));
3840
3878
  if (supersededBy === void 0) {
3841
- supersededBy = supersedingOwner();
3879
+ supersededBy = await supersedingOwner();
3842
3880
  if (supersededBy !== void 0) {
3843
3881
  ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: supervisor ${String(supersededBy)} now owns the front door; taking no new sessions and exiting once the sessions this door serves have ended`);
3844
3882
  ports.doorHealth.superseded(supersededBy);
@@ -4337,7 +4375,7 @@ var init_package = __esm({
4337
4375
  "package.json"() {
4338
4376
  package_default = {
4339
4377
  name: "claude-use",
4340
- version: "11.53.0",
4378
+ version: "11.54.1",
4341
4379
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
4342
4380
  license: "MIT",
4343
4381
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -5571,7 +5609,7 @@ var init_logStyle = __esm({
5571
5609
  });
5572
5610
 
5573
5611
  // src/realPorts.ts
5574
- import { spawn as spawn2, spawnSync } from "node:child_process";
5612
+ import { execFile, spawn as spawn2, spawnSync } from "node:child_process";
5575
5613
  import { createHash as createHash5 } from "node:crypto";
5576
5614
  import fs3 from "node:fs";
5577
5615
  import os3 from "node:os";
@@ -5596,6 +5634,7 @@ function realIsProcessAlive(pid) {
5596
5634
  }
5597
5635
  }
5598
5636
  function realIsProcessZombie(pid) {
5637
+ refuseToBlockInRole("a synchronous process-table read");
5599
5638
  const result = spawnSync("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
5600
5639
  return result.status === 0 && result.stdout.trim().startsWith("Z");
5601
5640
  }
@@ -5603,14 +5642,17 @@ function realIsProcessRunning(pid) {
5603
5642
  return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
5604
5643
  }
5605
5644
  function realIsFrontDoorSupervisor(pid) {
5645
+ refuseToBlockInRole("a synchronous process-table read");
5606
5646
  const result = spawnSync("ps", ["-o", "pid=,command=", "-p", String(pid)], { encoding: "utf8" });
5607
5647
  return result.status === 0 && parseFrontDoorSupervisorPids(result.stdout).includes(pid);
5608
5648
  }
5609
5649
  function realProcessCommandLine(pid) {
5650
+ refuseToBlockInRole("a synchronous process-table read");
5610
5651
  const result = spawnSync("ps", ["-o", "command=", "-p", String(pid)], { encoding: "utf8", timeout: PS_TIMEOUT_MS });
5611
5652
  return result.status === 0 ? result.stdout.trim() : void 0;
5612
5653
  }
5613
5654
  function realFrontDoorLiveness(pids) {
5655
+ refuseToBlockInRole("a synchronous process-table read");
5614
5656
  return frontDoorLivenessOf(pids, (asked) => {
5615
5657
  const result = spawnSync("ps", ["-o", "pid=,stat=,command=", "-p", asked.map(String).join(",")], { encoding: "utf8", timeout: PS_TIMEOUT_MS });
5616
5658
  return { status: result.status, stdout: result.stdout, stderr: result.stderr, ...result.error === void 0 ? {} : { error: result.error } };