trainfabric 0.1.3 → 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 +148 -91
  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,30 +8891,142 @@ 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.3";
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");
8898
8898
  var KEYCHAIN_SERVICE = "trainfabric-cli";
8899
- function printLoginBanner() {
8900
- const logo = [
8901
- " _______ __ _ _ _ ____ _____",
8902
- " |__ __| [ | (_) | | (_) / __ \\ |_ _|",
8903
- " | |_ __ _ _ _ __ | | ____ _ _ __ _ __ | |_ _ __ _ _ __ | | | | | | ",
8904
- " | | '__| | | | '_ \\ | |/ / _` | '_ \\| '_ \\ | __| |/ _` | '_ \\ | | | | | | ",
8905
- " | | | | |_| | | | || < (_| | |_) | |_) | | |_| | (_| | | | | | |__| |_ | | ",
8906
- " |_|_| \\__,_|_| |_| |_|\\_\\| .__/| .__/ \\__|_|\\__,_|_| |_| \\____/|_____|",
8907
- " | | | | "
8908
- ];
8899
+ var LOGIN_BANNER_LINES = [
8900
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557",
8901
+ "\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
8902
+ " \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 ",
8903
+ " \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 ",
8904
+ " \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
8905
+ " \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D"
8906
+ ];
8907
+ var LOGIN_BANNER_POOL = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*<>?+=/\\|~";
8908
+ function scrambleLine(line, progress) {
8909
+ const lockUntil = Math.floor(line.length * progress);
8910
+ return [...line].map((char, index) => {
8911
+ if (char === " " || index <= lockUntil) {
8912
+ return char;
8913
+ }
8914
+ return LOGIN_BANNER_POOL[Math.floor(Math.random() * LOGIN_BANNER_POOL.length)];
8915
+ }).join("");
8916
+ }
8917
+ function renderLoginBanner(progress) {
8918
+ return LOGIN_BANNER_LINES.map((row) => scrambleLine(row, progress));
8919
+ }
8920
+ async function printLoginBanner() {
8909
8921
  console.log("");
8910
- for (const row of logo) {
8922
+ if (!process.stdout.isTTY) {
8923
+ for (const row of LOGIN_BANNER_LINES) {
8924
+ console.log(row);
8925
+ }
8926
+ console.log(" TrainFabric CLI Login");
8927
+ console.log("");
8928
+ return;
8929
+ }
8930
+ process.stdout.write("\x1B[?25l");
8931
+ const frameCount = 40;
8932
+ const totalFrames = frameCount * 3;
8933
+ for (let frame = 0; frame <= totalFrames; frame += 1) {
8934
+ if (frame > 0) {
8935
+ process.stdout.write(`\x1B[${LOGIN_BANNER_LINES.length}A`);
8936
+ }
8937
+ const progress = frame % frameCount / frameCount;
8938
+ for (const row of renderLoginBanner(progress)) {
8939
+ process.stdout.write(`${row}
8940
+ `);
8941
+ }
8942
+ await (0, import_promises3.setTimeout)(85);
8943
+ }
8944
+ process.stdout.write("\x1B[?25h");
8945
+ console.log(" TrainFabric CLI Login");
8946
+ console.log("");
8947
+ }
8948
+ function printStaticLoginBanner() {
8949
+ console.log("");
8950
+ for (const row of LOGIN_BANNER_LINES) {
8911
8951
  console.log(row);
8912
8952
  }
8913
8953
  console.log(" TrainFabric CLI Login");
8914
8954
  console.log("");
8915
8955
  }
8916
8956
  function sleep(ms) {
8917
- 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
+ });
8918
9030
  }
8919
9031
  async function runSpinnerUntil(label, action) {
8920
9032
  const frames = ["/", "-", "\\", "|"];
@@ -8936,15 +9048,15 @@ async function runSpinnerUntil(label, action) {
8936
9048
  }
8937
9049
  }
8938
9050
  function printLoginFallbackHint(baseUrl) {
8939
- console.error("\nCould not complete browser login (Unauthorized or callback failure).");
8940
- 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");
8941
9055
  console.error("");
8942
- console.error(" export TRAINFABRIC_API_KEY=<api-key>");
8943
- console.error(` trainfabric config:set-api-key <api-key>`);
8944
- console.error(` trainfabric --version`);
9056
+ console.error("You can also set it directly:");
9057
+ console.error(" trainfabric config:set-api-key <api-key>");
8945
9058
  console.error("");
8946
- console.error(`To verify dashboard link and API route, run: trainfabric config:set-base-url ${baseUrl}`);
8947
- console.error("then run trainfabric login again.");
9059
+ console.error(`Current API base URL: ${baseUrl}`);
8948
9060
  }
