trainfabric 0.1.23 → 0.1.24

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 +62 -12
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -8816,6 +8816,11 @@ var import_node_crypto = __toESM(require("node:crypto"), 1);
8816
8816
  var import_node_fs = __toESM(require("node:fs"), 1);
8817
8817
  var import_node_os = __toESM(require("node:os"), 1);
8818
8818
  var import_node_path2 = __toESM(require("node:path"), 1);
8819
+ var idPatterns = {
8820
+ org: /^org_[A-Za-z0-9_-]+$/,
8821
+ project: /^proj_[A-Za-z0-9_-]+$/,
8822
+ run: /^run_[A-Za-z0-9_-]+$/
8823
+ };
8819
8824
  function collectRuntimeFiles(repoPath) {
8820
8825
  const manifestFiles = /* @__PURE__ */ new Set([
8821
8826
  "training.yaml",
@@ -8912,6 +8917,29 @@ function parseApiKeyScopes(input) {
8912
8917
  }
8913
8918
  return scopes;
8914
8919
  }
8920
+ function normalizeHumanName(value, label) {
8921
+ let name = String(value ?? "").trim();
8922
+ while (name.length >= 2 && (name.startsWith("'") && name.endsWith("'") || name.startsWith('"') && name.endsWith('"'))) {
8923
+ name = name.slice(1, -1).trim();
8924
+ }
8925
+ if (!/[A-Za-z0-9]/.test(name)) {
8926
+ throw new Error(`${label} is required.`);
8927
+ }
8928
+ return name;
8929
+ }
8930
+ function normalizeId(value, kind, label) {
8931
+ const id = String(value ?? "").trim();
8932
+ if (!id || !idPatterns[kind].test(id)) {
8933
+ throw new Error(`${label} is invalid.`);
8934
+ }
8935
+ return id;
8936
+ }
8937
+ function normalizeOptionalId(value, kind, label) {
8938
+ if (value === void 0) {
8939
+ return void 0;
8940
+ }
8941
+ return normalizeId(value, kind, label);
8942
+ }
8915
8943
 
8916
8944
  // src/run_input.ts
8917
8945
  var BASE_MODEL_ALIASES = {
@@ -8954,7 +8982,7 @@ function buildComputeSpec(options) {
8954
8982
 
8955
8983
  // src/index.ts
8956
8984
  var DEFAULT_TRAINFABRIC_API_URL2 = "https://api.trainfabric.com";
8957
- var CLI_VERSION = "0.1.23";
8985
+ var CLI_VERSION = "0.1.24";
8958
8986
  var CONFIG_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".trainfabric");
8959
8987
  var CONFIG_PATH = import_node_path3.default.join(CONFIG_DIR, "config.json");
8960
8988
  var FALLBACK_SECRET_PATH = import_node_path3.default.join(CONFIG_DIR, "session.enc");
@@ -9293,7 +9321,7 @@ function visibleConfig(config) {
9293
9321
  };
9294
9322
  }
9295
9323
  function requireProjectId(options, config) {
9296
- const projectId = options.project ?? config.projectId;
9324
+ const projectId = normalizeOptionalId(options.project ?? config.projectId, "project", "Project ID");
9297
9325
  if (!projectId) {
9298
9326
  throw new Error("Project is required. Pass --project <projectId> or run `trainfabric config:set-project <projectId>`.");
9299
9327
  }
@@ -9418,6 +9446,17 @@ function createClient(config) {
9418
9446
  projectId: config.projectId
9419
9447
  });
9420
9448
  }
9449
+ function assertReadableFile(filePath, label) {
9450
+ let stats;
9451
+ try {
9452
+ stats = import_node_fs2.default.statSync(filePath);
9453
+ } catch {
9454
+ throw new Error(`${label} not found: ${filePath}`);
9455
+ }
9456
+ if (!stats.isFile()) {
9457
+ throw new Error(`${label} must be a file: ${filePath}`);
9458
+ }
9459
+ }
9421
9460
  function normalizeHttpBaseUrl(value) {
9422
9461
  let url;
9423
9462
  try {
@@ -9485,8 +9524,9 @@ program2.command("config:set-base-url").argument("<baseUrl>").description("Set t
9485
9524
  printJson(visibleConfig(config));
9486
9525
  });
9487
9526
  program2.command("config:set-org").argument("<orgId>").description("Set the default organization ID").action(async (orgId) => {
9527
+ const normalizedOrgId = normalizeId(orgId, "org", "Organization ID");
9488
9528
  const currentConfig = loadConfig();
9489
- const organization = await createClient(currentConfig).organizations.get(String(orgId));
9529
+ const organization = await createClient(currentConfig).organizations.get(normalizedOrgId);
9490
9530
  const updatedConfig = updateConfig((current) => {
9491
9531
  current.orgId = organization.id;
9492
9532
  if (current.projectId) {
@@ -9496,8 +9536,9 @@ program2.command("config:set-org").argument("<orgId>").description("Set the defa
9496
9536
  printJson(visibleConfig(updatedConfig));
9497
9537
  });
9498
9538
  program2.command("config:set-project").argument("<projectId>").description("Set the default project ID").action(async (projectId) => {
9539
+ const normalizedProjectId = normalizeId(projectId, "project", "Project ID");
9499
9540
  const currentConfig = loadConfig();
9500
- const project = await createClient(currentConfig).projects.get(String(projectId));
9541
+ const project = await createClient(currentConfig).projects.get(normalizedProjectId);
9501
9542
  if (currentConfig.orgId && project.organizationId !== currentConfig.orgId) {
9502
9543
  throw new Error(`Project ${project.id} does not belong to configured organization ${currentConfig.orgId}.`);
9503
9544
  }
@@ -9532,9 +9573,15 @@ program2.command("projects:list").description("List projects in the active organ
9532
9573
  printJson(await createClient(loadConfig()).projects.list());
9533
9574
  });
9534
9575
  program2.command("projects:create").requiredOption("--name <name>").option("--org <organizationId>").description("Create a project").action(async (options) => {
9535
- printJson(await createClient(loadConfig()).projects.create({ name: options.name, organizationId: options.org }));
9576
+ printJson(
9577
+ await createClient(loadConfig()).projects.create({
9578
+ name: normalizeHumanName(options.name, "Project name"),
9579
+ organizationId: normalizeOptionalId(options.org, "org", "Organization ID")
9580
+ })
9581
+ );
9536
9582
  });
9537
9583
  program2.command("datasets:validate").argument("<file>").description("Validate a local dataset file").action(async (file) => {
9584
+ assertReadableFile(file, "Dataset path");
9538
9585
  const validation = await createClient(loadConfig()).datasets.validate({
9539
9586
  path: file,
9540
9587
  format: "chat_jsonl"
@@ -9545,7 +9592,7 @@ program2.command("datasets:validate").argument("<file>").description("Validate a
9545
9592
  }
9546
9593
  });
9547
9594
  program2.command("datasets:list").option("--project <projectId>").description("List datasets").action(async (options) => {
9548
- printJson(await createClient(loadConfig()).datasets.list(options.project));
9595
+ printJson(await createClient(loadConfig()).datasets.list(normalizeOptionalId(options.project, "project", "Project ID")));
9549
9596
  });
9550
9597
  program2.command("datasets:get").argument("<datasetId>").description("Fetch dataset detail").action(async (datasetId) => {
9551
9598
  printJson(await createClient(loadConfig()).datasets.get(String(datasetId)));
@@ -9555,12 +9602,15 @@ program2.command("datasets:delete").argument("<datasetId>").description("Delete
9555
9602
  printJson({ deleted: true, id: String(datasetId) });
9556
9603
  });
9557
9604
  program2.command("datasets:upload").argument("<file>").option("--project <projectId>").option("--name <name>").description("Upload a dataset through upload sessions").action(async (file, options) => {
9605
+ assertReadableFile(file, "Dataset path");
9558
9606
  const config = loadConfig();
9607
+ const projectId = normalizeOptionalId(options.project ?? config.projectId, "project", "Project ID");
9608
+ const name = options.name === void 0 ? void 0 : normalizeHumanName(options.name, "Dataset name");
9559
9609
  const dataset = await createClient(config).datasets.create({
9560
9610
  path: file,
9561
9611
  format: "chat_jsonl",
9562
- name: options.name,
9563
- projectId: options.project ?? config.projectId
9612
+ name,
9613
+ projectId
9564
9614
  });
9565
9615
  printJson(dataset);
9566
9616
  });
@@ -9609,7 +9659,7 @@ program2.command("runs:quote").option("--project <projectId>").requiredOption("-
9609
9659
  }
9610
9660
  });
9611
9661
  program2.command("runs:list").option("--project <projectId>").description("List runs").action(async (options) => {
9612
- printJson(await createClient(loadConfig()).runs.list(options.project));
9662
+ printJson(await createClient(loadConfig()).runs.list(normalizeOptionalId(options.project, "project", "Project ID")));
9613
9663
  });
9614
9664
  program2.command("runs:status").argument("<runId>").description("Fetch full run detail").action(async (runId) => {
9615
9665
  printJson(await createClient(loadConfig()).runs.get(String(runId)));
@@ -9691,7 +9741,7 @@ program2.command("billing:summary").description("Fetch billing summary").action(
9691
9741
  printJson(await createClient(loadConfig()).billing.getSummary());
9692
9742
  });
9693
9743
  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) => {
9694
- printJson(await createClient(loadConfig()).billing.getUsage(options.unit, options.run));
9744
+ printJson(await createClient(loadConfig()).billing.getUsage(options.unit, normalizeOptionalId(options.run, "run", "Run ID")));
9695
9745
  });
9696
9746
  program2.command("billing:invoices").description("List invoices").action(async () => {
9697
9747
  printJson(await createClient(loadConfig()).billing.listInvoices());
@@ -9724,7 +9774,7 @@ program2.command("api-keys:create").requiredOption("--name <name>").option("--ow
9724
9774
  const config = loadConfig();
9725
9775
  const session = await createClient(config).auth.me();
9726
9776
  const apiKey = await createClient(config).apiKeys.create({
9727
- name: options.name,
9777
+ name: normalizeHumanName(options.name, "API key name"),
9728
9778
  ownerType: options.ownerType,
9729
9779
  ownerId: options.ownerId ?? session.user.id,
9730
9780
  scopes: parseApiKeyScopes(String(options.scopes))
@@ -9741,7 +9791,7 @@ program2.command("api-keys:revoke").argument("<apiKeyId>").description("Revoke a
9741
9791
  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) => {
9742
9792
  printJson(
9743
9793
  await createClient(loadConfig()).serviceAccounts.create({
9744
- name: options.name,
9794
+ name: normalizeHumanName(options.name, "Service account name"),
9745
9795
  scopes: parseApiKeyScopes(String(options.scopes))
9746
9796
  })
9747
9797
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trainfabric",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
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",