trainfabric 0.1.24 → 0.1.25

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 +115 -30
  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,14 @@ 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
+ dataset: /^ds_[A-Za-z0-9_-]+$/,
8824
+ deployment: /^dep_[A-Za-z0-9_-]+$/,
8825
+ model: /^mdl_[A-Za-z0-9_-]+$/,
8820
8826
  org: /^org_[A-Za-z0-9_-]+$/,
8821
8827
  project: /^proj_[A-Za-z0-9_-]+$/,
8822
8828
  run: /^run_[A-Za-z0-9_-]+$/
8823
8829
  };
8830
+ var allowedBillingUnits = /* @__PURE__ */ new Set(["normalized_tflop_seconds", "reserved_gpu_seconds", "usd"]);
8824
8831
  function collectRuntimeFiles(repoPath) {
8825
8832
  const manifestFiles = /* @__PURE__ */ new Set([
8826
8833
  "training.yaml",
@@ -8868,12 +8875,56 @@ function collectRuntimeFiles(repoPath) {
8868
8875
  walk(repoPath);
8869
8876
  return found.sort((left, right) => left.path.localeCompare(right.path));
8870
8877
  }
8878
+ function inferEntrypoint(files) {
8879
+ const trainingYaml = files.find((file) => file.path === "training.yaml" || file.path === "train.runtime.yaml");
8880
+ if (trainingYaml) {
8881
+ const match = trainingYaml.content.match(/entrypoint\s*[:=]\s*["']?([A-Za-z0-9_./-]+)["']?/i);
8882
+ if (match?.[1]) {
8883
+ const entry = match[1];
8884
+ const exact = files.find((file) => file.path === entry);
8885
+ if (exact) {
8886
+ return exact.path;
8887
+ }
8888
+ const nested = files.find((file) => file.path.endsWith(`/${entry}`));
8889
+ if (nested) {
8890
+ return nested.path;
8891
+ }
8892
+ return entry;
8893
+ }
8894
+ }
8895
+ return files.find((file) => /(^|\/)train\.(py|sh)$/i.test(file.path))?.path;
8896
+ }
8897
+ function validateGitUrl(value) {
8898
+ const url = new URL(value);
8899
+ if (url.protocol !== "https:" && url.protocol !== "http:" && url.protocol !== "ssh:" && url.protocol !== "git:") {
8900
+ throw new Error(`Invalid git URL "${value}". Use an absolute repository URL.`);
8901
+ }
8902
+ return value;
8903
+ }
8904
+ function validateRepoPath(repoPath) {
8905
+ const absolute = import_node_path2.default.resolve(repoPath);
8906
+ const parsed = import_node_path2.default.parse(absolute);
8907
+ if (absolute === parsed.root || absolute === import_node_os.default.homedir()) {
8908
+ throw new Error(`Refusing to package unsafe repository path: ${repoPath}`);
8909
+ }
8910
+ const stat = import_node_fs.default.statSync(absolute);
8911
+ if (!stat.isDirectory()) {
8912
+ throw new Error(`Repository path must be a directory: ${repoPath}`);
8913
+ }
8914
+ return absolute;
8915
+ }
8871
8916
  function buildSourceOptions(options) {
8917
+ if (options.repo && options.git) {
8918
+ throw new Error("Use either --repo or --git, not both.");
8919
+ }
8872
8920
  if (options.repo) {
8921
+ const repoPath = validateRepoPath(options.repo);
8922
+ const files = collectRuntimeFiles(repoPath);
8873
8923
  return {
8874
8924
  source: {
8875
8925
  kind: "inline",
8876
- files: collectRuntimeFiles(import_node_path2.default.resolve(options.repo))
8926
+ entrypoint: inferEntrypoint(files),
8927
+ files
8877
8928
  }
8878
8929
  };
8879
8930
  }
@@ -8881,7 +8932,7 @@ function buildSourceOptions(options) {
8881
8932
  return {
8882
8933
  source: {
8883
8934
  kind: "git",
8884
- repoUrl: options.git,
8935
+ repoUrl: validateGitUrl(options.git),
8885
8936
  branch: options.branch
8886
8937
  }
8887
8938
  };
@@ -8915,7 +8966,7 @@ function parseApiKeyScopes(input) {
8915
8966
  if (invalidScope) {
8916
8967
  throw new Error(`Invalid API key scope: ${invalidScope}`);
8917
8968
  }
8918
- return scopes;
8969
+ return [...new Set(scopes)];
8919
8970
  }
8920
8971
  function normalizeHumanName(value, label) {
8921
8972
  let name = String(value ?? "").trim();
@@ -8940,6 +8991,16 @@ function normalizeOptionalId(value, kind, label) {
8940
8991
  }
8941
8992
  return normalizeId(value, kind, label);
8942
8993
  }
8994
+ function normalizeOptionalBillingUnit(value) {
8995
+ if (value === void 0) {
8996
+ return void 0;
8997
+ }
8998
+ const unit = String(value).trim();
8999
+ if (!allowedBillingUnits.has(unit)) {
9000
+ throw new Error("Billing unit is invalid. Use normalized_tflop_seconds, reserved_gpu_seconds, or usd.");
9001
+ }
9002
+ return unit;
9003
+ }
8943
9004
 
8944
9005
  // src/run_input.ts
8945
9006
  var BASE_MODEL_ALIASES = {
@@ -8982,7 +9043,7 @@ function buildComputeSpec(options) {
8982
9043
 
8983
9044
  // src/index.ts
8984
9045
  var DEFAULT_TRAINFABRIC_API_URL2 = "https://api.trainfabric.com";
8985
- var CLI_VERSION = "0.1.24";
9046
+ var CLI_VERSION = "0.1.25";
8986
9047
  var CONFIG_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".trainfabric");
8987
9048
  var CONFIG_PATH = import_node_path3.default.join(CONFIG_DIR, "config.json");
8988
9049
  var FALLBACK_SECRET_PATH = import_node_path3.default.join(CONFIG_DIR, "session.enc");
@@ -9379,6 +9440,16 @@ function filterQuoteBundleForRequestedMode(bundle, options) {
9379
9440
  quotes: bundle.quotes.filter((item) => item.mode === options.mode || item.quote.mode === options.mode)
9380
9441
  };
9381
9442
  }
9443
+ 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
+ }
9449
+ return current;
9450
+ })
9451
+ );
9452
+ }
9382
9453
  async function watchRun(runId, json = false, timeoutMs, pollMs) {
9383
9454
  const handle = await createClient(loadConfig()).runs.watch(runId);
9384
9455
  if (json) {
@@ -9595,11 +9666,12 @@ program2.command("datasets:list").option("--project <projectId>").description("L
9595
9666
  printJson(await createClient(loadConfig()).datasets.list(normalizeOptionalId(options.project, "project", "Project ID")));
9596
9667
  });
9597
9668
  program2.command("datasets:get").argument("<datasetId>").description("Fetch dataset detail").action(async (datasetId) => {
9598
- printJson(await createClient(loadConfig()).datasets.get(String(datasetId)));
9669
+ printJson(await createClient(loadConfig()).datasets.get(normalizeId(datasetId, "dataset", "Dataset ID")));
9599
9670
  });
9600
9671
  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) });
