trainfabric 0.1.11 → 0.1.13

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 CHANGED
@@ -28,8 +28,8 @@ Secrets are stored in macOS Keychain when available. Other platforms use an encr
28
28
  trainfabric projects:list
29
29
  trainfabric datasets:upload ./train.jsonl --project <projectId>
30
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>
31
+ trainfabric runs:create --yes --quote <quoteId> --project <projectId> --dataset <datasetId> --model llama-3-8b
32
+ trainfabric runs:watch <runId> --timeout 30m
33
33
  trainfabric runs:cost-breakdown <runId> --summary
34
34
  ```
35
35
 
package/dist/index.cjs CHANGED
@@ -7975,12 +7975,6 @@ function validateChatJsonl(text) {
7975
7975
  }
7976
7976
  tokenEstimate += estimateTokens(result.data.messages.map((message) => `${message.role}:${message.content}`).join("\n"));
7977
7977
  }
7978
- if (tokenEstimate < 100) {
7979
- warnings.push({
7980
- code: "small_dataset",
7981
- message: "Dataset is valid but very small for a meaningful training run."
7982
- });
7983
- }
7984
7978
  const invalidWarnings = /* @__PURE__ */ new Set([
7985
7979
  "empty_file",
7986
7980
  "invalid_jsonl",
@@ -7988,8 +7982,15 @@ function validateChatJsonl(text) {
7988
7982
  "missing_assistant",
7989
7983
  "empty_assistant"
7990
7984
  ]);
7985
+ const isValid2 = !warnings.some((warning) => invalidWarnings.has(warning.code));
7986
+ if (tokenEstimate < 100) {
7987
+ warnings.push({
7988
+ code: "small_dataset",
7989
+ message: isValid2 ? "Dataset is valid but very small for a meaningful training run." : "Dataset is very small; fix schema errors before using it for a training run."
7990
+ });
7991
+ }
7991
7992
  return {
7992
- valid: !warnings.some((warning) => invalidWarnings.has(warning.code)),
7993
+ valid: isValid2,
7993
7994
  format: "chat_jsonl",
7994
7995
  rowCount,
7995
7996
  duplicateCount,
@@ -8388,8 +8389,11 @@ var RunHandle = class {
8388
8389
  this.listeners.get(event)?.delete(listener);
8389
8390
  };
8390
8391
  }
8391
- async wait() {
8392
+ async wait(options) {
8392
8393
  this.ensureStreaming();
8394
+ const startedAt = Date.now();
8395
+ const pollMs = options?.pollMs ?? 1e3;
8396
+ const timeoutMs = options?.timeoutMs;
8393
8397
  for (; ; ) {
8394
8398
  const detail = await this.client.runs.get(this.snapshot.id);
8395
8399
  this.snapshot = detail.run;
@@ -8406,7 +8410,11 @@ var RunHandle = class {
8406
8410
  }
8407
8411
  return detail;
8408
8412
  }
8409
- await new Promise((resolve) => setTimeout(resolve, 1e3));
8413
+ if (timeoutMs !== void 0 && Date.now() - startedAt > timeoutMs) {
8414
+ this.close();
8415
+ throw new Error(`Timed out waiting for run ${this.snapshot.id}.`);
8416
+ }
8417
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
8410
8418
  }
8411
8419
  }
8412
8420
  close() {
@@ -8624,9 +8632,9 @@ var RunsClient = class extends ResourceClient {
8624
8632
  explanation(runId) {
8625
8633
  return this.requestGet(`/v1/runs/${runId}/explanation`);
8626
8634
  }
8627
- async wait(runId) {
8635
+ async wait(runId, options) {
8628
8636
  const handle = await this.watch(runId);
8629
- return handle.wait();
8637
+ return handle.wait(options);
8630
8638
  }
8631
8639
  };
8632
8640
 
@@ -8914,7 +8922,7 @@ function buildComputeSpec(options) {
8914
8922
 
8915
8923
  // src/index.ts
8916
8924
  var DEFAULT_TRAINFABRIC_API_URL2 = "https://api.trainfabric.com";
8917
- var CLI_VERSION = "0.1.11";
8925
+ var CLI_VERSION = "0.1.13";
8918
8926
  var CONFIG_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".trainfabric");
8919
8927
  var CONFIG_PATH = import_node_path3.default.join(CONFIG_DIR, "config.json");
8920
8928
  var FALLBACK_SECRET_PATH = import_node_path3.default.join(CONFIG_DIR, "session.enc");
@@ -9208,6 +9216,7 @@ function printQuoteSummary(bundle) {
9208
9216
  console.log(` data path: ${quote.dataPath ?? "auto"}`);
9209
9217
  console.log(` GPUs: ${gpuCount}`);
9210
9218
  console.log(` pool: ${quote.selectedPoolId}`);
9219
+ console.log(` launch with: --quote ${quote.id}`);
9211
9220
  }
9212
9221
  console.log("");
9213
9222
  console.log("Launch only after the customer accepts the quote. Streaming/data-plane cost is included in the estimate when the selected data path is stream, and realized usage may differ.");
@@ -9260,6 +9269,7 @@ function buildRunInput(options) {
9260
9269
  baseModel: normalizeBaseModel(options.model),
9261
9270
  datasetId: options.dataset,
9262
9271
  evalDatasetId: options.eval,
9272
+ pricingQuoteId: options.quote,
9263
9273
  ...sourceOptions,
9264
9274
  mode: options.mode,
9265
9275
  compute: buildComputeSpec(options),
@@ -9290,7 +9300,7 @@ function assertQuoteOptions(bundle, options) {
9290
9300
  )}). Omit --accelerator or increase --min-memory.`
