trainfabric 0.1.1 → 0.1.3
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/README.md +9 -9
- package/dist/index.cjs +115 -33
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,13 +11,13 @@ npm install -g trainfabric
|
|
|
11
11
|
## Authenticate
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
|
|
14
|
+
trainfabric login
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
For service-account flows:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
|
|
20
|
+
trainfabric config:set-api-key <api-key>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Secrets are stored in macOS Keychain when available. Other platforms use an encrypted file under `~/.trainfabric` with restricted permissions.
|
|
@@ -25,18 +25,18 @@ Secrets are stored in macOS Keychain when available. Other platforms use an encr
|
|
|
25
25
|
## Run A Job
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
trainfabric projects:list
|
|
29
|
+
trainfabric datasets:upload ./train.jsonl --project <projectId>
|
|
30
|
+
trainfabric runs:quote --summary --project <projectId> --dataset <datasetId> --model llama-3-8b
|
|
31
|
+
trainfabric runs:create --yes --project <projectId> --dataset <datasetId> --model llama-3-8b
|
|
32
|
+
trainfabric runs:watch <runId>
|
|
33
|
+
trainfabric runs:cost-breakdown <runId> --summary
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
The CLI defaults to `https://api.trainfabric.com`. Override with:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
|
|
39
|
+
trainfabric config:set-base-url https://your-api.example.com
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Pricing, supplier costs, and profitability are estimates unless invoice-grade supplier reconciliation is explicitly configured.
|
package/dist/index.cjs
CHANGED
|
@@ -3462,6 +3462,7 @@ var import_node_http = __toESM(require("node:http"), 1);
|
|
|
3462
3462
|
var import_node_os2 = __toESM(require("node:os"), 1);
|
|
3463
3463
|
var import_node_path3 = __toESM(require("node:path"), 1);
|
|
3464
3464
|
var import_node_child_process = require("node:child_process");
|
|
3465
|
+
var import_promises2 = require("node:timers/promises");
|
|
3465
3466
|
|
|
3466
3467
|
// ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/esm.mjs
|
|
3467
3468
|
var import_index = __toESM(require_commander(), 1);
|
|
@@ -7738,6 +7739,9 @@ var serviceAccountCreateSchema = external_exports.object({
|
|
|
7738
7739
|
scopes: external_exports.array(external_exports.enum(apiKeyScopes)).min(1)
|
|
7739
7740
|
});
|
|
7740
7741
|
var billingCheckoutSessionCreateSchema = external_exports.object({
|
|
7742
|
+
amountUsd: external_exports.number().positive().max(1e5).optional(),
|
|
7743
|
+
mode: external_exports.enum(["subscription", "credits"]).default("subscription"),
|
|
7744
|
+
planId: external_exports.enum(["hobby", "pro"]).optional(),
|
|
7741
7745
|
priceId: external_exports.string().min(1).optional()
|
|
7742
7746
|
});
|
|
7743
7747
|
var treasuryCounterpartyCreateSchema = external_exports.object({
|
|
@@ -8887,11 +8891,61 @@ function buildComputeSpec(options) {
|
|
|
8887
8891
|
|
|
8888
8892
|
// src/index.ts
|
|
8889
8893
|
var DEFAULT_TRAINFABRIC_API_URL2 = "https://api.trainfabric.com";
|
|
8890
|
-
var CLI_VERSION = "0.1.
|
|
8894
|
+
var CLI_VERSION = "0.1.3";
|
|
8891
8895
|
var CONFIG_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".trainfabric");
|
|
8892
8896
|
var CONFIG_PATH = import_node_path3.default.join(CONFIG_DIR, "config.json");
|
|
8893
8897
|
var FALLBACK_SECRET_PATH = import_node_path3.default.join(CONFIG_DIR, "session.enc");
|
|
8894
8898
|
var KEYCHAIN_SERVICE = "trainfabric-cli";
|
|
8899
|
+
function printLoginBanner() {
|
|
8900
|
+
const logo = [
|
|
8901
|
+
" _______ __ _ _ _ ____ _____",
|
|
8902
|
+
" |__ __| [ | (_) | | (_) / __ \\ |_ _|",
|
|
8903
|
+
" | |_ __ _ _ _ __ | | ____ _ _ __ _ __ | |_ _ __ _ _ __ | | | | | | ",
|
|
8904
|
+
" | | '__| | | | '_ \\ | |/ / _` | '_ \\| '_ \\ | __| |/ _` | '_ \\ | | | | | | ",
|
|
8905
|
+
" | | | | |_| | | | || < (_| | |_) | |_) | | |_| | (_| | | | | | |__| |_ | | ",
|
|
8906
|
+
" |_|_| \\__,_|_| |_| |_|\\_\\| .__/| .__/ \\__|_|\\__,_|_| |_| \\____/|_____|",
|
|
8907
|
+
" | | | | "
|
|
8908
|
+
];
|
|
8909
|
+
console.log("");
|
|
8910
|
+
for (const row of logo) {
|
|
8911
|
+
console.log(row);
|
|
8912
|
+
}
|
|
8913
|
+
console.log(" TrainFabric CLI Login");
|
|
8914
|
+
console.log("");
|
|
8915
|
+
}
|
|
8916
|
+
function sleep(ms) {
|
|
8917
|
+
return (0, import_promises2.setTimeout)(ms);
|
|
8918
|
+
}
|
|
8919
|
+
async function runSpinnerUntil(label, action) {
|
|
8920
|
+
const frames = ["/", "-", "\\", "|"];
|
|
8921
|
+
let i = 0;
|
|
8922
|
+
const timer = setInterval(() => {
|
|
8923
|
+
process.stdout.write(`\r${frames[i++ % frames.length]} ${label}`);
|
|
8924
|
+
}, 90);
|
|
8925
|
+
try {
|
|
8926
|
+
const result = await action();
|
|
8927
|
+
clearInterval(timer);
|
|
8928
|
+
process.stdout.write(`\r[ok] ${label}... done${" ".repeat(16)}
|
|
8929
|
+
`);
|
|
8930
|
+
return result;
|
|
8931
|
+
} catch (error) {
|
|
8932
|
+
clearInterval(timer);
|
|
8933
|
+
process.stdout.write(`\r[x] ${label} failed${" ".repeat(16)}
|
|
8934
|
+
`);
|
|
8935
|
+
throw error;
|
|
8936
|
+
}
|
|
8937
|
+
}
|
|
8938
|
+
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:");
|
|
8941
|
+
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`);
|
|
8945
|
+
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.");
|
|
8948
|
+
}
|
|
8895
8949
|
function ensureConfigDir() {
|
|
8896
8950
|
import_node_fs2.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
8897
8951
|
import_node_fs2.default.chmodSync(CONFIG_DIR, 448);
|
|
@@ -9162,8 +9216,15 @@ function createClient(config) {
|
|
|
9162
9216
|
});
|
|
9163
9217
|
}
|
|
9164
9218
|
async function login(config) {
|
|
9165
|
-
|
|
9219
|
+
printLoginBanner();
|
|
9220
|
+
const loginClient = new TrainingClient({ baseUrl: config.baseUrl });
|
|
9166
9221
|
const server = import_node_http.default.createServer();
|
|
9222
|
+
const closeServer = () => {
|
|
9223
|
+
try {
|
|
9224
|
+
server.close();
|
|
9225
|
+
} catch {
|
|
9226
|
+
}
|
|
9227
|
+
};
|
|
9167
9228
|
const port = await new Promise((resolve) => {
|
|
9168
9229
|
server.listen(0, "127.0.0.1", () => {
|
|
9169
9230
|
const address = server.address();
|
|
@@ -9174,39 +9235,60 @@ async function login(config) {
|
|
|
9174
9235
|
});
|
|
9175
9236
|
});
|
|
9176
9237
|
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
9177
|
-
const loginUrl = await
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
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);
|
|
9242
|
+
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({
|
|
9270
|
+
baseUrl: config.baseUrl,
|
|
9271
|
+
accessToken: tokenResult.accessToken
|
|
9192
9272
|
});
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9273
|
+
const session = await runSpinnerUntil("Finalizing login", async () => {
|
|
9274
|
+
await sleep(100);
|
|
9275
|
+
return authedClient.auth.me();
|
|
9276
|
+
});
|
|
9277
|
+
config.accessToken = tokenResult.accessToken;
|
|
9278
|
+
config.refreshToken = tokenResult.refreshToken;
|
|
9279
|
+
config.orgId = tokenResult.organizationId ?? session.organizationId;
|
|
9280
|
+
config.apiKey = void 0;
|
|
9281
|
+
saveConfig(config);
|
|
9282
|
+
console.log(`Logged in as ${session.user.email} for organization ${config.orgId}.`);
|
|
9283
|
+
} catch (error) {
|
|
9284
|
+
printLoginFallbackHint(config.baseUrl);
|
|
9285
|
+
throw error;
|
|
9286
|
+
} finally {
|
|
9287
|
+
closeServer();
|
|
9288
|
+
}
|
|
9207
9289
|
}
|
|
9208
9290
|
var program2 = new Command();
|
|
9209
|
-
program2.name("
|
|
9291
|
+
program2.name("trainfabric").description("Trainfabric CLI for launching and monitoring GPU training jobs").version(CLI_VERSION);
|
|
9210
9292
|
program2.command("login").description("Authenticate in the browser and store CLI credentials").action(async () => {
|
|
9211
9293
|
await login(loadConfig());
|
|
9212
9294
|
});
|
|
@@ -9295,7 +9377,7 @@ program2.command("datasets:upload").argument("<file>").option("--project <projec
|
|
|
9295
9377
|
});
|
|
9296
9378
|
program2.command("runs:create").requiredOption("--project <projectId>").requiredOption("--dataset <datasetId>").requiredOption("--model <baseModel>").option("--eval <evalDatasetId>").option("--epochs <epochs>", "number of epochs", "3").option("--lr <lr>", "learning rate", "0.0002").option("--gpus <gpuCount>", "gpu count", "1").option("--nodes <nodeCount>", "node count", "1").option("--accelerator <acceleratorClass>", "optional hard accelerator constraint (for example: a10g, a100, h100)").option("--min-memory <gigabytes>", "minimum GPU memory in GB").option("--precision <precision>", "fp16_bf16, fp8, or fp32").option("--interconnect <interconnect>", "pcie or nvlink").option("--mode <mode>", "efficient, balanced, or power", "balanced").option("--repo <path>", "local repo path for runtime autodetect").option("--git <url>", "git repo URL for runtime metadata").option("--branch <branch>", "git branch for runtime metadata").option("--yes", "confirm that you reviewed pricing with runs:quote --summary and accept fluctuating realized usage").description("Create a training run").action(async (options) => {
|
|
9297
9379
|
if (!options.yes) {
|
|
9298
|
-
throw new Error("Refusing to launch without explicit cost acceptance. Run `
|
|
9380
|
+
throw new Error("Refusing to launch without explicit cost acceptance. Run `trainfabric runs:quote --summary ...` first, then rerun `runs:create` with --yes.");
|
|
9299
9381
|
}
|
|
9300
9382
|
const client = createClient(loadConfig());
|
|
9301
9383
|
const run = await client.runs.create(buildRunInput(options));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trainfabric",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"dist/index.cjs"
|
|
9
9
|
],
|
|
10
10
|
"bin": {
|
|
11
|
-
"
|
|
11
|
+
"trainfabric": "dist/index.cjs"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json && esbuild src/index.ts --bundle --platform=node --format=cjs --target=node20 --define:process.env.TRAINFABRIC_CLI_VERSION='\"'$(node -p \"require('./package.json').version\")'\"' --outfile=dist/index.cjs",
|