bitfab-cli 0.2.31 → 0.2.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +91 -99
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7347,49 +7347,35 @@ function isProcessAlive(pid) {
7347
7347
  return err.code === "EPERM";
7348
7348
  }
7349
7349
  }
7350
- function stampActiveStudioSession(sessionId, field) {
7351
- try {
7352
- const current = readActiveStudioSessionRaw();
7353
- if (!current || current.sessionId !== sessionId || current[field]) {
7354
- return;
7355
- }
7356
- if (current.pid !== process.pid && isProcessAlive(current.pid)) {
7357
- return;
7358
- }
7359
- writeRecord({ ...current, [field]: (/* @__PURE__ */ new Date()).toISOString() });
7360
- } catch {
7361
- }
7362
- }
7363
- function markActiveStudioSessionClosed(sessionId) {
7364
- stampActiveStudioSession(sessionId, "closedAt");
7365
- }
7366
- function markActiveStudioSessionEnded(sessionId) {
7367
- stampActiveStudioSession(sessionId, "endedAt");
7368
- }
7369
- function reactivateActiveStudioSession(sessionId) {
7350
+ function clearActiveStudioSession(sessionId, opts = {}) {
7370
7351
  try {
7371
7352
  const current = readActiveStudioSessionRaw();
7372
7353
  if (!current || current.sessionId !== sessionId) {
7373
- return;
7354
+ return false;
7374
7355
  }
7375
- if (!current.endedAt && !current.closedAt) {
7376
- return;
7356
+ if (!opts.force && current.pid !== process.pid && isProcessAlive(current.pid)) {
7357
+ return false;
7377
7358
  }
7378
- writeRecord({
7379
- sessionId: current.sessionId,
7380
- serviceUrl: current.serviceUrl,
7381
- pid: current.pid,
7382
- startedAt: current.startedAt
7383
- });
7359
+ fs6.unlinkSync(filePath());
7360
+ return true;
7384
7361
  } catch {
7362
+ return false;
7385
7363
  }
7386
7364
  }
7387
7365
  function readActiveStudioSessionRaw() {
7388
7366
  try {
7389
7367
  const raw = fs6.readFileSync(filePath(), "utf-8");
7390
7368
  const parsed = JSON.parse(raw);
7391
- if (typeof parsed.sessionId === "string" && typeof parsed.serviceUrl === "string" && typeof parsed.pid === "number" && typeof parsed.startedAt === "string" && (parsed.closedAt === void 0 || typeof parsed.closedAt === "string") && (parsed.endedAt === void 0 || typeof parsed.endedAt === "string")) {
7392
- return parsed;
7369
+ if (parsed.closedAt != null || parsed.endedAt != null) {
7370
+ return null;
7371
+ }
7372
+ if (typeof parsed.sessionId === "string" && typeof parsed.serviceUrl === "string" && typeof parsed.pid === "number" && typeof parsed.startedAt === "string") {
7373
+ return {
7374
+ sessionId: parsed.sessionId,
7375
+ serviceUrl: parsed.serviceUrl,
7376
+ pid: parsed.pid,
7377
+ startedAt: parsed.startedAt
7378
+ };
7393
7379
  }
7394
7380
  return null;
7395
7381
  } catch {
@@ -7915,26 +7901,29 @@ var StudioNavigationError = class extends Error {
7915
7901
  this.name = "StudioNavigationError";
7916
7902
  }
7917
7903
  };
7918
- async function openStudioTo(path15, opts) {
7919
- const sessionId = opts.sessionId ?? crypto4.randomUUID();
7904
+ async function openStudioTo(path15, opts = {}) {
7905
+ const config2 = getConfig();
7906
+ const serviceUrl = config2.serviceUrl;
7907
+ const apiKey = hasCredentials() ? config2.apiKey : null;
7920
7908
  const noop = () => {
7921
7909
  };
7922
7910
  const restoreFocus = recordFocus();
7923
- if (!opts.apiKey) {
7911
+ if (!apiKey) {
7912
+ const sessionId2 = crypto4.randomUUID();
7924
7913
  const separator = path15.includes("?") ? "&" : "?";
7925
- const redirectPath = `${path15}${separator}session=${encodeURIComponent(sessionId)}&pluginLogin=true`;
7914
+ const redirectPath = `${path15}${separator}session=${encodeURIComponent(sessionId2)}&pluginLogin=true`;
7926
7915
  const signInPath = `/studio/sign-in?redirect_url=${encodeURIComponent(redirectPath)}`;
7927
- const signInUrl = `${opts.serviceUrl}${signInPath}&session=${encodeURIComponent(sessionId)}`;
7916
+ const signInUrl = `${serviceUrl}${signInPath}&session=${encodeURIComponent(sessionId2)}`;
7928
7917
  openChromelessWindow(signInUrl);
7929
- opts.onLoginRequired?.(sessionId, signInUrl);
7918
+ opts.onLoginRequired?.(sessionId2, signInUrl);
7930
7919
  const abortController = new AbortController();
7931
7920
  const timer = setTimeout(() => abortController.abort(), LOGIN_TIMEOUT_MS);
7932
7921
  const keepalive = setInterval(() => process.stderr.write(""), 3e4);
7933
- let apiKey;
7922
+ let loginApiKey;
7934
7923
  try {
7935
- apiKey = await pollLoginEvents({
7936
- serviceUrl: opts.serviceUrl,
7937
- sessionId,
7924
+ loginApiKey = await pollLoginEvents({
7925
+ serviceUrl,
7926
+ sessionId: sessionId2,
7938
7927
  abortSignal: abortController.signal
7939
7928
  });
7940
7929
  } catch (err) {
@@ -7944,82 +7933,90 @@ async function openStudioTo(path15, opts) {
7944
7933
  }
7945
7934
  clearTimeout(timer);
7946
7935
  clearInterval(keepalive);
7947
- saveCredentials(apiKey);
7948
- opts.onAuthenticated?.(sessionId);
7936
+ saveCredentials(loginApiKey);
7937
+ opts.onAuthenticated?.(sessionId2);
7949
7938
  if (opts.onEvent) {
7950
7939
  await waitForSessionReady({
7951
- serviceUrl: opts.serviceUrl,
7952
- apiKey,
7953
- sessionId
7940
+ serviceUrl,
7941
+ apiKey: loginApiKey,
7942
+ sessionId: sessionId2
7954
7943
  });
7955
- writeActiveStudioSession({ sessionId, serviceUrl: opts.serviceUrl });
7956
- const poller2 = startEventPoller(opts.serviceUrl, apiKey, sessionId, opts.onEvent, restoreFocus);
7944
+ writeActiveStudioSession({ sessionId: sessionId2, serviceUrl });
7945
+ const poller2 = startEventPoller(serviceUrl, loginApiKey, sessionId2, opts.onEvent, restoreFocus);
7957
7946
  return {
7958
- sessionId,
7959
- serviceUrl: opts.serviceUrl,
7960
- apiKey,
7947
+ sessionId: sessionId2,
7948
+ serviceUrl,
7949
+ apiKey: loginApiKey,
7961
7950
  opened: true,
7962
7951
  ...poller2
7963
7952
  };
7964
7953
  }
7965
7954
  restoreFocus();
7966
7955
  return {
7967
- sessionId,
7968
- serviceUrl: opts.serviceUrl,
7969
- apiKey,
7956
+ sessionId: sessionId2,
7957
+ serviceUrl,
7958
+ apiKey: loginApiKey,
7970
7959
  opened: true,
7971
7960
  abort: noop,
7972
7961
  done: Promise.resolve()
7973
7962
  };
7974
7963
  }
7975
- const active = opts.forceNew ? null : readActiveStudioSession();
7976
- const reachable = active && !active.closedAt ? active : null;
7977
- const existing = opts.sessionId != null && (reachable?.sessionId !== opts.sessionId || reachable?.serviceUrl !== opts.serviceUrl) ? null : reachable;
7964
+ const existing = readActiveStudioSession();
7978
7965
  if (existing) {
7979
7966
  const client = {
7980
7967
  serviceUrl: existing.serviceUrl,
7981
- apiKey: opts.apiKey,
7968
+ apiKey,
7982
7969
  sessionId: existing.sessionId
7983
7970
  };
7984
7971
  const ack = await navigateStudioAndAwaitAck(client, path15);
7985
- if (ack.acked) {
7986
- if (existing.endedAt != null || existing.closedAt != null) {
7987
- reactivateActiveStudioSession(existing.sessionId);
7988
- }
7989
- const poller2 = opts.onEvent ? startEventPoller(existing.serviceUrl, opts.apiKey, existing.sessionId, opts.onEvent, restoreFocus) : { abort: noop, done: Promise.resolve() };
7990
- return {
7991
- sessionId: existing.sessionId,
7992
- serviceUrl: existing.serviceUrl,
7993
- apiKey: opts.apiKey,
7994
- opened: false,
7995
- ...poller2
7996
- };
7997
- }
7998
- const tabGone = ack.reason === "session-ended" || ack.reason === "push-failed" || ack.reason === "timeout";
7999
- const canReopenSilently = tabGone && (existing.endedAt != null || opts.sessionId != null);
8000
- if (!canReopenSilently) {
7972
+ if (!ack.acked) {
8001
7973
  throw new StudioNavigationError(ack.reason ?? "unknown", ack.blockedReason, existing.sessionId);
8002
7974
  }
7975
+ const poller2 = opts.onEvent ? startEventPoller(existing.serviceUrl, apiKey, existing.sessionId, opts.onEvent, restoreFocus) : { abort: noop, done: Promise.resolve() };
7976
+ return {
7977
+ sessionId: existing.sessionId,
7978
+ serviceUrl: existing.serviceUrl,
7979
+ apiKey,
7980
+ opened: false,
7981
+ ...poller2
7982
+ };
8003
7983
  }
7984
+ const sessionId = crypto4.randomUUID();
8004
7985
  const session = await createStudioSession({
8005
- serviceUrl: opts.serviceUrl,
8006
- apiKey: opts.apiKey,
7986
+ serviceUrl,
7987
+ apiKey,
8007
7988
  sessionId,
8008
7989
  initialPath: path15,
8009
7990
  clientHeaders: opts.clientHeaders
8010
7991
  });
8011
- const poller = opts.onEvent ? startEventPoller(session.serviceUrl, opts.apiKey, session.sessionId, opts.onEvent, restoreFocus) : { abort: noop, done: Promise.resolve() };
7992
+ const poller = opts.onEvent ? startEventPoller(session.serviceUrl, apiKey, session.sessionId, opts.onEvent, restoreFocus) : { abort: noop, done: Promise.resolve() };
8012
7993
  return {
8013
7994
  sessionId: session.sessionId,
8014
7995
  serviceUrl: session.serviceUrl,
8015
- apiKey: opts.apiKey,
7996
+ apiKey,
8016
7997
  opened: true,
8017
7998
  ...poller
8018
7999
  };
8019
8000
  }
8020
8001
  function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus) {
8021
8002
  const abortController = new AbortController();
8022
- let sessionEndGraceTimer = null;
8003
+ let graceTimer = null;
8004
+ let sawSessionEnded = false;
8005
+ const clearGraceTimer = () => {
8006
+ if (graceTimer) {
8007
+ clearTimeout(graceTimer);
8008
+ graceTimer = null;
8009
+ }
8010
+ };
8011
+ const terminate = (clear, baseEvent) => {
8012
+ graceTimer = null;
8013
+ if (clear) {
8014
+ clearActiveStudioSession(sessionId);
8015
+ }
8016
+ restoreFocus();
8017
+ onEvent({ ...baseEvent, type: "studio:session-ended" });
8018
+ abortController.abort();
8019
+ };
8023
8020
  const done = pollAgentSessionEvents({
8024
8021
  serviceUrl,
8025
8022
  apiKey,
@@ -8027,35 +8024,31 @@ function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus)
8027
8024
  abortSignal: abortController.signal,
8028
8025
  onEvent: (event) => {
8029
8026
  if (event.type === "studio:session-closed") {
8030
- if (sessionEndGraceTimer) {
8031
- return;
8027
+ clearGraceTimer();
8028
+ if (sawSessionEnded) {
8029
+ terminate(true, event);
8030
+ } else {
8031
+ graceTimer = setTimeout(() => {
8032
+ terminate(true, event);
8033
+ }, SESSION_CLOSE_GRACE_PERIOD_MS);
8032
8034
  }
8033
- sessionEndGraceTimer = setTimeout(() => {
8034
- sessionEndGraceTimer = null;
8035
- markActiveStudioSessionClosed(sessionId);
8036
- restoreFocus();
8037
- onEvent({ ...event, type: "studio:session-ended" });
8038
- abortController.abort();
8039
- }, SESSION_CLOSE_GRACE_PERIOD_MS);
8040
8035
  return;
8041
8036
  }
8042
8037
  if (event.type === "studio:session-started") {
8043
- if (sessionEndGraceTimer) {
8044
- clearTimeout(sessionEndGraceTimer);
8045
- sessionEndGraceTimer = null;
8038
+ if (graceTimer) {
8039
+ clearGraceTimer();
8046
8040
  console.error("Studio reconnected after page refresh");
8047
8041
  }
8042
+ sawSessionEnded = false;
8048
8043
  return;
8049
8044
  }
8050
8045
  if (event.type === "studio:session-ended") {
8051
- if (sessionEndGraceTimer) {
8052
- clearTimeout(sessionEndGraceTimer);
8053
- sessionEndGraceTimer = null;
8046
+ sawSessionEnded = true;
8047
+ if (!graceTimer) {
8048
+ graceTimer = setTimeout(() => {
8049
+ terminate(false, event);
8050
+ }, SESSION_CLOSE_GRACE_PERIOD_MS);
8054
8051
  }
8055
- markActiveStudioSessionEnded(sessionId);
8056
- restoreFocus();
8057
- onEvent(event);
8058
- abortController.abort();
8059
8052
  return;
8060
8053
  }
8061
8054
  if (event.type === "studio:not-member") {
@@ -8101,7 +8094,6 @@ async function runLogin(platform2, pluginVersion, options) {
8101
8094
  const closePath = `/studio/close?autoClose=true&message=${encodeURIComponent("Login complete")}`;
8102
8095
  try {
8103
8096
  const result = await openStudioTo(closePath, {
8104
- serviceUrl: config2.serviceUrl,
8105
8097
  onLoginRequired: (_sessionId, signInUrl) => {
8106
8098
  console.log(`
8107
8099
  If the browser didn't open, visit this URL manually:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitfab-cli",
3
- "version": "0.2.31",
3
+ "version": "0.2.32",
4
4
  "description": "Install and configure the Bitfab plugin in Claude Code, Codex, or Cursor.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",