9291
9301
  );
9292
9302
  }
9293
- async function watchRun(runId, json = false) {
9303
+ async function watchRun(runId, json = false, timeoutMs) {
9294
9304
  const handle = await createClient(loadConfig()).runs.watch(runId);
9295
9305
  if (json) {
9296
9306
  const emit = (type, payload) => printJson({ type, payload });
@@ -9327,9 +9337,26 @@ async function watchRun(runId, json = false) {
9327
9337
  console.log(`[failed] ${payload.reason ?? "Run failed."}`);
9328
9338
  });
9329
9339
  }
9330
- const detail = await handle.wait();
9340
+ const detail = await handle.wait({ timeoutMs });
9331
9341
  printJson(detail);
9332
9342
  }
9343
+ function parseDurationMs(value) {
9344
+ if (!value) {
9345
+ return void 0;
9346
+ }
9347
+ const trimmed = value.trim().toLowerCase();
9348
+ const match = trimmed.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
9349
+ if (!match) {
9350
+ throw new Error(`Invalid duration "${value}". Use values like 30s, 10m, 1h, or 5000ms.`);
9351
+ }
9352
+ const amount = Number(match[1]);
9353
+ const unit = match[2] ?? "ms";
9354
+ const multiplier = unit === "h" ? 36e5 : unit === "m" ? 6e4 : unit === "s" ? 1e3 : 1;
9355
+ return Math.max(1, Math.round(amount * multiplier));
9356
+ }
9357
+ function parsePositiveMs(value, fallback) {
9358
+ return parseDurationMs(value) ?? fallback;
9359
+ }
9333
9360
  function createClient(config) {
9334
9361
  return new TrainingClient({
9335
9362
  accessToken: config.accessToken,
@@ -9429,12 +9456,14 @@ program2.command("projects:create").requiredOption("--name <name>").option("--or
9429
9456
  printJson(await createClient(loadConfig()).projects.create({ name: options.name, organizationId: options.org }));
9430
9457
  });
9431
9458
  program2.command("datasets:validate").argument("<file>").description("Validate a local dataset file").action(async (file) => {
9432
- printJson(
9433
- await createClient(loadConfig()).datasets.validate({
9434
- path: file,
9435
- format: "chat_jsonl"
9436
- })
9437
- );
9459
+ const validation = await createClient(loadConfig()).datasets.validate({
9460
+ path: file,
9461
+ format: "chat_jsonl"
9462
+ });
9463
+ printJson(validation);
9464
+ if (!validation.valid) {
9465
+ process.exitCode = 1;
9466
+ }
9438
9467
  });
9439
9468
  program2.command("datasets:list").option("--project <projectId>").description("List datasets").action(async (options) => {
9440
9469
  printJson(await createClient(loadConfig()).datasets.list(options.project));
@@ -9452,7 +9481,7 @@ program2.command("datasets:upload").argument("<file>").option("--project <projec
9452
9481
  });
9453
9482
  printJson(dataset);
9454
9483
  });
9455
- 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) => {
9484
+ 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("--quote <pricingQuoteId>", "launch the exact accepted quote id from runs:quote").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) => {
9456
9485
  if (!options.yes) {
9457
9486
  throw new Error("Refusing to launch without explicit cost acceptance. Run `trainfabric runs:quote --summary ...` first, then rerun `runs:create` with --yes.");
9458
9487
  }
@@ -9498,11 +9527,16 @@ program2.command("runs:status").argument("<runId>").description("Fetch full run
9498
9527
  program2.command("runs:logs").argument("<runId>").description("Fetch run logs").action(async (runId) => {
9499
9528
  printJson(await createClient(loadConfig()).runs.logs(String(runId)));
9500
9529
  });
9501
- program2.command("runs:wait").argument("<runId>").description("Wait for a run to reach a terminal state").action(async (runId) => {
9502
- printJson(await createClient(loadConfig()).runs.wait(String(runId)));
9530
+ program2.command("runs:wait").argument("<runId>").option("--timeout <duration>", "maximum wait duration, for example 30s, 10m, or 1h").option("--poll <duration>", "poll interval, for example 1s or 500ms").description("Wait for a run to reach a terminal state").action(async (runId, options) => {
9531
+ printJson(
9532
+ await createClient(loadConfig()).runs.wait(String(runId), {
9533
+ timeoutMs: parseDurationMs(options.timeout),
9534
+ pollMs: parsePositiveMs(options.poll, 1e3)
9535
+ })
9536
+ );
9503
9537
  });
9504
- program2.command("runs:watch").argument("<runId>").option("--json", "emit event output as JSON").description("Stream a run until it completes").action(async (runId, options) => {
9505
- await watchRun(String(runId), Boolean(options.json));
9538
+ program2.command("runs:watch").argument("<runId>").option("--json", "emit event output as JSON").option("--timeout <duration>", "maximum watch duration, for example 30s, 10m, or 1h").description("Stream a run until it completes").action(async (runId, options) => {
9539
+ await watchRun(String(runId), Boolean(options.json), parseDurationMs(options.timeout));
9506
9540
  });
9507
9541
  program2.command("runs:usage").argument("<runId>").option("--summary", "print a human-readable cost summary instead of raw JSON").description("Fetch run usage summary").action(async (runId, options) => {
9508
9542
  const usage = await createClient(loadConfig()).runs.usage(String(runId));
@@ -9553,7 +9587,7 @@ program2.command("models:export").argument("<modelId>").description("Fetch the e
9553
9587
  program2.command("deployments:list").description("List deployments").action(async () => {
9554
9588
  printJson(await createClient(loadConfig()).deployments.list());
9555
9589
  });
9556
- program2.command("deployments:create").requiredOption("--model <modelId>").description("Create a deployment for a model").action(async (options) => {
9590
+ program2.command("deployments:create").requiredOption("--model <modelId>").option("--project <projectId>", "accepted for consistency; deployment project is inferred from the model").description("Create a deployment for a model").action(async (options) => {
9557
9591
  printJson(await createClient(loadConfig()).deployments.create({ modelId: options.model }));
9558
9592
  });
9559
9593
  program2.command("deployments:get").argument("<deploymentId>").description("Fetch a deployment").action(async (deploymentId) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trainfabric",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
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",