@tokenbuddy/tb-admin 1.0.15 → 1.0.28

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 (43) hide show
  1. package/dist/src/cli.d.ts.map +1 -1
  2. package/dist/src/cli.js +323 -14
  3. package/dist/src/cli.js.map +1 -1
  4. package/dist/src/client.d.ts +12 -3
  5. package/dist/src/client.d.ts.map +1 -1
  6. package/dist/src/client.js +12 -8
  7. package/dist/src/client.js.map +1 -1
  8. package/dist/src/display-format.d.ts +39 -0
  9. package/dist/src/display-format.d.ts.map +1 -0
  10. package/dist/src/display-format.js +354 -0
  11. package/dist/src/display-format.js.map +1 -0
  12. package/dist/src/server-cmd.d.ts +3 -0
  13. package/dist/src/server-cmd.d.ts.map +1 -1
  14. package/dist/src/server-cmd.js +32 -9
  15. package/dist/src/server-cmd.js.map +1 -1
  16. package/dist/src/ui-actions.d.ts +2 -0
  17. package/dist/src/ui-actions.d.ts.map +1 -1
  18. package/dist/src/ui-actions.js +123 -63
  19. package/dist/src/ui-actions.js.map +1 -1
  20. package/dist/src/ui-command.js +1 -1
  21. package/dist/src/ui-command.js.map +1 -1
  22. package/dist/src/ui-server.d.ts +0 -1
  23. package/dist/src/ui-server.d.ts.map +1 -1
  24. package/dist/src/ui-server.js +25 -9
  25. package/dist/src/ui-server.js.map +1 -1
  26. package/dist/src/ui-state.d.ts +7 -1
  27. package/dist/src/ui-state.d.ts.map +1 -1
  28. package/dist/src/ui-state.js +55 -24
  29. package/dist/src/ui-state.js.map +1 -1
  30. package/dist/src/ui-static.d.ts.map +1 -1
  31. package/dist/src/ui-static.js +371 -46
  32. package/dist/src/ui-static.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/cli.ts +367 -14
  35. package/src/client.ts +13 -8
  36. package/src/display-format.ts +398 -0
  37. package/src/server-cmd.ts +35 -9
  38. package/src/ui-actions.ts +129 -72
  39. package/src/ui-command.ts +1 -1
  40. package/src/ui-server.ts +24 -10
  41. package/src/ui-state.ts +64 -25
  42. package/src/ui-static.ts +374 -46
  43. package/tests/admin.test.ts +590 -41
