trainfabric 0.1.24 → 0.1.26

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 +201 -53
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -7613,6 +7613,7 @@ var runCreateSchema = external_exports.object({
7613
7613
  repoUrl: external_exports.string().min(1).optional(),
7614
7614
  branch: external_exports.string().min(1).optional(),
7615
7615
  commitSha: external_exports.string().min(1).optional(),
7616
+ entrypoint: external_exports.string().min(1).optional(),
7616
7617
  rootDirName: external_exports.string().min(1).optional(),
7617
7618
  files: external_exports.array(external_exports.object({
7618
7619
  path: external_exports.string().min(1),
@@ -7638,6 +7639,7 @@ var runtimeDetectSchema = external_exports.object({
7638
7639
  repoUrl: external_exports.string().min(1).optional(),
7639
7640
  branch: external_exports.string().min(1).optional(),
7640
7641
  commitSha: external_exports.string().min(1).optional(),
7642
+ entrypoint: external_exports.string().min(1).optional(),
7641
7643
  rootDirName: external_exports.string().min(1).optional(),
7642
7644
  files: external_exports.array(external_exports.object({
7643
7645
  path: external_exports.string().min(1),
@@ -7661,6 +7663,7 @@ var runQuoteSchema = external_exports.object({
7661
7663
  repoUrl: external_exports.string().min(1).optional(),
7662
7664
  branch: external_exports.string().min(1).optional(),
7663
7665
  commitSha: external_exports.string().min(1).optional(),
7666
+ entrypoint: external_exports.string().min(1).optional(),
7664
7667
  rootDirName: external_exports.string().min(1).optional(),
7665
7668
  files: external_exports.array(external_exports.object({
7666
7669
  path: external_exports.string().min(1),
@@ -8817,10 +8820,18 @@ var import_node_fs = __toESM(require("node:fs"), 1);
8817
8820
  var import_node_os = __toESM(require("node:os"), 1);
8818
8821
  var import_node_path2 = __toESM(require("node:path"), 1);
8819
8822
  var idPatterns = {
8823
+ apiKey: /^key_[A-Za-z0-9_-]+$/,
8824
+ dataset: /^ds_[A-Za-z0-9_-]+$/,
8825
+ deployment: /^dep_[A-Za-z0-9_-]+$/,
8826
+ model: /^mdl_[A-Za-z0-9_-]+$/,
8820
8827
  org: /^org_[A-Za-z0-9_-]+$/,
8828
+ pool: /^pool_[A-Za-z0-9_-]+$/,
8821
8829
  project: /^proj_[A-Za-z0-9_-]+$/,
8822
- 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_-]*$/
8823
8833
  };
8834
+ var allowedBillingUnits = /* @__PURE__ */ new Set(["normalized_tflop_seconds", "reserved_gpu_seconds", "usd"]);
8824
8835
  function collectRuntimeFiles(repoPath) {
8825
8836
  const manifestFiles = /* @__PURE__ */ new Set([
8826
8837
  "training.yaml",
@@ -8868,12 +8879,56 @@ function collectRuntimeFiles(repoPath) {
8868
8879
  walk(repoPath);
8869
8880
  return found.sort((left, right) => left.path.localeCompare(right.path));
8870
8881
  }
8882
+ function inferEntrypoint(files) {
8883
+ const trainingYaml = files.find((file) => file.path === "training.yaml" || file.path === "train.runtime.yaml");
8884
+ if (trainingYaml) {
8885
+ const match = trainingYaml.content.match(/entrypoint\s*[:=]\s*["']?([A-Za-z0-9_./-]+)["']?/i);
8886
+ if (match?.[1]) {
8887
+ const entry = match[1];
8888
+ const exact = files.find((file) => file.path === entry);
8889
+ if (exact) {
8890
+ return exact.path;
8891
+ }
8892
+ const nested = files.find((file) => file.path.endsWith(`/${entry}`));
8893
+ if (nested) {
8894
+ return nested.path;
8895
+ }
8896
+ return entry;
8897
+ }
8898
+ }
8899
+ return files.find((file) => /(^|\/)train\.(py|sh)$/i.test(file.path))?.path;
8900
+ }
8901
+ function validateGitUrl(value) {
8902
+ const url = new URL(value);
8903
+ if (url.protocol !== "https:" && url.protocol !== "http:" && url.protocol !== "ssh:" && url.protocol !== "git:") {
8904
+ throw new Error(`Invalid git URL "${value}". Use an absolute repository URL.`);
8905
+ }
8906
+ return value;
8907
+ }
8908
+ function validateRepoPath(repoPath) {
8909
+ const absolute = import_node_path2.default.resolve(repoPath);
8910
+ const parsed = import_node_path2.default.parse(absolute);
8911
+ if (absolute === parsed.root || absolute === import_node_os.default.homedir()) {
8912
+ throw new Error(`Refusing to package unsafe repository path: ${repoPath}`);
8913
+ }
8914
+ const stat = import_node_fs.default.statSync(absolute);
8915
+ if (!stat.isDirectory()) {
8916
+ throw new Error(`Repository path must be a directory: ${repoPath}`);
8917
+ }
8918
+ return absolute;
8919
+ }
8871
8920
  function buildSourceOptions(options) {
8921
+ if (options.repo && options.git) {
8922
+ throw new Error("Use either --repo or --git, not both.");
8923
+ }
8872
8924
  if (options.repo) {
8925
+ const repoPath = validateRepoPath(options.repo);
8926
+ const files = collectRuntimeFiles(repoPath);
8873
8927
  return {
8874
8928
  source: {
8875
8929
  kind: "inline",
8876
- files: collectRuntimeFiles(import_node_path2.default.resolve(options.repo))
8930
+ entrypoint: inferEntrypoint(files),
8931
+ files
8877
8932
  }
8878
8933
  };
8879
8934
  }
@@ -8881,7 +8936,7 @@ function buildSourceOptions(options) {
8881
8936
  return {
8882
8937
  source: {
8883
8938
  kind: "git",
8884
- repoUrl: options.git,
8939
+ repoUrl: validateGitUrl(options.git),
8885
8940
  branch: options.branch
8886
8941
  }
8887
8942
  };
@@ -8910,12 +8965,15 @@ function decrypt(payload) {
8910
8965
  }
8911
8966
  function parseApiKeyScopes(input) {
8912
8967
  const allowedScopes = new Set(apiKeyScopes);
8913
- 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
+ }
8914
8972
  const invalidScope = scopes.find((scope) => !allowedScopes.has(scope));
8915
8973
  if (invalidScope) {
8916
8974
  throw new Error(`Invalid API key scope: ${invalidScope}`);
8917
8975
  }
8918
- return scopes;
8976
+ return [...new Set(scopes)];
8919
8977
  }
8920
8978
  function normalizeHumanName(value, label) {
8921
8979
  let name = String(value ?? "").trim();
@@ -8940,6 +8998,16 @@ function normalizeOptionalId(value, kind, label) {
8940
8998
  }
8941
8999
  return normalizeId(value, kind, label);
8942
9000
  }
9001
+ function normalizeOptionalBillingUnit(value) {
9002
+ if (value === void 0) {
9003
+ return void 0;
9004
+ }
9005
+ const unit = String(value).trim();
9006
+ if (!allowedBillingUnits.has(unit)) {
9007
+ throw new Error("Billing unit is invalid. Use normalized_tflop_seconds, reserved_gpu_seconds, or usd.");
9008
+ }
9009
+ return unit;
9010
+ }
8943
9011
 
8944
9012
  // src/run_input.ts
8945
9013
  var BASE_MODEL_ALIASES = {
@@ -8953,36 +9021,75 @@ var BASE_MODEL_ALIASES = {
8953
9021
  "qwen2.5-7b": "qwen-2.5-7b",
8954
9022
  qwen_2_5_7b: "qwen-2.5-7b"
8955
9023
  };
9024
+ var ACCELERATOR_CLASSES = /* @__PURE__ */ new Set(["a10g", "l4", "a40", "l40s", "a100", "h100", "h200", "b200"]);
9025
+ var PRECISION_MODES = /* @__PURE__ */ new Set(["fp16_bf16", "fp8", "fp32"]);
9026
+ var INTERCONNECT_TYPES = /* @__PURE__ */ new Set(["pcie", "nvlink"]);
9027
+ function normalizePositiveInteger(value, label, fallback) {
9028
+ const parsed = Number(value ?? String(fallback));
9029
+ if (!Number.isInteger(parsed) || parsed <= 0) {
9030
+ throw new Error(`${label} must be a positive integer.`);
9031
+ }
9032
+ return parsed;
9033
+ }
9034
+ function normalizeOptionalPositiveNumber(value, label) {
9035
+ if (value === void 0) {
9036
+ return void 0;
9037
+ }
9038
+ const parsed = Number(value);
9039
+ if (!Number.isFinite(parsed) || parsed <= 0) {
9040
+ throw new Error(`${label} must be a positive number.`);
9041
+ }
9042
+ return parsed;
9043
+ }
9044
+ function normalizeOptionalChoice(value, allowed, label, examples) {
9045
+ if (value === void 0) {
9046
+ return void 0;
9047
+ }
9048
+ const normalized = String(value).trim().toLowerCase();
9049
+ if (!allowed.has(normalized)) {
9050
+ throw new Error(`${label} is invalid. Use ${examples}.`);
9051
+ }
9052
+ return normalized;
9053
+ }
8956
9054
  function normalizeBaseModel(model) {
8957
9055
  return BASE_MODEL_ALIASES[model] ?? model;
8958
9056
  }
8959
9057
  function buildComputeSpec(options) {
8960
- const gpuCount = Number(options.gpus ?? "1");
8961
- const nodeCount = Number(options.nodes ?? "1");
9058
+ const gpuCount = normalizePositiveInteger(options.gpus, "GPU count", 1);
9059
+ const nodeCount = normalizePositiveInteger(options.nodes, "Node count", 1);
8962
9060
  const compute = {
8963
9061
  gpuCount,
8964
9062
  nodeCount,
8965
9063
  distributionStrategy: gpuCount > 1 || nodeCount > 1 ? "ddp" : "single_gpu",
8966
9064
  target: "local"
8967
9065
  };
8968
- if (options.accelerator) {
8969
- compute.acceleratorClass = options.accelerator;
9066
+ const accelerator = normalizeOptionalChoice(
9067
+ options.accelerator,
9068
+ ACCELERATOR_CLASSES,
9069
+ "Accelerator",
9070
+ "a10g, l4, a40, l40s, a100, h100, h200, or b200"
9071
+ );
9072
+ const minMemory = normalizeOptionalPositiveNumber(options.minMemory, "Minimum GPU memory");
9073
+ const precision = normalizeOptionalChoice(options.precision, PRECISION_MODES, "Precision", "fp16_bf16, fp8, or fp32");
9074
+ const interconnect = normalizeOptionalChoice(options.interconnect, INTERCONNECT_TYPES, "Interconnect", "pcie or nvlink");
9075
+ if (accelerator) {
9076
+ compute.acceleratorClass = accelerator;
8970
9077
  }
8971
- if (options.minMemory) {
8972
- compute.minGpuMemoryGb = Number(options.minMemory);
9078
+ if (minMemory !== void 0) {
9079
+ compute.minGpuMemoryGb = minMemory;
8973
9080
  }
8974
- if (options.precision) {
8975
- compute.precision = options.precision;
9081
+ if (precision) {
9082
+ compute.precision = precision;
8976
9083
  }
8977
- if (options.interconnect) {
8978
- compute.interconnect = options.interconnect;
9084
+ if (interconnect) {
9085
+ compute.interconnect = interconnect;
8979
9086
  }
8980
9087
  return compute;
8981
9088
  }
8982
9089
 
8983
9090
  // src/index.ts
8984
9091
  var DEFAULT_TRAINFABRIC_API_URL2 = "https://api.trainfabric.com";
8985
- var CLI_VERSION = "0.1.24";
9092
+ var CLI_VERSION = "0.1.26";
8986
9093
  var CONFIG_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".trainfabric");
8987
9094
  var CONFIG_PATH = import_node_path3.default.join(CONFIG_DIR, "config.json");
8988
9095
  var FALLBACK_SECRET_PATH = import_node_path3.default.join(CONFIG_DIR, "session.enc");
@@ -9379,6 +9486,30 @@ function filterQuoteBundleForRequestedMode(bundle, options) {
9379
9486
  quotes: bundle.quotes.filter((item) => item.mode === options.mode || item.quote.mode === options.mode)
9380
9487
  };
9381
9488
  }
9489
+ function redactInlineSource(value) {
9490
+ const redact = (current) => {
9491
+ if (Array.isArray(current)) {
9492
+ return current.map((item) => redact(item));
9493
+ }
9494
+ if (!current || typeof current !== "object") {
9495
+ return current;
9496
+ }
9497
+ const object = current;
9498
+ const next = {};
9499
+ for (const [key, child] of Object.entries(object)) {
9500
+ next[key] = key === "content" && typeof child === "string" ? "<redacted>" : redact(child);
9501
+ }
9502
+ return next;
9503
+ };
9504
+ return redact(value);
9505
+ }
9506
+ function normalizePositiveUsd(value, label) {
9507
+ const amount = Number(value);
9508
+ if (!Number.isFinite(amount) || amount <= 0) {
9509
+ throw new Error(`${label} must be a positive USD amount.`);
9510
+ }
9511
+ return amount;
9512
+ }
9382
9513
  async function watchRun(runId, json = false, timeoutMs, pollMs) {
9383
9514
  const handle = await createClient(loadConfig()).runs.watch(runId);
9384
9515
  if (json) {
@@ -9595,11 +9726,12 @@ program2.command("datasets:list").option("--project <projectId>").description("L
9595
9726
  printJson(await createClient(loadConfig()).datasets.list(normalizeOptionalId(options.project, "project", "Project ID")));
9596
9727
  });
9597
9728
  program2.command("datasets:get").argument("<datasetId>").description("Fetch dataset detail").action(async (datasetId) => {
9598
- printJson(await createClient(loadConfig()).datasets.get(String(datasetId)));
9729
+ printJson(await createClient(loadConfig()).datasets.get(normalizeId(datasetId, "dataset", "Dataset ID")));
9599
9730
  });
9600
9731
  program2.command("datasets:delete").argument("<datasetId>").description("Delete a dataset").action(async (datasetId) => {
9601
- await createClient(loadConfig()).datasets.delete(String(datasetId));
9602
- printJson({ deleted: true, id: String(datasetId) });
9732
+ const normalizedDatasetId = normalizeId(datasetId, "dataset", "Dataset ID");
9733
+ await createClient(loadConfig()).datasets.delete(normalizedDatasetId);
9734
+ printJson({ deleted: true, id: normalizedDatasetId });
9603
9735
  });
9604
9736
  program2.command("datasets:upload").argument("<file>").option("--project <projectId>").option("--name <name>").description("Upload a dataset through upload sessions").action(async (file, options) => {
9605
9737
  assertReadableFile(file, "Dataset path");
@@ -9615,12 +9747,13 @@ program2.command("datasets:upload").argument("<file>").option("--project <projec
9615
9747
  printJson(dataset);
9616
9748
  });
9617
9749
  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) => {
9750
+ const config = loadConfig();
9751
+ const runInput = buildRunInput(options, config);
9618
9752
  if (!options.yes) {
9619
9753
  throw new Error("Refusing to launch without explicit cost acceptance. Run `trainfabric runs:quote --summary ...` first, then rerun `runs:create` with --yes.");
9620
9754
  }
9621
- const config = loadConfig();
9622
9755
  const client = createClient(config);
9623
- const run = await client.runs.create(buildRunInput(options, config));
9756
+ const run = await client.runs.create(runInput);
9624
9757
  printJson(run.snapshot);
9625
9758
  });
9626
9759
  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) => {
@@ -9629,12 +9762,11 @@ program2.command("runtime:detect").option("--project <projectId>").option("--rep
9629
9762
  throw new Error("Provide --repo or --git.");
9630
9763
  }
9631
9764
  const config = loadConfig();
9632
- printJson(
9633
- await createClient(config).runtime.detect({
9634
- projectId: requireProjectId(options, config),
9635
- source: sourceOptions.source
9636
- })
9637
- );
9765
+ const detection = await createClient(config).runtime.detect({
9766
+ projectId: requireProjectId(options, config),
9767
+ source: sourceOptions.source
9768
+ });
9769
+ printJson(redactInlineSource(detection));
9638
9770
  });
9639
9771
  program2.command("runtime:build").option("--project <projectId>").requiredOption("--detection <detectionId>").description("Build or reuse a cached runtime image from a runtime detection").action(async (options) => {
9640
9772
  const config = loadConfig();
@@ -9655,31 +9787,32 @@ program2.command("runs:quote").option("--project <projectId>").requiredOption("-
9655
9787
  if (options.summary) {
9656
9788
  printQuoteSummary(bundle);
9657
9789
  } else {
9658
- printJson(bundle);
9790
+ printJson(redactInlineSource(bundle));
9659
9791
  }
9660
9792
  });
9661
9793
  program2.command("runs:list").option("--project <projectId>").description("List runs").action(async (options) => {
9662
9794
  printJson(await createClient(loadConfig()).runs.list(normalizeOptionalId(options.project, "project", "Project ID")));
9663
9795
  });
9664
9796
  program2.command("runs:status").argument("<runId>").description("Fetch full run detail").action(async (runId) => {
9665
- printJson(await createClient(loadConfig()).runs.get(String(runId)));
9797
+ printJson(await createClient(loadConfig()).runs.get(normalizeId(runId, "run", "Run ID")));
9666
9798
  });
9667
9799
  program2.command("runs:logs").argument("<runId>").description("Fetch run logs").action(async (runId) => {
9668
- printJson(await createClient(loadConfig()).runs.logs(String(runId)));
9800
+ printJson(await createClient(loadConfig()).runs.logs(normalizeId(runId, "run", "Run ID")));
9669
9801
  });
9670
9802
  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) => {
9803
+ const normalizedRunId = normalizeId(runId, "run", "Run ID");
9671
9804
  printJson(
9672
- await createClient(loadConfig()).runs.wait(String(runId), {
9805
+ await createClient(loadConfig()).runs.wait(normalizedRunId, {
9673
9806
  timeoutMs: parseDurationMs(options.timeout),
9674
9807
  pollMs: parsePositiveMs(options.poll, 1e3)
9675
9808
  })
9676
9809
  );
9677
9810
  });
9678
9811
  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").option("--poll <duration>", "poll interval, for example 1s or 500ms").description("Stream a run until it completes").action(async (runId, options) => {
9679
- await watchRun(String(runId), Boolean(options.json), parseDurationMs(options.timeout), parsePositiveMs(options.poll, 1e3));
9812
+ await watchRun(normalizeId(runId, "run", "Run ID"), Boolean(options.json), parseDurationMs(options.timeout), parsePositiveMs(options.poll, 1e3));
9680
9813
  });
9681
9814
  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) => {
9682
- const usage = await createClient(loadConfig()).runs.usage(String(runId));
9815
+ const usage = await createClient(loadConfig()).runs.usage(normalizeId(runId, "run", "Run ID"));
9683
9816
  if (options.summary) {
9684
9817
  printUsageSummary(usage);
9685
9818
  } else {
@@ -9687,7 +9820,7 @@ program2.command("runs:usage").argument("<runId>").option("--summary", "print a
9687
9820
  }
9688
9821
  });
9689
9822
  program2.command("runs:cost-breakdown").argument("<runId>").option("--summary", "print a human-readable cost summary instead of raw JSON").description("Fetch realized run cost and profitability").action(async (runId, options) => {
9690
- const breakdown = await createClient(loadConfig()).runs.costBreakdown(String(runId));
9823
+ const breakdown = await createClient(loadConfig()).runs.costBreakdown(normalizeId(runId, "run", "Run ID"));
9691
9824
  if (options.summary) {
9692
9825
  printUsageSummary(breakdown);
9693
9826
  } else {
@@ -9695,22 +9828,22 @@ program2.command("runs:cost-breakdown").argument("<runId>").option("--summary",
9695
9828
  }
9696
9829
  });
9697
9830
  program2.command("runs:allocation").argument("<runId>").description("Fetch run allocation").action(async (runId) => {
9698
- printJson(await createClient(loadConfig()).runs.allocation(String(runId)));
9831
+ printJson(await createClient(loadConfig()).runs.allocation(normalizeId(runId, "run", "Run ID")));
9699
9832
  });
9700
9833
  program2.command("runs:configuration").argument("<runId>").description("Fetch run placement configuration").action(async (runId) => {
9701
- printJson(await createClient(loadConfig()).runs.configuration(String(runId)));
9834
+ printJson(await createClient(loadConfig()).runs.configuration(normalizeId(runId, "run", "Run ID")));
9702
9835
  });
9703
9836
  program2.command("runs:explanation").argument("<runId>").description("Fetch run planning explanation").action(async (runId) => {
9704
- printJson(await createClient(loadConfig()).runs.explanation(String(runId)));
9837
+ printJson(await createClient(loadConfig()).runs.explanation(normalizeId(runId, "run", "Run ID")));
9705
9838
  });
9706
9839
  program2.command("runs:cancel").argument("<runId>").description("Cancel a run").action(async (runId) => {
9707
- printJson(await createClient(loadConfig()).runs.cancel(String(runId)));
9840
+ printJson(await createClient(loadConfig()).runs.cancel(normalizeId(runId, "run", "Run ID")));
9708
9841
  });
9709
9842
  program2.command("runs:resume").argument("<runId>").description("Resume a run from the latest checkpoint").action(async (runId) => {
9710
- printJson(await createClient(loadConfig()).runs.resume(String(runId)));
9843
+ printJson(await createClient(loadConfig()).runs.resume(normalizeId(runId, "run", "Run ID")));
9711
9844
  });
9712
9845
  program2.command("runs:terminate").argument("<runId>").description("Terminate a queued, launchable, or active run").action(async (runId) => {
9713
- printJson(await createClient(loadConfig()).runs.terminate(String(runId)));
9846
+ printJson(await createClient(loadConfig()).runs.terminate(normalizeId(runId, "run", "Run ID")));
9714
9847
  });
9715
9848
  program2.command("cluster:capacity").description("Fetch cluster capacity view").action(async () => {
9716
9849
  printJson(await createClient(loadConfig()).cluster.getCapacity());
@@ -9722,26 +9855,39 @@ program2.command("models:list").description("List exported models").action(async
9722
9855
  printJson(await createClient(loadConfig()).models.list());
9723
9856
  });
9724
9857
  program2.command("models:export").argument("<modelId>").description("Fetch the export package path for a model").action(async (modelId) => {
9725
- printJson(await createClient(loadConfig()).models.export(String(modelId)));
9858
+ printJson(await createClient(loadConfig()).models.export(normalizeId(modelId, "model", "Model ID")));
9726
9859
  });
9727
9860
  program2.command("deployments:list").description("List deployments").action(async () => {
9728
9861
  printJson(await createClient(loadConfig()).deployments.list());
9729
9862
  });
9730
9863
  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) => {
9731
- printJson(await createClient(loadConfig()).deployments.create({ modelId: options.model }));
9864
+ const config = loadConfig();
9865
+ const client = createClient(config);
9866
+ const projectId = normalizeOptionalId(options.project, "project", "Project ID");
9867
+ if (projectId) {
9868
+ await client.projects.get(projectId);
9869
+ }
9870
+ const modelId = normalizeId(options.model, "model", "Model ID");
9871
+ printJson(await client.deployments.create({ modelId }));
9732
9872
  });
9733
9873
  program2.command("deployments:get").argument("<deploymentId>").description("Fetch a deployment").action(async (deploymentId) => {
9734
- printJson(await createClient(loadConfig()).deployments.get(String(deploymentId)));
9874
+ printJson(await createClient(loadConfig()).deployments.get(normalizeId(deploymentId, "deployment", "Deployment ID")));
9735
9875
  });
9736
9876
  program2.command("deployments:delete").argument("<deploymentId>").description("Delete a pending or cataloged deployment").action(async (deploymentId) => {
9737
- await createClient(loadConfig()).deployments.delete(String(deploymentId));
9738
- printJson({ deleted: true, id: String(deploymentId) });
9877
+ const normalizedDeploymentId = normalizeId(deploymentId, "deployment", "Deployment ID");
9878
+ await createClient(loadConfig()).deployments.delete(normalizedDeploymentId);
9879
+ printJson({ deleted: true, id: normalizedDeploymentId });
9739
9880
  });
9740
9881
  program2.command("billing:summary").description("Fetch billing summary").action(async () => {
9741
9882
  printJson(await createClient(loadConfig()).billing.getSummary());
9742
9883
  });
9743
9884
  program2.command("billing:usage").option("--unit <unit>", "normalized_tflop_seconds, reserved_gpu_seconds, or usd").option("--run <runId>", "filter usage records to one run").description("List billing usage records").action(async (options) => {
9744
- printJson(await createClient(loadConfig()).billing.getUsage(options.unit, normalizeOptionalId(options.run, "run", "Run ID")));
9885
+ printJson(
9886
+ await createClient(loadConfig()).billing.getUsage(
9887
+ normalizeOptionalBillingUnit(options.unit),
9888
+ normalizeOptionalId(options.run, "run", "Run ID")
9889
+ )
9890
+ );
9745
9891
  });
9746
9892
  program2.command("billing:invoices").description("List invoices").action(async () => {
9747
9893
  printJson(await createClient(loadConfig()).billing.listInvoices());
@@ -9764,9 +9910,9 @@ program2.command("treasury:funding").description("List supplier funding summarie
9764
9910
  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) => {
9765
9911
  printJson(
9766
9912
  await createClient(loadConfig()).treasury.previewFunding({
9767
- estimatedSupplierCostUsd: Number(options.estimatedSupplierCost),
9768
- poolId: options.pool,
9769
- supplier: options.supplier
9913
+ estimatedSupplierCostUsd: normalizePositiveUsd(options.estimatedSupplierCost, "Estimated supplier cost"),
9914
+ poolId: normalizeOptionalId(options.pool, "pool", "Pool ID"),
9915
+ supplier: normalizeOptionalId(options.supplier, "supplier", "Supplier ID")
9770
9916
  })
9771
9917
  );
9772
9918
  });
@@ -9785,8 +9931,9 @@ program2.command("api-keys:list").description("List API keys").action(async () =
9785
9931
  printJson(await createClient(loadConfig()).apiKeys.list());
9786
9932
  });
9787
9933
  program2.command("api-keys:revoke").argument("<apiKeyId>").description("Revoke an API key").action(async (apiKeyId) => {
9788
- await createClient(loadConfig()).apiKeys.revoke(String(apiKeyId));
9789
- console.log(`Revoked API key ${String(apiKeyId)}.`);
9934
+ const normalizedApiKeyId = normalizeId(apiKeyId, "apiKey", "API key ID");
9935
+ await createClient(loadConfig()).apiKeys.revoke(normalizedApiKeyId);
9936
+ console.log(`Revoked API key ${normalizedApiKeyId}.`);
9790
9937
  });
9791
9938
  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) => {
9792
9939
  printJson(
@@ -9800,8 +9947,9 @@ program2.command("service-accounts:list").description("List service accounts").a
9800
9947
  printJson(await createClient(loadConfig()).serviceAccounts.list());
9801
9948
  });
9802
9949
  program2.command("service-accounts:revoke").argument("<serviceAccountId>").description("Revoke a service account").action(async (serviceAccountId) => {
9803
- await createClient(loadConfig()).serviceAccounts.revoke(String(serviceAccountId));
9804
- console.log(`Revoked service account ${String(serviceAccountId)}.`);
9950
+ const normalizedServiceAccountId = normalizeId(serviceAccountId, "serviceAccount", "Service account ID");
9951
+ await createClient(loadConfig()).serviceAccounts.revoke(normalizedServiceAccountId);
9952
+ console.log(`Revoked service account ${normalizedServiceAccountId}.`);
9805
9953
  });
9806
9954
  void program2.parseAsync(process.argv).catch((error) => {
9807
9955
  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.24",
3
+ "version": "0.1.26",
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",