@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.
- package/dist/src/bootstrap-registry.d.ts +1 -0
- package/dist/src/bootstrap-registry.d.ts.map +1 -1
- package/dist/src/bootstrap-registry.js.map +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +294 -13
- package/dist/src/cli.js.map +1 -1
- package/dist/src/client.d.ts +12 -3
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +12 -8
- package/dist/src/client.js.map +1 -1
- package/dist/src/display-format.d.ts +39 -0
- package/dist/src/display-format.d.ts.map +1 -0
- package/dist/src/display-format.js +354 -0
- package/dist/src/display-format.js.map +1 -0
- package/dist/src/server-cmd.d.ts +25 -1
- package/dist/src/server-cmd.d.ts.map +1 -1
- package/dist/src/server-cmd.js +116 -16
- package/dist/src/server-cmd.js.map +1 -1
- package/dist/src/ui-actions.d.ts +90 -0
- package/dist/src/ui-actions.d.ts.map +1 -0
- package/dist/src/ui-actions.js +823 -0
- package/dist/src/ui-actions.js.map +1 -0
- package/dist/src/ui-command.d.ts +4 -0
- package/dist/src/ui-command.d.ts.map +1 -0
- package/dist/src/ui-command.js +37 -0
- package/dist/src/ui-command.js.map +1 -0
- package/dist/src/ui-server.d.ts +22 -0
- package/dist/src/ui-server.d.ts.map +1 -0
- package/dist/src/ui-server.js +261 -0
- package/dist/src/ui-server.js.map +1 -0
- package/dist/src/ui-state.d.ts +140 -0
- package/dist/src/ui-state.d.ts.map +1 -0
- package/dist/src/ui-state.js +438 -0
- package/dist/src/ui-state.js.map +1 -0
- package/dist/src/ui-static.d.ts +2 -0
- package/dist/src/ui-static.d.ts.map +1 -0
- package/dist/src/ui-static.js +469 -0
- package/dist/src/ui-static.js.map +1 -0
- package/dist/src/upstream-balance-probe.d.ts +41 -0
- package/dist/src/upstream-balance-probe.d.ts.map +1 -0
- package/dist/src/upstream-balance-probe.js +379 -0
- package/dist/src/upstream-balance-probe.js.map +1 -0
- package/package.json +1 -1
- package/src/bootstrap-registry.ts +1 -0
- package/src/cli.ts +335 -13
- package/src/client.ts +13 -8
- package/src/display-format.ts +398 -0
- package/src/server-cmd.ts +145 -20
- package/src/ui-actions.ts +958 -0
- package/src/ui-command.ts +39 -0
- package/src/ui-server.ts +322 -0
- package/src/ui-state.ts +614 -0
- package/src/ui-static.ts +472 -0
- package/src/upstream-balance-probe.ts +505 -0
- 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 (
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
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 =
|
|
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("
|
|
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(`
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
56
|
-
return this.request(path, "
|
|
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
|
}
|