trainfabric 0.1.4 → 0.1.5

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.cjs +107 -87
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -3458,11 +3458,11 @@ var require_commander = __commonJS({
3458
3458
 
3459
3459
  // src/index.ts
3460
3460
  var import_node_fs2 = __toESM(require("node:fs"), 1);
3461
- var import_node_http = __toESM(require("node:http"), 1);
3462
3461
  var import_node_os2 = __toESM(require("node:os"), 1);
3463
3462
  var import_node_path3 = __toESM(require("node:path"), 1);
3463
+ var import_promises2 = __toESM(require("node:readline/promises"), 1);
3464
3464
  var import_node_child_process = require("node:child_process");
3465
- var import_promises2 = require("node:timers/promises");
3465
+ var import_promises3 = require("node:timers/promises");
3466
3466
 
3467
3467
  // ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/esm.mjs
3468
3468
  var import_index = __toESM(require_commander(), 1);
@@ -8891,7 +8891,7 @@ function buildComputeSpec(options) {
8891
8891
 
8892
8892
  // src/index.ts
8893
8893
  var DEFAULT_TRAINFABRIC_API_URL2 = "https://api.trainfabric.com";
8894
- var CLI_VERSION = "0.1.4";
8894
+ var CLI_VERSION = "0.1.5";
8895
8895
  var CONFIG_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".trainfabric");
8896
8896
  var CONFIG_PATH = import_node_path3.default.join(CONFIG_DIR, "config.json");
8897
8897
  var FALLBACK_SECRET_PATH = import_node_path3.default.join(CONFIG_DIR, "session.enc");
@@ -8914,6 +8914,9 @@ function scrambleLine(line, progress) {
8914
8914
  return LOGIN_BANNER_POOL[Math.floor(Math.random() * LOGIN_BANNER_POOL.length)];
8915
8915
  }).join("");
8916
8916
  }