9672
+ const normalizedDatasetId = normalizeId(datasetId, "dataset", "Dataset ID");
9673
+ await createClient(loadConfig()).datasets.delete(normalizedDatasetId);
9674
+ printJson({ deleted: true, id: normalizedDatasetId });
9603
9675
  });
9604
9676
  program2.command("datasets:upload").argument("<file>").option("--project <projectId>").option("--name <name>").description("Upload a dataset through upload sessions").action(async (file, options) => {
9605
9677
  assertReadableFile(file, "Dataset path");
@@ -9629,12 +9701,11 @@ program2.command("runtime:detect").option("--project <projectId>").option("--rep
9629
9701
  throw new Error("Provide --repo or --git.");
9630
9702
  }
9631
9703
  const config = loadConfig();
9632
- printJson(
9633
- await createClient(config).runtime.detect({
9634
- projectId: requireProjectId(options, config),
9635
- source: sourceOptions.source
9636
- })
9637
- );
9704
+ const detection = await createClient(config).runtime.detect({
9705
+ projectId: requireProjectId(options, config),
9706
+ source: sourceOptions.source
9707
+ });
9708
+ printJson(redactInlineSource(detection));
9638
9709
  });
9639
9710
  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
9711
  const config = loadConfig();
@@ -9662,24 +9733,25 @@ program2.command("runs:list").option("--project <projectId>").description("List
9662
9733
  printJson(await createClient(loadConfig()).runs.list(normalizeOptionalId(options.project, "project", "Project ID")));
9663
9734
  });
9664
9735
  program2.command("runs:status").argument("<runId>").description("Fetch full run detail").action(async (runId) => {
9665
- printJson(await createClient(loadConfig()).runs.get(String(runId)));
9736
+ printJson(await createClient(loadConfig()).runs.get(normalizeId(runId, "run", "Run ID")));
9666
9737
  });
9667
9738
  program2.command("runs:logs").argument("<runId>").description("Fetch run logs").action(async (runId) => {
9668
- printJson(await createClient(loadConfig()).runs.logs(String(runId)));
9739
+ printJson(await createClient(loadConfig()).runs.logs(normalizeId(runId, "run", "Run ID")));
9669
9740
  });
9670
9741
  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) => {
9742
+ const normalizedRunId = normalizeId(runId, "run", "Run ID");
9671
9743
  printJson(
9672
- await createClient(loadConfig()).runs.wait(String(runId), {
9744
+ await createClient(loadConfig()).runs.wait(normalizedRunId, {
9673
9745
  timeoutMs: parseDurationMs(options.timeout),
9674
9746
  pollMs: parsePositiveMs(options.poll, 1e3)
9675
9747
  })
9676
9748
  );
9677
9749
  });
9678
9750
  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));
