trainfabric 0.1.29 → 0.1.30

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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.cjs +117 -28
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -26,6 +26,8 @@ Secrets are stored in macOS Keychain when available. Other platforms use an encr
26
26
 
27
27
  ```bash
28
28
  trainfabric projects:list
29
+ trainfabric projects:create --name <name>
30
+ trainfabric projects:delete <projectId>
29
31
  trainfabric datasets:upload ./train.jsonl --project <projectId>
30
32
  trainfabric runs:quote --summary --project <projectId> --dataset <datasetId> --model llama-3-8b
31
33
  trainfabric runs:create --yes --quote <quoteId> --project <projectId> --dataset <datasetId> --model llama-3-8b
package/dist/index.cjs CHANGED
@@ -8077,6 +8077,15 @@ var ResourceClient = class {
8077
8077
  };
8078
8078
 
8079
8079
  // ../sdk/dist/client/admin.js
8080
+ var apiKeyIdPattern = /^key_[A-Za-z0-9_-]+$/;
8081
+ var serviceAccountIdPattern = /^svc_[A-Za-z0-9_-]+$/;
8082
+ function normalizeRequiredId(value, pattern, label) {
8083
+ const id = String(value ?? "").trim();
8084
+ if (!pattern.test(id)) {
8085
+ throw new Error(`${label} is invalid.`);
8086
+ }
8087
+ return encodeURIComponent(id);
8088
+ }
8080
8089
  var ApiKeysClient = class extends ResourceClient {
8081
8090
  create(input) {
8082
8091
  apiKeyCreateSchema.parse(input);
@@ -8086,7 +8095,7 @@ var ApiKeysClient = class extends ResourceClient {
8086
8095
  return this.requestGet("/v1/api-keys");
8087
8096
  }
8088
8097
  revoke(apiKeyId) {
8089
- return this.requestDelete(`/v1/api-keys/${apiKeyId}`);
8098
+ return this.requestDelete(`/v1/api-keys/${normalizeRequiredId(apiKeyId, apiKeyIdPattern, "API key ID")}`);
8090
8099
  }
8091
8100
  };
8092
8101
  var ServiceAccountsClient = class extends ResourceClient {
@@ -8098,7 +8107,7 @@ var ServiceAccountsClient = class extends ResourceClient {
8098
8107
  return this.requestGet("/v1/service-accounts");
8099
8108
  }
8100
8109
  revoke(serviceAccountId) {
8101
- return this.requestDelete(`/v1/service-accounts/${serviceAccountId}`);
8110
+ return this.requestDelete(`/v1/service-accounts/${normalizeRequiredId(serviceAccountId, serviceAccountIdPattern, "Service account ID")}`);
8102
8111
  }
8103
8112
  };
8104
8113
 
@@ -8164,6 +8173,14 @@ var ClusterClient = class extends ResourceClient {
8164
8173
  // ../sdk/dist/client/datasets.js
8165
8174
  var import_promises = __toESM(require("node:fs/promises"), 1);
8166
8175
  var import_node_path = __toESM(require("node:path"), 1);
8176
+ var datasetIdPattern = /^ds_[A-Za-z0-9_-]+$/;
8177
+ function encodeDatasetId(datasetId) {
8178
+ const id = String(datasetId ?? "").trim();
8179
+ if (!datasetIdPattern.test(id)) {
8180
+ throw new Error("Dataset ID is invalid.");
8181
+ }
8182
+ return encodeURIComponent(id);
8183
+ }
8167
8184
  var DatasetsClient = class extends ResourceClient {
8168
8185
  async create(input) {
8169
8186
  const validation = await this.validate({
@@ -8178,7 +8195,7 @@ var DatasetsClient = class extends ResourceClient {
8178
8195
  throw new Error("projectId is required.");
8179
8196
  }
8180
8197
  const fileName = import_node_path.default.basename(input.path);
8181
- const fileBuffer = await import_promises.default.readFile(input.path);
8198
+ const fileBuffer = await readDatasetFile(input.path);
8182
8199
  const uploadSessionInput = uploadSessionCreateSchema.parse({
8183
8200
  projectId,
8184
8201
  fileName,
@@ -8204,21 +8221,46 @@ var DatasetsClient = class extends ResourceClient {
8204
8221
  if (input.format !== "chat_jsonl") {
8205
8222
  throw new Error("Only chat_jsonl datasets are supported.");
8206
8223
  }
8207
- return validateChatJsonl(await import_promises.default.readFile(input.path, "utf8"));
8224
+ return validateChatJsonl(await readDatasetFile(input.path, "utf8"));
8208
8225
  }
8209
8226
  get(datasetId) {
8210
- return this.requestGet(`/v1/datasets/${datasetId}`);
8227
+ return this.requestGet(`/v1/datasets/${encodeDatasetId(datasetId)}`);
8211
8228
  }
8212
8229
  list(projectId) {
8213
8230
  const effectiveProjectId = projectId ?? this.parent.projectId;
8214
- return this.requestGet(`/v1/datasets${effectiveProjectId ? `?projectId=${effectiveProjectId}` : ""}`);
8231
+ const params = new URLSearchParams();
8232
+ if (effectiveProjectId) {
8233
+ params.set("projectId", effectiveProjectId);
8234
+ }
8235
+ const query = params.toString();
8236
+ return this.requestGet(`/v1/datasets${query ? `?${query}` : ""}`);
8215
8237
  }
8216
8238
  delete(datasetId) {
8217
- return this.requestDelete(`/v1/datasets/${datasetId}`);
8239
+ return this.requestDelete(`/v1/datasets/${encodeDatasetId(datasetId)}`);
8218
8240
  }
8219
8241
  };
8242
+ async function readDatasetFile(pathname, encoding) {
8243
+ let stats;
8244
+ try {
8245
+ stats = await import_promises.default.stat(pathname);
8246
+ } catch {
8247
+ throw new Error(`Dataset file not found: ${pathname}`);
8248
+ }
8249
+ if (!stats.isFile()) {
8250
+ throw new Error(`Dataset path must be a file: ${pathname}`);
8251
+ }
8252
+ return encoding ? import_promises.default.readFile(pathname, encoding) : import_promises.default.readFile(pathname);
8253
+ }
8220
8254
 
8221
8255
  // ../sdk/dist/client/deployments.js
8256
+ var deploymentIdPattern = /^dep_[A-Za-z0-9_-]+$/;
8257
+ function encodeDeploymentId(deploymentId) {
8258
+ const id = String(deploymentId ?? "").trim();
8259
+ if (!deploymentIdPattern.test(id)) {
8260
+ throw new Error("Deployment ID is invalid.");
8261
+ }
8262
+ return encodeURIComponent(id);
8263
+ }
8222
8264
  var DeploymentsClient = class extends ResourceClient {
8223
8265
  list() {
8224
8266
  return this.requestGet("/v1/deployments");
@@ -8227,23 +8269,31 @@ var DeploymentsClient = class extends ResourceClient {
8227
8269
  return this.requestPost("/v1/deployments", input);
8228
8270
  }
8229
8271
  get(id) {
8230
- return this.requestGet(`/v1/deployments/${id}`);
8272
+ return this.requestGet(`/v1/deployments/${encodeDeploymentId(id)}`);
8231
8273
  }
8232
8274
  delete(id) {
8233
- return this.requestDelete(`/v1/deployments/${id}`);
8275
+ return this.requestDelete(`/v1/deployments/${encodeDeploymentId(id)}`);
8234
8276
  }
8235
8277
  };
8236
8278
 
8237
8279
  // ../sdk/dist/client/models.js
8280
+ var modelIdPattern = /^mdl_[A-Za-z0-9_-]+$/;
8281
+ function normalizeModelId(modelId) {
8282
+ const normalized = String(modelId ?? "").trim();
8283
+ if (!modelIdPattern.test(normalized)) {
8284
+ throw new Error("Model ID is invalid.");
8285
+ }
8286
+ return encodeURIComponent(normalized);
8287
+ }
8238
8288
  var ModelsClient = class extends ResourceClient {
8239
8289
  list() {
8240
8290
  return this.requestGet("/v1/models");
8241
8291
  }
8242
8292
  get(modelId) {
8243
- return this.requestGet(`/v1/models/${modelId}`);
8293
+ return this.requestGet(`/v1/models/${normalizeModelId(modelId)}`);
8244
8294
  }
8245
8295
  export(modelId) {
8246
- return this.requestPost(`/v1/models/${modelId}/export`);
8296
+ return this.requestPost(`/v1/models/${normalizeModelId(modelId)}/export`);
8247
8297
  }
8248
8298
  };
8249
8299
 
@@ -8255,12 +8305,20 @@ var ProjectsClient = class extends ResourceClient {
8255
8305
  organizationId: input.organizationId ?? this.parent.orgId
8256
8306
  });
8257
8307
  }
8308
+ delete(projectId) {
8309
+ return this.requestDelete(`/v1/projects/${projectId}`);
8310
+ }
8258
8311
  get(projectId) {
8259
8312
  return this.requestGet(`/v1/projects/${projectId}`);
8260
8313
  }
8261
8314
  list(organizationId) {
8262
8315
  const suffix = organizationId ?? this.parent.orgId;
8263
- return this.requestGet(`/v1/projects${suffix ? `?organizationId=${suffix}` : ""}`);
8316
+ const params = new URLSearchParams();
8317
+ if (suffix) {
8318
+ params.set("organizationId", suffix);
8319
+ }
8320
+ const query = params.toString();
8321
+ return this.requestGet(`/v1/projects${query ? `?${query}` : ""}`);
8264
8322
  }
8265
8323
  };
8266
8324
  var OrganizationsClient = class extends ResourceClient {
@@ -8514,6 +8572,14 @@ var RunHandle = class {
8514
8572
  };
8515
8573
 
8516
8574
  // ../sdk/dist/client/runs.js
8575
+ var runIdPattern = /^run_[A-Za-z0-9_-]+$/;
8576
+ function encodeRunId(runId) {
8577
+ const id = String(runId ?? "").trim();
8578
+ if (!runIdPattern.test(id)) {
8579
+ throw new Error("Run ID is invalid.");
8580
+ }
8581
+ return encodeURIComponent(id);
8582
+ }
8517
8583
  var RunsClient = class extends ResourceClient {
8518
8584
  describeEmptyQuoteFailure(input) {
8519
8585
  const compute = input.compute && input.compute !== "auto" && input.compute !== "cpu-sim" ? input.compute : void 0;
@@ -8616,42 +8682,47 @@ var RunsClient = class extends ResourceClient {
8616
8682
  return new RunHandle(this.parent, run);
8617
8683
  }
8618
8684
  get(runId) {
8619
- return this.requestGet(`/v1/runs/${runId}`);
8685
+ return this.requestGet(`/v1/runs/${encodeRunId(runId)}`);
8620
8686
  }
8621
8687
  list(projectId) {
8622
8688
  const effectiveProjectId = projectId ?? this.parent.projectId;
8623
- return this.requestGet(`/v1/runs${effectiveProjectId ? `?projectId=${effectiveProjectId}` : ""}`);
8689
+ const params = new URLSearchParams();
8690
+ if (effectiveProjectId) {
8691
+ params.set("projectId", effectiveProjectId);
8692
+ }
8693
+ const query = params.toString();
8694
+ return this.requestGet(`/v1/runs${query ? `?${query}` : ""}`);
8624
8695
  }
8625
8696
  cancel(runId) {
8626
- return this.requestPost(`/v1/runs/${runId}/cancel`);
8697
+ return this.requestPost(`/v1/runs/${encodeRunId(runId)}/cancel`);
8627
8698
  }
8628
8699
  resume(runId) {
8629
- return this.requestPost(`/v1/runs/${runId}/resume`);
8700
+ return this.requestPost(`/v1/runs/${encodeRunId(runId)}/resume`);
8630
8701
  }
8631
8702
  terminate(runId) {
8632
- return this.requestPost(`/v1/runs/${runId}/terminate`);
8703
+ return this.requestPost(`/v1/runs/${encodeRunId(runId)}/terminate`);
8633
8704
  }
8634
8705
  logs(runId) {
8635
- return this.requestGet(`/v1/runs/${runId}/logs`);
8706
+ return this.requestGet(`/v1/runs/${encodeRunId(runId)}/logs`);
8636
8707
  }
8637
8708
  async watch(runId) {
8638
8709
  const detail = await this.get(runId);
8639
8710
  return new RunHandle(this.parent, detail.run);
8640
8711
  }
8641
8712
  allocation(runId) {
8642
- return this.requestGet(`/v1/runs/${runId}/allocation`);
8713
+ return this.requestGet(`/v1/runs/${encodeRunId(runId)}/allocation`);
8643
8714
  }
8644
8715
  usage(runId) {
8645
- return this.requestGet(`/v1/runs/${runId}/usage`);
8716
+ return this.requestGet(`/v1/runs/${encodeRunId(runId)}/usage`);
8646
8717
  }
8647
8718
  costBreakdown(runId) {
8648
- return this.requestGet(`/v1/runs/${runId}/cost-breakdown`);
8719
+ return this.requestGet(`/v1/runs/${encodeRunId(runId)}/cost-breakdown`);
8649
8720
  }
8650
8721
  configuration(runId) {
8651
- return this.requestGet(`/v1/runs/${runId}/configuration`);
8722
+ return this.requestGet(`/v1/runs/${encodeRunId(runId)}/configuration`);
8652
8723
  }
8653
8724
  explanation(runId) {
8654
- return this.requestGet(`/v1/runs/${runId}/explanation`);
8725
+ return this.requestGet(`/v1/runs/${encodeRunId(runId)}/explanation`);
8655
8726
  }
8656
8727
  async wait(runId, options) {
8657
8728
  const handle = await this.watch(runId);
@@ -9139,7 +9210,7 @@ function buildComputeSpec(options) {
9139
9210
 
9140
9211
  // src/index.ts
9141
9212
  var DEFAULT_TRAINFABRIC_API_URL2 = "https://api.trainfabric.com";
9142
- var CLI_VERSION = "0.1.29";
9213
+ var CLI_VERSION = "0.1.30";
9143
9214
  var CONFIG_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".trainfabric");
9144
9215
  var CONFIG_PATH = import_node_path3.default.join(CONFIG_DIR, "config.json");
9145
9216
  var FALLBACK_SECRET_PATH = import_node_path3.default.join(CONFIG_DIR, "session.enc");
@@ -9761,6 +9832,11 @@ program2.command("projects:create").requiredOption("--name <name>").option("--or
9761
9832
  })
9762
9833
  );
9763
9834
  });
9835
+ program2.command("projects:delete").argument("<projectId>").description("Delete an empty project").action(async (projectId) => {
9836
+ const normalizedProjectId = normalizeId(projectId, "project", "Project ID");
9837
+ await createClient(loadConfig()).projects.delete(normalizedProjectId);
9838
+ printJson({ deleted: true, id: normalizedProjectId });
9839
+ });
9764
9840
  program2.command("datasets:validate").argument("<file>").description("Validate a local dataset file").action(async (file) => {
9765
9841
  assertReadableFile(file, "Dataset path");
9766
9842
  const validation = await createClient(loadConfig()).datasets.validate({
@@ -9812,10 +9888,23 @@ program2.command("runtime:detect").option("--project <projectId>").option("--rep
9812
9888
  throw new Error("Provide --repo or --git.");
9813
9889
  }
9814
9890
  const config = loadConfig();
9815
- const detection = await createClient(config).runtime.detect({
9816
- projectId: requireProjectId(options, config),
9817
- source: sourceOptions.source
9818
- });
9891
+ let detection;
9892
+ try {
9893
+ detection = await createClient(config).runtime.detect({
9894
+ projectId: requireProjectId(options, config),
9895
+ source: sourceOptions.source
9896
+ });
9897
+ } catch (error) {
9898
+ if (sourceOptions.source.kind === "git" && error instanceof Error && /Request failed with status 404/.test(error.message)) {
9899
+ throw new Error("Git repository could not be resolved. Check the repository URL, branch, and access permissions.");
9900
+ }
9901
+ throw error;
9902
+ }
9903
+ if (sourceOptions.source.kind === "git" && detection.status !== "detected") {
9904
+ throw new Error(
9905
+ `Runtime source is ${detection.status}. Git repository could not be resolved into a supported runtime source.`
9906
+ );
9907
+ }
9819
9908
  printJson(redactInlineSource(detection));
9820
9909
  });
9821
9910
  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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trainfabric",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
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",