trainfabric 0.1.25 → 0.1.27

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.
Files changed (2) hide show
  1. package/dist/index.cjs +139 -38
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -8820,12 +8820,16 @@ var import_node_fs = __toESM(require("node:fs"), 1);
8820
8820
  var import_node_os = __toESM(require("node:os"), 1);
8821
8821
  var import_node_path2 = __toESM(require("node:path"), 1);
8822
8822
  var idPatterns = {
8823
+ apiKey: /^key_[A-Za-z0-9_-]+$/,
8823
8824
  dataset: /^ds_[A-Za-z0-9_-]+$/,
8824
8825
  deployment: /^dep_[A-Za-z0-9_-]+$/,
8825
8826
  model: /^mdl_[A-Za-z0-9_-]+$/,
8826
8827
  org: /^org_[A-Za-z0-9_-]+$/,
8828
+ pool: /^pool_[A-Za-z0-9_-]+$/,
8827
8829
  project: /^proj_[A-Za-z0-9_-]+$/,
8828
- run: /^run_[A-Za-z0-9_-]+$/
8830
+ run: /^run_[A-Za-z0-9_-]+$/,
8831
+ serviceAccount: /^svc_[A-Za-z0-9_-]+$/,
8832
+ supplier: /^[A-Za-z][A-Za-z0-9_-]*$/
8829
8833
  };
8830
8834
  var allowedBillingUnits = /* @__PURE__ */ new Set(["normalized_tflop_seconds", "reserved_gpu_seconds", "usd"]);
