@tokenbuddy/tb-admin 1.0.14 → 1.0.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 (55) hide show
  1. package/dist/src/bootstrap-registry.d.ts +1 -0
  2. package/dist/src/bootstrap-registry.d.ts.map +1 -1
  3. package/dist/src/bootstrap-registry.js.map +1 -1
  4. package/dist/src/cli.d.ts.map +1 -1
  5. package/dist/src/cli.js +294 -13
  6. package/dist/src/cli.js.map +1 -1
  7. package/dist/src/client.d.ts +12 -3
  8. package/dist/src/client.d.ts.map +1 -1
  9. package/dist/src/client.js +12 -8
  10. package/dist/src/client.js.map +1 -1
  11. package/dist/src/display-format.d.ts +39 -0
  12. package/dist/src/display-format.d.ts.map +1 -0
  13. package/dist/src/display-format.js +354 -0
  14. package/dist/src/display-format.js.map +1 -0
  15. package/dist/src/server-cmd.d.ts +25 -1
  16. package/dist/src/server-cmd.d.ts.map +1 -1
  17. package/dist/src/server-cmd.js +116 -16
  18. package/dist/src/server-cmd.js.map +1 -1
  19. package/dist/src/ui-actions.d.ts +90 -0
  20. package/dist/src/ui-actions.d.ts.map +1 -0
  21. package/dist/src/ui-actions.js +823 -0
  22. package/dist/src/ui-actions.js.map +1 -0
  23. package/dist/src/ui-command.d.ts +4 -0
  24. package/dist/src/ui-command.d.ts.map +1 -0
  25. package/dist/src/ui-command.js +37 -0
  26. package/dist/src/ui-command.js.map +1 -0
  27. package/dist/src/ui-server.d.ts +22 -0
  28. package/dist/src/ui-server.d.ts.map +1 -0
  29. package/dist/src/ui-server.js +261 -0
  30. package/dist/src/ui-server.js.map +1 -0
  31. package/dist/src/ui-state.d.ts +140 -0
  32. package/dist/src/ui-state.d.ts.map +1 -0
  33. package/dist/src/ui-state.js +438 -0
  34. package/dist/src/ui-state.js.map +1 -0
  35. package/dist/src/ui-static.d.ts +2 -0
  36. package/dist/src/ui-static.d.ts.map +1 -0
  37. package/dist/src/ui-static.js +469 -0
  38. package/dist/src/ui-static.js.map +1 -0
  39. package/dist/src/upstream-balance-probe.d.ts +41 -0
  40. package/dist/src/upstream-balance-probe.d.ts.map +1 -0
  41. package/dist/src/upstream-balance-probe.js +379 -0
  42. package/dist/src/upstream-balance-probe.js.map +1 -0
  43. package/package.json +1 -1
  44. package/src/bootstrap-registry.ts +1 -0
  45. package/src/cli.ts +335 -13
  46. package/src/client.ts +13 -8
  47. package/src/display-format.ts +398 -0
  48. package/src/server-cmd.ts +145 -20
  49. package/src/ui-actions.ts +958 -0
  50. package/src/ui-command.ts +39 -0
  51. package/src/ui-server.ts +322 -0
  52. package/src/ui-state.ts +614 -0
  53. package/src/ui-static.ts +472 -0
  54. package/src/upstream-balance-probe.ts +505 -0
  55. package/tests/admin.test.ts +1404 -2
package/src/cli.ts CHANGED
@@ -2,6 +2,7 @@ import { Command } from "commander";
2
2
  import { ConfigManager } from "./config.js";
3
3
  import { AdminClient } from "./client.js";
4
4
  import { FlyProvider } from "./server-cmd.js";
