trainfabric 0.1.4 → 0.1.6
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 +111 -113
- 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.6";
|
|
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");
|
|
@@ -8905,6 +8905,18 @@ var LOGIN_BANNER_LINES = [
|
|
|
8905
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
8906
|
];
|
|
8907
8907
|
var LOGIN_BANNER_POOL = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*<>?+=/\\|~";
|
|
8908
|
+
var LOGIN_BANNER_COMPACT_LINES = [
|
|
8909
|
+
"######### ######## ###### #### ## ## ######## ###### ######## ######## #### ######",
|
|
8910
|
+
" ### ## ## ## ## ## ### ## ## ## ## ## ## ## ## ## ## ##",
|
|
8911
|
+
" ### ######## ######## ## ## ## ## ###### ######## ######## ######## ## ##",
|
|
8912
|
+
" ### ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##",
|
|
8913
|
+
" ### ## ## ## ## #### ## #### ## ## ## ######## ## ## #### ######"
|
|
8914
|
+
];
|
|
8915
|
+
function getLoginBannerLines() {
|
|
8916
|
+
const columns = process.stdout.columns ?? 120;
|
|
8917
|
+
const maxWideLine = Math.max(...LOGIN_BANNER_LINES.map((line) => line.length));
|
|
8918
|
+
return columns > maxWideLine + 2 ? LOGIN_BANNER_LINES : LOGIN_BANNER_COMPACT_LINES;
|
|
8919
|
+
}
|
|
8908
8920
|
function scrambleLine(line, progress) {
|
|
8909
8921
|
const lockUntil = Math.floor(line.length * progress);
|
|
8910
8922
|
return [...line].map((char, index) => {
|
|
@@ -8914,42 +8926,86 @@ function scrambleLine(line, progress) {
|
|
|
8914
8926
|
return LOGIN_BANNER_POOL[Math.floor(Math.random() * LOGIN_BANNER_POOL.length)];
|
|
8915
8927
|
}).join("");
|
|
8916
8928
|
}
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8929
|
+
function renderLoginBanner(progress, lines = getLoginBannerLines()) {
|
|
8930
|
+
return lines.map((row) => scrambleLine(row, progress));
|
|
8931
|
+
}
|
|
8932
|
+
function sleep(ms) {
|
|
8933
|
+
return (0, import_promises3.setTimeout)(ms);
|
|
8934
|
+
}
|
|
8935
|
+
async function promptApiKey() {
|
|
8936
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || !process.stdin.setRawMode) {
|
|
8937
|
+
const rl = import_promises2.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
8938
|
+
try {
|
|
8939
|
+
return (await rl.question("Paste your TrainFabric API key: ")).trim();
|
|
8940
|
+
} finally {
|
|
8941
|
+
rl.close();
|
|
8942
|
+
}
|
|
8943
|
+
}
|
|
8944
|
+
let apiKey = "";
|
|
8945
|
+
let frame = 0;
|
|
8946
|
+
let closed = false;
|
|
8947
|
+
const lines = getLoginBannerLines();
|
|
8948
|
+
const frameCount = 40;
|
|
8949
|
+
const totalLines = lines.length + 4;
|
|
8950
|
+
const redraw = () => {
|
|
8951
|
+
const progress = frame % frameCount / frameCount;
|
|
8952
|
+
process.stdout.write(`\x1B[${totalLines}A`);
|
|
8953
|
+
for (const row of renderLoginBanner(progress, lines)) {
|
|
8954
|
+
process.stdout.write(`\x1B[2K${row}
|
|
8955
|
+
`);
|
|
8956
|
+
}
|
|
8957
|
+
process.stdout.write("\x1B[2K TrainFabric CLI Login\n");
|
|
8958
|
+
process.stdout.write("\x1B[2K\n");
|
|
8959
|
+
process.stdout.write(`\x1B[2KPaste your TrainFabric API key: ${"*".repeat(apiKey.length)}
|
|
8960
|
+
`);
|
|
8961
|
+
process.stdout.write("\x1B[2KPress Enter to continue.\n");
|
|
8962
|
+
frame += 1;
|
|
8963
|
+
};
|
|
8964
|
+
return await new Promise((resolve, reject) => {
|
|
8965
|
+
const cleanup = () => {
|
|
8966
|
+
if (closed) {
|
|
8967
|
+
return;
|
|
8968
|
+
}
|
|
8969
|
+
closed = true;
|
|
8970
|
+
clearInterval(timer);
|
|
8971
|
+
process.stdin.off("data", onData);
|
|
8972
|
+
process.stdin.setRawMode(false);
|
|
8973
|
+
process.stdin.pause();
|
|
8974
|
+
process.stdout.write("\x1B[?25h\n");
|
|
8975
|
+
};
|
|
8976
|
+
const onData = (chunk) => {
|
|
8977
|
+
const value = chunk.toString("utf8");
|
|
8978
|
+
if (value === "") {
|
|
8979
|
+
cleanup();
|
|
8980
|
+
reject(new Error("Login cancelled."));
|
|
8981
|
+
return;
|
|
8982
|
+
}
|
|
8983
|
+
if (value === "\r" || value === "\n") {
|
|
8984
|
+
cleanup();
|
|
8985
|
+
resolve(apiKey.trim());
|
|
8986
|
+
return;
|
|
8987
|
+
}
|
|
8988
|
+
if (value === "\x7F" || value === "\b") {
|
|
8989
|
+
apiKey = apiKey.slice(0, -1);
|
|
8990
|
+
redraw();
|
|
8991
|
+
return;
|
|
8992
|
+
}
|
|
8993
|
+
apiKey += value.replace(/[\r\n]/g, "");
|
|
8994
|
+
redraw();
|
|
8995
|
+
};
|
|
8996
|
+
for (const row of lines) {
|
|
8921
8997
|
console.log(row);
|
|
8922
8998
|
}
|
|
8923
8999
|
console.log(" TrainFabric CLI Login");
|
|
8924
9000
|
console.log("");
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
for (const row of LOGIN_BANNER_LINES) {
|
|
8934
|
-
process.stdout.write(`${scrambleLine(row, progress)}
|
|
8935
|
-
`);
|
|
8936
|
-
}
|
|
8937
|
-
await (0, import_promises2.setTimeout)(frame < 14 ? 32 : 18);
|
|
8938
|
-
}
|
|
8939
|
-
process.stdout.write("\x1B[?25h");
|
|
8940
|
-
console.log(" TrainFabric CLI Login");
|
|
8941
|
-
console.log("");
|
|
8942
|
-
}
|
|
8943
|
-
function printStaticLoginBanner() {
|
|
8944
|
-
console.log("");
|
|
8945
|
-
for (const row of LOGIN_BANNER_LINES) {
|
|
8946
|
-
console.log(row);
|
|
8947
|
-
}
|
|
8948
|
-
console.log(" TrainFabric CLI Login");
|
|
8949
|
-
console.log("");
|
|
8950
|
-
}
|
|
8951
|
-
function sleep(ms) {
|
|
8952
|
-
return (0, import_promises2.setTimeout)(ms);
|
|
9001
|
+
console.log("Paste your TrainFabric API key: ");
|
|
9002
|
+
console.log("Press Enter to continue.");
|
|
9003
|
+
process.stdout.write("\x1B[?25l");
|
|
9004
|
+
process.stdin.setRawMode(true);
|
|
9005
|
+
process.stdin.resume();
|
|
9006
|
+
process.stdin.on("data", onData);
|
|
9007
|
+
const timer = setInterval(redraw, 75);
|
|
9008
|
+
});
|
|
8953
9009
|
}
|
|
8954
9010
|
async function runSpinnerUntil(label, action) {
|
|
8955
9011
|
const frames = ["/", "-", "\\", "|"];
|
|
@@ -8971,15 +9027,15 @@ async function runSpinnerUntil(label, action) {
|
|
|
8971
9027
|
}
|
|
8972
9028
|
}
|
|
8973
9029
|
function printLoginFallbackHint(baseUrl) {
|
|
8974
|
-
console.error("\nCould not
|
|
8975
|
-
console.error("
|
|
9030
|
+
console.error("\nCould not verify that API key with TrainFabric.");
|
|
9031
|
+
console.error("");
|
|
9032
|
+
console.error("Check that the key is active in the dashboard, then retry:");
|
|
9033
|
+
console.error(" trainfabric login");
|
|
8976
9034
|
console.error("");
|
|
8977
|
-
console.error("
|
|
8978
|
-
console.error(
|
|
8979
|
-
console.error(` trainfabric --version`);
|
|
9035
|
+
console.error("You can also set it directly:");
|
|
9036
|
+
console.error(" trainfabric config:set-api-key <api-key>");
|
|
8980
9037
|
console.error("");
|
|
8981
|
-
console.error(`
|
|
8982
|
-
console.error("then run trainfabric login again.");
|
|
9038
|
+
console.error(`Current API base URL: ${baseUrl}`);
|
|
8983
9039
|
}
|
|
8984
9040
|
function ensureConfigDir() {
|
|
8985
9041
|
import_node_fs2.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
@@ -9153,15 +9209,6 @@ function visibleConfig(config) {
|
|
|
9153
9209
|
refreshToken: config.refreshToken ? "<stored>" : void 0
|
|
9154
9210
|
};
|
|
9155
9211
|
}
|
|
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
9212
|
function buildRunInput(options) {
|
|
9166
9213
|
const sourceOptions = buildSourceOptions(options);
|
|
9167
9214
|
return {
|
|
@@ -9251,82 +9298,33 @@ function createClient(config) {
|
|
|
9251
9298
|
});
|
|
9252
9299
|
}
|
|
9253
9300
|
async function login(config) {
|
|
9254
|
-
await
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
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
|
-
});
|
|
9301
|
+
const apiKey = await promptApiKey();
|
|
9302
|
+
if (!apiKey) {
|
|
9303
|
+
throw new Error("API key is required.");
|
|
9304
|
+
}
|
|
9274
9305
|
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({
|
|
9306
|
+
const client = new TrainingClient({
|
|
9307
9307
|
baseUrl: config.baseUrl,
|
|
9308
|
-
|
|
9308
|
+
apiKey
|
|
9309
9309
|
});
|
|
9310
9310
|
const session = await runSpinnerUntil("Finalizing login", async () => {
|
|
9311
9311
|
await sleep(100);
|
|
9312
|
-
return
|
|
9312
|
+
return client.auth.me();
|
|
9313
9313
|
});
|
|
9314
|
-
config.accessToken =
|
|
9315
|
-
config.refreshToken =
|
|
9316
|
-
config.orgId =
|
|
9317
|
-
config.apiKey =
|
|
9314
|
+
config.accessToken = void 0;
|
|
9315
|
+
config.refreshToken = void 0;
|
|
9316
|
+
config.orgId = session.organizationId;
|
|
9317
|
+
config.apiKey = apiKey;
|
|
9318
9318
|
saveConfig(config);
|
|
9319
|
-
console.log(`Logged in as ${session.user.email} for organization ${config.orgId}.`);
|
|
9319
|
+
console.log(`Logged in with API key as ${session.user.email} for organization ${config.orgId}.`);
|
|
9320
9320
|
} catch (error) {
|
|
9321
9321
|
printLoginFallbackHint(config.baseUrl);
|
|
9322
9322
|
throw error;
|
|
9323
|
-
} finally {
|
|
9324
|
-
closeServer();
|
|
9325
9323
|
}
|
|
9326
9324
|
}
|
|
9327
9325
|
var program2 = new Command();
|
|
9328
9326
|
program2.name("trainfabric").description("Trainfabric CLI for launching and monitoring GPU training jobs").version(CLI_VERSION);
|
|
9329
|
-
program2.command("login").description("
|
|
9327
|
+
program2.command("login").description("Paste and store a TrainFabric API key").action(async () => {
|
|
9330
9328
|
await login(loadConfig());
|
|
9331
9329
|
});
|
|
9332
9330
|
program2.command("logout").description("Remove stored credentials").action(() => {
|