bitfab-cli 0.2.4 → 0.2.8

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 +430 -779
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3482,7 +3482,7 @@ var require_schemes = __commonJS({
3482
3482
  urnComponent.nss = (uuidComponent.uuid || "").toLowerCase();
3483
3483
  return urnComponent;
3484
3484
  }
3485
- var http3 = (
3485
+ var http = (
3486
3486
  /** @type {SchemeHandler} */
3487
3487
  {
3488
3488
  scheme: "http",
@@ -3495,7 +3495,7 @@ var require_schemes = __commonJS({
3495
3495
  /** @type {SchemeHandler} */
3496
3496
  {
3497
3497
  scheme: "https",
3498
- domainHost: http3.domainHost,
3498
+ domainHost: http.domainHost,
3499
3499
  parse: httpParse,
3500
3500
  serialize: httpSerialize
3501
3501
  }
@@ -3539,7 +3539,7 @@ var require_schemes = __commonJS({
3539
3539
  var SCHEMES = (
3540
3540
  /** @type {Record<SchemeName, SchemeHandler>} */
3541
3541
  {
3542
- http: http3,
3542
+ http,
3543
3543
  https,
3544
3544
  ws,
3545
3545
  wss,
@@ -6805,15 +6805,215 @@ import { cancel as cancel2 } from "@clack/prompts";
6805
6805
  import { execSync as execSync3 } from "child_process";
6806
6806
  import * as p4 from "@clack/prompts";
6807
6807
 
6808
- // ../bitfab-plugin-lib/dist/browserHandoff.js
6809
- import http2 from "http";
6808
+ // ../bitfab-plugin-lib/dist/buildInfo.js
6809
+ import crypto from "crypto";
6810
+ import fs from "fs";
6811
+ import os from "os";
6812
+ import path from "path";
6813
+ import { fileURLToPath } from "url";
6814
+
6815
+ // ../bitfab-plugin-lib/dist/chatSessions/index.js
6816
+ import { execFileSync } from "child_process";
6817
+ import fs5 from "fs";
6818
+ import os4 from "os";
6819
+ import path4 from "path";
6820
+
6821
+ // ../bitfab-plugin-lib/dist/skills.js
6822
+ var BITFAB_SKILLS = ["setup", "assistant", "update"];
6823
+
6824
+ // ../bitfab-plugin-lib/dist/hooks/skillDetection.js
6825
+ var SKILL_PATTERN = new RegExp(`^\\s*\\/bitfab[:-](?<skill>${BITFAB_SKILLS.join("|")})(?=\\s|$)`, "i");
6826
+
6827
+ // ../bitfab-plugin-lib/dist/chatSessions/arming.js
6828
+ import fs2 from "fs";
6829
+ import os2 from "os";
6830
+ import path2 from "path";
6831
+
6832
+ // ../bitfab-plugin-lib/dist/config.js
6833
+ import crypto2 from "crypto";
6834
+ import fs3 from "fs";
6835
+ import os3 from "os";
6836
+ import path3 from "path";
6837
+ var DEFAULT_SERVICE_URL = "https://bitfab.ai";
6838
+ var GLOBAL_CONFIG_DIR = path3.join(os3.homedir(), ".config", "bitfab");
6839
+ var GLOBAL_CONFIG_FILE = path3.join(GLOBAL_CONFIG_DIR, "config.json");
6840
+ var GLOBAL_CREDENTIALS_FILE = path3.join(GLOBAL_CONFIG_DIR, "credentials.json");
6841
+ var PROJECT_CONFIG_RELATIVE = path3.join(".bitfab", "config.local.json");
6842
+ function readJsonFile(filePath) {
6843
+ try {
6844
+ const content = fs3.readFileSync(filePath, "utf-8");
6845
+ return JSON.parse(content);
6846
+ } catch {
6847
+ return null;
6848
+ }
6849
+ }
6850
+ function findProjectConfigPath(startDir = process.cwd()) {
6851
+ let dir = startDir;
6852
+ while (true) {
6853
+ const candidate = path3.join(dir, PROJECT_CONFIG_RELATIVE);
6854
+ if (fs3.existsSync(candidate)) {
6855
+ return candidate;
6856
+ }
6857
+ const parent = path3.dirname(dir);
6858
+ if (parent === dir) {
6859
+ return null;
6860
+ }
6861
+ dir = parent;
6862
+ }
6863
+ }
6864
+ function getProjectConfigData() {
6865
+ const filePath = findProjectConfigPath();
6866
+ if (!filePath) {
6867
+ return null;
6868
+ }
6869
+ return readJsonFile(filePath);
6870
+ }
6871
+ function getConfigData() {
6872
+ return readJsonFile(GLOBAL_CONFIG_FILE) ?? {};
6873
+ }
6874
+ function getCredentialsData() {
6875
+ return readJsonFile(GLOBAL_CREDENTIALS_FILE) ?? {};
6876
+ }
6877
+ function getServiceUrl() {
6878
+ if (process.env.BITFAB_SERVICE_URL) {
6879
+ return process.env.BITFAB_SERVICE_URL;
6880
+ }
6881
+ const project = getProjectConfigData();
6882
+ if (project && typeof project.serviceUrl === "string") {
6883
+ return project.serviceUrl;
6884
+ }
6885
+ const config2 = getConfigData();
6886
+ return typeof config2.serviceUrl === "string" ? config2.serviceUrl : DEFAULT_SERVICE_URL;
6887
+ }
6888
+ function getApiKey() {
6889
+ if (process.env.BITFAB_API_KEY) {
6890
+ return process.env.BITFAB_API_KEY;
6891
+ }
6892
+ const creds = getCredentialsData();
6893
+ return typeof creds.apiKey === "string" ? creds.apiKey : null;
6894
+ }
6895
+ function getVerbose() {
6896
+ if (process.env.BITFAB_VERBOSE === "true" || process.env.BITFAB_VERBOSE === "1") {
6897
+ return true;
6898
+ }
6899
+ if (process.env.BITFAB_VERBOSE === "false" || process.env.BITFAB_VERBOSE === "0") {
6900
+ return false;
6901
+ }
6902
+ const config2 = getConfigData();
6903
+ return config2.verbose === true;
6904
+ }
6905
+ function getDebug() {
6906
+ if (process.env.BITFAB_DEBUG === "true" || process.env.BITFAB_DEBUG === "1") {
6907
+ return true;
6908
+ }
6909
+ const config2 = getConfigData();
6910
+ return config2.debug === true;
6911
+ }
6912
+ function getSessionLogConsentValue() {
6913
+ const config2 = getConfigData();
6914
+ if (config2.sessionLogConsent === true) {
6915
+ return true;
6916
+ }
6917
+ if (config2.sessionLogConsent === false) {
6918
+ return false;
6919
+ }
6920
+ return null;
6921
+ }
6922
+ function getConfig() {
6923
+ return {
6924
+ serviceUrl: getServiceUrl(),
6925
+ apiKey: getApiKey(),
6926
+ verbose: getVerbose(),
6927
+ debug: getDebug(),
6928
+ sessionLogConsent: getSessionLogConsentValue()
6929
+ };
6930
+ }
6931
+ function saveCredentials(apiKey) {
6932
+ fs3.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
6933
+ fs3.writeFileSync(GLOBAL_CREDENTIALS_FILE, `${JSON.stringify({ apiKey }, null, 2)}
6934
+ `);
6935
+ }
6936
+ function deleteCredentials() {
6937
+ try {
6938
+ fs3.unlinkSync(GLOBAL_CREDENTIALS_FILE);
6939
+ } catch {
6940
+ }
6941
+ }
6942
+ function hasCredentials() {
6943
+ return getApiKey() !== null;
6944
+ }
6945
+ function getOrCreateInstallId() {
6946
+ const config2 = getConfigData();
6947
+ if (typeof config2.installId === "string" && config2.installId.length > 0) {
6948
+ return config2.installId;
6949
+ }
6950
+ const installId = crypto2.randomUUID();
6951
+ try {
6952
+ fs3.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
6953
+ fs3.writeFileSync(GLOBAL_CONFIG_FILE, `${JSON.stringify({ ...config2, installId }, null, 2)}
6954
+ `);
6955
+ } catch {
6956
+ }
6957
+ return installId;
6958
+ }
6959
+
6960
+ // ../bitfab-plugin-lib/dist/chatSessions/transcriptReader.js
6961
+ import fs4 from "fs";
6962
+
6963
+ // ../bitfab-plugin-lib/dist/clientHeaders.js
6964
+ import os5 from "os";
6965
+ function buildBitfabClientHeaders(pluginVersion, platform2) {
6966
+ const resolvedOptions = Intl.DateTimeFormat().resolvedOptions();
6967
+ return {
6968
+ "x-bitfab-client-name": platform2.cliBinary,
6969
+ "x-bitfab-client-display-name": platform2.displayName,
6970
+ "x-bitfab-client-version": pluginVersion,
6971
+ "x-bitfab-client-timezone": resolvedOptions.timeZone,
6972
+ "x-bitfab-client-locale": resolvedOptions.locale,
6973
+ "x-bitfab-client-os": os5.platform(),
6974
+ "x-bitfab-client-arch": os5.arch(),
6975
+ "x-bitfab-client-node-version": process.version,
6976
+ "x-bitfab-install-id": getOrCreateInstallId()
6977
+ };
6978
+ }
6979
+ function buildBitfabRequestHeaders(apiKey, pluginVersion, platform2) {
6980
+ return {
6981
+ "Content-Type": "application/json",
6982
+ Authorization: `Bearer ${apiKey}`,
6983
+ ...buildBitfabClientHeaders(pluginVersion, platform2)
6984
+ };
6985
+ }
6986
+
6987
+ // ../bitfab-plugin-lib/dist/commands/login.js
6988
+ import crypto3 from "crypto";
6989
+
6990
+ // ../bitfab-plugin-lib/dist/analytics.js
6991
+ async function reportHandoff(apiKey, pluginVersion, platform2, body) {
6992
+ const config2 = getConfig();
6993
+ const headers = buildBitfabRequestHeaders(apiKey, pluginVersion, platform2);
6994
+ try {
6995
+ const response = await fetch(`${config2.serviceUrl}/api/plugin/analytics/handoff`, {
6996
+ method: "POST",
6997
+ headers,
6998
+ body: JSON.stringify(body)
6999
+ });
7000
+ if (config2.debug && !response.ok) {
7001
+ console.error(`reportHandoff: server returned ${response.status} for ${body.flow}`);
7002
+ }
7003
+ } catch (err) {
7004
+ if (config2.debug) {
7005
+ const message = err instanceof Error ? err.message : String(err);
7006
+ console.error(`reportHandoff: request failed for ${body.flow}: ${message}`);
7007
+ }
7008
+ }
7009
+ }
6810
7010
 
6811
7011
  // ../bitfab-plugin-lib/dist/browser.js
6812
7012
  import { execSync, spawn } from "child_process";
6813
- import fs from "fs";
6814
- import os from "os";
7013
+ import fs6 from "fs";
7014
+ import os6 from "os";
6815
7015
  function getChromiumBrowsers() {
6816
- const platform2 = os.platform();
7016
+ const platform2 = os6.platform();
6817
7017
  if (platform2 === "darwin") {
6818
7018
  return [
6819
7019
  {
@@ -6901,7 +7101,7 @@ function getChromiumBrowsers() {
6901
7101
  function findExistingBinary(paths) {
6902
7102
  for (const candidate of paths) {
6903
7103
  if (candidate.includes("/") || candidate.includes("\\")) {
6904
- if (fs.existsSync(candidate)) {
7104
+ if (fs6.existsSync(candidate)) {
6905
7105
  return candidate;
6906
7106
  }
6907
7107
  } else {
@@ -6915,10 +7115,10 @@ function findExistingBinary(paths) {
6915
7115
  return null;
6916
7116
  }
6917
7117
  function getDefaultBrowserId() {
6918
- const platform2 = os.platform();
7118
+ const platform2 = os6.platform();
6919
7119
  try {
6920
7120
  if (platform2 === "darwin") {
6921
- const plistPath = `${os.homedir()}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`;
7121
+ const plistPath = `${os6.homedir()}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`;
6922
7122
  const json2 = execSync(`plutil -convert json -o - "${plistPath}"`, {
6923
7123
  stdio: ["ignore", "pipe", "ignore"],
6924
7124
  encoding: "utf-8",
@@ -6959,7 +7159,7 @@ function matchChromiumBrowser(defaultId, browsers) {
6959
7159
  var SCREEN_FRACTION = 0.95;
6960
7160
  function getWorkArea() {
6961
7161
  try {
6962
- const platform2 = os.platform();
7162
+ const platform2 = os6.platform();
6963
7163
  if (platform2 === "darwin") {
6964
7164
  const out = execSync(`osascript -l JavaScript -e 'ObjC.import("AppKit"); const s = $.NSScreen.mainScreen; const f = s.frame; const v = s.visibleFrame; const topY = f.size.height - (v.origin.y + v.size.height); JSON.stringify([v.origin.x, topY, v.size.width, v.size.height])'`, {
6965
7165
  stdio: ["ignore", "pipe", "ignore"],
@@ -6992,631 +7192,91 @@ function getWorkArea() {
6992
7192
  timeout: 3e3
6993
7193
  });
6994
7194
  const match = out.match(/(-?\d+),(-?\d+),(\d+),(\d+)/);
6995
- if (match) {
6996
- return {
6997
- x: Number(match[1]),
6998
- y: Number(match[2]),
6999
- width: Number(match[3]),
7000
- height: Number(match[4])
7001
- };
7002
- }
7003
- }
7004
- } catch {
7005
- }
7006
- return null;
7007
- }
7008
- function computeWindowArgs(area) {
7009
- if (!area) {
7010
- return [];
7011
- }
7012
- const w = Math.floor(area.width * SCREEN_FRACTION);
7013
- const h = Math.floor(area.height * SCREEN_FRACTION);
7014
- const x = area.x + Math.floor((area.width - w) / 2);
7015
- const y = area.y + Math.floor((area.height - h) / 2);
7016
- return [`--window-size=${w},${h}`, `--window-position=${x},${y}`];
7017
- }
7018
- function getWindowSizeArgs() {
7019
- return computeWindowArgs(getWorkArea());
7020
- }
7021
- function spawnDetached(command, args) {
7022
- const child = spawn(command, [...args], { detached: true, stdio: "ignore" });
7023
- child.on("error", () => {
7024
- });
7025
- child.unref();
7026
- }
7027
- function openChromiumApp(binaryPath, url2, sizeArgs) {
7028
- spawnDetached(binaryPath, [`--app=${url2}`, ...sizeArgs]);
7029
- }
7030
- function openOsDefault(url2) {
7031
- const platform2 = os.platform();
7032
- if (platform2 === "darwin") {
7033
- spawnDetached("open", [url2]);
7034
- } else if (platform2 === "win32") {
7035
- spawnDetached("cmd", ["/c", "start", "", url2]);
7036
- } else {
7037
- spawnDetached("xdg-open", [url2]);
7038
- }
7039
- }
7040
- function isCodexOnMacos() {
7041
- if (process.platform !== "darwin") {
7042
- return false;
7043
- }
7044
- return process.env.CODEX_SANDBOX === "seatbelt" || process.env.CODEX_CI === "1" || process.env.CODEX_THREAD_ID != null;
7045
- }
7046
- function shouldDisableChromelessWindows() {
7047
- if (process.env.BITFAB_DISABLE_CHROME_APP_WINDOWS === "1") {
7048
- return true;
7049
- }
7050
- return isCodexOnMacos();
7051
- }
7052
- function openChromelessWindow(url2) {
7053
- if (shouldDisableChromelessWindows()) {
7054
- openOsDefault(url2);
7055
- return;
7056
- }
7057
- const browsers = getChromiumBrowsers();
7058
- const defaultId = getDefaultBrowserId();
7059
- const defaultChromium = matchChromiumBrowser(defaultId, browsers);
7060
- const sizeArgs = getWindowSizeArgs();
7061
- if (defaultChromium) {
7062
- const binary = findExistingBinary(defaultChromium.binaryPaths);
7063
- if (binary) {
7064
- openChromiumApp(binary, url2, sizeArgs);
7065
- return;
7066
- }
7067
- }
7068
- if (defaultId !== null && defaultChromium === null) {
7069
- openOsDefault(url2);
7070
- return;
7071
- }
7072
- for (const browser of browsers) {
7073
- const binary = findExistingBinary(browser.binaryPaths);
7074
- if (binary) {
7075
- openChromiumApp(binary, url2, sizeArgs);
7076
- return;
7077
- }
7078
- }
7079
- openOsDefault(url2);
7080
- }
7081
-
7082
- // ../bitfab-plugin-lib/dist/handoffTickets.js
7083
- var DEFAULT_POLL_INTERVAL_MS = 1500;
7084
- async function createHandoffTicket(serviceUrl, kind, opts) {
7085
- const headers = {
7086
- "Content-Type": "application/json"
7087
- };
7088
- if (opts?.bearer) {
7089
- headers.Authorization = `Bearer ${opts.bearer}`;
7090
- }
7091
- const res = await fetch(`${serviceUrl}/api/plugin/handoff-tickets`, {
7092
- method: "POST",
7093
- headers,
7094
- body: JSON.stringify({
7095
- kind,
7096
- pkceChallenge: opts?.pkceChallenge,
7097
- pkceMethod: opts?.pkceMethod,
7098
- ttlSeconds: opts?.ttlSeconds
7099
- })
7100
- });
7101
- if (!res.ok) {
7102
- const body = await res.json().catch(() => ({}));
7103
- throw new Error(body.error ?? `Failed to create handoff ticket (${res.status})`);
7104
- }
7105
- return await res.json();
7106
- }
7107
- async function pollHandoffTicket(serviceUrl, ticketId, opts) {
7108
- const interval = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
7109
- const deadline = Date.now() + opts.timeoutMs;
7110
- while (Date.now() < deadline) {
7111
- if (opts.abortSignal?.aborted) {
7112
- return { expired: true };
7113
- }
7114
- const url2 = new URL(`${serviceUrl}/api/plugin/handoff-tickets/${ticketId}`);
7115
- if (opts.verifier) {
7116
- url2.searchParams.set("verifier", opts.verifier);
7117
- }
7118
- const headers = {};
7119
- if (opts.bearer) {
7120
- headers.Authorization = `Bearer ${opts.bearer}`;
7121
- }
7122
- try {
7123
- const res = await fetch(url2.toString(), { headers });
7124
- if (res.ok) {
7125
- const body = await res.json();
7126
- if (body.status === "completed" && body.data) {
7127
- return { data: body.data };
7128
- }
7129
- if (body.status === "expired" || body.status === "cancelled") {
7130
- return { expired: true };
7131
- }
7132
- }
7133
- } catch {
7134
- }
7135
- await new Promise((resolve, reject) => {
7136
- const timer = setTimeout(resolve, interval);
7137
- opts.abortSignal?.addEventListener("abort", () => {
7138
- clearTimeout(timer);
7139
- reject(new DOMException("Aborted", "AbortError"));
7140
- }, { once: true });
7141
- }).catch(() => {
7142
- });
7143
- }
7144
- return { expired: true };
7145
- }
7146
-
7147
- // ../bitfab-plugin-lib/dist/pkce.js
7148
- import crypto from "crypto";
7149
- function generatePkce() {
7150
- const verifier = crypto.randomBytes(32).toString("base64url");
7151
- const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
7152
- return { verifier, challenge };
7153
- }
7154
-
7155
- // ../bitfab-plugin-lib/dist/handoffTicketChannel.js
7156
- async function startHandoffTicketChannel(opts) {
7157
- const { verifier, challenge } = generatePkce();
7158
- const abort = new AbortController();
7159
- let done = false;
7160
- let resolveFn = () => {
7161
- };
7162
- let rejectFn = () => {
7163
- };
7164
- const promise2 = new Promise((resolve, reject) => {
7165
- resolveFn = (value) => {
7166
- if (done) {
7167
- return;
7168
- }
7169
- done = true;
7170
- resolve(value);
7171
- };
7172
- rejectFn = (err) => {
7173
- if (done) {
7174
- return;
7175
- }
7176
- done = true;
7177
- reject(err);
7178
- };
7179
- });
7180
- let startError = null;
7181
- let ticket = null;
7182
- try {
7183
- ticket = await createHandoffTicket(opts.serviceUrl, opts.kind, {
7184
- pkceChallenge: challenge,
7185
- pkceMethod: "S256",
7186
- bearer: opts.bearer,
7187
- ttlSeconds: opts.ttlSeconds
7188
- });
7189
- } catch (err) {
7190
- startError = err instanceof Error ? err : new Error(String(err));
7191
- }
7192
- const available = ticket !== null;
7193
- if (ticket) {
7194
- pollHandoffTicket(opts.serviceUrl, ticket.id, {
7195
- verifier,
7196
- bearer: opts.bearer,
7197
- timeoutMs: opts.timeoutMs,
7198
- intervalMs: ticket.pollIntervalMs,
7199
- abortSignal: abort.signal
7200
- }).then((result) => {
7201
- if ("expired" in result) {
7202
- rejectFn(new Error("Handoff ticket expired before completion."));
7203
- return;
7204
- }
7205
- resolveFn(result.data);
7206
- }).catch((err) => {
7207
- rejectFn(err instanceof Error ? err : new Error(String(err)));
7208
- });
7209
- } else {
7210
- promise2.catch(() => {
7211
- });
7212
- }
7213
- return {
7214
- promise: promise2,
7215
- get done() {
7216
- return done;
7217
- },
7218
- available,
7219
- startError,
7220
- ticketId: ticket?.id ?? null,
7221
- reject: rejectFn,
7222
- close: () => {
7223
- abort.abort();
7224
- rejectFn(new Error("Handoff ticket channel closed."));
7225
- }
7226
- };
7227
- }
7228
-
7229
- // ../bitfab-plugin-lib/dist/loopback.js
7230
- import http from "http";
7231
- async function startLoopbackServer(port, routes) {
7232
- let done = false;
7233
- let resolveFn = () => {
7234
- };
7235
- let rejectFn = () => {
7236
- };
7237
- const promise2 = new Promise((resolve, reject) => {
7238
- resolveFn = (value) => {
7239
- if (done) {
7240
- return;
7241
- }
7242
- done = true;
7243
- resolve(value);
7244
- };
7245
- rejectFn = (err) => {
7246
- if (done) {
7247
- return;
7248
- }
7249
- done = true;
7250
- reject(err);
7251
- };
7252
- });
7253
- const server = http.createServer((req, res) => {
7254
- res.setHeader("Access-Control-Allow-Origin", "*");
7255
- res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
7256
- res.setHeader("Access-Control-Allow-Private-Network", "true");
7257
- if (req.method === "OPTIONS") {
7258
- res.writeHead(204);
7259
- res.end();
7260
- return;
7261
- }
7262
- const url2 = new URL(req.url ?? "/", `http://localhost:${port}`);
7263
- if (url2.pathname === "/health") {
7264
- res.writeHead(200, { "Content-Type": "application/json" });
7265
- res.end(JSON.stringify({ ok: true }));
7266
- return;
7267
- }
7268
- const route = routes.find((r) => r.path === url2.pathname);
7269
- if (route) {
7270
- try {
7271
- route.handle(url2.searchParams, res, resolveFn);
7272
- } catch (err) {
7273
- rejectFn(err);
7274
- }
7275
- return;
7276
- }
7277
- res.writeHead(404);
7278
- res.end();
7279
- });
7280
- let bindError = null;
7281
- try {
7282
- await new Promise((resolve, reject) => {
7283
- const onError = (err) => {
7284
- server.removeListener("listening", onListening);
7285
- reject(err);
7286
- };
7287
- const onListening = () => {
7288
- server.removeListener("error", onError);
7289
- resolve();
7290
- };
7291
- server.once("error", onError);
7292
- server.once("listening", onListening);
7293
- server.listen(port, "127.0.0.1");
7294
- });
7295
- } catch (err) {
7296
- bindError = err;
7297
- }
7298
- const available = bindError === null;
7299
- if (available) {
7300
- server.on("error", (err) => {
7301
- rejectFn(new Error(`Loopback server error: ${err.message}`));
7302
- });
7303
- } else {
7304
- promise2.catch(() => {
7305
- });
7306
- }
7307
- return {
7308
- promise: promise2,
7309
- get done() {
7310
- return done;
7311
- },
7312
- available,
7313
- bindError,
7314
- resolve: resolveFn,
7315
- reject: rejectFn,
7316
- close: () => {
7317
- if (available) {
7318
- server.close();
7319
- }
7320
- }
7321
- };
7322
- }
7323
-
7324
- // ../bitfab-plugin-lib/dist/browserHandoff.js
7325
- var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
7326
- var DEFAULT_REDISPLAY_DELAY_MS = 4 * 1e3;
7327
- var DEFAULT_HEARTBEAT_INTERVAL_MS = 30 * 1e3;
7328
- function findOpenPort() {
7329
- return new Promise((resolve, reject) => {
7330
- const server = http2.createServer();
7331
- server.on("error", (err) => {
7332
- server.close(() => reject(err));
7333
- });
7334
- server.listen(0, "127.0.0.1", () => {
7335
- const addr = server.address();
7336
- if (addr && typeof addr === "object") {
7337
- const port = addr.port;
7338
- server.close(() => resolve(port));
7339
- } else {
7340
- reject(new Error("Could not find open port"));
7341
- }
7342
- });
7343
- });
7344
- }
7345
- async function startBrowserHandoff(opts) {
7346
- const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
7347
- const forceTicket = process.env.BITFAB_FORCE_TICKET === "1";
7348
- let port = null;
7349
- let portBindError = null;
7350
- if (!forceTicket) {
7351
- try {
7352
- port = await findOpenPort();
7353
- } catch (err) {
7354
- portBindError = err;
7355
- }
7356
- }
7357
- const loopbackUnavailable = {
7358
- promise: new Promise(() => {
7359
- }),
7360
- get done() {
7361
- return false;
7362
- },
7363
- available: false,
7364
- bindError: portBindError ?? new Error("BITFAB_FORCE_TICKET=1 disabled loopback"),
7365
- resolve: () => {
7366
- },
7367
- reject: () => {
7368
- },
7369
- close: () => {
7370
- }
7371
- };
7372
- const [loopback, ticket] = await Promise.all([
7373
- forceTicket || port === null ? Promise.resolve(loopbackUnavailable) : startLoopbackServer(port, opts.loopbackRoutes),
7374
- startHandoffTicketChannel({
7375
- serviceUrl: opts.serviceUrl,
7376
- kind: opts.ticketKind,
7377
- timeoutMs,
7378
- ttlSeconds: opts.ticketTtlSeconds,
7379
- bearer: opts.ticketBearer
7380
- })
7381
- ]);
7382
- const browserUrl = opts.buildUrl({
7383
- port: loopback.available ? port : null,
7384
- ticketId: ticket.ticketId
7385
- });
7386
- return { loopback, ticket, port, browserUrl, timeoutMs };
7387
- }
7388
- function attachBrowserHandoffKeepalive(opts) {
7389
- const redisplayDelay = opts.redisplayDelayMs ?? DEFAULT_REDISPLAY_DELAY_MS;
7390
- const heartbeatInterval = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
7391
- const startedAt = Date.now();
7392
- const done = () => opts.loopback.done || opts.ticket.done;
7393
- const redisplayTimer = setTimeout(() => {
7394
- if (done()) {
7395
- return;
7396
- }
7397
- console.error("");
7398
- console.error("Still waiting for browser handoff. If the browser didn't open,");
7399
- console.error("copy this URL into any browser \u2014 this session is still valid:");
7400
- console.error("");
7401
- console.error(` ${opts.browserUrl}`);
7402
- console.error("");
7403
- }, redisplayDelay);
7404
- const heartbeat = process.stdout.isTTY ? null : setInterval(() => {
7405
- if (done()) {
7406
- return;
7407
- }
7408
- const elapsed = Math.round((Date.now() - startedAt) / 1e3);
7409
- console.error(`[bitfab] waiting for browser handoff... ${elapsed}s elapsed, session still live`);
7410
- }, heartbeatInterval);
7411
- return () => {
7412
- clearTimeout(redisplayTimer);
7413
- if (heartbeat) {
7414
- clearInterval(heartbeat);
7415
- }
7416
- };
7417
- }
7418
-
7419
- // ../bitfab-plugin-lib/dist/buildInfo.js
7420
- import crypto2 from "crypto";
7421
- import fs2 from "fs";
7422
- import os2 from "os";
7423
- import path from "path";
7424
- import { fileURLToPath } from "url";
7425
-
7426
- // ../bitfab-plugin-lib/dist/chatSessions/index.js
7427
- import { execFileSync } from "child_process";
7428
- import fs6 from "fs";
7429
- import os5 from "os";
7430
- import path4 from "path";
7431
-
7432
- // ../bitfab-plugin-lib/dist/skills.js
7433
- var BITFAB_SKILLS = ["setup", "assistant", "update"];
7434
-
7435
- // ../bitfab-plugin-lib/dist/hooks/skillDetection.js
7436
- var SKILL_PATTERN = new RegExp(`^\\s*\\/bitfab[:-](?<skill>${BITFAB_SKILLS.join("|")})(?=\\s|$)`, "i");
7437
-
7438
- // ../bitfab-plugin-lib/dist/chatSessions/arming.js
7439
- import fs3 from "fs";
7440
- import os3 from "os";
7441
- import path2 from "path";
7442
-
7443
- // ../bitfab-plugin-lib/dist/config.js
7444
- import crypto3 from "crypto";
7445
- import fs4 from "fs";
7446
- import os4 from "os";
7447
- import path3 from "path";
7448
- var DEFAULT_SERVICE_URL = "https://bitfab.ai";
7449
- var GLOBAL_CONFIG_DIR = path3.join(os4.homedir(), ".config", "bitfab");
7450
- var GLOBAL_CONFIG_FILE = path3.join(GLOBAL_CONFIG_DIR, "config.json");
7451
- var GLOBAL_CREDENTIALS_FILE = path3.join(GLOBAL_CONFIG_DIR, "credentials.json");
7452
- var PROJECT_CONFIG_RELATIVE = path3.join(".bitfab", "config.local.json");
7453
- function readJsonFile(filePath) {
7454
- try {
7455
- const content = fs4.readFileSync(filePath, "utf-8");
7456
- return JSON.parse(content);
7457
- } catch {
7458
- return null;
7459
- }
7460
- }
7461
- function findProjectConfigPath(startDir = process.cwd()) {
7462
- let dir = startDir;
7463
- while (true) {
7464
- const candidate = path3.join(dir, PROJECT_CONFIG_RELATIVE);
7465
- if (fs4.existsSync(candidate)) {
7466
- return candidate;
7467
- }
7468
- const parent = path3.dirname(dir);
7469
- if (parent === dir) {
7470
- return null;
7195
+ if (match) {
7196
+ return {
7197
+ x: Number(match[1]),
7198
+ y: Number(match[2]),
7199
+ width: Number(match[3]),
7200
+ height: Number(match[4])
7201
+ };
7202
+ }
7471
7203
  }
7472
- dir = parent;
7204
+ } catch {
7473
7205
  }
7206
+ return null;
7474
7207
  }
7475
- function getProjectConfigData() {
7476
- const filePath = findProjectConfigPath();
7477
- if (!filePath) {
7478
- return null;
7208
+ function computeWindowArgs(area) {
7209
+ if (!area) {
7210
+ return [];
7479
7211
  }
7480
- return readJsonFile(filePath);
7212
+ const w = Math.floor(area.width * SCREEN_FRACTION);
7213
+ const h = Math.floor(area.height * SCREEN_FRACTION);
7214
+ const x = area.x + Math.floor((area.width - w) / 2);
7215
+ const y = area.y + Math.floor((area.height - h) / 2);
7216
+ return [`--window-size=${w},${h}`, `--window-position=${x},${y}`];
7481
7217
  }
7482
- function getConfigData() {
7483
- return readJsonFile(GLOBAL_CONFIG_FILE) ?? {};
7218
+ function getWindowSizeArgs() {
7219
+ return computeWindowArgs(getWorkArea());
7484
7220
  }
7485
- function getCredentialsData() {
7486
- return readJsonFile(GLOBAL_CREDENTIALS_FILE) ?? {};
7221
+ function spawnDetached(command, args) {
7222
+ const child = spawn(command, [...args], { detached: true, stdio: "ignore" });
7223
+ child.on("error", () => {
7224
+ });
7225
+ child.unref();
7487
7226
  }
7488
- function getServiceUrl() {
7489
- if (process.env.BITFAB_SERVICE_URL) {
7490
- return process.env.BITFAB_SERVICE_URL;
7491
- }
7492
- const project = getProjectConfigData();
7493
- if (project && typeof project.serviceUrl === "string") {
7494
- return project.serviceUrl;
7495
- }
7496
- const config2 = getConfigData();
7497
- return typeof config2.serviceUrl === "string" ? config2.serviceUrl : DEFAULT_SERVICE_URL;
7227
+ function openChromiumApp(binaryPath, url2, sizeArgs) {
7228
+ spawnDetached(binaryPath, [`--app=${url2}`, ...sizeArgs]);
7498
7229
  }
7499
- function getApiKey() {
7500
- if (process.env.BITFAB_API_KEY) {
7501
- return process.env.BITFAB_API_KEY;
7230
+ function openOsDefault(url2) {
7231
+ const platform2 = os6.platform();
7232
+ if (platform2 === "darwin") {
7233
+ spawnDetached("open", [url2]);
7234
+ } else if (platform2 === "win32") {
7235
+ spawnDetached("cmd", ["/c", "start", "", url2]);
7236
+ } else {
7237
+ spawnDetached("xdg-open", [url2]);
7502
7238
  }
7503
- const creds = getCredentialsData();
7504
- return typeof creds.apiKey === "string" ? creds.apiKey : null;
7505
7239
  }
7506
- function getVerbose() {
7507
- if (process.env.BITFAB_VERBOSE === "true" || process.env.BITFAB_VERBOSE === "1") {
7508
- return true;
7509
- }
7510
- if (process.env.BITFAB_VERBOSE === "false" || process.env.BITFAB_VERBOSE === "0") {
7240
+ function isCodexOnMacos() {
7241
+ if (process.platform !== "darwin") {
7511
7242
  return false;
7512
7243
  }
7513
- const config2 = getConfigData();
7514
- return config2.verbose === true;
7515
- }
7516
- function getDebug() {
7517
- if (process.env.BITFAB_DEBUG === "true" || process.env.BITFAB_DEBUG === "1") {
7518
- return true;
7519
- }
7520
- const config2 = getConfigData();
7521
- return config2.debug === true;
7244
+ return process.env.CODEX_SANDBOX === "seatbelt" || process.env.CODEX_CI === "1" || process.env.CODEX_THREAD_ID != null;
7522
7245
  }
7523
- function getSessionLogConsentValue() {
7524
- const config2 = getConfigData();
7525
- if (config2.sessionLogConsent === true) {
7246
+ function shouldDisableChromelessWindows() {
7247
+ if (process.env.BITFAB_DISABLE_CHROME_APP_WINDOWS === "1") {
7526
7248
  return true;
7527
7249
  }
7528
- if (config2.sessionLogConsent === false) {
7529
- return false;
7530
- }
7531
- return null;
7532
- }
7533
- function getConfig() {
7534
- return {
7535
- serviceUrl: getServiceUrl(),
7536
- apiKey: getApiKey(),
7537
- verbose: getVerbose(),
7538
- debug: getDebug(),
7539
- sessionLogConsent: getSessionLogConsentValue()
7540
- };
7541
- }
7542
- function saveCredentials(apiKey) {
7543
- fs4.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
7544
- fs4.writeFileSync(GLOBAL_CREDENTIALS_FILE, `${JSON.stringify({ apiKey }, null, 2)}
7545
- `);
7250
+ return isCodexOnMacos();
7546
7251
  }
7547
- function deleteCredentials() {
7548
- try {
7549
- fs4.unlinkSync(GLOBAL_CREDENTIALS_FILE);
7550
- } catch {
7252
+ function openChromelessWindow(url2) {
7253
+ if (shouldDisableChromelessWindows()) {
7254
+ openOsDefault(url2);
7255
+ return;
7551
7256
  }
7552
- }
7553
- function hasCredentials() {
7554
- return getApiKey() !== null;
7555
- }
7556
- function getOrCreateInstallId() {
7557
- const config2 = getConfigData();
7558
- if (typeof config2.installId === "string" && config2.installId.length > 0) {
7559
- return config2.installId;
7257
+ const browsers = getChromiumBrowsers();
7258
+ const defaultId = getDefaultBrowserId();
7259
+ const defaultChromium = matchChromiumBrowser(defaultId, browsers);
7260
+ const sizeArgs = getWindowSizeArgs();
7261
+ if (defaultChromium) {
7262
+ const binary = findExistingBinary(defaultChromium.binaryPaths);
7263
+ if (binary) {
7264
+ openChromiumApp(binary, url2, sizeArgs);
7265
+ return;
7266
+ }
7560
7267
  }
7561
- const installId = crypto3.randomUUID();
7562
- try {
7563
- fs4.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
7564
- fs4.writeFileSync(GLOBAL_CONFIG_FILE, `${JSON.stringify({ ...config2, installId }, null, 2)}
7565
- `);
7566
- } catch {
7268
+ if (defaultId !== null && defaultChromium === null) {
7269
+ openOsDefault(url2);
7270
+ return;
7567
7271
  }
7568
- return installId;
7569
- }
7570
-
7571
- // ../bitfab-plugin-lib/dist/chatSessions/transcriptReader.js
7572
- import fs5 from "fs";
7573
-
7574
- // ../bitfab-plugin-lib/dist/clientHeaders.js
7575
- import os6 from "os";
7576
- function buildBitfabClientHeaders(pluginVersion, platform2) {
7577
- const resolvedOptions = Intl.DateTimeFormat().resolvedOptions();
7578
- return {
7579
- "x-bitfab-client-name": platform2.cliBinary,
7580
- "x-bitfab-client-display-name": platform2.displayName,
7581
- "x-bitfab-client-version": pluginVersion,
7582
- "x-bitfab-client-timezone": resolvedOptions.timeZone,
7583
- "x-bitfab-client-locale": resolvedOptions.locale,
7584
- "x-bitfab-client-os": os6.platform(),
7585
- "x-bitfab-client-arch": os6.arch(),
7586
- "x-bitfab-client-node-version": process.version,
7587
- "x-bitfab-install-id": getOrCreateInstallId()
7588
- };
7589
- }
7590
- function buildBitfabRequestHeaders(apiKey, pluginVersion, platform2) {
7591
- return {
7592
- "Content-Type": "application/json",
7593
- Authorization: `Bearer ${apiKey}`,
7594
- ...buildBitfabClientHeaders(pluginVersion, platform2)
7595
- };
7596
- }
7597
-
7598
- // ../bitfab-plugin-lib/dist/commands/login.js
7599
- import readline from "readline";
7600
-
7601
- // ../bitfab-plugin-lib/dist/analytics.js
7602
- async function reportHandoff(apiKey, pluginVersion, platform2, body) {
7603
- const config2 = getConfig();
7604
- const headers = buildBitfabRequestHeaders(apiKey, pluginVersion, platform2);
7605
- try {
7606
- const response = await fetch(`${config2.serviceUrl}/api/plugin/analytics/handoff`, {
7607
- method: "POST",
7608
- headers,
7609
- body: JSON.stringify(body)
7610
- });
7611
- if (config2.debug && !response.ok) {
7612
- console.error(`reportHandoff: server returned ${response.status} for ${body.flow}`);
7613
- }
7614
- } catch (err) {
7615
- if (config2.debug) {
7616
- const message = err instanceof Error ? err.message : String(err);
7617
- console.error(`reportHandoff: request failed for ${body.flow}: ${message}`);
7272
+ for (const browser of browsers) {
7273
+ const binary = findExistingBinary(browser.binaryPaths);
7274
+ if (binary) {
7275
+ openChromiumApp(binary, url2, sizeArgs);
7276
+ return;
7618
7277
  }
7619
7278
  }
7279
+ openOsDefault(url2);
7620
7280
  }
7621
7281
 
7622
7282
  // ../bitfab-plugin-lib/dist/frontmostApp.js
@@ -7766,16 +7426,7 @@ function recordFocus() {
7766
7426
 
7767
7427
  // ../bitfab-plugin-lib/dist/commands/login.js
7768
7428
  var LOGIN_TIMEOUT_MS = 10 * 60 * 1e3;
7769
- var PASTE_HINT_DELAY_MS = 8 * 1e3;
7770
- var CALLBACK_HTML = `<html>
7771
- <body style="margin:0;background:#f8fafc;color:#0f172a;font-family:system-ui,-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;overflow:hidden">
7772
- <div style="text-align:center;max-width:400px">
7773
- <h1 style="font-size:24px;font-weight:600;margin:0 0 16px 0">Bitfab</h1>
7774
- <p style="margin:0;color:#059669">Authenticated! You can close this window.</p>
7775
- <button onclick="window.close()" style="margin-top:16px;padding:8px 16px;background:#0f172a;color:white;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer">Close Window</button>
7776
- </div>
7777
- </body>
7778
- </html>`;
7429
+ var POLL_INTERVAL_MS = 1500;
7779
7430
  async function verifyToken(serviceUrl, token) {
7780
7431
  try {
7781
7432
  const res = await fetch(`${serviceUrl}/api/plugin/whoami`, {
@@ -7789,141 +7440,86 @@ async function verifyToken(serviceUrl, token) {
7789
7440
  return null;
7790
7441
  }
7791
7442
  }
7443
+ async function pollLoginStatus(serviceUrl, loginId, signal) {
7444
+ while (!signal.aborted) {
7445
+ try {
7446
+ const url2 = `${serviceUrl}/api/studio/login-status?loginId=${encodeURIComponent(loginId)}`;
7447
+ const res = await fetch(url2, { signal });
7448
+ if (res.ok) {
7449
+ const body = await res.json();
7450
+ if (body.status === "complete" && body.token) {
7451
+ return body.token;
7452
+ }
7453
+ }
7454
+ } catch {
7455
+ if (signal.aborted) {
7456
+ throw new Error("Login polling aborted");
7457
+ }
7458
+ }
7459
+ await new Promise((resolve) => {
7460
+ const timer = setTimeout(resolve, POLL_INTERVAL_MS);
7461
+ signal.addEventListener("abort", () => {
7462
+ clearTimeout(timer);
7463
+ resolve();
7464
+ }, { once: true });
7465
+ });
7466
+ }
7467
+ throw new Error("Login polling aborted");
7468
+ }
7792
7469
  async function runLogin(platform2, pluginVersion, options) {
7793
7470
  const exitOnComplete = options?.exitOnComplete ?? true;
7794
7471
  const config2 = getConfig();
7795
7472
  const restoreFocus = recordFocus();
7796
- const interactive = process.stdin.isTTY === true;
7797
- const { loopback, ticket, browserUrl } = await startBrowserHandoff({
7798
- serviceUrl: config2.serviceUrl,
7799
- ticketKind: "auth.login",
7800
- timeoutMs: LOGIN_TIMEOUT_MS,
7801
- buildUrl: ({ port, ticketId }) => {
7802
- const urlParams = new URLSearchParams();
7803
- if (port !== null) {
7804
- urlParams.set("callbackPort", String(port));
7805
- }
7806
- if (ticketId !== null) {
7807
- urlParams.set("ticket", ticketId);
7808
- }
7809
- const base = `${config2.serviceUrl}/plugin/auth/${platform2.authPath}`;
7810
- const qs = urlParams.toString();
7811
- return qs ? `${base}?${qs}` : base;
7812
- },
7813
- loopbackRoutes: [
7814
- {
7815
- path: "/callback",
7816
- handle: (params, res, resolve) => {
7817
- const token = params.get("token");
7818
- if (token) {
7819
- res.writeHead(200, { "Content-Type": "text/html" });
7820
- res.end(CALLBACK_HTML);
7821
- resolve({ token });
7822
- } else {
7823
- res.writeHead(400, { "Content-Type": "application/json" });
7824
- res.end(JSON.stringify({ ok: false, error: "No token received" }));
7825
- }
7826
- }
7827
- }
7828
- ]
7829
- });
7830
- console.log("\nOpening your browser to sign in...");
7473
+ const loginId = crypto3.randomUUID();
7474
+ const redirectUrl = `/studio/close?pluginLogin=true&loginId=${loginId}`;
7475
+ const browserUrl = `${config2.serviceUrl}/studio/sign-in?redirect_url=${encodeURIComponent(redirectUrl)}`;
7476
+ console.log("\nOpening Studio to sign in...");
7831
7477
  console.log(` ${browserUrl}`);
7832
7478
  console.log("");
7833
7479
  console.log("(If the browser didn't open, visit the URL above manually.)");
7834
7480
  openChromelessWindow(browserUrl);
7835
- const stopKeepalive = attachBrowserHandoffKeepalive({
7836
- loopback,
7837
- ticket,
7838
- browserUrl
7839
- });
7840
- let resolvePaste = () => {
7841
- };
7842
- let pasteDone = false;
7843
- const pastePromise = new Promise((resolve) => {
7844
- resolvePaste = (value) => {
7845
- if (pasteDone) {
7846
- return;
7847
- }
7848
- pasteDone = true;
7849
- resolve(value);
7850
- };
7851
- });
7852
- const rl = interactive ? readline.createInterface({
7853
- input: process.stdin,
7854
- output: process.stdout
7855
- }) : null;
7856
- const hintTimer = interactive ? setTimeout(() => {
7857
- if (loopback.done || ticket.done || pasteDone) {
7858
- return;
7859
- }
7860
- console.log("");
7861
- console.log("Having trouble? If your browser shows a token, paste it here and press Enter:");
7862
- }, PASTE_HINT_DELAY_MS) : null;
7863
- rl?.on("line", async (line) => {
7864
- if (loopback.done || ticket.done || pasteDone) {
7865
- return;
7866
- }
7867
- const trimmed = line.trim();
7868
- if (!trimmed) {
7869
- return;
7870
- }
7871
- process.stdout.write("Verifying token... ");
7872
- const who = await verifyToken(config2.serviceUrl, trimmed);
7873
- if (who) {
7874
- console.log("ok.");
7875
- resolvePaste({ token: trimmed });
7876
- } else {
7877
- console.log("invalid token. Try again or complete the browser flow.");
7878
- }
7879
- });
7481
+ const abortController = new AbortController();
7880
7482
  let loginTimer;
7881
7483
  const timeoutPromise = new Promise((_resolve, reject) => {
7882
- loginTimer = setTimeout(() => reject(new Error("Authentication timed out after 10 minutes. Run login again.")), LOGIN_TIMEOUT_MS);
7883
- });
7884
- function cleanup() {
7885
- stopKeepalive();
7886
- if (hintTimer) {
7887
- clearTimeout(hintTimer);
7888
- }
7484
+ loginTimer = setTimeout(() => {
7485
+ abortController.abort();
7486
+ reject(new Error("Authentication timed out after 10 minutes. Run login again."));
7487
+ }, LOGIN_TIMEOUT_MS);
7488
+ });
7489
+ const keepaliveTimer = setInterval(() => {
7490
+ process.stdout.write("");
7491
+ }, 3e4);
7492
+ try {
7493
+ const token = await Promise.race([
7494
+ pollLoginStatus(config2.serviceUrl, loginId, abortController.signal),
7495
+ timeoutPromise
7496
+ ]);
7889
7497
  if (loginTimer) {
7890
7498
  clearTimeout(loginTimer);
7891
7499
  }
7892
- loopback.close();
7893
- ticket.close();
7894
- rl?.close();
7895
- }
7896
- const racers = [];
7897
- if (loopback.available) {
7898
- racers.push(loopback.promise.then((v) => ({ token: v.token, wonBy: "loopback" })));
7899
- }
7900
- if (ticket.available) {
7901
- racers.push(ticket.promise.then((v) => ({ token: v.token, wonBy: "ticket" })));
7902
- }
7903
- if (interactive) {
7904
- racers.push(pastePromise.then((v) => ({ token: v.token, wonBy: "paste" })));
7905
- }
7906
- try {
7907
- const { token, wonBy } = await Promise.race([...racers, timeoutPromise]);
7908
- cleanup();
7500
+ clearInterval(keepaliveTimer);
7909
7501
  saveCredentials(token);
7910
7502
  const who = await verifyToken(config2.serviceUrl, token);
7911
7503
  const greeting = who?.user.email ? `Logged in as ${who.user.email}.` : "Authentication successful.";
7912
7504
  console.log(`
7913
7505
  ${greeting}`);
7914
- console.log(`Bitfab MCP tools are now active. (via ${wonBy})`);
7506
+ console.log("Bitfab MCP tools are now active. (via studio)");
7915
7507
  await reportHandoff(token, pluginVersion, platform2, {
7916
7508
  flow: "auth.login",
7917
- wonBy,
7918
- loopbackAvailable: loopback.available,
7919
- ticketAvailable: ticket.available
7509
+ wonBy: "studio",
7510
+ loopbackAvailable: false,
7511
+ ticketAvailable: false
7920
7512
  });
7921
7513
  restoreFocus();
7922
7514
  if (exitOnComplete) {
7923
7515
  process.exit(0);
7924
7516
  }
7925
7517
  } catch (err) {
7926
- cleanup();
7518
+ if (loginTimer) {
7519
+ clearTimeout(loginTimer);
7520
+ }
7521
+ clearInterval(keepaliveTimer);
7522
+ abortController.abort();
7927
7523
  if (exitOnComplete) {
7928
7524
  console.error(`
7929
7525
  ${err.message}`);
@@ -7953,6 +7549,9 @@ import fs7 from "fs";
7953
7549
  import os8 from "os";
7954
7550
  import path5 from "path";
7955
7551
 
7552
+ // ../bitfab-plugin-lib/dist/withStudioSession.js
7553
+ import crypto6 from "crypto";
7554
+
7956
7555
  // ../bitfab-plugin-lib/dist/commands/openTracePlan.js
7957
7556
  var OPEN_TRACE_PLAN_TIMEOUT_MS = 30 * 60 * 1e3;
7958
7557
 
@@ -21753,21 +21352,12 @@ var inputFileSchema = external_exports.object({
21753
21352
  verdicts: external_exports.array(external_exports.unknown()).min(1)
21754
21353
  });
21755
21354
 
21756
- // ../bitfab-plugin-lib/dist/commands/startDataset.js
21757
- var START_DATASET_TIMEOUT_MS = 30 * 60 * 1e3;
21758
-
21759
- // ../bitfab-plugin-lib/dist/commands/startTemplatePreview.js
21760
- import { randomUUID } from "crypto";
21761
-
21762
21355
  // ../bitfab-plugin-lib/dist/activePreviewSession.js
21763
21356
  import fs8 from "fs";
21764
21357
  import os9 from "os";
21765
21358
  import path6 from "path";
21766
21359
  var FILE_PATH = path6.join(os9.homedir(), ".config", "bitfab", "active-preview-session.json");
21767
21360
 
21768
- // ../bitfab-plugin-lib/dist/commands/startTemplatePreview.js
21769
- var START_TEMPLATE_PREVIEW_TIMEOUT_MS = 8 * 60 * 60 * 1e3;
21770
-
21771
21361
  // ../bitfab-plugin-lib/dist/updates.js
21772
21362
  import fs9 from "fs";
21773
21363
  import os10 from "os";
@@ -21785,7 +21375,7 @@ import fs10 from "fs";
21785
21375
  import path8 from "path";
21786
21376
 
21787
21377
  // ../bitfab-plugin-lib/dist/commands/waitForTrace.js
21788
- var DEFAULT_TIMEOUT_MS2 = 10 * 60 * 1e3;
21378
+ var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
21789
21379
 
21790
21380
  // ../bitfab-plugin-lib/dist/hooks/captureHook.js
21791
21381
  import fs12 from "fs";
@@ -27212,34 +26802,51 @@ function runClaudeInstall() {
27212
26802
  ]);
27213
26803
  if (installOut.includes("already")) {
27214
26804
  s.stop("Plugin already installed");
26805
+ if (isPluginDisabled()) {
26806
+ runClaude(["plugin", "enable", PLUGIN_KEY]);
26807
+ p.log.step("Plugin enabled");
26808
+ }
27215
26809
  } else {
27216
26810
  s.stop("Plugin installed");
27217
26811
  }
27218
26812
  p.log.success("Bitfab plugin ready in Claude Code");
27219
26813
  }
27220
- function runClaudeSetup() {
26814
+ function runClaudeSetup(skipPermissions) {
27221
26815
  p.log.info(`Launching ${SETUP_COMMAND}...
27222
26816
  `);
27223
- spawnSync("claude", [SETUP_COMMAND], { stdio: "inherit", ...SHELL_OPTS });
26817
+ const flags = skipPermissions ? ["--dangerously-skip-permissions"] : [];
26818
+ spawnSync("claude", [...flags, SETUP_COMMAND], {
26819
+ stdio: "inherit",
26820
+ ...SHELL_OPTS
26821
+ });
27224
26822
  }
27225
- function runClaudeAssistant(args) {
26823
+ function runClaudeAssistant(args, skipPermissions) {
27226
26824
  const cmd = [ASSISTANT_COMMAND, ...args].join(" ");
27227
26825
  p.log.info(`Launching ${cmd}...
27228
26826
  `);
27229
- spawnSync("claude", [ASSISTANT_COMMAND, ...args], {
26827
+ const flags = skipPermissions ? ["--dangerously-skip-permissions"] : [];
26828
+ spawnSync("claude", [...flags, ASSISTANT_COMMAND, ...args], {
27230
26829
  stdio: "inherit",
27231
26830
  ...SHELL_OPTS
27232
26831
  });
27233
26832
  }
27234
- function runClaudeUpdate(args) {
26833
+ function runClaudeUpdate(args, skipPermissions) {
27235
26834
  const cmd = [UPDATE_COMMAND, ...args].join(" ");
27236
26835
  p.log.info(`Launching ${cmd}...
27237
26836
  `);
27238
- spawnSync("claude", [UPDATE_COMMAND, ...args], {
26837
+ const flags = skipPermissions ? ["--dangerously-skip-permissions"] : [];
26838
+ spawnSync("claude", [...flags, UPDATE_COMMAND, ...args], {
27239
26839
  stdio: "inherit",
27240
26840
  ...SHELL_OPTS
27241
26841
  });
27242
26842
  }
26843
+ function isPluginDisabled() {
26844
+ const listOut = runClaude(["plugin", "list"]);
26845
+ const pattern = new RegExp(
26846
+ `${PLUGIN_KEY.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n.*\\n.*\\n.*disabled`
26847
+ );
26848
+ return pattern.test(listOut);
26849
+ }
27243
26850
  function runClaude(args) {
27244
26851
  const result = spawnSync("claude", [...args], {
27245
26852
  stdio: "pipe",
@@ -27316,25 +26923,31 @@ function runCodexInstall() {
27316
26923
  }
27317
26924
  p2.log.success("Bitfab plugin ready in Codex");
27318
26925
  }
27319
- function runCodexSetup() {
26926
+ function runCodexSetup(skipPermissions) {
27320
26927
  p2.log.info(`Launching ${SETUP_COMMAND2}...
27321
26928
  `);
27322
- spawnSync2("codex", [SETUP_COMMAND2], { stdio: "inherit", ...SHELL_OPTS2 });
26929
+ const flags = skipPermissions ? ["--full-auto"] : [];
26930
+ spawnSync2("codex", [...flags, SETUP_COMMAND2], {
26931
+ stdio: "inherit",
26932
+ ...SHELL_OPTS2
26933
+ });
27323
26934
  }
27324
- function runCodexAssistant(args) {
26935
+ function runCodexAssistant(args, skipPermissions) {
27325
26936
  const cmd = [ASSISTANT_COMMAND2, ...args].join(" ");
27326
26937
  p2.log.info(`Launching ${cmd}...
27327
26938
  `);
27328
- spawnSync2("codex", [ASSISTANT_COMMAND2, ...args], {
26939
+ const flags = skipPermissions ? ["--full-auto"] : [];
26940
+ spawnSync2("codex", [...flags, ASSISTANT_COMMAND2, ...args], {
27329
26941
  stdio: "inherit",
27330
26942
  ...SHELL_OPTS2
27331
26943
  });
27332
26944
  }
27333
- function runCodexUpdate(args) {
26945
+ function runCodexUpdate(args, skipPermissions) {
27334
26946
  const cmd = [UPDATE_COMMAND2, ...args].join(" ");
27335
26947
  p2.log.info(`Launching ${cmd}...
27336
26948
  `);
27337
- spawnSync2("codex", [UPDATE_COMMAND2, ...args], {
26949
+ const flags = skipPermissions ? ["--full-auto"] : [];
26950
+ spawnSync2("codex", [...flags, UPDATE_COMMAND2, ...args], {
27338
26951
  stdio: "inherit",
27339
26952
  ...SHELL_OPTS2
27340
26953
  });
@@ -27408,14 +27021,23 @@ function runCursorInstall() {
27408
27021
  });
27409
27022
  child.unref();
27410
27023
  }
27411
- function runCursorSetup() {
27024
+ function runCursorSetup(skipPermissions) {
27025
+ if (skipPermissions) {
27026
+ p3.log.warn("--skip-permissions is not supported for Cursor");
27027
+ }
27412
27028
  p3.log.info(`To complete setup, run ${SETUP_COMMAND3} in Cursor.`);
27413
27029
  }
27414
- function runCursorAssistant(args) {
27030
+ function runCursorAssistant(args, skipPermissions) {
27031
+ if (skipPermissions) {
27032
+ p3.log.warn("--skip-permissions is not supported for Cursor");
27033
+ }
27415
27034
  const cmd = [ASSISTANT_COMMAND3, ...args].join(" ");
27416
27035
  p3.log.info(`To start the assistant, run ${cmd} in Cursor.`);
27417
27036
  }
27418
- function runCursorUpdate(args) {
27037
+ function runCursorUpdate(args, skipPermissions) {
27038
+ if (skipPermissions) {
27039
+ p3.log.warn("--skip-permissions is not supported for Cursor");
27040
+ }
27419
27041
  const cmd = [UPDATE_COMMAND3, ...args].join(" ");
27420
27042
  p3.log.info(`To update, run ${cmd} in Cursor.`);
27421
27043
  }
@@ -27536,6 +27158,20 @@ async function pickEditor(detected) {
27536
27158
  }
27537
27159
  return choice;
27538
27160
  }
27161
+ async function resolveSkipPermissions(value) {
27162
+ if (value !== void 0) {
27163
+ return value;
27164
+ }
27165
+ const answer = await p4.confirm({
27166
+ message: "Skip permission prompts? (runs the agent autonomously)",
27167
+ initialValue: false
27168
+ });
27169
+ if (p4.isCancel(answer)) {
27170
+ p4.cancel("Cancelled.");
27171
+ process.exit(0);
27172
+ }
27173
+ return answer;
27174
+ }
27539
27175
  async function resolveEditor({ editor }) {
27540
27176
  const detected = detectInstalledEditors();
27541
27177
  if (detected.length === 0) {
@@ -27570,15 +27206,18 @@ async function runInstall(opts) {
27570
27206
  }
27571
27207
  async function runSetup(opts) {
27572
27208
  const chosen = await resolveEditor(opts);
27573
- SETUP_FN[chosen]();
27209
+ const skip = await resolveSkipPermissions(opts.skipPermissions);
27210
+ SETUP_FN[chosen](skip);
27574
27211
  }
27575
27212
  async function runAssistant(opts, args) {
27576
27213
  const chosen = await resolveEditor(opts);
27577
- ASSISTANT_FN[chosen](args);
27214
+ const skip = await resolveSkipPermissions(opts.skipPermissions);
27215
+ ASSISTANT_FN[chosen](args, skip);
27578
27216
  }
27579
27217
  async function runUpdate2(opts, args) {
27580
27218
  const chosen = await resolveEditor(opts);
27581
- UPDATE_FN[chosen](args);
27219
+ const skip = await resolveSkipPermissions(opts.skipPermissions);
27220
+ UPDATE_FN[chosen](args, skip);
27582
27221
  }
27583
27222
  async function runInit(opts) {
27584
27223
  p4.intro("bitfab");
@@ -27592,7 +27231,8 @@ async function runInit(opts) {
27592
27231
  p4.log.step("Signing in to Bitfab");
27593
27232
  await runLoginCommand({ exitOnComplete: false });
27594
27233
  }
27595
- SETUP_FN[chosen]();
27234
+ const skip = await resolveSkipPermissions(opts.skipPermissions);
27235
+ SETUP_FN[chosen](skip);
27596
27236
  const detected = detectInstalledEditors();
27597
27237
  const others = detected.filter((e) => e !== chosen);
27598
27238
  if (others.length > 0) {
@@ -27640,20 +27280,22 @@ Commands:
27640
27280
  update [--editor <name>] [args] Launch /bitfab:update in the editor
27641
27281
  help Show this help
27642
27282
 
27643
- Editor names: claude, codex, cursor
27283
+ Options:
27284
+ --editor, -e <name> Target editor: claude, codex, cursor
27285
+ --skip-permissions Skip permission prompts (runs the agent autonomously)
27286
+ --no-skip-permissions Keep permission prompts (default, skips the interactive question)
27644
27287
 
27645
27288
  Examples:
27646
- bitfab init Full setup (detect editor, install, login, setup)
27647
- bitfab init --editor claude Full setup for Claude Code
27648
- bitfab plugin-install Install plugin only
27649
- bitfab login Sign in to Bitfab
27650
- bitfab logout Sign out
27651
- bitfab assistant investigate Investigate traces
27652
- bitfab update plugin Update the Bitfab plugin
27289
+ bitfab init Full setup (detect editor, install, login, setup)
27290
+ bitfab init --editor claude Full setup for Claude Code
27291
+ bitfab assistant investigate Investigate traces
27292
+ bitfab setup --skip-permissions Setup without permission prompts
27293
+ bitfab assistant --skip-permissions Run assistant autonomously
27653
27294
  `;
27654
27295
  function parseArgs(argv) {
27655
27296
  const [command, ...tail] = argv;
27656
27297
  let editor;
27298
+ let skipPermissions;
27657
27299
  const rest = [];
27658
27300
  for (let i = 0; i < tail.length; i++) {
27659
27301
  const arg = tail[i];
@@ -27666,24 +27308,30 @@ function parseArgs(argv) {
27666
27308
  }
27667
27309
  } else if (arg?.startsWith("--editor=")) {
27668
27310
  editor = arg.slice("--editor=".length);
27311
+ } else if (arg === "--skip-permissions") {
27312
+ skipPermissions = true;
27313
+ } else if (arg === "--no-skip-permissions") {
27314
+ skipPermissions = false;
27669
27315
  } else if (arg !== void 0) {
27670
27316
  rest.push(arg);
27671
27317
  }
27672
27318
  }
27673
- return { command, editor, rest };
27319
+ return { command, editor, skipPermissions, rest };
27674
27320
  }
27675
27321
  async function main() {
27676
- const { command, editor, rest } = parseArgs(process.argv.slice(2));
27322
+ const { command, editor, skipPermissions, rest } = parseArgs(
27323
+ process.argv.slice(2)
27324
+ );
27677
27325
  if (!command || command === "help" || command === "--help" || command === "-h") {
27678
27326
  process.stdout.write(HELP_TEXT);
27679
27327
  return;
27680
27328
  }
27681
27329
  if (command === "init") {
27682
- await runInit({ editor });
27330
+ await runInit({ editor, skipPermissions });
27683
27331
  return;
27684
27332
  }
27685
27333
  if (command === "plugin-install") {
27686
- await runInstall({ editor });
27334
+ await runInstall({ editor, skipPermissions });
27687
27335
  return;
27688
27336
  }
27689
27337
  if (command === "login") {
@@ -27695,15 +27343,15 @@ async function main() {
27695
27343
  return;
27696
27344
  }
27697
27345
  if (command === "setup") {
27698
- await runSetup({ editor });
27346
+ await runSetup({ editor, skipPermissions });
27699
27347
  return;
27700
27348
  }
27701
27349
  if (command === "assistant") {
27702
- await runAssistant({ editor }, rest);
27350
+ await runAssistant({ editor, skipPermissions }, rest);
27703
27351
  return;
27704
27352
  }
27705
27353
  if (command === "update") {
27706
- await runUpdate2({ editor }, rest);
27354
+ await runUpdate2({ editor, skipPermissions }, rest);
27707
27355
  return;
27708
27356
  }
27709
27357
  process.stderr.write(`Unknown command: ${command}
@@ -27716,3 +27364,6 @@ main().catch((err) => {
27716
27364
  cancel2(message);
27717
27365
  process.exit(1);
27718
27366
  });
27367
+ export {
27368
+ parseArgs
27369
+ };