8831
8835
  function collectRuntimeFiles(repoPath) {
@@ -8961,7 +8965,10 @@ function decrypt(payload) {
8961
8965
  }
8962
8966
  function parseApiKeyScopes(input) {
8963
8967
  const allowedScopes = new Set(apiKeyScopes);
8964
- const scopes = input.split(",").map((scope) => scope.trim()).filter(Boolean);
8968
+ const scopes = input.split(",").map((scope) => scope.trim());
8969
+ if (scopes.some((scope) => scope.length === 0)) {
8970
+ throw new Error("API key scopes cannot contain empty entries.");
8971
+ }
8965
8972
  const invalidScope = scopes.find((scope) => !allowedScopes.has(scope));
8966
8973
  if (invalidScope) {
8967
8974
  throw new Error(`Invalid API key scope: ${invalidScope}`);
@@ -8976,6 +8983,9 @@ function normalizeHumanName(value, label) {
8976
8983
  if (!/[A-Za-z0-9]/.test(name)) {
8977
8984
  throw new Error(`${label} is required.`);
8978
8985
  }
8986
+ if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,79}$/.test(name) || name.includes("..")) {
8987
+ throw new Error(`${label} contains invalid characters. Use letters, numbers, spaces, dots, underscores, or hyphens.`);
8988
+ }
8979
8989
  return name;
8980
8990
  }
8981
8991
  function normalizeId(value, kind, label) {
@@ -9014,36 +9024,105 @@ var BASE_MODEL_ALIASES = {
9014
9024
  "qwen2.5-7b": "qwen-2.5-7b",
9015
9025
  qwen_2_5_7b: "qwen-2.5-7b"
9016
9026
  };
9027
+ var ACCELERATOR_CLASSES = /* @__PURE__ */ new Set(["a10g", "l4", "a40", "l40s", "a100", "h100", "h200", "b200"]);
9028
+ var PRECISION_MODES = /* @__PURE__ */ new Set(["fp16_bf16", "fp8", "fp32"]);
9029
+ var INTERCONNECT_TYPES = /* @__PURE__ */ new Set(["pcie", "nvlink"]);
9030
+ var BASE_MODELS = new Set(baseModels);
9031
+ var TRAINING_MODES = new Set(trainingModes);
9032
+ function normalizePositiveInteger(value, label, fallback) {
9033
+ const parsed = Number(value ?? String(fallback));
9034
+ if (!Number.isInteger(parsed) || parsed <= 0) {
9035
+ throw new Error(`${label} must be a positive integer.`);
9036
+ }
9037
+ return parsed;
9038
+ }
9039
+ function normalizeOptionalPositiveNumber(value, label) {
9040
+ if (value === void 0) {
9041
+ return void 0;
9042
+ }
9043
+ const parsed = Number(value);
9044
+ if (!Number.isFinite(parsed) || parsed <= 0) {
9045
+ throw new Error(`${label} must be a positive number.`);
9046
+ }
9047
+ return parsed;
9048
+ }
9049
+ function normalizeOptionalChoice(value, allowed, label, examples) {
9050
+ if (value === void 0) {
9051
+ return void 0;
9052
+ }
9053
+ const normalized = String(value).trim().toLowerCase();
9054
+ if (!allowed.has(normalized)) {
9055
+ throw new Error(`${label} is invalid. Use ${examples}.`);
9056
+ }
9057
+ return normalized;
9058
+ }
9017
9059
  function normalizeBaseModel(model) {
9018
- return BASE_MODEL_ALIASES[model] ?? model;
9060
+ const normalized = BASE_MODEL_ALIASES[String(model ?? "").trim().toLowerCase()];
9061
+ if (normalized) {
9062
+ return normalized;
9063
+ }
9064
+ const rawModel = String(model ?? "").trim();
9065
+ if (!BASE_MODELS.has(rawModel)) {
9066
+ throw new Error("Model is invalid. Use llama-3-8b, mistral-7b, or qwen-2.5-7b.");
9067
+ }
9068
+ return rawModel;
9069
+ }
9070
+ function normalizeTrainingMode(mode) {
9071
+ if (mode === void 0) {
9072
+ return void 0;
9073
+ }
9074
+ const normalized = String(mode).trim().toLowerCase();
9075
+ if (!TRAINING_MODES.has(normalized)) {
9076
+ throw new Error("Mode is invalid. Use efficient, balanced, or power.");
9077
+ }
9078
+ return normalized;
9079
+ }
9080
+ function normalizeEpochs(epochs) {
9081
+ return normalizePositiveInteger(epochs, "Epochs", 3);
9082
+ }
9083
+ function normalizeLearningRate(lr) {
9084
+ const parsed = Number(lr ?? "0.0002");
9085
+ if (!Number.isFinite(parsed) || parsed < 1e-5 || parsed > 0.01) {
9086
+ throw new Error("Learning rate must be a number between 0.00001 and 0.01.");
9087
+ }
9088
+ return parsed;
9019
9089
  }
9020
9090
  function buildComputeSpec(options) {
9021
- const gpuCount = Number(options.gpus ?? "1");
9022
- const nodeCount = Number(options.nodes ?? "1");
9091
+ const gpuCount = normalizePositiveInteger(options.gpus, "GPU count", 1);
9092
+ const nodeCount = normalizePositiveInteger(options.nodes, "Node count", 1);
9023
9093
  const compute = {
9024
9094
  gpuCount,
9025
9095
  nodeCount,
9026
9096
  distributionStrategy: gpuCount > 1 || nodeCount > 1 ? "ddp" : "single_gpu",
9027
9097
  target: "local"
9028
9098
  };
9029
- if (options.accelerator) {
9030
- compute.acceleratorClass = options.accelerator;
9099
+ const accelerator = normalizeOptionalChoice(
9100
+ options.accelerator,
9101
+ ACCELERATOR_CLASSES,
9102
+ "Accelerator",
9103
+ "a10g, l4, a40, l40s, a100, h100, h200, or b200"
9104
+ );
9105
+ const minMemory = normalizeOptionalPositiveNumber(options.minMemory, "Minimum GPU memory");
9106
+ const precision = normalizeOptionalChoice(options.precision, PRECISION_MODES, "Precision", "fp16_bf16, fp8, or fp32");
9107
+ const interconnect = normalizeOptionalChoice(options.interconnect, INTERCONNECT_TYPES, "Interconnect", "pcie or nvlink");
9108
+ if (accelerator) {
9109
+ compute.acceleratorClass = accelerator;
9031
9110
  }
9032
- if (options.minMemory) {
9033
- compute.minGpuMemoryGb = Number(options.minMemory);
9111
+ if (minMemory !== void 0) {
9112
+ compute.minGpuMemoryGb = minMemory;
9034
9113
  }
9035
- if (options.precision) {
9036
- compute.precision = options.precision;
9114
+ if (precision) {
9115
+ compute.precision = precision;
9037
9116
  }
9038
- if (options.interconnect) {
9039
- compute.interconnect = options.interconnect;
9117
+ if (interconnect) {
9118
+ compute.interconnect = interconnect;
9040
9119
  }
9041
9120
  return compute;
9042
9121
  }
9043
9122
 
9044
9123
  // src/index.ts
9045
9124
  var DEFAULT_TRAINFABRIC_API_URL2 = "https://api.trainfabric.com";
9046
- var CLI_VERSION = "0.1.25";
9125
+ var CLI_VERSION = "0.1.27";
9047
9126
  var CONFIG_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".trainfabric");
9048
9127
  var CONFIG_PATH = import_node_path3.default.join(CONFIG_DIR, "config.json");
9049
9128
  var FALLBACK_SECRET_PATH = import_node_path3.default.join(CONFIG_DIR, "session.enc");
@@ -9395,15 +9474,15 @@ function buildRunInput(options, config = loadConfig()) {
9395
9474
  task: "sft",
9396
9475
  method: "lora",
9397
9476
  baseModel: normalizeBaseModel(options.model),
9398
- datasetId: options.dataset,
9399
- evalDatasetId: options.eval,
9477
+ datasetId: normalizeId(options.dataset, "dataset", "Dataset ID"),
9478
+ evalDatasetId: normalizeOptionalId(options.eval, "dataset", "Eval dataset ID"),
9400
9479
  pricingQuoteId: options.quote,
9401
9480
  ...sourceOptions,
9402
- mode: options.mode,
9481
+ mode: normalizeTrainingMode(options.mode),
9403
9482
  compute: buildComputeSpec(options),
9404
9483
  hyperparameters: {
9405
- epochs: Number(options.epochs ?? "3"),
9406
- lr: Number(options.lr ?? "0.0002"),
9484
+ epochs: normalizeEpochs(options.epochs),
9485
+ lr: normalizeLearningRate(options.lr),
9407
9486
  batchSize: "auto"
9408
9487
  }
9409
9488
  };
@@ -9441,14 +9520,28 @@ function filterQuoteBundleForRequestedMode(bundle, options) {
9441
9520
  };
9442
9521
  }
9443
9522
  function redactInlineSource(value) {
9444
- return JSON.parse(
9445
- JSON.stringify(value, (_key, current) => {
9446
- if (current && typeof current === "object" && typeof current.path === "string" && typeof current.content === "string") {
9447
- return { ...current, content: "<redacted>" };
9448
- }
9523
+ const redact = (current) => {
9524
+ if (Array.isArray(current)) {
9525
+ return current.map((item) => redact(item));
9526
+ }
9527
+ if (!current || typeof current !== "object") {
9449
9528
  return current;
9450
- })
9451
- );
9529
+ }
9530
+ const object = current;
9531
+ const next = {};
9532
+ for (const [key, child] of Object.entries(object)) {
9533
+ next[key] = key === "content" && typeof child === "string" ? "<redacted>" : redact(child);
9534
+ }
9535
+ return next;
9536
+ };
9537
+ return redact(value);
9538
+ }
9539
+ function normalizePositiveUsd(value, label) {
9540
+ const amount = Number(value);
9541
+ if (!Number.isFinite(amount) || amount <= 0) {
9542
+ throw new Error(`${label} must be a positive USD amount.`);
9543
+ }
9544
+ return amount;
9452
9545
  }
9453
9546
  async function watchRun(runId, json = false, timeoutMs, pollMs) {
9454
9547
  const handle = await createClient(loadConfig()).runs.watch(runId);
@@ -9687,12 +9780,13 @@ program2.command("datasets:upload").argument("<file>").option("--project <projec
9687
9780
  printJson(dataset);
9688
9781
  });
9689
9782
  program2.command("runs:create").option("--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) => {
9783
+ const config = loadConfig();
9784
+ const runInput = buildRunInput(options, config);
9690
9785
  if (!options.yes) {
9691
9786
  throw new Error("Refusing to launch without explicit cost acceptance. Run `trainfabric runs:quote --summary ...` first, then rerun `runs:create` with --yes.");
9692
9787
  }
9693
- const config = loadConfig();
9694
9788
  const client = createClient(config);
9695
- const run = await client.runs.create(buildRunInput(options, config));
9789
+ const run = await client.runs.create(runInput);
9696
9790
  printJson(run.snapshot);
9697
9791
  });
9698
9792
  program2.command("runtime:detect").option("--project <projectId>").option("--repo <path>").option("--git <url>").option("--branch <branch>").description("Detect a supported runtime from a local repo snapshot or git metadata").action(async (options) => {
@@ -9726,7 +9820,7 @@ program2.command("runs:quote").option("--project <projectId>").requiredOption("-
9726
9820
  if (options.summary) {
9727
9821
  printQuoteSummary(bundle);
9728
9822
  } else {
9729
- printJson(bundle);
9823
+ printJson(redactInlineSource(bundle));
9730
9824
  }
9731
9825
  });
9732
9826
  program2.command("runs:list").option("--project <projectId>").description("List runs").action(async (options) => {
@@ -9794,7 +9888,7 @@ program2.command("models:list").description("List exported models").action(async
9794
9888
  printJson(await createClient(loadConfig()).models.list());
9795
9889
  });
9796
9890
  program2.command("models:export").argument("<modelId>").description("Fetch the export package path for a model").action(async (modelId) => {
9797
- printJson(await createClient(loadConfig()).models.export(String(modelId)));
9891
+ printJson(await createClient(loadConfig()).models.export(normalizeId(modelId, "model", "Model ID")));
9798
9892
  });
9799
9893
  program2.command("deployments:list").description("List deployments").action(async () => {
9800
9894
  printJson(await createClient(loadConfig()).deployments.list());
@@ -9807,7 +9901,12 @@ program2.command("deployments:create").requiredOption("--model <modelId>").optio
9807
9901
  await client.projects.get(projectId);
9808
9902
  }
9809
9903
  const modelId = normalizeId(options.model, "model", "Model ID");
9810
- printJson(await client.deployments.create({ modelId }));
9904
+ try {
9905
+ printJson(await client.deployments.create({ modelId }));
9906
+ } catch (error) {
9907
+ printJson({ error: error instanceof Error ? error.message : String(error) });
9908
+ process.exitCode = 1;
9909
+ }
9811
9910
  });
9812
9911
  program2.command("deployments:get").argument("<deploymentId>").description("Fetch a deployment").action(async (deploymentId) => {
9813
9912
  printJson(await createClient(loadConfig()).deployments.get(normalizeId(deploymentId, "deployment", "Deployment ID")));
@@ -9849,9 +9948,9 @@ program2.command("treasury:funding").description("List supplier funding summarie
9849
9948
  program2.command("treasury:funding-preview").requiredOption("--estimated-supplier-cost <usd>").option("--supplier <supplierId>").option("--pool <poolId>").description("Preview treasury funding readiness for a supplier-backed launch").action(async (options) => {
9850
9949
  printJson(
9851
9950
  await createClient(loadConfig()).treasury.previewFunding({
9852
- estimatedSupplierCostUsd: Number(options.estimatedSupplierCost),
9853
- poolId: options.pool,
9854
- supplier: options.supplier
9951
+ estimatedSupplierCostUsd: normalizePositiveUsd(options.estimatedSupplierCost, "Estimated supplier cost"),
9952
+ poolId: normalizeOptionalId(options.pool, "pool", "Pool ID"),
9953
+ supplier: normalizeOptionalId(options.supplier, "supplier", "Supplier ID")
9855
9954
  })
9856
9955
  );
9857
9956
  });
@@ -9870,8 +9969,9 @@ program2.command("api-keys:list").description("List API keys").action(async () =
9870
9969
  printJson(await createClient(loadConfig()).apiKeys.list());
9871
9970
  });
9872
9971
  program2.command("api-keys:revoke").argument("<apiKeyId>").description("Revoke an API key").action(async (apiKeyId) => {
9873
- await createClient(loadConfig()).apiKeys.revoke(String(apiKeyId));
9874
- console.log(`Revoked API key ${String(apiKeyId)}.`);
9972
+ const normalizedApiKeyId = normalizeId(apiKeyId, "apiKey", "API key ID");
9973
+ await createClient(loadConfig()).apiKeys.revoke(normalizedApiKeyId);
9974
+ console.log(`Revoked API key ${normalizedApiKeyId}.`);
9875
9975
  });
9876
9976
  program2.command("service-accounts:create").requiredOption("--name <name>").option("--scopes <scopes>", "comma-separated scopes", "org:read,project:read,project:write,dataset:read,dataset:write,run:read,run:write").description("Create a service account").action(async (options) => {
9877
9977
  printJson(
@@ -9885,8 +9985,9 @@ program2.command("service-accounts:list").description("List service accounts").a
9885
9985
  printJson(await createClient(loadConfig()).serviceAccounts.list());
9886
9986
  });
9887
9987
  program2.command("service-accounts:revoke").argument("<serviceAccountId>").description("Revoke a service account").action(async (serviceAccountId) => {
9888
- await createClient(loadConfig()).serviceAccounts.revoke(String(serviceAccountId));
9889
- console.log(`Revoked service account ${String(serviceAccountId)}.`);
9988
+ const normalizedServiceAccountId = normalizeId(serviceAccountId, "serviceAccount", "Service account ID");
9989
+ await createClient(loadConfig()).serviceAccounts.revoke(normalizedServiceAccountId);
9990
+ console.log(`Revoked service account ${normalizedServiceAccountId}.`);
9890
9991
  });
9891
9992
  void program2.parseAsync(process.argv).catch((error) => {
9892
9993
  console.error(error instanceof Error ? error.message : String(error));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trainfabric",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
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",