8949
9061
  function ensureConfigDir() {
8950
9062
  import_node_fs2.default.mkdirSync(CONFIG_DIR, { recursive: true });
@@ -9118,15 +9230,6 @@ function visibleConfig(config) {
9118
9230
  refreshToken: config.refreshToken ? "<stored>" : void 0
9119
9231
  };
9120
9232
  }
9121
- function openBrowser(url) {
9122
- const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
9123
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
9124
- const child = (0, import_node_child_process.spawn)(command, args, {
9125
- detached: true,
9126
- stdio: "ignore"
9127
- });
9128
- child.unref();
9129
- }
9130
9233
  function buildRunInput(options) {
9131
9234
  const sourceOptions = buildSourceOptions(options);
9132
9235
  return {
@@ -9216,80 +9319,34 @@ function createClient(config) {
9216
9319
  });
9217
9320
  }
9218
9321
  async function login(config) {
9219
- printLoginBanner();
9220
- const loginClient = new TrainingClient({ baseUrl: config.baseUrl });
9221
- const server = import_node_http.default.createServer();
9222
- const closeServer = () => {
9223
- try {
9224
- server.close();
9225
- } catch {
9226
- }
9227
- };
9228
- const port = await new Promise((resolve) => {
9229
- server.listen(0, "127.0.0.1", () => {
9230
- const address = server.address();
9231
- if (!address || typeof address === "string") {
9232
- throw new Error("Failed to bind callback server.");
9233
- }
9234
- resolve(address.port);
9235
- });
9236
- });
9237
- const callbackUrl = `http://127.0.0.1:${port}/callback`;
9238
- const loginUrl = await runSpinnerUntil("Preparing browser login", () => loginClient.auth.browserLoginUrl(callbackUrl));
9239
- console.log("Waiting for authentication in your browser...");
9240
- console.log(`Callback URL: ${callbackUrl}`);
9241
- openBrowser(loginUrl.authorizationUrl);
9322
+ await printLoginBanner().catch(() => printStaticLoginBanner());
9323
+ const apiKey = await promptApiKey();
9324
+ if (!apiKey) {
9325
+ throw new Error("API key is required.");
9326
+ }
9242
9327
  try {
9243
- const tokenResult = await Promise.race([
9244
- new Promise((resolve, reject) => {
9245
- const timeoutId = setTimeout(() => {
9246
- reject(new Error("Login callback timed out after 5 minutes. Please retry."));
9247
- }, 5 * 60 * 1e3);
9248
- server.on("request", (request, response) => {
9249
- const url = new URL(request.url ?? "/", callbackUrl);
9250
- const accessToken = url.searchParams.get("access_token");
9251
- const refreshToken = url.searchParams.get("refresh_token") ?? void 0;
9252
- const organizationId = url.searchParams.get("organization_id") ?? void 0;
9253
- if (!accessToken) {
9254
- response.statusCode = 400;
9255
- response.end("Missing access token.");
9256
- clearTimeout(timeoutId);
9257
- reject(new Error("Login callback did not contain an access token."));
9258
- return;
9259
- }
9260
- response.end("Authentication completed. You can close this window.");
9261
- clearTimeout(timeoutId);
9262
- resolve({ accessToken, refreshToken, organizationId });
9263
- });
9264
- }),
9265
- (0, import_promises2.setTimeout)(5 * 60 * 1e3).then(() => {
9266
- throw new Error("Login callback timed out after 5 minutes. Please retry.");
9267
- })
9268
- ]);
9269
- const authedClient = new TrainingClient({
9328
+ const client = new TrainingClient({
9270
9329
  baseUrl: config.baseUrl,
9271
- accessToken: tokenResult.accessToken
9330
+ apiKey
9272
9331
  });
9273
9332
  const session = await runSpinnerUntil("Finalizing login", async () => {
9274
9333
  await sleep(100);
9275
- return authedClient.auth.me();
9334
+ return client.auth.me();
9276
9335
  });
9277
- config.accessToken = tokenResult.accessToken;
9278
- config.refreshToken = tokenResult.refreshToken;
9279
- config.orgId = tokenResult.organizationId ?? session.organizationId;
9280
- config.apiKey = void 0;
9336
+ config.accessToken = void 0;
9337
+ config.refreshToken = void 0;
9338
+ config.orgId = session.organizationId;
9339
+ config.apiKey = apiKey;
9281
9340
  saveConfig(config);
9282
- 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}.`);
9283
9342
  } catch (error) {
9284
9343
  printLoginFallbackHint(config.baseUrl);
9285
9344
  throw error;
9286
- } finally {
9287
- closeServer();
9288
9345
  }
9289
9346
  }
9290
9347
  var program2 = new Command();
9291
9348
  program2.name("trainfabric").description("Trainfabric CLI for launching and monitoring GPU training jobs").version(CLI_VERSION);
9292
- 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 () => {
9293
9350
  await login(loadConfig());
9294
9351
  });
9295
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.3",
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",