8917
+ function renderLoginBanner(progress) {
8918
+ return LOGIN_BANNER_LINES.map((row) => scrambleLine(row, progress));
8919
+ }
8917
8920
  async function printLoginBanner() {
8918
8921
  console.log("");
8919
8922
  if (!process.stdout.isTTY) {
@@ -8925,16 +8928,18 @@ async function printLoginBanner() {
8925
8928
  return;
8926
8929
  }
8927
8930
  process.stdout.write("\x1B[?25l");
8928
- for (let frame = 0; frame <= 20; frame += 1) {
8931
+ const frameCount = 40;
8932
+ const totalFrames = frameCount * 3;
8933
+ for (let frame = 0; frame <= totalFrames; frame += 1) {
8929
8934
  if (frame > 0) {
8930
8935
  process.stdout.write(`\x1B[${LOGIN_BANNER_LINES.length}A`);
8931
8936
  }
8932
- const progress = frame / 20;
8933
- for (const row of LOGIN_BANNER_LINES) {
8934
- process.stdout.write(`${scrambleLine(row, progress)}
8937
+ const progress = frame % frameCount / frameCount;
8938
+ for (const row of renderLoginBanner(progress)) {
8939
+ process.stdout.write(`${row}
8935
8940
  `);
8936
8941
  }
8937
- await (0, import_promises2.setTimeout)(frame < 14 ? 32 : 18);
8942
+ await (0, import_promises3.setTimeout)(85);
8938
8943
  }
8939
8944
  process.stdout.write("\x1B[?25h");
8940
8945
  console.log(" TrainFabric CLI Login");
@@ -8949,7 +8954,79 @@ function printStaticLoginBanner() {
8949
8954
  console.log("");
8950
8955
  }
8951
8956
  function sleep(ms) {
8952
- return (0, import_promises2.setTimeout)(ms);
8957
+ return (0, import_promises3.setTimeout)(ms);
8958
+ }
8959
+ async function promptApiKey() {
8960
+ if (!process.stdin.isTTY || !process.stdout.isTTY || !process.stdin.setRawMode) {
8961
+ const rl = import_promises2.default.createInterface({ input: process.stdin, output: process.stdout });
8962
+ try {
8963
+ return (await rl.question("Paste your TrainFabric API key: ")).trim();
8964
+ } finally {
8965
+ rl.close();
8966
+ }
8967
+ }
8968
+ let apiKey = "";
8969
+ let frame = 0;
8970
+ let closed = false;
8971
+ const totalLines = LOGIN_BANNER_LINES.length + 4;
8972
+ const redraw = () => {
8973
+ const progress = frame % 48 / 48;
8974
+ process.stdout.write(`\x1B[${totalLines}A`);
8975
+ for (const row of renderLoginBanner(progress)) {
8976
+ process.stdout.write(`\x1B[2K${row}
8977
+ `);
8978
+ }
8979
+ process.stdout.write("\x1B[2K TrainFabric CLI Login\n");
8980
+ process.stdout.write("\x1B[2K\n");
8981
+ process.stdout.write(`\x1B[2KPaste your TrainFabric API key: ${"*".repeat(apiKey.length)}
8982
+ `);
8983
+ process.stdout.write("\x1B[2KPress Enter to continue.\n");
8984
+ frame += 1;
8985
+ };
8986
+ return await new Promise((resolve, reject) => {
8987
+ const cleanup = () => {
8988
+ if (closed) {
8989
+ return;
8990
+ }
8991
+ closed = true;
8992
+ clearInterval(timer);
8993
+ process.stdin.off("data", onData);
8994
+ process.stdin.setRawMode(false);
8995
+ process.stdout.write("\x1B[?25h\n");
8996
+ };
8997
+ const onData = (chunk) => {
8998
+ const value = chunk.toString("utf8");
8999
+ if (value === "") {
9000
+ cleanup();
9001
+ reject(new Error("Login cancelled."));
9002
+ return;
9003
+ }
9004
+ if (value === "\r" || value === "\n") {
9005
+ cleanup();
9006
+ resolve(apiKey.trim());
9007
+ return;
9008
+ }
9009
+ if (value === "\x7F" || value === "\b") {
9010
+ apiKey = apiKey.slice(0, -1);
9011
+ redraw();
9012
+ return;
9013
+ }
9014
+ apiKey += value.replace(/[\r\n]/g, "");
9015
+ redraw();
9016
+ };
9017
+ for (const row of LOGIN_BANNER_LINES) {
9018
+ console.log(row);
9019
+ }
9020
+ console.log(" TrainFabric CLI Login");
9021
+ console.log("");
9022
+ console.log("Paste your TrainFabric API key: ");
9023
+ console.log("Press Enter to continue.");
9024
+ process.stdout.write("\x1B[?25l");
9025
+ process.stdin.setRawMode(true);
9026
+ process.stdin.resume();
9027
+ process.stdin.on("data", onData);
9028
+ const timer = setInterval(redraw, 160);
9029
+ });
8953
9030
  }
8954
9031
  async function runSpinnerUntil(label, action) {
8955
9032
  const frames = ["/", "-", "\\", "|"];
@@ -8971,15 +9048,15 @@ async function runSpinnerUntil(label, action) {
8971
9048
  }
8972
9049
  }
8973
9050
  function printLoginFallbackHint(baseUrl) {
8974
- console.error("\nCould not complete browser login (Unauthorized or callback failure).");
8975
- console.error("If this is due to a blocked browser flow, use an API key instead:");
9051
+ console.error("\nCould not verify that API key with TrainFabric.");
9052
+ console.error("");
9053
+ console.error("Check that the key is active in the dashboard, then retry:");
9054
+ console.error(" trainfabric login");
8976
9055
  console.error("");
8977
- console.error(" export TRAINFABRIC_API_KEY=<api-key>");
8978
- console.error(` trainfabric config:set-api-key <api-key>`);
8979
- console.error(` trainfabric --version`);
9056
+ console.error("You can also set it directly:");
9057
+ console.error(" trainfabric config:set-api-key <api-key>");
8980
9058
  console.error("");
8981
- console.error(`To verify dashboard link and API route, run: trainfabric config:set-base-url ${baseUrl}`);
8982
- console.error("then run trainfabric login again.");
9059
+ console.error(`Current API base URL: ${baseUrl}`);
8983
9060
  }
8984
9061
  function ensureConfigDir() {
8985
9062
  import_node_fs2.default.mkdirSync(CONFIG_DIR, { recursive: true });
@@ -9153,15 +9230,6 @@ function visibleConfig(config) {
9153
9230
  refreshToken: config.refreshToken ? "<stored>" : void 0
9154
9231
  };
9155
9232
  }
9156
- function openBrowser(url) {
9157
- const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
9158
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
9159
- const child = (0, import_node_child_process.spawn)(command, args, {
9160
- detached: true,
9161
- stdio: "ignore"
9162
- });
9163
- child.unref();
9164
- }
9165
9233
  function buildRunInput(options) {
9166
9234
  const sourceOptions = buildSourceOptions(options);
9167
9235
  return {
@@ -9251,82 +9319,34 @@ function createClient(config) {
9251
9319
  });
9252
9320
  }
9253
9321
  async function login(config) {
9254
- await printLoginBanner().catch(() => {
9255
- printStaticLoginBanner();
9256
- });
9257
- const loginClient = new TrainingClient({ baseUrl: config.baseUrl });
9258
- const server = import_node_http.default.createServer();
9259
- const closeServer = () => {
9260
- try {
9261
- server.close();
9262
- } catch {
9263
- }
9264
- };
9265
- const port = await new Promise((resolve) => {
9266
- server.listen(0, "127.0.0.1", () => {
9267
- const address = server.address();
9268
- if (!address || typeof address === "string") {
9269
- throw new Error("Failed to bind callback server.");
9270
- }
9271
- resolve(address.port);
9272
- });
9273
- });
9322
+ await printLoginBanner().catch(() => printStaticLoginBanner());
9323
+ const apiKey = await promptApiKey();
9324
+ if (!apiKey) {
9325
+ throw new Error("API key is required.");
9326
+ }
9274
9327
  try {
9275
- const callbackUrl = `http://127.0.0.1:${port}/callback`;
9276
- const loginUrl = await runSpinnerUntil("Preparing browser login", () => loginClient.auth.browserLoginUrl(callbackUrl));
9277
- console.log("Waiting for authentication in your browser...");
9278
- console.log(`Callback URL: ${callbackUrl}`);
9279
- openBrowser(loginUrl.authorizationUrl);
9280
- const tokenResult = await Promise.race([
9281
- new Promise((resolve, reject) => {
9282
- const timeoutId = setTimeout(() => {
9283
- reject(new Error("Login callback timed out after 5 minutes. Please retry."));
9284
- }, 5 * 60 * 1e3);
9285
- server.on("request", (request, response) => {
9286
- const url = new URL(request.url ?? "/", callbackUrl);
9287
- const accessToken = url.searchParams.get("access_token");
9288
- const refreshToken = url.searchParams.get("refresh_token") ?? void 0;
9289
- const organizationId = url.searchParams.get("organization_id") ?? void 0;
9290
- if (!accessToken) {
9291
- response.statusCode = 400;
9292
- response.end("Missing access token.");
9293
- clearTimeout(timeoutId);
9294
- reject(new Error("Login callback did not contain an access token."));
9295
- return;
9296
- }
9297
- response.end("Authentication completed. You can close this window.");
9298
- clearTimeout(timeoutId);
9299
- resolve({ accessToken, refreshToken, organizationId });
9300
- });
9301
- }),
9302
- (0, import_promises2.setTimeout)(5 * 60 * 1e3).then(() => {
9303
- throw new Error("Login callback timed out after 5 minutes. Please retry.");
9304
- })
9305
- ]);
9306
- const authedClient = new TrainingClient({
9328
+ const client = new TrainingClient({
9307
9329
  baseUrl: config.baseUrl,
9308
- accessToken: tokenResult.accessToken
9330
+ apiKey
9309
9331
  });
9310
9332
  const session = await runSpinnerUntil("Finalizing login", async () => {
9311
9333
  await sleep(100);
9312
- return authedClient.auth.me();
9334
+ return client.auth.me();
9313
9335
  });
9314
- config.accessToken = tokenResult.accessToken;
9315
- config.refreshToken = tokenResult.refreshToken;
9316
- config.orgId = tokenResult.organizationId ?? session.organizationId;
9317
- config.apiKey = void 0;
9336
+ config.accessToken = void 0;
9337
+ config.refreshToken = void 0;
9338
+ config.orgId = session.organizationId;
9339
+ config.apiKey = apiKey;
9318
9340
  saveConfig(config);
9319
- console.log(`Logged in as ${session.user.email} for organization ${config.orgId}.`);
9341
+ console.log(`Logged in with API key as ${session.user.email} for organization ${config.orgId}.`);
9320
9342
  } catch (error) {
9321
9343
  printLoginFallbackHint(config.baseUrl);
9322
9344
  throw error;
9323
- } finally {
9324
- closeServer();
9325
9345
  }
9326
9346
  }
9327
9347
  var program2 = new Command();
9328
9348
  program2.name("trainfabric").description("Trainfabric CLI for launching and monitoring GPU training jobs").version(CLI_VERSION);
9329
- program2.command("login").description("Authenticate in the browser and store CLI credentials").action(async () => {
9349
+ program2.command("login").description("Paste and store a TrainFabric API key").action(async () => {
9330
9350
  await login(loadConfig());
9331
9351
  });
9332
9352
  program2.command("logout").description("Remove stored credentials").action(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trainfabric",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Trainfabric CLI for launching GPU training jobs on the hosted Trainfabric backend.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",