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.
- package/dist/index.cjs +148 -91
- 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,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.
|
|
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
|
-
|
|
8900
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
8940
|
-
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");
|
|
8941
9055
|
console.error("");
|
|
8942
|
-
console.error("
|
|
8943
|
-
console.error(
|
|
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(`
|
|
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
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
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
|
|
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
|
-
|
|
9330
|
+
apiKey
|
|
9272
9331
|
});
|
|
9273
9332
|
const session = await runSpinnerUntil("Finalizing login", async () => {
|
|
9274
9333
|
await sleep(100);
|
|
9275
|
-
return
|
|
9334
|
+
return client.auth.me();
|
|
9276
9335
|
});
|
|
9277
|
-
config.accessToken =
|
|
9278
|
-
config.refreshToken =
|
|
9279
|
-
config.orgId =
|
|
9280
|
-
config.apiKey =
|
|
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("
|
|
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(() => {
|