@@ -1 +1 @@
1
- {"version":3,"file":"ui-static.js","sourceRoot":"","sources":["../../src/ui-static.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW;IACzB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YA+CG,aAAa,EAAE;;QAEnB,CAAC;AACT,CAAC;AAED,SAAS,aAAa;IACpB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwFR,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"ui-static.js","sourceRoot":"","sources":["../../src/ui-static.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,EAAE;AACF,qEAAqE;AACrE,qEAAqE;AACrE,0DAA0D;AAE1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,MAAM,UAAU,WAAW;IACzB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YA8RG,aAAa,EAAE;;QAEnB,CAAC;AACT,CAAC;AAED,SAAS,aAAa;IACpB,gFAAgF;IAChF,OAAO;EACP,mBAAmB,EAAE;EACrB,mBAAmB,EAAE;CACtB,CAAC;AACF,CAAC;AAED,SAAS,mBAAmB;IAC1B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiKR,CAAC;AACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenbuddy/tb-admin",
3
- "version": "1.0.15",
3
+ "version": "1.0.28",
4
4
  "description": "Remote admin CLI for TokenBuddy seller apps",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
package/src/cli.ts CHANGED
@@ -10,6 +10,8 @@ import {
10
10
  } from "./bootstrap-registry.js";
11
11
  import Table from "cli-table3";
12
12
  import * as fs from "fs";
13
+ import * as path from "path";
14
+ import { fileURLToPath } from "url";
13
15
  import YAML from "js-yaml";
14
16
 
15
17
  interface BootstrapConfigDocument {
@@ -33,6 +35,44 @@ interface BootstrapConfigDocument {
33
35
 
34
36
  type SellerConfigDocument = Record<string, any>;
35
37
 
38
+ function currentModuleDir(): string {
39
+ if (typeof __dirname !== "undefined") {
40
+ return __dirname;
41
+ }
42
+
43
+ const stack = new Error().stack || "";
44
+ const fileUrlMatch = stack.match(/(file:\/\/\/[^)\n]+\/cli\.js):\d+:\d+/);
45
+ if (fileUrlMatch) {
46
+ return path.dirname(fileURLToPath(fileUrlMatch[1]));
47
+ }
48
+
49
+ const filePathMatch = stack.match(/(\/[^)\n]+\/cli\.(?:js|ts)):\d+:\d+/);
50
+ if (filePathMatch) {
51
+ return path.dirname(filePathMatch[1]);
52
+ }
53
+
54
+ return process.cwd();
55
+ }
56
+
57
+ function readAdminPackageVersion(): string {
58
+ let current = path.resolve(currentModuleDir(), "..");
59
+ const seen = new Set<string>();
60
+ while (!seen.has(current)) {
61
+ seen.add(current);
62
+ const packageJsonPath = path.join(current, "package.json");
63
+ if (fs.existsSync(packageJsonPath)) {
64
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as { version?: unknown };
65
+ if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
66
+ return packageJson.version;
67
+ }
68
+ }
69
+ const parent = path.dirname(current);
70
+ if (parent === current) break;
71
+ current = parent;
72
+ }
73
+ return "0.0.0";
74
+ }
75
+
36
76
  function requireString(value: unknown, field: string): string {
37
77
  const normalized = String(value || "").trim();
38
78
  if (!normalized) {
@@ -143,11 +183,48 @@ function loadYamlOrJsonFile(filePath: string): any {
143
183
  return YAML.load(fs.readFileSync(filePath, "utf8"));
144
184
  }
145
185
 
186
+ function loadSellerRegistryEntryFile(filePath: string): any {
187
+ const parsed = loadYamlOrJsonFile(filePath);
188
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
189
+ throw new Error("seller registry entry document is required");
190
+ }
191
+ validateRegistryDocument({ version: 1, sellers: [parsed as any] });
192
+ return parsed;
193
+ }
194
+
195
+ function loadSellerRegistryPatchFile(filePath: string): any {
196
+ const parsed = loadYamlOrJsonFile(filePath);
197
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
198
+ throw new Error("seller registry patch document is required");
199
+ }
200
+ if ("id" in parsed) {
201
+ throw new Error("seller registry patch must not include id");
202
+ }
203
+ return parsed;
204
+ }
205
+
206
+ function withExpectedVersion(body: Record<string, any>, expectedVersion: unknown): Record<string, any> {
207
+ if (expectedVersion === undefined) {
208
+ return body;
209
+ }
210
+ const version = Number(expectedVersion);
211
+ if (!Number.isInteger(version) || version < 1) {
212
+ throw new Error("expected version must be >= 1");
213
+ }
214
+ return { ...body, expectedVersion: version };
215
+ }
216
+
146
217
  function requireUpstreamModels(data: any): any[] {
147
- if (!Array.isArray(data?.models)) {
148
- throw new Error("operator upstream summary must contain top-level models array");
218
+ if (Array.isArray(data?.models)) {
219
+ return data.models;
220
+ }
221
+ if (Array.isArray(data?.upstreams)) {
222
+ const firstWithModels = data.upstreams.find((entry: any) => Array.isArray(entry?.models));
223
+ if (firstWithModels) {
224
+ return firstWithModels.models;
225
+ }
149
226
  }
150
- return data.models;
227
+ throw new Error("operator upstream summary must contain models array");
151
228
  }
152
229
 
153
230
  /**
@@ -162,7 +239,7 @@ export function buildAdminCli(configManager: ConfigManager): Command {
162
239
  program
163
240
  .name("tb-admin")
164
241
  .description("Remote admin CLI for TokenBuddy seller apps")
165
- .version("1.0.0")
242
+ .version(readAdminPackageVersion())
166
243
  .option("--url <url>", "Remote seller core API url")
167
244
  .option("--token <token>", "Operator Bearer token")
168
245
  .option("--profile <profile>", "Use custom profile instead of default")
@@ -614,14 +691,19 @@ export function buildAdminCli(configManager: ConfigManager): Command {
614
691
  // 6. Bootstrap Registry Command
615
692
  const bootstrap = program.command("bootstrap").description("Manage wallet bootstrap service");
616
693
  const bootstrapSellers = bootstrap.command("sellers").description("Manage public seller registry");
694
+ const bootstrapDefaultSeller = bootstrap.command("default-seller").description("Manage bootstrap default seller");
695
+ const bootstrapRegistry = bootstrap.command("registry").description("Manage signed registry versions and publishing");
617
696
  const bootstrapConfig = bootstrap.command("config").description("Manage wallet bootstrap YAML config");
618
697
 
619
698
  bootstrapSellers
620
699
  .command("get")
621
- .description("Fetch public seller registry")
622
- .action(async () => {
700
+ .description("Fetch public seller registry or one seller by id")
701
+ .argument("[id]", "Seller id")
702
+ .action(async (id?: string) => {
623
703
  try {
624
- const data = await publicGet("/registry/sellers") as SellerRegistryDocument;
704
+ const data = id
705
+ ? await getClient().get(`/operator/registry/sellers/${encodeURIComponent(id)}`)
706
+ : await publicGet("/registry/sellers") as SellerRegistryDocument;
625
707
  console.log(JSON.stringify(data, null, 2));
626
708
  } catch (err: any) {
627
709
  console.error("Error:", err.message);
@@ -629,16 +711,129 @@ export function buildAdminCli(configManager: ConfigManager): Command {
629
711
  }
630
712
  });
631
713
 
714
+ bootstrapSellers
715
+ .command("list")
716
+ .description("List seller entries from the operator registry")
717
+ .action(async () => {
718
+ try {
719
+ const data = await getClient().get("/operator/registry/sellers") as { sellers: any[] };
720
+ const table = new Table({ head: ["ID", "Status", "URL", "Models", "Protocols", "Payments"] });
721
+ for (const seller of data.sellers || []) {
722
+ table.push([
723
+ seller.id,
724
+ seller.status || "active",
725
+ seller.url,
726
+ String(seller.models?.length || seller.modelsCount || 0),
727
+ (seller.supportedProtocols || []).join(","),
728
+ (seller.paymentMethods || []).join(",")
729
+ ]);
730
+ }
731
+ console.log(table.toString());
732
+ } catch (err: any) {
733
+ console.error("Error:", err.message);
734
+ process.exit(1);
735
+ }
736
+ });
737
+
738
+ bootstrapSellers
739
+ .command("add")
740
+ .description("Add one seller entry without replacing the full registry")
741
+ .requiredOption("--file <path>", "Seller entry YAML/JSON file")
742
+ .requiredOption("--expect-version <n>", "Expected current registry version")
743
+ .option("--idempotency-key <key>", "Idempotency key for retry-safe writes")
744
+ .action(async (options) => {
745
+ try {
746
+ const seller = loadSellerRegistryEntryFile(options.file);
747
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : undefined;
748
+ const response = await getClient().post(
749
+ "/operator/registry/sellers",
750
+ withExpectedVersion(seller, options.expectVersion),
751
+ headers
752
+ ) as SellerRegistryDocument;
753
+ console.log(`Added seller ${seller.id}: version=${response.version} sellers=${response.sellers.length} publish=pending`);
754
+ } catch (err: any) {
755
+ console.error("Error:", err.message);
756
+ process.exit(1);
757
+ }
758
+ });
759
+
760
+ bootstrapSellers
761
+ .command("update <id>")
762
+ .description("Patch one seller entry without replacing the full registry")
763
+ .requiredOption("--file <path>", "Seller patch YAML/JSON file")
764
+ .requiredOption("--expect-version <n>", "Expected current registry version")
765
+ .option("--idempotency-key <key>", "Idempotency key for retry-safe writes")
766
+ .action(async (id, options) => {
767
+ try {
768
+ const patch = loadSellerRegistryPatchFile(options.file);
769
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : undefined;
770
+ const response = await getClient().patch(
771
+ `/operator/registry/sellers/${encodeURIComponent(id)}`,
772
+ withExpectedVersion(patch, options.expectVersion),
773
+ headers
774
+ ) as SellerRegistryDocument;
775
+ console.log(`Updated seller ${id}: version=${response.version} publish=pending`);
776
+ } catch (err: any) {
777
+ console.error("Error:", err.message);
778
+ process.exit(1);
779
+ }
780
+ });
781
+
782
+ bootstrapSellers
783
+ .command("status <id> <status>")
784
+ .description("Set one seller registry status: active, draining, or offline")
785
+ .requiredOption("--expect-version <n>", "Expected current registry version")
786
+ .option("--idempotency-key <key>", "Idempotency key for retry-safe writes")
787
+ .action(async (id, status, options) => {
788
+ try {
789
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : undefined;
790
+ const response = await getClient().put(
791
+ `/operator/registry/sellers/${encodeURIComponent(id)}/status`,
792
+ withExpectedVersion({ status }, options.expectVersion),
793
+ headers
794
+ ) as SellerRegistryDocument;
795
+ console.log(`Set seller ${id} status=${status}: version=${response.version} publish=pending`);
796
+ } catch (err: any) {
797
+ console.error("Error:", err.message);
798
+ process.exit(1);
799
+ }
800
+ });
801
+
802
+ bootstrapSellers
803
+ .command("remove <id>")
804
+ .description("Remove one non-active seller entry")
805
+ .requiredOption("--expect-version <n>", "Expected current registry version")
806
+ .option("--force", "Allow forced removal when the service permits it")
807
+ .option("--idempotency-key <key>", "Idempotency key for retry-safe writes")
808
+ .action(async (id, options) => {
809
+ try {
810
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : undefined;
811
+ const response = await getClient().delete(
812
+ `/operator/registry/sellers/${encodeURIComponent(id)}`,
813
+ withExpectedVersion({ force: Boolean(options.force) }, options.expectVersion),
814
+ headers
815
+ ) as SellerRegistryDocument;
816
+ console.log(`Removed seller ${id}: version=${response.version} sellers=${response.sellers.length} publish=pending`);
817
+ } catch (err: any) {
818
+ console.error("Error:", err.message);
819
+ process.exit(1);
820
+ }
821
+ });
822
+
632
823
  bootstrapSellers
633
824
  .command("put")
634
- .description("Update public seller registry")
825
+ .description("Deprecated: dangerously replace public seller registry")
635
826
  .requiredOption("--file <path>", "Seller registry JSON file")
827
+ .option("--force", "Acknowledge this full replacement is dangerous")
636
828
  .action(async (options) => {
637
829
  try {
830
+ if (!options.force) {
831
+ throw new Error("bootstrap sellers put is deprecated; use bootstrap registry import --dry-run first, then --force");
832
+ }
638
833
  const document = loadRegistryFile(options.file);
639
834
  const client = getClient();
640
835
  const response = await client.put("/operator/registry/sellers", document) as SellerRegistryDocument;
641
- console.log(`Updated seller registry: version=${response.version} sellers=${response.sellers.length}`);
836
+ console.log(`Dangerously replaced seller registry: version=${response.version} sellers=${response.sellers.length} publish=pending`);
642
837
  } catch (err: any) {
643
838
  console.error("Error:", err.message);
644
839
  process.exit(1);
@@ -660,6 +855,159 @@ export function buildAdminCli(configManager: ConfigManager): Command {
660
855
  }
661
856
  });
662
857
 
858
+ bootstrapDefaultSeller
859
+ .command("set <id>")
860
+ .description("Set default seller")
861
+ .requiredOption("--expect-version <n>", "Expected current registry version")
862
+ .option("--idempotency-key <key>", "Idempotency key for retry-safe writes")
863
+ .action(async (id, options) => {
864
+ try {
865
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : undefined;
866
+ const response = await getClient().put(
867
+ "/operator/registry/default-seller",
868
+ withExpectedVersion({ sellerId: id }, options.expectVersion),
869
+ headers
870
+ ) as SellerRegistryDocument;
871
+ console.log(`Set default seller ${id}: version=${response.version} publish=pending`);
872
+ } catch (err: any) {
873
+ console.error("Error:", err.message);
874
+ process.exit(1);
875
+ }
876
+ });
877
+
878
+ bootstrapRegistry
879
+ .command("diff")
880
+ .description("Compare a local registry JSON file against the operator registry")
881
+ .requiredOption("--file <path>", "Seller registry JSON file")
882
+ .action(async (options) => {
883
+ try {
884
+ const local = loadRegistryFile(options.file);
885
+ const remote = await getClient().get("/operator/registry") as SellerRegistryDocument;
886
+ const localIds = new Set(local.sellers.map((seller) => seller.id));
887
+ const remoteIds = new Set(remote.sellers.map((seller) => seller.id));
888
+ const added = local.sellers.filter((seller) => !remoteIds.has(seller.id)).map((seller) => seller.id);
889
+ const removed = remote.sellers.filter((seller) => !localIds.has(seller.id)).map((seller) => seller.id);
890
+ const changed = local.sellers
891
+ .filter((seller) => remoteIds.has(seller.id))
892
+ .filter((seller) => JSON.stringify(seller) !== JSON.stringify(remote.sellers.find((entry) => entry.id === seller.id)))
893
+ .map((seller) => seller.id);
894
+ console.log(JSON.stringify({
895
+ remoteVersion: remote.version,
896
+ localVersion: local.version,
897
+ added,
898
+ removed,
899
+ changed,
900
+ defaultSellerChanged: remote.defaultSeller !== local.defaultSeller
901
+ }, null, 2));
902
+ } catch (err: any) {
903
+ console.error("Error:", err.message);
904
+ process.exit(1);
905
+ }
906
+ });
907
+
908
+ bootstrapRegistry
909
+ .command("import")
910
+ .description("Import a full registry JSON as a dangerous disaster-recovery operation")
911
+ .requiredOption("--file <path>", "Seller registry JSON file")
912
+ .option("--dry-run", "Only validate and show diff")
913
+ .option("--force", "Actually perform the full replacement")
914
+ .option("--expect-version <n>", "Expected current registry version before import")
915
+ .option("--force-delete-count <n>", "Maximum allowed delete count when forcing", "1")
916
+ .action(async (options) => {
917
+ try {
918
+ const local = loadRegistryFile(options.file);
919
+ const remote = await getClient().get("/operator/registry") as SellerRegistryDocument;
920
+ const localIds = new Set(local.sellers.map((seller) => seller.id));
921
+ const removed = remote.sellers.filter((seller) => !localIds.has(seller.id));
922
+ const forceDeleteCount = Number(options.forceDeleteCount);
923
+ if (!Number.isInteger(forceDeleteCount) || forceDeleteCount < 0) {
924
+ throw new Error("--force-delete-count must be >= 0");
925
+ }
926
+ console.log(`Import diff: remoteVersion=${remote.version} localVersion=${local.version} removes=${removed.length}`);
927
+ if (options.dryRun || !options.force) {
928
+ if (!options.dryRun) {
929
+ throw new Error("refusing full import without --force; run with --dry-run first");
930
+ }
931
+ return;
932
+ }
933
+ if (removed.length > forceDeleteCount) {
934
+ throw new Error(`import would remove ${removed.length} sellers; pass --force-delete-count ${removed.length} to allow`);
935
+ }
936
+ const body = withExpectedVersion(local as any, options.expectVersion);
937
+ const response = await getClient().put("/operator/registry/sellers", body) as SellerRegistryDocument;
938
+ console.log(`Imported seller registry: version=${response.version} sellers=${response.sellers.length} publish=pending`);
939
+ } catch (err: any) {
940
+ console.error("Error:", err.message);
941
+ process.exit(1);
942
+ }
943
+ });
944
+
945
+ bootstrapRegistry
946
+ .command("publish")
947
+ .description("Publish the current signed registry to R2")
948
+ .option("--version <n>", "Registry version to publish")
949
+ .action(async (options) => {
950
+ try {
951
+ const version = options.version ? Number(options.version) : undefined;
952
+ if (version !== undefined && (!Number.isInteger(version) || version < 1)) {
953
+ throw new Error("--version must be a positive integer");
954
+ }
955
+ const body = version === undefined ? {} : { version };
956
+ const response = await getClient().post("/operator/registry/publish", body) as {
957
+ version: number;
958
+ registrySha256: string;
959
+ signingKeyId: string;
960
+ artifacts?: Array<{ key: string; url: string }>;
961
+ };
962
+ console.log(`Published registry version=${response.version} sha256=${response.registrySha256} signingKey=${response.signingKeyId}`);
963
+ for (const artifact of response.artifacts || []) {
964
+ console.log(` ${artifact.key} -> ${artifact.url}`);
965
+ }
966
+ } catch (err: any) {
967
+ console.error("Error:", err.message);
968
+ process.exit(1);
969
+ }
970
+ });
971
+
972
+ bootstrapRegistry
973
+ .command("versions")
974
+ .description("List registry versions")
975
+ .option("--limit <n>", "Maximum versions to list", "20")
976
+ .action(async (options) => {
977
+ try {
978
+ const limit = Number(options.limit);
979
+ if (!Number.isInteger(limit) || limit < 1) {
980
+ throw new Error("--limit must be a positive integer");
981
+ }
982
+ const response = await getClient().get(`/operator/registry/versions?limit=${encodeURIComponent(String(limit))}`) as {
983
+ versions?: Array<{
984
+ version: number;
985
+ registrySha256: string;
986
+ signingKeyId?: string;
987
+ signed?: boolean;
988
+ sellerCount?: number;
989
+ defaultSeller?: string;
990
+ createdAt?: string;
991
+ publishedAt?: string;
992
+ }>;
993
+ };
994
+ for (const version of response.versions || []) {
995
+ console.log([
996
+ `version=${version.version}`,
997
+ `sellers=${version.sellerCount ?? "?"}`,
998
+ `default=${version.defaultSeller || "-"}`,
999
+ `signed=${version.signed ? "yes" : "no"}`,
1000
+ `published=${version.publishedAt || "pending"}`,
1001
+ `sha256=${version.registrySha256}`,
1002
+ `key=${version.signingKeyId || "-"}`
1003
+ ].join(" "));
1004
+ }
1005
+ } catch (err: any) {
1006
+ console.error("Error:", err.message);
1007
+ process.exit(1);
1008
+ }
1009
+ });
1010
+
663
1011
  bootstrapConfig
664
1012
  .command("get")
665
1013
  .description("Fetch wallet bootstrap runtime config")
@@ -765,14 +1113,19 @@ export function buildAdminCli(configManager: ConfigManager): Command {
765
1113
 
766
1114
  // 8. Seller Command (Fly.io)
767
1115
  const sellerCmd = program.command("seller").description("Deploy and manage seller containers on Fly.io");
768
- const flyProvider = new FlyProvider();
1116
+
1117
+ function getFlyProvider(): FlyProvider {
1118
+ const opts = program.opts();
1119
+ const mgr = opts.config ? new ConfigManager(opts.config) : configManager;
1120
+ return new FlyProvider(mgr.getSellerProvider("fly"));
1121
+ }
769
1122
 
770
1123
  sellerCmd
771
1124
  .command("ls")
772
1125
  .description("List all deployed apps on Fly.io")
773
1126
  .action(() => {
774
1127
  try {
775
- const out = flyProvider.listApps();
1128
+ const out = getFlyProvider().listApps();
776
1129
  console.log(out);
777
1130
  } catch (err: any) {
778
1131
  console.error("Error:", err.message);
@@ -796,7 +1149,7 @@ export function buildAdminCli(configManager: ConfigManager): Command {
796
1149
  .option("--dry-run", "Dry run display without actual execution")
797
1150
  .action((name, options) => {
798
1151
  try {
799
- const res = flyProvider.createSeller({
1152
+ const res = getFlyProvider().createSeller({
800
1153
  name,
801
1154
  app: options.app,
802
1155
  region: options.region,
@@ -824,7 +1177,7 @@ export function buildAdminCli(configManager: ConfigManager): Command {
824
1177
  .option("--dry-run", "Dry run")
825
1178
  .action((app, options) => {
826
1179
  try {
827
- const res = flyProvider.deploySeller({
1180
+ const res = getFlyProvider().deploySeller({
828
1181
  app,
829
1182
  image: options.image,
830
1183
  dryRun: options.dryRun
@@ -845,7 +1198,7 @@ export function buildAdminCli(configManager: ConfigManager): Command {
845
1198
  .action((name, options) => {
846
1199
  try {
847
1200
  const appName = options.app || name;
848
- const res = flyProvider.removeSeller(appName, options.dryRun);
1201
+ const res = getFlyProvider().removeSeller(appName, options.dryRun);
849
1202
  console.log(res);
850
1203
  if (options.removeProfile && !options.dryRun) {
851
1204
  const appBase = appName.includes("-") ? appName.replace(/^tb-seller-/, "") : appName;
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
  }