9751
+ await watchRun(normalizeId(runId, "run", "Run ID"), Boolean(options.json), parseDurationMs(options.timeout), parsePositiveMs(options.poll, 1e3));
9680
9752
  });
9681
9753
  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));
9754
+ const usage = await createClient(loadConfig()).runs.usage(normalizeId(runId, "run", "Run ID"));
9683
9755
  if (options.summary) {
9684
9756
  printUsageSummary(usage);
9685
9757
  } else {
@@ -9687,7 +9759,7 @@ program2.command("runs:usage").argument("<runId>").option("--summary", "print a
9687
9759
  }
9688
9760
  });
9689
9761
  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));
9762
+ const breakdown = await createClient(loadConfig()).runs.costBreakdown(normalizeId(runId, "run", "Run ID"));
9691
9763
  if (options.summary) {
9692
9764
  printUsageSummary(breakdown);
9693
9765
  } else {
@@ -9695,22 +9767,22 @@ program2.command("runs:cost-breakdown").argument("<runId>").option("--summary",
9695
9767
  }
9696
9768
  });
9697
9769
  program2.command("runs:allocation").argument("<runId>").description("Fetch run allocation").action(async (runId) => {
9698
- printJson(await createClient(loadConfig()).runs.allocation(String(runId)));
9770
+ printJson(await createClient(loadConfig()).runs.allocation(normalizeId(runId, "run", "Run ID")));
9699
9771
  });
9700
9772
  program2.command("runs:configuration").argument("<runId>").description("Fetch run placement configuration").action(async (runId) => {
9701
- printJson(await createClient(loadConfig()).runs.configuration(String(runId)));
9773
+ printJson(await createClient(loadConfig()).runs.configuration(normalizeId(runId, "run", "Run ID")));
9702
9774
  });
9703
9775
  program2.command("runs:explanation").argument("<runId>").description("Fetch run planning explanation").action(async (runId) => {
9704
- printJson(await createClient(loadConfig()).runs.explanation(String(runId)));
9776
+ printJson(await createClient(loadConfig()).runs.explanation(normalizeId(runId, "run", "Run ID")));
9705
9777
  });
9706
9778
  program2.command("runs:cancel").argument("<runId>").description("Cancel a run").action(async (runId) => {
9707
- printJson(await createClient(loadConfig()).runs.cancel(String(runId)));
9779
+ printJson(await createClient(loadConfig()).runs.cancel(normalizeId(runId, "run", "Run ID")));
9708
9780
  });
9709
9781
  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)));
9782
+ printJson(await createClient(loadConfig()).runs.resume(normalizeId(runId, "run", "Run ID")));
9711
9783
  });
9712
9784
  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)));
9785
+ printJson(await createClient(loadConfig()).runs.terminate(normalizeId(runId, "run", "Run ID")));
9714
9786
  });
9715
9787
  program2.command("cluster:capacity").description("Fetch cluster capacity view").action(async () => {
9716
9788
  printJson(await createClient(loadConfig()).cluster.getCapacity());
@@ -9728,20 +9800,33 @@ program2.command("deployments:list").description("List deployments").action(asyn
9728
9800
  printJson(await createClient(loadConfig()).deployments.list());
9729
9801
  });
9730
9802
  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 }));
9803
+ const config = loadConfig();
9804
+ const client = createClient(config);
9805
+ const projectId = normalizeOptionalId(options.project, "project", "Project ID");
9806
+ if (projectId) {
9807
+ await client.projects.get(projectId);
9808
+ }
9809
+ const modelId = normalizeId(options.model, "model", "Model ID");
9810
+ printJson(await client.deployments.create({ modelId }));
9732
9811
  });
9733
9812
  program2.command("deployments:get").argument("<deploymentId>").description("Fetch a deployment").action(async (deploymentId) => {
9734
- printJson(await createClient(loadConfig()).deployments.get(String(deploymentId)));
9813
+ printJson(await createClient(loadConfig()).deployments.get(normalizeId(deploymentId, "deployment", "Deployment ID")));
9735
9814
  });
9736
9815
  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) });
9816
+ const normalizedDeploymentId = normalizeId(deploymentId, "deployment", "Deployment ID");
9817
+ await createClient(loadConfig()).deployments.delete(normalizedDeploymentId);
9818
+ printJson({ deleted: true, id: normalizedDeploymentId });
9739
9819
  });
9740
9820
  program2.command("billing:summary").description("Fetch billing summary").action(async () => {
9741
9821
  printJson(await createClient(loadConfig()).billing.getSummary());
9742
9822
  });
9743
9823
  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")));
9824
+ printJson(
9825
+ await createClient(loadConfig()).billing.getUsage(
9826
+ normalizeOptionalBillingUnit(options.unit),
9827
+ normalizeOptionalId(options.run, "run", "Run ID")
9828
+ )
9829
+ );
9745
9830
  });
9746
9831
  program2.command("billing:invoices").description("List invoices").action(async () => {
9747
9832
  printJson(await createClient(loadConfig()).billing.listInvoices());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trainfabric",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
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",