5
+ import { bindAdminUiCommand } from "./ui-command.js";
5
6
  import {
6
7
  loadRegistryFile,
7
8
  SellerRegistryDocument,
@@ -142,11 +143,48 @@ function loadYamlOrJsonFile(filePath: string): any {
142
143
  return YAML.load(fs.readFileSync(filePath, "utf8"));
143
144
  }
144
145
 
146
+ function loadSellerRegistryEntryFile(filePath: string): any {
147
+ const parsed = loadYamlOrJsonFile(filePath);
148
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
149
+ throw new Error("seller registry entry document is required");
150
+ }
151
+ validateRegistryDocument({ version: 1, sellers: [parsed as any] });
152
+ return parsed;
153
+ }
154
+
155
+ function loadSellerRegistryPatchFile(filePath: string): any {
156
+ const parsed = loadYamlOrJsonFile(filePath);
157
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
158
+ throw new Error("seller registry patch document is required");
159
+ }
160
+ if ("id" in parsed) {
161
+ throw new Error("seller registry patch must not include id");
162
+ }
163
+ return parsed;
164
+ }
165
+
166
+ function withExpectedVersion(body: Record<string, any>, expectedVersion: unknown): Record<string, any> {
167
+ if (expectedVersion === undefined) {
168
+ return body;
169
+ }
170
+ const version = Number(expectedVersion);
171
+ if (!Number.isInteger(version) || version < 1) {
172
+ throw new Error("expected version must be >= 1");
173
+ }
174
+ return { ...body, expectedVersion: version };
175
+ }
176
+
145
177
  function requireUpstreamModels(data: any): any[] {
146
- if (!Array.isArray(data?.models)) {
147
- throw new Error("operator upstream summary must contain top-level models array");
178
+ if (Array.isArray(data?.models)) {
179
+ return data.models;
180
+ }
181
+ if (Array.isArray(data?.upstreams)) {
182
+ const firstWithModels = data.upstreams.find((entry: any) => Array.isArray(entry?.models));
183
+ if (firstWithModels) {
184
+ return firstWithModels.models;
185
+ }
148
186
  }
149
- return data.models;
187
+ throw new Error("operator upstream summary must contain models array");
150
188
  }
151
189
 
152
190
  /**
@@ -167,6 +205,8 @@ export function buildAdminCli(configManager: ConfigManager): Command {
167
205
  .option("--profile <profile>", "Use custom profile instead of default")
168
206
  .option("--config <path>", "Use custom config file path");
169
207
 
208
+ bindAdminUiCommand(program, configManager);
209
+
170
210
  // Helper to resolve client
171
211
  function getClient(): AdminClient {
172
212
  const opts = program.opts();
@@ -611,14 +651,19 @@ export function buildAdminCli(configManager: ConfigManager): Command {
611
651
  // 6. Bootstrap Registry Command
612
652
  const bootstrap = program.command("bootstrap").description("Manage wallet bootstrap service");
613
653
  const bootstrapSellers = bootstrap.command("sellers").description("Manage public seller registry");
654
+ const bootstrapDefaultSeller = bootstrap.command("default-seller").description("Manage bootstrap default seller");
655
+ const bootstrapRegistry = bootstrap.command("registry").description("Manage signed registry versions and publishing");
614
656
  const bootstrapConfig = bootstrap.command("config").description("Manage wallet bootstrap YAML config");
615
657
 
616
658
  bootstrapSellers
617
659
  .command("get")
618
- .description("Fetch public seller registry")
619
- .action(async () => {
660
+ .description("Fetch public seller registry or one seller by id")
661
+ .argument("[id]", "Seller id")
662
+ .action(async (id?: string) => {
620
663
  try {
621
- const data = await publicGet("/registry/sellers") as SellerRegistryDocument;
664
+ const data = id
665
+ ? await getClient().get(`/operator/registry/sellers/${encodeURIComponent(id)}`)
666
+ : await publicGet("/registry/sellers") as SellerRegistryDocument;
622
667
  console.log(JSON.stringify(data, null, 2));
623
668
  } catch (err: any) {
624
669
  console.error("Error:", err.message);
@@ -626,16 +671,129 @@ export function buildAdminCli(configManager: ConfigManager): Command {
626
671
  }
627
672
  });
628
673
 
674
+ bootstrapSellers
675
+ .command("list")
676
+ .description("List seller entries from the operator registry")
677
+ .action(async () => {
678
+ try {
679
+ const data = await getClient().get("/operator/registry/sellers") as { sellers: any[] };
680
+ const table = new Table({ head: ["ID", "Status", "URL", "Models", "Protocols", "Payments"] });
681
+ for (const seller of data.sellers || []) {
682
+ table.push([
683
+ seller.id,
684
+ seller.status || "active",
685
+ seller.url,
686
+ String(seller.models?.length || seller.modelsCount || 0),
687
+ (seller.supportedProtocols || []).join(","),
688
+ (seller.paymentMethods || []).join(",")
689
+ ]);
690
+ }
691
+ console.log(table.toString());
692
+ } catch (err: any) {
693
+ console.error("Error:", err.message);
694
+ process.exit(1);
695
+ }
696
+ });
697
+
698
+ bootstrapSellers
699
+ .command("add")
700
+ .description("Add one seller entry without replacing the full registry")
701
+ .requiredOption("--file <path>", "Seller entry YAML/JSON file")
702
+ .requiredOption("--expect-version <n>", "Expected current registry version")
703
+ .option("--idempotency-key <key>", "Idempotency key for retry-safe writes")
704
+ .action(async (options) => {
705
+ try {
706
+ const seller = loadSellerRegistryEntryFile(options.file);
707
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : undefined;
708
+ const response = await getClient().post(
709
+ "/operator/registry/sellers",
710
+ withExpectedVersion(seller, options.expectVersion),
711
+ headers
712
+ ) as SellerRegistryDocument;
713
+ console.log(`Added seller ${seller.id}: version=${response.version} sellers=${response.sellers.length} publish=pending`);
714
+ } catch (err: any) {
715
+ console.error("Error:", err.message);
716
+ process.exit(1);
717
+ }
718
+ });
719
+
720
+ bootstrapSellers
721
+ .command("update <id>")
722
+ .description("Patch one seller entry without replacing the full registry")
723
+ .requiredOption("--file <path>", "Seller patch YAML/JSON file")
724
+ .requiredOption("--expect-version <n>", "Expected current registry version")
725
+ .option("--idempotency-key <key>", "Idempotency key for retry-safe writes")
726
+ .action(async (id, options) => {
727
+ try {
728
+ const patch = loadSellerRegistryPatchFile(options.file);
729
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : undefined;
730
+ const response = await getClient().patch(
731
+ `/operator/registry/sellers/${encodeURIComponent(id)}`,
732
+ withExpectedVersion(patch, options.expectVersion),
733
+ headers
734
+ ) as SellerRegistryDocument;
735
+ console.log(`Updated seller ${id}: version=${response.version} publish=pending`);
736
+ } catch (err: any) {
737
+ console.error("Error:", err.message);
738
+ process.exit(1);
739
+ }
740
+ });
741
+
742
+ bootstrapSellers
743
+ .command("status <id> <status>")
744
+ .description("Set one seller registry status: active, draining, or offline")
745
+ .requiredOption("--expect-version <n>", "Expected current registry version")
746
+ .option("--idempotency-key <key>", "Idempotency key for retry-safe writes")
747
+ .action(async (id, status, options) => {
748
+ try {
749
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : undefined;
750
+ const response = await getClient().put(
751
+ `/operator/registry/sellers/${encodeURIComponent(id)}/status`,
752
+ withExpectedVersion({ status }, options.expectVersion),
753
+ headers
754
+ ) as SellerRegistryDocument;
755
+ console.log(`Set seller ${id} status=${status}: version=${response.version} publish=pending`);
756
+ } catch (err: any) {
757
+ console.error("Error:", err.message);
758
+ process.exit(1);
759
+ }
760
+ });
761
+
762
+ bootstrapSellers
763
+ .command("remove <id>")
764
+ .description("Remove one non-active seller entry")
765
+ .requiredOption("--expect-version <n>", "Expected current registry version")
766
+ .option("--force", "Allow forced removal when the service permits it")
767
+ .option("--idempotency-key <key>", "Idempotency key for retry-safe writes")
768
+ .action(async (id, options) => {
769
+ try {
770
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : undefined;
771
+ const response = await getClient().delete(
772
+ `/operator/registry/sellers/${encodeURIComponent(id)}`,
773
+ withExpectedVersion({ force: Boolean(options.force) }, options.expectVersion),
774
+ headers
775
+ ) as SellerRegistryDocument;
776
+ console.log(`Removed seller ${id}: version=${response.version} sellers=${response.sellers.length} publish=pending`);
777
+ } catch (err: any) {
778
+ console.error("Error:", err.message);
779
+ process.exit(1);
780
+ }
781
+ });
782
+
629
783
  bootstrapSellers
630
784
  .command("put")
631
- .description("Update public seller registry")
785
+ .description("Deprecated: dangerously replace public seller registry")
632
786
  .requiredOption("--file <path>", "Seller registry JSON file")
787
+ .option("--force", "Acknowledge this full replacement is dangerous")
633
788
  .action(async (options) => {
634
789
  try {
790
+ if (!options.force) {
791
+ throw new Error("bootstrap sellers put is deprecated; use bootstrap registry import --dry-run first, then --force");
792
+ }
635
793
  const document = loadRegistryFile(options.file);
636
794
  const client = getClient();
637
795
  const response = await client.put("/operator/registry/sellers", document) as SellerRegistryDocument;
638
- console.log(`Updated seller registry: version=${response.version} sellers=${response.sellers.length}`);
796
+ console.log(`Dangerously replaced seller registry: version=${response.version} sellers=${response.sellers.length} publish=pending`);
639
797
  } catch (err: any) {
640
798
  console.error("Error:", err.message);
641
799
  process.exit(1);
@@ -657,6 +815,159 @@ export function buildAdminCli(configManager: ConfigManager): Command {
657
815
  }
658
816
  });
659
817
 
818
+ bootstrapDefaultSeller
819
+ .command("set <id>")
820
+ .description("Set default seller")
821
+ .requiredOption("--expect-version <n>", "Expected current registry version")
822
+ .option("--idempotency-key <key>", "Idempotency key for retry-safe writes")
823
+ .action(async (id, options) => {
824
+ try {
825
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : undefined;
826
+ const response = await getClient().put(
827
+ "/operator/registry/default-seller",
828
+ withExpectedVersion({ sellerId: id }, options.expectVersion),
829
+ headers
830
+ ) as SellerRegistryDocument;
831
+ console.log(`Set default seller ${id}: version=${response.version} publish=pending`);
832
+ } catch (err: any) {
833
+ console.error("Error:", err.message);
834
+ process.exit(1);
835
+ }
836
+ });
837
+
838
+ bootstrapRegistry
839
+ .command("diff")
840
+ .description("Compare a local registry JSON file against the operator registry")
841
+ .requiredOption("--file <path>", "Seller registry JSON file")
842
+ .action(async (options) => {
843
+ try {
844
+ const local = loadRegistryFile(options.file);
845
+ const remote = await getClient().get("/operator/registry") as SellerRegistryDocument;
846
+ const localIds = new Set(local.sellers.map((seller) => seller.id));
847
+ const remoteIds = new Set(remote.sellers.map((seller) => seller.id));
848
+ const added = local.sellers.filter((seller) => !remoteIds.has(seller.id)).map((seller) => seller.id);
849
+ const removed = remote.sellers.filter((seller) => !localIds.has(seller.id)).map((seller) => seller.id);
850
+ const changed = local.sellers
851
+ .filter((seller) => remoteIds.has(seller.id))
852
+ .filter((seller) => JSON.stringify(seller) !== JSON.stringify(remote.sellers.find((entry) => entry.id === seller.id)))
853
+ .map((seller) => seller.id);
854
+ console.log(JSON.stringify({
855
+ remoteVersion: remote.version,
856
+ localVersion: local.version,
857
+ added,
858
+ removed,
859
+ changed,
860
+ defaultSellerChanged: remote.defaultSeller !== local.defaultSeller
861
+ }, null, 2));
862
+ } catch (err: any) {
863
+ console.error("Error:", err.message);
864
+ process.exit(1);
865
+ }
866
+ });
867
+
868
+ bootstrapRegistry
869
+ .command("import")
870
+ .description("Import a full registry JSON as a dangerous disaster-recovery operation")
871
+ .requiredOption("--file <path>", "Seller registry JSON file")
872
+ .option("--dry-run", "Only validate and show diff")
873
+ .option("--force", "Actually perform the full replacement")
874
+ .option("--expect-version <n>", "Expected current registry version before import")
875
+ .option("--force-delete-count <n>", "Maximum allowed delete count when forcing", "1")
876
+ .action(async (options) => {
877
+ try {
878
+ const local = loadRegistryFile(options.file);
879
+ const remote = await getClient().get("/operator/registry") as SellerRegistryDocument;
880
+ const localIds = new Set(local.sellers.map((seller) => seller.id));
881
+ const removed = remote.sellers.filter((seller) => !localIds.has(seller.id));
882
+ const forceDeleteCount = Number(options.forceDeleteCount);
883
+ if (!Number.isInteger(forceDeleteCount) || forceDeleteCount < 0) {
884
+ throw new Error("--force-delete-count must be >= 0");
885
+ }
886
+ console.log(`Import diff: remoteVersion=${remote.version} localVersion=${local.version} removes=${removed.length}`);
887
+ if (options.dryRun || !options.force) {
888
+ if (!options.dryRun) {
889
+ throw new Error("refusing full import without --force; run with --dry-run first");
890
+ }
891
+ return;
892
+ }
893
+ if (removed.length > forceDeleteCount) {
894
+ throw new Error(`import would remove ${removed.length} sellers; pass --force-delete-count ${removed.length} to allow`);
895
+ }
896
+ const body = withExpectedVersion(local as any, options.expectVersion);
897
+ const response = await getClient().put("/operator/registry/sellers", body) as SellerRegistryDocument;
898
+ console.log(`Imported seller registry: version=${response.version} sellers=${response.sellers.length} publish=pending`);
899
+ } catch (err: any) {
900
+ console.error("Error:", err.message);
901
+ process.exit(1);
902
+ }
903
+ });
904
+
905
+ bootstrapRegistry
906
+ .command("publish")
907
+ .description("Publish the current signed registry to R2")
908
+ .option("--version <n>", "Registry version to publish")
909
+ .action(async (options) => {
910
+ try {
911
+ const version = options.version ? Number(options.version) : undefined;
912
+ if (version !== undefined && (!Number.isInteger(version) || version < 1)) {
913
+ throw new Error("--version must be a positive integer");
914
+ }
915
+ const body = version === undefined ? {} : { version };
916
+ const response = await getClient().post("/operator/registry/publish", body) as {
917
+ version: number;
918
+ registrySha256: string;
919
+ signingKeyId: string;
920
+ artifacts?: Array<{ key: string; url: string }>;
921
+ };
922
+ console.log(`Published registry version=${response.version} sha256=${response.registrySha256} signingKey=${response.signingKeyId}`);
923
+ for (const artifact of response.artifacts || []) {
924
+ console.log(` ${artifact.key} -> ${artifact.url}`);
925
+ }
926
+ } catch (err: any) {
927
+ console.error("Error:", err.message);
928
+ process.exit(1);
929
+ }
930
+ });
931
+
932
+ bootstrapRegistry
933
+ .command("versions")
934
+ .description("List registry versions")
935
+ .option("--limit <n>", "Maximum versions to list", "20")
936
+ .action(async (options) => {
937
+ try {
938
+ const limit = Number(options.limit);
939
+ if (!Number.isInteger(limit) || limit < 1) {
940
+ throw new Error("--limit must be a positive integer");
941
+ }
942
+ const response = await getClient().get(`/operator/registry/versions?limit=${encodeURIComponent(String(limit))}`) as {
943
+ versions?: Array<{
944
+ version: number;
945
+ registrySha256: string;
946
+ signingKeyId?: string;
947
+ signed?: boolean;
948
+ sellerCount?: number;
949
+ defaultSeller?: string;
950
+ createdAt?: string;
951
+ publishedAt?: string;
952
+ }>;
953
+ };
954
+ for (const version of response.versions || []) {
955
+ console.log([
956
+ `version=${version.version}`,
957
+ `sellers=${version.sellerCount ?? "?"}`,
958
+ `default=${version.defaultSeller || "-"}`,
959
+ `signed=${version.signed ? "yes" : "no"}`,
960
+ `published=${version.publishedAt || "pending"}`,
961
+ `sha256=${version.registrySha256}`,
962
+ `key=${version.signingKeyId || "-"}`
963
+ ].join(" "));
964
+ }
965
+ } catch (err: any) {
966
+ console.error("Error:", err.message);
967
+ process.exit(1);
968
+ }
969
+ });
970
+
660
971
  bootstrapConfig
661
972
  .command("get")
662
973
  .description("Fetch wallet bootstrap runtime config")
@@ -762,17 +1073,23 @@ export function buildAdminCli(configManager: ConfigManager): Command {
762
1073
 
763
1074
  // 8. Seller Command (Fly.io)
764
1075
  const sellerCmd = program.command("seller").description("Deploy and manage seller containers on Fly.io");
765
- const flyProvider = new FlyProvider();
1076
+
1077
+ function getFlyProvider(): FlyProvider {
1078
+ const opts = program.opts();
1079
+ const mgr = opts.config ? new ConfigManager(opts.config) : configManager;
1080
+ return new FlyProvider(mgr.getSellerProvider("fly"));
1081
+ }
766
1082
 
767
1083
  sellerCmd
768
1084
  .command("ls")
769
1085
  .description("List all deployed apps on Fly.io")
770
1086
  .action(() => {
771
1087
  try {
772
- const out = flyProvider.listApps();
1088
+ const out = getFlyProvider().listApps();
773
1089
  console.log(out);
774
1090
  } catch (err: any) {
775
1091
  console.error("Error:", err.message);
1092
+ process.exit(1);
776
1093
  }
777
1094
  });
778
1095
 
@@ -787,11 +1104,12 @@ export function buildAdminCli(configManager: ConfigManager): Command {
787
1104
  .option("--volume-size-gb <gb>", "Persistent volume size in GB", (v) => parseInt(v, 10))
788
1105
  .option("--volume-id <id>", "Attach existing volume by ID (skips volume creation)")
789
1106
  .option("--volume-snapshot-retention-days <days>", "Volume snapshot retention days", (v) => parseInt(v, 10))
1107
+ .option("--initial-config <path>", "Initial seller YAML config to inject as TOKENBUDDY_SELLER_CONFIG_B64")
790
1108
  .requiredOption("--operator-secret <secret>", "Operator secret to configure")
791
1109
  .option("--dry-run", "Dry run display without actual execution")
792
1110
  .action((name, options) => {
793
1111
  try {
794
- const res = flyProvider.createSeller({
1112
+ const res = getFlyProvider().createSeller({
795
1113
  name,
796
1114
  app: options.app,
797
1115
  region: options.region,
@@ -801,12 +1119,14 @@ export function buildAdminCli(configManager: ConfigManager): Command {
801
1119
  volumeSizeGb: options.volumeSizeGb,
802
1120
  volumeId: options.volumeId,
803
1121
  volumeSnapshotRetentionDays: options.volumeSnapshotRetentionDays,
1122
+ initialConfigPath: options.initialConfig,
804
1123
  operatorSecret: options.operatorSecret,
805
1124
  dryRun: options.dryRun
806
1125
  });
807
1126
  console.log(res);
808
1127
  } catch (err: any) {
809
1128
  console.error("Error:", err.message);
1129
+ process.exit(1);
810
1130
  }
811
1131
  });
812
1132
 
@@ -817,7 +1137,7 @@ export function buildAdminCli(configManager: ConfigManager): Command {
817
1137
  .option("--dry-run", "Dry run")
818
1138
  .action((app, options) => {
819
1139
  try {
820
- const res = flyProvider.deploySeller({
1140
+ const res = getFlyProvider().deploySeller({
821
1141
  app,
822
1142
  image: options.image,
823
1143
  dryRun: options.dryRun
@@ -825,6 +1145,7 @@ export function buildAdminCli(configManager: ConfigManager): Command {
825
1145
  console.log(res);
826
1146
  } catch (err: any) {
827
1147
  console.error("Error:", err.message);
1148
+ process.exit(1);
828
1149
  }
829
1150
  });
830
1151
 
@@ -837,7 +1158,7 @@ export function buildAdminCli(configManager: ConfigManager): Command {
837
1158
  .action((name, options) => {
838
1159
  try {
839
1160
  const appName = options.app || name;
840
- const res = flyProvider.removeSeller(appName, options.dryRun);
1161
+ const res = getFlyProvider().removeSeller(appName, options.dryRun);
841
1162
  console.log(res);
842
1163
  if (options.removeProfile && !options.dryRun) {
843
1164
  const appBase = appName.includes("-") ? appName.replace(/^tb-seller-/, "") : appName;
@@ -856,6 +1177,7 @@ export function buildAdminCli(configManager: ConfigManager): Command {
856
1177
  }
857
1178
  } catch (err: any) {
858
1179
  console.error("Error:", err.message);
1180
+ process.exit(1);
859
1181
  }
860
1182
  });
861
1183
 
package/src/client.ts CHANGED
@@ -11,11 +11,12 @@ export class AdminClient {
11
11
  this.token = token;
12
12
  }
13
13
 
14
- private async request(path: string, method: string, body?: any): Promise<any> {
14
+ private async request(path: string, method: string, body?: any, extraHeaders: { [key: string]: string } = {}): Promise<any> {
15
15
  const url = `${this.baseUrl}${path}`;
16
16
  const headers: { [key: string]: string } = {
17
17
  "Content-Type": "application/json",
18
- "Authorization": `Bearer ${this.token}`
18
+ "Authorization": `Bearer ${this.token}`,
19
+ ...extraHeaders
19
20
  };
20
21
 
21
22
  const options: RequestInit = {
@@ -44,15 +45,19 @@ export class AdminClient {
44
45
  return this.request(path, "GET");
45
46
  }
46
47
 
47
- public async put(path: string, body?: any): Promise<any> {
48
- return this.request(path, "PUT", body);
48
+ public async put(path: string, body?: any, headers?: { [key: string]: string }): Promise<any> {
49
+ return this.request(path, "PUT", body, headers);
49
50
  }
50
51
 
51
- public async post(path: string, body?: any): Promise<any> {
52
- return this.request(path, "POST", body);
52
+ public async post(path: string, body?: any, headers?: { [key: string]: string }): Promise<any> {
53
+ return this.request(path, "POST", body, headers);
53
54
  }
54
55
 
55
- public async delete(path: string): Promise<any> {
56
- return this.request(path, "DELETE");
56
+ public async patch(path: string, body?: any, headers?: { [key: string]: string }): Promise<any> {
57
+ return this.request(path, "PATCH", body, headers);
58
+ }
59
+
60
+ public async delete(path: string, body?: any, headers?: { [key: string]: string }): Promise<any> {
61
+ return this.request(path, "DELETE", body, headers);
57
62
  }
58
63
  }