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.
- package/dist/index.cjs +107 -87
- 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
|
|
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.
|
|
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
|
-
|
|
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 /
|
|
8933
|
-
for (const row of
|
|
8934
|
-
process.stdout.write(`${
|
|
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,
|
|
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,
|
|
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
|
|
8975
|
-
console.error("
|
|
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("
|
|
8978
|
-
console.error(
|
|
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(`
|
|
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
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
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
|
|
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
|
-
|
|
9330
|
+
apiKey
|
|
9309
9331
|
});
|
|
9310
9332
|
const session = await runSpinnerUntil("Finalizing login", async () => {
|
|
9311
9333
|
await sleep(100);
|
|
9312
|
-
return
|
|
9334
|
+
return client.auth.me();
|
|
9313
9335
|
});
|
|
9314
|
-
config.accessToken =
|
|
9315
|
-
config.refreshToken =
|
|
9316
|
-
config.orgId =
|
|
9317
|
-
config.apiKey =
|
|
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("
|
|
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(() => {
|